212 lines
6.9 KiB
Python
212 lines
6.9 KiB
Python
"""
|
|
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
|