"""Base classes for pyflocker."""
from __future__ import annotations
from abc import ABCMeta, abstractmethod
[docs]
class BaseSymmetricCipher(metaclass=ABCMeta):
__slots__ = ()
[docs]
@abstractmethod
def is_encrypting(self) -> bool:
"""Whether the cipher is encrypting or not.
Returns:
``True`` if encrypting, else ``False``.
"""
[docs]
@abstractmethod
def update(self, data: bytes) -> bytes:
"""Takes bytes-like object and returns encrypted/decrypted
bytes object.
Args:
data: The bytes-like object to pass to the cipher.
Returns:
Encrypted/decrypted data as bytes.
"""
[docs]
@abstractmethod
def update_into(
self,
data: bytes,
out: bytearray | memoryview,
) -> None:
"""
Encrypt or decrypt the ``data`` and store it in a preallocated buffer
``out``.
Args:
data: The bytes-like object to pass to the cipher.
out:
The buffer interface where the encrypted/decrypted data must be
written into.
"""
[docs]
class BaseNonAEADCipher(BaseSymmetricCipher):
"""
Abstract Base Class for ciphers that have no authentication support.
These ciphers can be wrapped by classes that implement ``BaseAEADCipher``
and the wrappers must provide authentication support.
"""
__slots__ = ()
[docs]
@abstractmethod
def finalize(self) -> None:
"""Finalizes and closes the cipher.
Raises:
AlreadyFinalized: If the cipher was already finalized.
"""
[docs]
class BaseAEADCipher(BaseSymmetricCipher):
"""Abstract base class for AEAD ciphers.
Custom cipher wrappers that provide AEAD functionality to NonAEAD ciphers
must derive from this.
"""
__slots__ = ()
[docs]
@abstractmethod
def authenticate(self, data: bytes) -> None:
"""Authenticates part of the message that get delivered as is, without
any encryption.
Args:
data: The bytes-like object that must be authenticated.
Raises:
TypeError:
if this method is called after calling
:py:meth:`~BaseSymmetricCipher.update`.
"""
[docs]
@abstractmethod
def finalize(self, tag: bytes | None = None) -> None:
"""Finalizes and ends the cipher state.
Args:
tag:
The associated tag that authenticates the decryption. Tag is
required for decryption only.
Raises:
ValueError: If cipher is decrypting and tag is not supplied.
DecryptionError: If the decryption was incorrect.
"""
[docs]
@abstractmethod
def calculate_tag(self) -> bytes | None:
"""Calculates and returns the associated tag.
Returns:
Returns ``None`` if decrypting, otherwise the associated
authentication tag.
"""
[docs]
class BaseAEADOneShotCipher(BaseAEADCipher):
[docs]
@abstractmethod
def update(self, data: bytes, tag: bytes | None = None) -> bytes:
"""Encrypt or decrypt ``data``.
Tag is required only for decryption. The cipher is finalized after
calling this method.
Args:
data: A bytes-like object to pass to the cipher.
tag:
The associated tag that authenticates the decryption. Tag is
required for decryption only.
Returns:
Encrypted/decrypted data as bytes object.
"""
[docs]
@abstractmethod
def update_into(
self,
data: bytes,
out: bytearray | memoryview,
tag: bytes | None = None,
) -> None:
"""Encrypt or decrypt ``data`` and write it to ``out``.
If decrypting, the MAC tag must be provided. The cipher is finalized
after calling this method.
Args:
data: The bytes-like oject to pass to the cipher.
out:
The buffer interface where the encrypted/decrypted data must be
written into.
tag:
The associated tag that authenticates the decryption. Tag is
required for decryption only.
"""
[docs]
class BaseHash(metaclass=ABCMeta):
"""Abstract base class for hash functions. Follows PEP-0452.
Custom MACs must use this interface.
"""
__slots__ = ()
@property
@abstractmethod
def digest_size(self) -> int:
"""
The size of the digest produced by the hashing object, measured in
bytes. If the hash has a variable output size, this output size must be
chosen when the hashing object is created, and this attribute must
contain the selected size. Therefore, None is not a legal value for
this attribute.
Returns:
Digest size as integer.
"""
@property
@abstractmethod
def block_size(self) -> int:
"""
An integer value or :any:`NotImplemented`; the internal block size of
the hash algorithm in bytes. The block size is used by the HMAC module
to pad the secret key to digest_size or to hash the secret key if it is
longer than digest_size. If no HMAC algorithm is standardized for the
hash algorithm, returns :any:`NotImplemented` instead.
Returns:
An integer if block size is available, otherwise
:any:`NotImplemented`.
See Also:
PEP 452 -- API for Cryptographic Hash Functions v2.0,
https://www.python.org/dev/peps/pep-0452
"""
[docs]
@abstractmethod
def update(self, data: bytes) -> None:
"""
Hash string into the current state of the hashing object. ``update()``
can be called any number of times during a hashing object's lifetime.
Args:
data: The chunk of message being hashed.
Raises:
AlreadyFinalized:
Raised if :py:meth:`~digest` or :py:meth:`~hexdigest` has been
called.
"""
[docs]
@abstractmethod
def digest(self) -> bytes:
"""
Return the hash value of this hashing object as a string containing
8-bit data. The object is not altered in any way by this function; you
can continue updating the object after calling this function.
Returns:
Digest as binary form.
"""
[docs]
def hexdigest(self) -> str:
"""
Return the hash value of this hashing object as a string containing
hexadecimal digits.
Returns:
Digest, as a hexadecimal form.
"""
return self.digest().hex()
[docs]
@abstractmethod
def copy(self) -> BaseHash:
"""
Return a separate copy of this hashing object.
An update to this copy won't affect the original object.
Returns:
A copy of hash function.
Raises:
AlreadyFinalized:
This is raised if the method is called after calling
:py:meth:`~digest` method.
"""
@property
@abstractmethod
def name(self) -> str:
"""Name of the hash function."""
[docs]
@abstractmethod
def new(self, data: bytes | None = None) -> BaseHash:
"""Create a fresh hash object."""
def __repr__(self) -> str: # pragma: no cover
return f"<{type(self).__name__} {self.name!r} at {hex(id(self))}>"
[docs]
class BaseRSAPrivateKey(metaclass=ABCMeta):
@property
@abstractmethod
def n(self) -> int:
"""RSA public modulus.
The number `n` is such that `n == p * q`.
"""
@property
@abstractmethod
def e(self) -> int:
"""RSA public exponent."""
@property
@abstractmethod
def p(self) -> int:
"""First factor of RSA modulus."""
@property
@abstractmethod
def q(self) -> int:
"""Second factor of RSA modulus."""
@property
@abstractmethod
def d(self) -> int:
"""RSA private exponent."""
@property
@abstractmethod
def key_size(self) -> int:
"""Size of the key, in bits."""
[docs]
@abstractmethod
def decryptor(
self,
padding: BaseAsymmetricPadding | None = None,
) -> BaseDecryptorContext:
"""Creates a decryption context.
Args:
padding: The padding to use. Default is OAEP.
Returns:
object for decrypting ciphertexts.
"""
[docs]
@abstractmethod
def signer(
self,
padding: BaseAsymmetricPadding | None = None,
) -> BaseSignerContext:
"""Create a signer context.
Args:
padding: The padding to use. Default is PSS.
Returns:
Signer object for signing messages.
Note:
If the padding is PSS and ``salt_length`` is None, the salt length
will be maximized, as in OpenSSL.
"""
[docs]
@abstractmethod
def public_key(self) -> BaseRSAPublicKey:
"""Creates a public key from the private key.
Returns:
The RSA public key.
"""
[docs]
@abstractmethod
def serialize(
self,
encoding: str,
format: str,
passphrase: bytes | None = None,
) -> bytes:
"""Serialize the private key.
Args:
encoding: The encoding to use.
format: The format to use.
passphrase:
A bytes object to use for encrypting the private key. If
``passphrase`` is None, the private key will be exported in the
clear!
Returns:
Serialized key as a bytes object.
Raises:
ValueError: If the encoding or format is incorrect.
Important:
The ``encoding`` and ``format`` supported by one backend may not be
supported by the other. You should check the documentation of the
implementation of the method for supported options.
"""
[docs]
@classmethod
@abstractmethod
def load(
cls,
data: bytes,
passphrase: bytes | None = None,
) -> BaseRSAPrivateKey:
"""Loads the private key as bytes object and returns the Key interface.
Args:
data: The key as bytes object.
passphrase:
The passphrase that deserializes the private key. It must be a
bytes-like object if the key was encrypted while serialization,
otherwise ``None``.
Returns:
RSA private key.
Raises:
ValueError: if the key could not be deserialized.
"""
[docs]
class BaseRSAPublicKey(metaclass=ABCMeta):
@property
@abstractmethod
def n(self) -> int:
"""RSA public modulus.
The number `n` is such that `n = p * q`.
"""
@property
@abstractmethod
def e(self) -> int:
"""RSA public exponent."""
@property
@abstractmethod
def key_size(self) -> int:
"""Size of the key, in bits."""
[docs]
@abstractmethod
def encryptor(
self,
padding: BaseAsymmetricPadding | None = None,
) -> BaseEncryptorContext:
"""Creates a encryption context.
Args:
padding: The padding to use. Defaults to OAEP.
Returns:
object for encrypting plaintexts.
"""
[docs]
@abstractmethod
def verifier(
self,
padding: BaseAsymmetricPadding | None = None,
) -> BaseVerifierContext:
"""Creates a verifier context.
Args:
padding: The padding to use. Defaults to PSS.
Returns:
verifier object for verification.
"""
[docs]
@abstractmethod
def serialize(
self,
encoding: str,
format: str,
) -> bytes:
"""Serialize the public key.
Args:
encoding: The encoding to use.
format: The format to use.
Returns:
Serialized public key as bytes object.
Raises:
ValueError: if the encoding or format is incorrect or unsupported.
Important:
The ``encoding`` and ``format`` supported by one backend may not be
supported by the other. You should check the documentation of the
implementation of this method for supported options.
"""
[docs]
@classmethod
@abstractmethod
def load(cls, data: bytes) -> BaseRSAPublicKey:
"""Loads the public key as ``bytes`` object and returns
the Key interface.
Args:
data: The key as bytes object.
Returns:
The RSA public key.
Raises:
ValueError: if the key could not be deserialized.
"""
[docs]
class BaseAsymmetricPadding(metaclass=ABCMeta):
"""
Base class for padding schemes used by asymmetric algorithms.
"""
@property
@abstractmethod
def name(self) -> str:
"""Name of the padding scheme."""
[docs]
class BaseMGF(metaclass=ABCMeta):
"""
Base class for mask generation functions used by padding algorithms.
"""
[docs]
class BaseEllepticCurveExchangeAlgorithm(metaclass=ABCMeta):
"""
Base class for exchange algorithm for elleptic keys.
"""
[docs]
class BaseEllepticCurveSignatureAlgorithm(metaclass=ABCMeta):
"""
Base class for signing algorithm for elleptic keys.
"""
[docs]
class BaseECCPrivateKey(metaclass=ABCMeta):
@property
@abstractmethod
def key_size(self) -> int:
"""Size of ECC key, in bits."""
@property
@abstractmethod
def curve(self) -> str:
"""Name of the curve that this key is on."""
[docs]
@abstractmethod
def public_key(self) -> BaseECCPublicKey:
"""Creates a public key from the private key.
Returns:
The public key.
"""
[docs]
@abstractmethod
def serialize(
self,
encoding: str,
format: str,
passphrase: bytes | None = None,
) -> bytes:
"""Serialize the private key.
Args:
encoding: The encoding to use.
format: The format to use.
passphrase:
A bytes object to use for encrypting the private key. If
``passphrase`` is None, the private key will be exported in the
clear!
Returns:
Serialized key as a bytes object.
Raises:
ValueError: If the encoding or format is incorrect.
Important:
The ``encoding`` and ``format`` supported by one backend may not be
supported by the other. You should check the documentation of the
implementation of the method for supported options.
"""
[docs]
@classmethod
@abstractmethod
def load(
cls,
data: bytes,
*,
curve: str | None = None,
) -> BaseECCPrivateKey:
"""Loads the private key as bytes object and returns the Key interface.
Args:
data: The key as bytes object.
passphrase:
The passphrase that deserializes the private key. It must be a
bytes-like object if the key was encrypted while serialization,
otherwise ``None``.
Keyword Arguments:
curve:
The name of the curve as string. It is required only for
``Raw`` keys.
Returns:
RSA private key.
Raises:
ValueError: if the key could not be deserialized.
"""
[docs]
@abstractmethod
def exchange(
self,
peer_public_key: bytes | BaseECCPublicKey,
algorithm: BaseEllepticCurveExchangeAlgorithm | None = None,
) -> bytes:
"""Perform a key exchange.
Args:
peer_public_key:
The peer public key can be a bytes or an ECC public key object.
algorithm:
The algorithm to use for performing key exchange. Default is
ECDH.
Returns:
A shared key.
Raises:
TypeError:
if ``peer_public_key`` is not a bytes-like or ECC Public Key
object.
"""
[docs]
@abstractmethod
def signer(
self,
algorithm: BaseEllepticCurveSignatureAlgorithm,
) -> BaseSignerContext | BaseEdDSASignerContext:
"""Creates a signer context.
Args:
algorithm:
The signing algorithm to use. Default is ECDSA for NIST curves
and EdDSA for Edwards curves.
Returns:
signer object for signing.
"""
[docs]
class BaseECCPublicKey(metaclass=ABCMeta):
@property
@abstractmethod
def key_size(self) -> int:
"""Size of ECC key, in bits."""
@property
@abstractmethod
def curve(self) -> str:
"""Name of the curve that this key is on."""
[docs]
@abstractmethod
def serialize(self, encoding: str, format: str) -> bytes:
"""Serialize the public key.
Args:
encoding: The encoding to use.
format: The format to use.
Returns:
Serialized public key as bytes object.
Raises:
ValueError: if the encoding or format is incorrect or unsupported.
Important:
The ``encoding`` and ``format`` supported by one backend may not be
supported by the other. You should check the documentation of the
implementation of this method for supported options.
"""
[docs]
@classmethod
@abstractmethod
def load(
cls,
data: bytes,
*,
curve: str | None = None,
) -> BaseECCPublicKey:
"""Loads the public key as ``bytes`` object and returns
the Key interface.
Args:
data: The key as bytes object.
Keyword Arguments:
curve:
The name of the curve as a string. Required only for ``SEC1``
and ``Raw`` keys.
Returns:
The ECC public key.
Raises:
ValueError: if the key could not be deserialized.
"""
[docs]
@abstractmethod
def verifier(
self,
algorithm: BaseEllepticCurveSignatureAlgorithm | None = None,
) -> BaseVerifierContext | BaseEdDSAVerifierContext:
"""Creates a verifier context.
Args:
algorithm:
The signing algorithm to use. Default is ECDSA for NIST curves
and EdDSA for Edwards curves.
Returns:
verifier object for verification.
"""
[docs]
class BaseSignerContext(metaclass=ABCMeta):
[docs]
@abstractmethod
def sign(self, msghash: BaseHash) -> bytes:
"""Return the signature of the message hash.
Args:
msghash:
It must be a :any:`BaseHash` object, used to digest the message
to sign.
Returns:
signature of the message as bytes object.
"""
[docs]
class BaseVerifierContext(metaclass=ABCMeta):
[docs]
@abstractmethod
def verify(self, msghash: BaseHash, signature: bytes) -> None:
"""Verifies the signature of the message hash.
Args:
msghash:
It must be a :any:`BaseHash` object, used to digest the message
to sign.
signature: The signature of the message.
Raises:
SignatureError: if the signature was incorrect.
"""
[docs]
class BaseEdDSASignerContext(metaclass=ABCMeta):
[docs]
@abstractmethod
def sign(self, msghash: bytes) -> bytes:
"""Return the signature of the message.
Args:
msghash: A bytes object.
Returns:
signature of the message as bytes object.
"""
[docs]
class BaseEdDSAVerifierContext(metaclass=ABCMeta):
[docs]
@abstractmethod
def verify(self, msghash: bytes, signature: bytes) -> None:
"""Verifies the signature of the message.
Args:
msghash: A bytes object.
signature: The signature of the message.
Raises:
SignatureError: if the signature was incorrect.
"""
[docs]
class BaseEncryptorContext(metaclass=ABCMeta):
[docs]
@abstractmethod
def encrypt(self, plaintext: bytes) -> bytes:
"""Encrypts the plaintext and returns the ciphertext.
Args:
plaintext: The data to encrypt.
Returns:
encrypted bytes object.
"""
[docs]
class BaseDecryptorContext(metaclass=ABCMeta):
[docs]
@abstractmethod
def decrypt(self, ciphertext: bytes) -> bytes:
"""Decrypts the ciphertext and returns the plaintext.
Args:
ciphertext: The ciphertext to decrypt.
Returns:
The plaintext.
Raises:
DecryptionError: if the decryption was not successful.
"""
[docs]
class BaseDHParameters(metaclass=ABCMeta):
@property
@abstractmethod
def g(self) -> int:
"""The generator value."""
@property
@abstractmethod
def p(self) -> int:
"""The prime modulus value."""
@property
@abstractmethod
def q(self) -> int | None:
"""The p subgroup order value."""
[docs]
@abstractmethod
def private_key(self) -> BaseDHPrivateKey:
"""Create a DH private key from the parameters.
Returns:
A private key object.
"""
[docs]
@abstractmethod
def serialize(self, encoding: str, format: str) -> bytes:
"""Serialize the DH parameters.
Args:
encoding: The encoding to use.
format: The format to use.
Returns:
The parameters encoded as bytes object.
Raises:
ValueError: if the encoding of format is invalid.
Important:
The ``encoding`` and ``format`` supported by one backend may not be
supported by the other. You should check the documentation of the
implementation of this method for supported options.
"""
[docs]
@classmethod
@abstractmethod
def load(cls, data: bytes) -> BaseDHParameters:
"""Deserialize the encoded DH parameters.
Args:
data: The parameters as an encoded bytes object.
Returns:
DH parameter object.
"""
[docs]
@classmethod
@abstractmethod
def load_from_parameters(
cls,
p: int,
g: int = 2,
q: int | None = None,
) -> BaseDHParameters:
"""Generates a DH parameter group from the parameters.
Args:
p: The prime modulus value.
g: The generator value. Must be 2 or 5. Default is 2.
q: p subgroup order value. Defaults to ``None``.
Returns:
DH Parameter object.
"""
[docs]
class BaseDHPrivateKey(metaclass=ABCMeta):
[docs]
@abstractmethod
def parameters(self) -> BaseDHParameters:
"""Creates a new DH Parameters object from the key.
Returns:
The DH parameter object.
"""
@property
@abstractmethod
def key_size(self) -> int:
"""Size of the key, in bytes."""
[docs]
@abstractmethod
def public_key(self) -> BaseDHPublicKey:
"""Create a public key from the private key.
Returns:
A public key object.
"""
[docs]
@abstractmethod
def exchange(
self,
peer_public_key: bytes | BaseDHPublicKey,
) -> bytes:
"""Perform a key exchange.
Args:
peer_public_key:
The peer public key can be a bytes or a :any:`BaseDHPublicKey`
object.
Returns:
A shared key.
Raises:
TypeError:
if ``peer_public_key`` is not a bytes-like or DH Public Key
object.
"""
[docs]
@abstractmethod
def serialize(
self,
encoding: str,
format: str,
passphrase: bytes | None,
) -> bytes:
"""Serialize the private key.
Args:
encoding: The encoding to use.
format: The format to use.
passphrase:
The passphrase to use to protect the private key. ``None`` if
the private key is not encrypted.
Returns:
The private key as bytes object.
Raises:
ValueError: if the encoding or format is invalid.
TypeError: if the passphrase is not a bytes-like object.
Important:
The ``encoding`` and ``format`` supported by one backend may not be
supported by the other. You should check the documentation of the
implementation of this method for supported options.
"""
@property
@abstractmethod
def x(self) -> int:
"""The private value."""
[docs]
@classmethod
@abstractmethod
def load(
cls,
data: bytes,
passphrase: bytes | None = None,
) -> BaseDHPrivateKey:
"""Deserialize and load the the private key.
Args:
data: The serialized private key as bytes-like object.
passphrase:
The passphrase that was used to protect the private key. If key
is not protected, passphrase is ``None``.
Returns:
A private key.
Raises:
ValueError: If the key could not be deserialized.
TypeError: If passphrase is not a bytes-like object.
"""
[docs]
class BaseDHPublicKey(metaclass=ABCMeta):
[docs]
@abstractmethod
def parameters(self) -> BaseDHParameters:
"""Creates a new DH parameters object from the key.
Returns:
The DH parameter object.
"""
@property
@abstractmethod
def key_size(self) -> int:
"""Size of the key, in bytes."""
[docs]
@abstractmethod
def serialize(
self,
encoding: str,
format: str,
) -> bytes:
"""Serialize the public key.
Args:
encoding: The encoding to use.
format: The format to use.
Returns:
The public key as bytes object.
Raises:
ValueError: if the encoding or format is invalid.
"""
@property
@abstractmethod
def y(self) -> int:
"""The public value."""
[docs]
@classmethod
@abstractmethod
def load(cls, data: bytes) -> BaseDHPublicKey:
"""Deserialize and load the public key.
Args:
data: The serialized public key as bytes-like object.
Returns:
A public key object.
Raises:
ValueError: If the key could not be deserialized.
"""