Exemple #1
0
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.")
Exemple #2
0
    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.")
Exemple #3
0
    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,
        )
Exemple #4
0
    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)),
        )
Exemple #5
0
    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
Exemple #6
0
    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}".')
Exemple #7
0
    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}".')
Exemple #8
0
    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)))
Exemple #9
0
    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
Exemple #10
0
    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))
Exemple #11
0
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.')
Exemple #12
0
    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())
Exemple #13
0
    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__}".')
Exemple #14
0
    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
Exemple #15
0
    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