123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403 |
- import hashlib
- import hmac
- import json
- from .compat import constant_time_compare, string_types
- from .exceptions import InvalidKeyError
- from .utils import (
- base64url_decode, base64url_encode, der_to_raw_signature,
- force_bytes, force_unicode, from_base64url_uint, raw_to_der_signature,
- to_base64url_uint
- )
- try:
- from cryptography.hazmat.primitives import hashes
- from cryptography.hazmat.primitives.serialization import (
- load_pem_private_key, load_pem_public_key, load_ssh_public_key
- )
- from cryptography.hazmat.primitives.asymmetric.rsa import (
- RSAPrivateKey, RSAPublicKey, RSAPrivateNumbers, RSAPublicNumbers,
- rsa_recover_prime_factors, rsa_crt_dmp1, rsa_crt_dmq1, rsa_crt_iqmp
- )
- from cryptography.hazmat.primitives.asymmetric.ec import (
- EllipticCurvePrivateKey, EllipticCurvePublicKey
- )
- from cryptography.hazmat.primitives.asymmetric import ec, padding
- from cryptography.hazmat.backends import default_backend
- from cryptography.exceptions import InvalidSignature
- has_crypto = True
- except ImportError:
- has_crypto = False
- requires_cryptography = set(['RS256', 'RS384', 'RS512', 'ES256', 'ES384',
- 'ES521', 'ES512', 'PS256', 'PS384', 'PS512'])
- def get_default_algorithms():
- """
- Returns the algorithms that are implemented by the library.
- """
- default_algorithms = {
- 'none': NoneAlgorithm(),
- 'HS256': HMACAlgorithm(HMACAlgorithm.SHA256),
- 'HS384': HMACAlgorithm(HMACAlgorithm.SHA384),
- 'HS512': HMACAlgorithm(HMACAlgorithm.SHA512)
- }
- if has_crypto:
- default_algorithms.update({
- 'RS256': RSAAlgorithm(RSAAlgorithm.SHA256),
- 'RS384': RSAAlgorithm(RSAAlgorithm.SHA384),
- 'RS512': RSAAlgorithm(RSAAlgorithm.SHA512),
- 'ES256': ECAlgorithm(ECAlgorithm.SHA256),
- 'ES384': ECAlgorithm(ECAlgorithm.SHA384),
- 'ES521': ECAlgorithm(ECAlgorithm.SHA512),
- 'ES512': ECAlgorithm(ECAlgorithm.SHA512), # Backward compat for #219 fix
- 'PS256': RSAPSSAlgorithm(RSAPSSAlgorithm.SHA256),
- 'PS384': RSAPSSAlgorithm(RSAPSSAlgorithm.SHA384),
- 'PS512': RSAPSSAlgorithm(RSAPSSAlgorithm.SHA512)
- })
- return default_algorithms
- class Algorithm(object):
- """
- The interface for an algorithm used to sign and verify tokens.
- """
- def prepare_key(self, key):
- """
- Performs necessary validation and conversions on the key and returns
- the key value in the proper format for sign() and verify().
- """
- raise NotImplementedError
- def sign(self, msg, key):
- """
- Returns a digital signature for the specified message
- using the specified key value.
- """
- raise NotImplementedError
- def verify(self, msg, key, sig):
- """
- Verifies that the specified digital signature is valid
- for the specified message and key values.
- """
- raise NotImplementedError
- @staticmethod
- def to_jwk(key_obj):
- """
- Serializes a given RSA key into a JWK
- """
- raise NotImplementedError
- @staticmethod
- def from_jwk(jwk):
- """
- Deserializes a given RSA key from JWK back into a PublicKey or PrivateKey object
- """
- raise NotImplementedError
- class NoneAlgorithm(Algorithm):
- """
- Placeholder for use when no signing or verification
- operations are required.
- """
- def prepare_key(self, key):
- if key == '':
- key = None
- if key is not None:
- raise InvalidKeyError('When alg = "none", key value must be None.')
- return key
- def sign(self, msg, key):
- return b''
- def verify(self, msg, key, sig):
- return False
- class HMACAlgorithm(Algorithm):
- """
- Performs signing and verification operations using HMAC
- and the specified hash function.
- """
- SHA256 = hashlib.sha256
- SHA384 = hashlib.sha384
- SHA512 = hashlib.sha512
- def __init__(self, hash_alg):
- self.hash_alg = hash_alg
- def prepare_key(self, key):
- key = force_bytes(key)
- invalid_strings = [
- b'-----BEGIN PUBLIC KEY-----',
- b'-----BEGIN CERTIFICATE-----',
- b'-----BEGIN RSA PUBLIC KEY-----',
- b'ssh-rsa'
- ]
- if any([string_value in key for string_value in invalid_strings]):
- raise InvalidKeyError(
- 'The specified key is an asymmetric key or x509 certificate and'
- ' should not be used as an HMAC secret.')
- return key
- @staticmethod
- def to_jwk(key_obj):
- return json.dumps({
- 'k': force_unicode(base64url_encode(force_bytes(key_obj))),
- 'kty': 'oct'
- })
- @staticmethod
- def from_jwk(jwk):
- obj = json.loads(jwk)
- if obj.get('kty') != 'oct':
- raise InvalidKeyError('Not an HMAC key')
- return base64url_decode(obj['k'])
- def sign(self, msg, key):
- return hmac.new(key, msg, self.hash_alg).digest()
- def verify(self, msg, key, sig):
- return constant_time_compare(sig, self.sign(msg, key))
- if has_crypto:
- class RSAAlgorithm(Algorithm):
- """
- Performs signing and verification operations using
- RSASSA-PKCS-v1_5 and the specified hash function.
- """
- SHA256 = hashes.SHA256
- SHA384 = hashes.SHA384
- SHA512 = hashes.SHA512
- def __init__(self, hash_alg):
- self.hash_alg = hash_alg
- def prepare_key(self, key):
- if isinstance(key, RSAPrivateKey) or \
- isinstance(key, RSAPublicKey):
- return key
- if isinstance(key, string_types):
- key = force_bytes(key)
- try:
- if key.startswith(b'ssh-rsa'):
- key = load_ssh_public_key(key, backend=default_backend())
- else:
- key = load_pem_private_key(key, password=None, backend=default_backend())
- except ValueError:
- key = load_pem_public_key(key, backend=default_backend())
- else:
- raise TypeError('Expecting a PEM-formatted key.')
- return key
- @staticmethod
- def to_jwk(key_obj):
- obj = None
- if getattr(key_obj, 'private_numbers', None):
- # Private key
- numbers = key_obj.private_numbers()
- obj = {
- 'kty': 'RSA',
- 'key_ops': ['sign'],
- 'n': force_unicode(to_base64url_uint(numbers.public_numbers.n)),
- 'e': force_unicode(to_base64url_uint(numbers.public_numbers.e)),
- 'd': force_unicode(to_base64url_uint(numbers.d)),
- 'p': force_unicode(to_base64url_uint(numbers.p)),
- 'q': force_unicode(to_base64url_uint(numbers.q)),
- 'dp': force_unicode(to_base64url_uint(numbers.dmp1)),
- 'dq': force_unicode(to_base64url_uint(numbers.dmq1)),
- 'qi': force_unicode(to_base64url_uint(numbers.iqmp))
- }
- elif getattr(key_obj, 'verify', None):
- # Public key
- numbers = key_obj.public_numbers()
- obj = {
- 'kty': 'RSA',
- 'key_ops': ['verify'],
- 'n': force_unicode(to_base64url_uint(numbers.n)),
- 'e': force_unicode(to_base64url_uint(numbers.e))
- }
- else:
- raise InvalidKeyError('Not a public or private key')
- return json.dumps(obj)
- @staticmethod
- def from_jwk(jwk):
- try:
- obj = json.loads(jwk)
- except ValueError:
- raise InvalidKeyError('Key is not valid JSON')
- if obj.get('kty') != 'RSA':
- raise InvalidKeyError('Not an RSA key')
- if 'd' in obj and 'e' in obj and 'n' in obj:
- # Private key
- if 'oth' in obj:
- raise InvalidKeyError('Unsupported RSA private key: > 2 primes not supported')
- other_props = ['p', 'q', 'dp', 'dq', 'qi']
- props_found = [prop in obj for prop in other_props]
- any_props_found = any(props_found)
- if any_props_found and not all(props_found):
- raise InvalidKeyError('RSA key must include all parameters if any are present besides d')
- public_numbers = RSAPublicNumbers(
- from_base64url_uint(obj['e']), from_base64url_uint(obj['n'])
- )
- if any_props_found:
- numbers = RSAPrivateNumbers(
- d=from_base64url_uint(obj['d']),
- p=from_base64url_uint(obj['p']),
- q=from_base64url_uint(obj['q']),
- dmp1=from_base64url_uint(obj['dp']),
- dmq1=from_base64url_uint(obj['dq']),
- iqmp=from_base64url_uint(obj['qi']),
- public_numbers=public_numbers
- )
- else:
- d = from_base64url_uint(obj['d'])
- p, q = rsa_recover_prime_factors(
- public_numbers.n, d, public_numbers.e
- )
- numbers = RSAPrivateNumbers(
- d=d,
- p=p,
- q=q,
- dmp1=rsa_crt_dmp1(d, p),
- dmq1=rsa_crt_dmq1(d, q),
- iqmp=rsa_crt_iqmp(p, q),
- public_numbers=public_numbers
- )
- return numbers.private_key(default_backend())
- elif 'n' in obj and 'e' in obj:
- # Public key
- numbers = RSAPublicNumbers(
- from_base64url_uint(obj['e']), from_base64url_uint(obj['n'])
- )
- return numbers.public_key(default_backend())
- else:
- raise InvalidKeyError('Not a public or private key')
- def sign(self, msg, key):
- return key.sign(msg, padding.PKCS1v15(), self.hash_alg())
- def verify(self, msg, key, sig):
- try:
- key.verify(sig, msg, padding.PKCS1v15(), self.hash_alg())
- return True
- except InvalidSignature:
- return False
- class ECAlgorithm(Algorithm):
- """
- Performs signing and verification operations using
- ECDSA and the specified hash function
- """
- SHA256 = hashes.SHA256
- SHA384 = hashes.SHA384
- SHA512 = hashes.SHA512
- def __init__(self, hash_alg):
- self.hash_alg = hash_alg
- def prepare_key(self, key):
- if isinstance(key, EllipticCurvePrivateKey) or \
- isinstance(key, EllipticCurvePublicKey):
- return key
- if isinstance(key, string_types):
- key = force_bytes(key)
- # Attempt to load key. We don't know if it's
- # a Signing Key or a Verifying Key, so we try
- # the Verifying Key first.
- try:
- if key.startswith(b'ecdsa-sha2-'):
- key = load_ssh_public_key(key, backend=default_backend())
- else:
- key = load_pem_public_key(key, backend=default_backend())
- except ValueError:
- key = load_pem_private_key(key, password=None, backend=default_backend())
- else:
- raise TypeError('Expecting a PEM-formatted key.')
- return key
- def sign(self, msg, key):
- der_sig = key.sign(msg, ec.ECDSA(self.hash_alg()))
- return der_to_raw_signature(der_sig, key.curve)
- def verify(self, msg, key, sig):
- try:
- der_sig = raw_to_der_signature(sig, key.curve)
- except ValueError:
- return False
- try:
- key.verify(der_sig, msg, ec.ECDSA(self.hash_alg()))
- return True
- except InvalidSignature:
- return False
- class RSAPSSAlgorithm(RSAAlgorithm):
- """
- Performs a signature using RSASSA-PSS with MGF1
- """
- def sign(self, msg, key):
- return key.sign(
- msg,
- padding.PSS(
- mgf=padding.MGF1(self.hash_alg()),
- salt_length=self.hash_alg.digest_size
- ),
- self.hash_alg()
- )
- def verify(self, msg, key, sig):
- try:
- key.verify(
- sig,
- msg,
- padding.PSS(
- mgf=padding.MGF1(self.hash_alg()),
- salt_length=self.hash_alg.digest_size
- ),
- self.hash_alg()
- )
- return True
- except InvalidSignature:
- return False
|