Use the instance service key for registry JWT signing
This commit is contained in:
parent
a4aa5cc02a
commit
8887f09ba8
26 changed files with 457 additions and 278 deletions
81
util/security/jwtutil.py
Normal file
81
util/security/jwtutil.py
Normal file
|
@ -0,0 +1,81 @@
|
|||
import re
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from jwt import PyJWT
|
||||
from jwt.exceptions import (
|
||||
InvalidTokenError, DecodeError, InvalidAudienceError, ExpiredSignatureError,
|
||||
ImmatureSignatureError, InvalidIssuedAtError, InvalidIssuerError, MissingRequiredClaimError
|
||||
)
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicNumbers
|
||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers
|
||||
from jwkest.jwk import keyrep, RSAKey, ECKey
|
||||
|
||||
|
||||
# TOKEN_REGEX defines a regular expression for matching JWT bearer tokens.
|
||||
TOKEN_REGEX = re.compile(r'\ABearer (([a-zA-Z0-9+\-_/]+\.)+[a-zA-Z0-9+\-_/]+)\Z')
|
||||
|
||||
|
||||
class StrictJWT(PyJWT):
|
||||
""" StrictJWT defines a JWT decoder with extra checks. """
|
||||
|
||||
@staticmethod
|
||||
def _get_default_options():
|
||||
# Weird syntax to call super on a staticmethod
|
||||
defaults = super(StrictJWT, StrictJWT)._get_default_options()
|
||||
defaults.update({
|
||||
'require_exp': True,
|
||||
'require_iat': True,
|
||||
'require_nbf': True,
|
||||
'exp_max_s': None,
|
||||
})
|
||||
return defaults
|
||||
|
||||
def _validate_claims(self, payload, options, audience=None, issuer=None, leeway=0, **kwargs):
|
||||
if options.get('exp_max_s') is not None:
|
||||
if 'verify_expiration' in kwargs and not kwargs.get('verify_expiration'):
|
||||
raise ValueError('exp_max_s option implies verify_expiration')
|
||||
|
||||
options['verify_exp'] = True
|
||||
|
||||
# Do all of the other checks
|
||||
super(StrictJWT, self)._validate_claims(payload, options, audience, issuer, leeway, **kwargs)
|
||||
|
||||
if 'exp' in payload and options.get('exp_max_s') is not None:
|
||||
# Validate that the expiration was not more than exp_max_s seconds after the issue time
|
||||
# or in the absense of an issue time, more than exp_max_s in the future from now
|
||||
|
||||
# This will work because the parent method already checked the type of exp
|
||||
expiration = datetime.utcfromtimestamp(int(payload['exp']))
|
||||
max_signed_s = options.get('exp_max_s')
|
||||
|
||||
start_time = datetime.utcnow()
|
||||
if 'iat' in payload:
|
||||
start_time = datetime.utcfromtimestamp(int(payload['iat']))
|
||||
|
||||
if expiration > start_time + timedelta(seconds=max_signed_s):
|
||||
raise InvalidTokenError('Token was signed for more than %s seconds from %s', max_signed_s,
|
||||
start_time)
|
||||
|
||||
|
||||
def exp_max_s_option(max_exp_s):
|
||||
return {
|
||||
'exp_max_s': max_exp_s,
|
||||
}
|
||||
|
||||
|
||||
decode = StrictJWT().decode
|
||||
|
||||
|
||||
def jwk_dict_to_public_key(jwk):
|
||||
""" Converts the specified JWK into a public key. """
|
||||
jwkest_key = keyrep(jwk)
|
||||
if isinstance(jwkest_key, RSAKey):
|
||||
pycrypto_key = jwkest_key.key
|
||||
return RSAPublicNumbers(e=pycrypto_key.e, n=pycrypto_key.n).public_key(default_backend())
|
||||
elif isinstance(jwkest_key, ECKey):
|
||||
x, y = jwkest_key.get_key()
|
||||
return EllipticCurvePublicNumbers(x, y, jwkest_key.curve).public_key(default_backend())
|
||||
|
||||
raise Exception('Unsupported kind of JWK: %s', str(type(jwkest_key)))
|
Reference in a new issue