""" joserfc._rfc7518 ~~~~~~~~~~~~~~~~ Originally designed in ``authlib.jose.rfc7518``. "alg" (Algorithm) Header Parameter Values for JWS per `Section 3`_. .. _`Section 3`: https://tools.ietf.org/html/rfc7518#section-3 """ import hmac import hashlib import typing as t from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric.utils import ( decode_dss_signature, encode_dss_signature, ) from cryptography.hazmat.primitives.asymmetric.ec import ECDSA from cryptography.hazmat.primitives.asymmetric import padding from cryptography.exceptions import InvalidSignature from .._rfc7515.model import JWSAlgModel from ..errors import InvalidKeyCurveError from .oct_key import OctKey from .rsa_key import RSAKey from .ec_key import ECKey from .util import encode_int, decode_int class NoneAlgorithm(JWSAlgModel): name = "none" description = "No digital signature or MAC performed" security_warning = 'JWS algorithm "none" is deprecated, via draft-ietf-jose-deprecate-none-rsa15-02' def sign(self, msg: bytes, key: t.Any) -> bytes: return b"" def verify(self, msg: bytes, sig: bytes, key: t.Any) -> bool: return sig == b"" class HMACAlgorithm(JWSAlgModel): """HMAC using SHA algorithms for JWS. Available algorithms: - HS256: HMAC using SHA-256 - HS384: HMAC using SHA-384 - HS512: HMAC using SHA-512 """ SHA256 = hashlib.sha256 SHA384 = hashlib.sha384 SHA512 = hashlib.sha512 def __init__(self, sha_type: t.Literal[256, 384, 512], recommended: bool = False): self.name = f"HS{sha_type}" self.description = f"HMAC using SHA-{sha_type}" self.recommended = recommended self.hash_alg = getattr(self, f"SHA{sha_type}") self.algorithm_security = sha_type def sign(self, msg: bytes, key: OctKey) -> bytes: # it is faster than the one in cryptography op_key = key.get_op_key("sign") if not op_key: # Defence-in-depth: OctKey.import_key rejects empty input, but # an OctKey can also be constructed directly through other # internal paths. An empty HMAC key produces a forgeable digest. raise ValueError("HMAC key must not be empty") return hmac.new(op_key, msg, self.hash_alg).digest() def verify(self, msg: bytes, sig: bytes, key: OctKey) -> bool: op_key = key.get_op_key("verify") if not op_key: raise ValueError("HMAC key must not be empty") v_sig = hmac.new(op_key, msg, self.hash_alg).digest() return hmac.compare_digest(sig, v_sig) class _RSAAlgModel(JWSAlgModel): key_type = "RSA" SHA256 = hashes.SHA256 SHA384 = hashes.SHA384 SHA512 = hashes.SHA512 padding: padding.AsymmetricPadding def __init__(self, sha_type: t.Literal[256, 384, 512], recommended: bool = False): self.recommended = recommended self.hash_alg = getattr(self, f"SHA{sha_type}") self.algorithm_security = sha_type def sign(self, msg: bytes, key: RSAKey) -> bytes: op_key = key.get_op_key("sign") return op_key.sign(msg, self.padding, self.hash_alg()) def verify(self, msg: bytes, sig: bytes, key: RSAKey) -> bool: op_key = key.get_op_key("verify") try: op_key.verify(sig, msg, self.padding, self.hash_alg()) return True except InvalidSignature: return False class RSAAlgorithm(_RSAAlgModel): """RSA using SHA algorithms for JWS. Available algorithms: - RS256: RSASSA-PKCS1-v1_5 using SHA-256 - RS384: RSASSA-PKCS1-v1_5 using SHA-384 - RS512: RSASSA-PKCS1-v1_5 using SHA-512 """ padding = padding.PKCS1v15() def __init__(self, sha_type: t.Literal[256, 384, 512], recommended: bool = False): super().__init__(sha_type, recommended) self.name = f"RS{sha_type}" self.description = f"RSASSA-PKCS1-v1_5 using SHA-{sha_type}" class ESAlgorithm(JWSAlgModel): """ECDSA using SHA algorithms for JWS. Available algorithms: - ES256: ECDSA using P-256 and SHA-256 - ES384: ECDSA using P-384 and SHA-384 - ES512: ECDSA using P-521 and SHA-512 """ key_type = "EC" SHA256 = hashes.SHA256 SHA384 = hashes.SHA384 SHA512 = hashes.SHA512 def __init__(self, name: str, curve: str, sha_type: t.Literal[256, 384, 512], recommended: bool = False): self.name = name self.curve = curve self.description = f"ECDSA using {self.curve} and SHA-{sha_type}" self.recommended = recommended self.hash_alg = getattr(self, f"SHA{sha_type}") self.algorithm_security = sha_type def check_key(self, key: ECKey) -> None: super().check_key(key) if key.curve_name != self.curve: raise InvalidKeyCurveError(f"Key for '{self.name}' not supported, only '{self.curve}' allowed") def sign(self, msg: bytes, key: ECKey) -> bytes: op_key = key.get_op_key("sign") der_sig = op_key.sign(msg, ECDSA(self.hash_alg())) r, s = decode_dss_signature(der_sig) size = key.curve_key_size return encode_int(r, size) + encode_int(s, size) def verify(self, msg: bytes, sig: bytes, key: ECKey) -> bool: key_size = key.curve_key_size length = (key_size + 7) // 8 if len(sig) != 2 * length: return False r = decode_int(sig[:length]) s = decode_int(sig[length:]) der_sig = encode_dss_signature(r, s) try: op_key = key.get_op_key("verify") op_key.verify(der_sig, msg, ECDSA(self.hash_alg())) return True except InvalidSignature: return False class RSAPSSAlgorithm(_RSAAlgModel): """RSASSA-PSS using SHA algorithms for JWS. Available algorithms: - PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-256 - PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-384 - PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-512 """ def __init__(self, sha_type: t.Literal[256, 384, 512]): super().__init__(sha_type, False) self.name = f"PS{sha_type}" self.description = f"RSASSA-PSS using SHA-{sha_type} and MGF1 with SHA-{sha_type}" self.padding = padding.PSS(mgf=padding.MGF1(self.hash_alg()), salt_length=self.hash_alg.digest_size) JWS_ALGORITHMS: list[JWSAlgModel] = [ NoneAlgorithm(), # none HMACAlgorithm(256, True), # HS256 HMACAlgorithm(384), # HS384 HMACAlgorithm(512), # HS512 RSAAlgorithm(256, True), # RS256 RSAAlgorithm(384), # RS384 RSAAlgorithm(512), # RS512 ESAlgorithm("ES256", "P-256", 256, True), ESAlgorithm("ES384", "P-384", 384), ESAlgorithm("ES512", "P-521", 512), RSAPSSAlgorithm(256), # PS256 RSAPSSAlgorithm(384), # PS384 RSAPSSAlgorithm(512), # PS512 ] # compatible NoneAlgModel = NoneAlgorithm HMACAlgModel = HMACAlgorithm RSAAlgModel = RSAAlgorithm ECAlgModel = ESAlgorithm RSAPSSAlgModel = RSAPSSAlgorithm