def _validate_key(key: JsonWebKey, header: JsonWebSignatureHeader): """ Validates the provided key against the header algorithm's specifications and restrictions. :param key: JWK to be validated. :type key: JsonWebKey :param header: JWS Header used to validate the key against. :type header: JsonWebSignatureHeader :raises InvalidKey: The provided key is invalid. """ if not isinstance(key, JsonWebKey): raise InvalidKey if key.data.get("alg"): if key.data.get("alg") != header.get("alg"): raise InvalidKey( f'This key cannot be used by the algorithm "{header.get("alg")}".' ) if header.get("kid"): if key.data.get("kid") != header.get("kid"): raise InvalidKey( "The key ID does not match the specified on the header.") if key.data.get("use"): if key.data.get("use") != "sig": raise InvalidKey("This key cannot be used to sign a JWS.") if key.data.get("key_ops"): if any(op not in ("sign", "verify") for op in key.data.get("key_ops")): raise InvalidKey("This key cannot be used to sign a JWS.")
def sign(self, data: bytes, algorithm: AlgType, rsa_padding: RSAPadding) -> bytes: """ Returns a signature of the provided data. :param data: Data to be signed. :type data: bytes :param algorithm: Hash algorithm used to sign the provided data. :type algorithm: AlgType :param rsa_padding: Padding used by the key. :type rsa_padding: RSAPadding :return: Signature of the provided data. :rtype: bytes """ if not self._private: raise InvalidKey("Cannot sign with a public key.") alg = _get_alg(algorithm) if rsa_padding == "PKCS1v15": return self._private.sign(data, padding.PKCS1v15(), alg) if rsa_padding == "PSS": return self._private.sign( data, padding.PSS(padding.MGF1(alg), padding.PSS.MAX_LENGTH), alg) raise InvalidKey("Unsupported RSA padding.")
def export(self, public: bool = False) -> bytes: """ Exports the key in PEM format. :param public: Exports the public key, defaults to False. :type public: bool, optional :return: PEM encoded key data. :rtype: bytes """ if not public: if self._private: return self._private.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, serialization.NoEncryption(), ) raise InvalidKey("No private key found.") return self._public.public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo, )
def generate(cls, size: int = 2048) -> RSAKey: """ Generates a key on the fly based on the provided module size. :param size: Size of the modulus in bits, defaults to 2048. :type size: int, optional :raises InvalidKey: Invalid parameters for the key. :return: Generated key as RSAKey. :rtype: RSAKey """ if size < 512: raise InvalidKey("Size is too short. Must be AT LEAST 512 bits.") key = rsa.generate_private_key(65537, size, default_backend()) private = key.private_numbers() public = key.public_key().public_numbers() return cls( n=to_string(int_to_b64(public.n)), e=to_string(int_to_b64(public.e)), d=to_string(int_to_b64(private.d)), p=to_string(int_to_b64(private.p)), q=to_string(int_to_b64(private.q)), dp=to_string(int_to_b64(private.dmp1)), dq=to_string(int_to_b64(private.dmq1)), qi=to_string(int_to_b64(private.iqmp)), )
def parse( cls, path_or_secret: os.PathLike | bytes, password: bytes = None, format: str = "pem", ) -> OCTKey: """ Parses a raw secret into an OCTKey. :param path_or_secret: The path of the raw key or its bytes representation. :type path_or_secret: os.PathLike | bytes :param password: Password used to decrypt the raw key, defaults to None. :type password: bytes, optional :param format: The format of the raw key, defaults to `pem`. If `pem`, assumes it is Base64 Encoded. If `der`, assumes it is a regular sequence of bytes. :type format: str, optional :raises UnsupportedParsingMethod: Method not supported. :raises InvalidKey: The raw key type is different from the class. :return: Parsed key as OCTKey. :rtype: OCTKey """ raw = _parse_raw(path_or_secret) if format == "pem": invalid_strings = [ b"-----BEGIN CERTIFICATE-----", b"-----BEGIN PRIVATE KEY-----", b"-----BEGIN RSA PRIVATE KEY-----", b"-----BEGIN EC PRIVATE KEY-----", b"-----BEGIN PUBLIC KEY-----", b"-----BEGIN RSA PUBLIC KEY-----", b"-----BEGIN EC PUBLIC KEY-----", b"ssh-rsa", ] if any(string in raw for string in invalid_strings): raise InvalidKey( "The raw key is an asymmetric key or X.509 Certificate " "and CANNOT be used as a symmetric key.") data = to_string(base64url_encode(base64.b64decode(raw))) return cls(data) if format == "der": data = to_string(base64url_encode(raw)) return cls(data) raise UnsupportedParsingMethod
def generate(cls, curve: str) -> ECKey: """ Generates a key on the fly based on the provided curve name. :param curve: Curve used to generate the key. :type curve: str :raises InvalidKey: Invalid parameters for the key. :return: Generated key as ECKey. :rtype: ECKey """ if not (crv := cls.CURVES.get(curve)): raise InvalidKey(f'Unknown curve "{curve}".')
def validate_key(cls, key: JsonWebKey): """ Validates the provided key against the algorithm's specifications and restrictions. :param key: JWK to be validated. :type key: JsonWebKey :raises InvalidKey: The provided key is invalid. """ if not isinstance(key, JsonWebKey): raise InvalidKey # pylint: disable=used-before-assignment if (alg := key.data.get("alg")) and alg != cls.__algorithm__: raise InvalidKey( f'This key is intended to be used by the algorithm "{alg}".')
def sign(self, data: bytes, algorithm: AlgType) -> bytes: """ Returns a signature of the provided data. :param data: Data to be signed. :type data: bytes :param algorithm: Hash algorithm used to sign the provided data. :type algorithm: AlgType :return: Signature of the provided data. :rtype: bytes """ if not self._private: raise InvalidKey("Cannot sign with a public key.") return self._private.sign(data, ec.ECDSA(_get_alg(algorithm)))
def verify( self, signature: bytes, data: bytes, algorithm: AlgType, rsa_padding: RSAPadding, ) -> None: """ Compares the provided signature against the provided data. :param signature: Signature to be compared. :type signature: bytes :param data: Data to be compared against. :type data: bytes :param algorithm: Hash algorithm used to validate the data and the signature. :type algorithm: AlgType :param rsa_padding: Padding used by the key. :type rsa_padding: RSAPadding :raises InvalidSignature: The signature and data do not match. """ try: alg = _get_alg(algorithm) if rsa_padding == "PKCS1v15": return self._public.verify(signature, data, padding.PKCS1v15(), alg) if rsa_padding == "PSS": return self._public.verify( signature, data, padding.PSS(padding.MGF1(alg), padding.PSS.MAX_LENGTH), alg, ) raise InvalidKey("Unsupported padding.") except BaseInvalidSignature: raise InvalidSignature
def generate(cls, size: int = 32) -> OCTKey: """ Generates a secure random bytes sequence based on the provided size. :param size: Size of the secret in bytes, defaults to 32. :type size: int, optional :raises InvalidKey: Invalid parameters for the key. :return: Instance of an OCTKey. :rtype: OCTKey """ if size < 32: raise InvalidKey("Size is too short. MUST be AT LEAST 32 bytes.") secret = base64url_encode(secrets.token_bytes(size)) return cls(k=to_string(secret))
class JWSAlgorithm(abc.ABC): """ Implementation of the Section 3 of RFC 7518. This class provides the expected method signatures that will be used throughout the package. All JWS Algorithms **MUST** inherit from this class and implement its methods. :cvar ``__algorithm__``: Name of the algorithm. :cvar ``__hash_name__``: Name of the hash function used by the algorithm. :cvar ``__key_type__``: Type of the key that the algorithm accepts. """ __algorithm__: str = None __hash_name__: str = None __key_type__: str = None @classmethod def validate_key(cls, key: JsonWebKey): """ Validates the provided key against the algorithm's specifications and restrictions. :param key: JWK to be validated. :type key: JsonWebKey :raises InvalidKey: The provided key is invalid. """ if not isinstance(key, JsonWebKey): raise InvalidKey # pylint: disable=used-before-assignment if (alg := key.data.get("alg")) and alg != cls.__algorithm__: raise InvalidKey( f'This key is intended to be used by the algorithm "{alg}".') if key.data.get("kty") != cls.__key_type__: raise InvalidKey( f'This algorithm only accepts "{cls.__key_type__}" keys.')
def __init__(self, crv: str, x: str, y: str, d: Optional[str] = None, **ignore) -> None: if crv not in self.CURVES.keys(): raise InvalidKey(f'Unknown curve: "{crv}".') self._private = None self._public = None curve = self.CURVES[crv] x_coord = b64_to_int(x) y_coord = b64_to_int(y) public = ec.EllipticCurvePublicNumbers(x_coord, y_coord, curve) self._public: EC_PUBLIC = public.public_key(default_backend()) if d: private_value = b64_to_int(d) private = ec.EllipticCurvePrivateNumbers(private_value, public) self._private: EC_PRIVATE = private.private_key(default_backend())
def validate_key(cls, key: JsonWebKey): super(_EC, cls).validate_key(key) if key.data.get("crv") != cls.__curve__: raise InvalidKey( f'This algorithm only accepts the curve "{cls.__curve__}".')
def parse( cls, path_or_secret: os.PathLike | bytes, password: bytes = None, format: str = "pem", ) -> RSAKey: """ Parses a raw key into an RSAKey. :param path_or_secret: The path of the raw key or its bytes representation. :type path_or_secret: os.PathLike | bytes :param password: Password used to decrypt the raw key, defaults to None. :type password: bytes, optional :param format: The format of the raw key, defaults to `pem`. If `pem`, assumes it is PEM Encoded. If `der`, assumes it is a regular sequence of bytes. :type format: str, optional :raises UnsupportedParsingMethod: Method not supported. :raises InvalidKey: The raw key type is different from the class. :return: Parsed key as RSAKey. :rtype: RSAKey """ raw = _parse_raw(path_or_secret) if format == "pem": if b"PRIVATE" in raw: key: RSA_PRIVATE = serialization.load_pem_private_key( raw, password, default_backend()) if not isinstance(key, rsa.RSAPrivateKey): raise InvalidKey("The raw key is not an RSA Private Key.") private = key.private_numbers() public = key.public_key().public_numbers() return cls( n=to_string(int_to_b64(public.n)), e=to_string(int_to_b64(public.e)), d=to_string(int_to_b64(private.d)), p=to_string(int_to_b64(private.p)), q=to_string(int_to_b64(private.q)), dp=to_string(int_to_b64(private.dmp1)), dq=to_string(int_to_b64(private.dmq1)), qi=to_string(int_to_b64(private.iqmp)), ) if b"PUBLIC" in raw: key: RSA_PUBLIC = serialization.load_pem_public_key( raw, default_backend()) if not isinstance(key, rsa.RSAPublicKey): raise InvalidKey("The raw key is not an RSA Public Key.") public = key.public_numbers() return cls(n=to_string(int_to_b64(public.n)), e=to_string(int_to_b64(public.e))) raise InvalidKey("Unknown raw key format for RSA.") raise UnsupportedParsingMethod
def parse( cls, path_or_secret: os.PathLike | bytes, password: bytes = None, format: str = "pem", ) -> ECKey: """ Parses a raw key into an ECKey. :param path_or_secret: The path of the raw key or its bytes representation. :type path_or_secret: os.PathLike | bytes :param password: Password used to decrypt the raw key, defaults to None. :type password: bytes, optional :param format: The format of the raw key, defaults to `pem`. If `pem`, assumes it is PEM Encoded. If `der`, assumes it is a regular sequence of bytes. :type format: str, optional :raises UnsupportedParsingMethod: Method not supported. :raises InvalidKey: The raw key type is different from the class. :return: Parsed key as ECKey. :rtype: ECKey """ raw = _parse_raw(path_or_secret) if format == "pem": if b"PRIVATE" in raw: key: EC_PRIVATE = serialization.load_pem_private_key( raw, password, default_backend()) if not isinstance(key, ec.EllipticCurvePrivateKey): raise InvalidKey( "The raw key is not an Elliptic Curve Private Key.") private = key.private_numbers() public = key.public_key().public_numbers() return cls( crv=cls.CURVES_NAMES.get(public.curve.name), x=to_string(int_to_b64(public.x)), y=to_string(int_to_b64(public.y)), d=to_string(int_to_b64(private.private_value)), ) if b"PUBLIC" in raw: key: EC_PUBLIC = serialization.load_pem_public_key( raw, default_backend()) if not isinstance(key, ec.EllipticCurvePublicKey): raise InvalidKey( "The raw key is not an Elliptic Curve Public Key.") public = key.public_numbers() return cls( crv=cls.CURVES_NAMES.get(public.curve.name), x=to_string(int_to_b64(public.x)), y=to_string(int_to_b64(public.y)), ) raise InvalidKey("Unknown raw key format for Elliptic Curve.") raise UnsupportedParsingMethod