mirror of
https://github.com/openSUSE/osc.git
synced 2025-01-26 06:46:13 +01:00
Add support for the Signature authentication scheme
See https://tools.ietf.org/id/draft-cavage-http-signatures-12.html
This commit is contained in:
parent
e47a265388
commit
99ba3719c7
80
osc/conf.py
80
osc/conf.py
@ -45,6 +45,8 @@ import sys
|
|||||||
import ssl
|
import ssl
|
||||||
import warnings
|
import warnings
|
||||||
import getpass
|
import getpass
|
||||||
|
import time
|
||||||
|
import subprocess
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from http.cookiejar import LWPCookieJar, CookieJar
|
from http.cookiejar import LWPCookieJar, CookieJar
|
||||||
@ -54,6 +56,7 @@ try:
|
|||||||
from urllib.error import URLError
|
from urllib.error import URLError
|
||||||
from urllib.request import HTTPBasicAuthHandler, HTTPCookieProcessor, HTTPPasswordMgrWithDefaultRealm, ProxyHandler
|
from urllib.request import HTTPBasicAuthHandler, HTTPCookieProcessor, HTTPPasswordMgrWithDefaultRealm, ProxyHandler
|
||||||
from urllib.request import AbstractHTTPHandler, build_opener, proxy_bypass, HTTPSHandler
|
from urllib.request import AbstractHTTPHandler, build_opener, proxy_bypass, HTTPSHandler
|
||||||
|
from urllib.request import BaseHandler, parse_keqv_list, parse_http_list
|
||||||
except ImportError:
|
except ImportError:
|
||||||
#python 2.x
|
#python 2.x
|
||||||
from cookielib import LWPCookieJar, CookieJar
|
from cookielib import LWPCookieJar, CookieJar
|
||||||
@ -62,10 +65,11 @@ except ImportError:
|
|||||||
from urlparse import urlsplit
|
from urlparse import urlsplit
|
||||||
from urllib2 import URLError, HTTPBasicAuthHandler, HTTPCookieProcessor, HTTPPasswordMgrWithDefaultRealm, ProxyHandler, AbstractBasicAuthHandler
|
from urllib2 import URLError, HTTPBasicAuthHandler, HTTPCookieProcessor, HTTPPasswordMgrWithDefaultRealm, ProxyHandler, AbstractBasicAuthHandler
|
||||||
from urllib2 import AbstractHTTPHandler, build_opener, proxy_bypass, HTTPSHandler
|
from urllib2 import AbstractHTTPHandler, build_opener, proxy_bypass, HTTPSHandler
|
||||||
|
from urllib2 import BaseHandler, parse_keqv_list, parse_http_list
|
||||||
|
|
||||||
from . import OscConfigParser
|
from . import OscConfigParser
|
||||||
from osc import oscerr
|
from osc import oscerr
|
||||||
from osc.util.helper import raw_input
|
from osc.util.helper import raw_input, decode_it
|
||||||
from .oscsslexcp import NoSecureSSLError
|
from .oscsslexcp import NoSecureSSLError
|
||||||
from osc import credentials
|
from osc import credentials
|
||||||
|
|
||||||
@ -532,6 +536,80 @@ def _build_opener(apiurl):
|
|||||||
self.retried = 0
|
self.retried = 0
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
class OscHTTPSignatureAuthHandler(BaseHandler):
|
||||||
|
def __init__(self, user, sshkey):
|
||||||
|
super(self.__class__, self).__init__()
|
||||||
|
self.user = user
|
||||||
|
self.sshkey = sshkey
|
||||||
|
|
||||||
|
def guess_keyfile(self):
|
||||||
|
sshdir = os.path.expanduser('~/.ssh')
|
||||||
|
keyfiles = ('id_ed25519', 'id_rsa')
|
||||||
|
for keyfile in keyfiles:
|
||||||
|
keyfile_path = os.path.join(sshdir, keyfile)
|
||||||
|
if os.path.isfile(keyfile_path):
|
||||||
|
return keyfile_path
|
||||||
|
raise oscerr.OscIOError(None, 'could not guess ssh identity keyfile')
|
||||||
|
|
||||||
|
def ssh_sign(self, data, namespace, keyfile=None):
|
||||||
|
try:
|
||||||
|
data = bytes(data, 'utf-8')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if not keyfile:
|
||||||
|
keyfile = self.guess_keyfile()
|
||||||
|
else:
|
||||||
|
if '/' not in keyfile:
|
||||||
|
keyfile = '~/.ssh/' + keyfile
|
||||||
|
keyfile = os.path.expanduser(keyfile)
|
||||||
|
|
||||||
|
cmd = ['ssh-keygen', '-Y', 'sign', '-f', keyfile, '-n', namespace, '-q']
|
||||||
|
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||||
|
stdout, _ = proc.communicate(data)
|
||||||
|
if proc.returncode:
|
||||||
|
raise oscerr.OscIOError(None, 'ssh-keygen signature creation failed: %d' % proc.returncode)
|
||||||
|
|
||||||
|
signature = decode_it(stdout)
|
||||||
|
match = re.match(r"\A-----BEGIN SSH SIGNATURE-----\n(.*)\n-----END SSH SIGNATURE-----", signature, re.S)
|
||||||
|
if not match:
|
||||||
|
raise oscerr.OscIOError(None, 'could not extract ssh signature')
|
||||||
|
return base64.b64decode(match.group(1))
|
||||||
|
|
||||||
|
def get_authorization(self, req, chal):
|
||||||
|
realm = chal.get('realm', '')
|
||||||
|
now = int(time.time())
|
||||||
|
sigdata = "(created): %d" % now
|
||||||
|
signature = self.ssh_sign(sigdata, realm, self.sshkey)
|
||||||
|
signature = decode_it(base64.b64encode(signature))
|
||||||
|
return 'keyId="%s",algorithm="ssh",headers="(created)",created=%d,signature="%s"' \
|
||||||
|
% (self.user, now, signature)
|
||||||
|
|
||||||
|
def retry_http_signature_auth(self, req, auth):
|
||||||
|
old_auth_val = req.get_header('Authorization', None)
|
||||||
|
if old_auth_val:
|
||||||
|
old_scheme = old_auth_val.split()[0]
|
||||||
|
if old_scheme.lower() == 'signature':
|
||||||
|
return None
|
||||||
|
token, challenge = auth.split(' ', 1)
|
||||||
|
chal = parse_keqv_list(filter(None, parse_http_list(challenge)))
|
||||||
|
auth = self.get_authorization(req, chal)
|
||||||
|
if auth:
|
||||||
|
auth_val = 'Signature %s' % auth
|
||||||
|
req.add_unredirected_header('Authorization', auth_val)
|
||||||
|
return self.parent.open(req, timeout=req.timeout)
|
||||||
|
|
||||||
|
def http_error_401(self, req, fp, code, msg, headers):
|
||||||
|
authreq = headers.get('www-authenticate', None)
|
||||||
|
if authreq:
|
||||||
|
scheme = authreq.split()[0]
|
||||||
|
if scheme.lower() == 'signature':
|
||||||
|
return self.retry_http_signature_auth(req, authreq)
|
||||||
|
raise ValueError("OscHTTPSignatureAuthHandler does not support"
|
||||||
|
" the following scheme: '%s'" % scheme)
|
||||||
|
|
||||||
|
def sshkey_known(self):
|
||||||
|
return self.sshkey is not None
|
||||||
|
|
||||||
|
|
||||||
if 'last_opener' not in _build_opener.__dict__:
|
if 'last_opener' not in _build_opener.__dict__:
|
||||||
_build_opener.last_opener = (None, None)
|
_build_opener.last_opener = (None, None)
|
||||||
|
Loading…
Reference in New Issue
Block a user