moving to scripts
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,17 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
import abc
|
||||
|
||||
|
||||
# This exists to break an import cycle. It is normally accessible from the
|
||||
# asymmetric padding module.
|
||||
|
||||
|
||||
class AsymmetricPadding(metaclass=abc.ABCMeta):
|
||||
@abc.abstractproperty
|
||||
def name(self) -> str:
|
||||
"""
|
||||
A string naming this padding (e.g. "PSS", "PKCS1").
|
||||
"""
|
||||
@@ -0,0 +1,38 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
import abc
|
||||
import typing
|
||||
|
||||
|
||||
# This exists to break an import cycle. It is normally accessible from the
|
||||
# ciphers module.
|
||||
|
||||
|
||||
class CipherAlgorithm(metaclass=abc.ABCMeta):
|
||||
@abc.abstractproperty
|
||||
def name(self) -> str:
|
||||
"""
|
||||
A string naming this mode (e.g. "AES", "Camellia").
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
def key_sizes(self) -> typing.FrozenSet[int]:
|
||||
"""
|
||||
Valid key sizes for this algorithm in bits
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
def key_size(self) -> int:
|
||||
"""
|
||||
The size of the key being used as an integer in bits (e.g. 128, 256).
|
||||
"""
|
||||
|
||||
|
||||
class BlockCipherAlgorithm(metaclass=abc.ABCMeta):
|
||||
@abc.abstractproperty
|
||||
def block_size(self) -> int:
|
||||
"""
|
||||
The size of a block as an integer in bits (e.g. 64, 128).
|
||||
"""
|
||||
@@ -0,0 +1,55 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
import abc
|
||||
|
||||
from cryptography import utils
|
||||
|
||||
# This exists to break an import cycle. These classes are normally accessible
|
||||
# from the serialization module.
|
||||
|
||||
|
||||
class Encoding(utils.Enum):
|
||||
PEM = "PEM"
|
||||
DER = "DER"
|
||||
OpenSSH = "OpenSSH"
|
||||
Raw = "Raw"
|
||||
X962 = "ANSI X9.62"
|
||||
SMIME = "S/MIME"
|
||||
|
||||
|
||||
class PrivateFormat(utils.Enum):
|
||||
PKCS8 = "PKCS8"
|
||||
TraditionalOpenSSL = "TraditionalOpenSSL"
|
||||
Raw = "Raw"
|
||||
OpenSSH = "OpenSSH"
|
||||
|
||||
|
||||
class PublicFormat(utils.Enum):
|
||||
SubjectPublicKeyInfo = "X.509 subjectPublicKeyInfo with PKCS#1"
|
||||
PKCS1 = "Raw PKCS#1"
|
||||
OpenSSH = "OpenSSH"
|
||||
Raw = "Raw"
|
||||
CompressedPoint = "X9.62 Compressed Point"
|
||||
UncompressedPoint = "X9.62 Uncompressed Point"
|
||||
|
||||
|
||||
class ParameterFormat(utils.Enum):
|
||||
PKCS3 = "PKCS3"
|
||||
|
||||
|
||||
class KeySerializationEncryption(metaclass=abc.ABCMeta):
|
||||
pass
|
||||
|
||||
|
||||
class BestAvailableEncryption(KeySerializationEncryption):
|
||||
def __init__(self, password: bytes):
|
||||
if not isinstance(password, bytes) or len(password) == 0:
|
||||
raise ValueError("Password must be 1 or more bytes.")
|
||||
|
||||
self.password = password
|
||||
|
||||
|
||||
class NoEncryption(KeySerializationEncryption):
|
||||
pass
|
||||
@@ -0,0 +1,35 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import abc
|
||||
|
||||
|
||||
class AsymmetricSignatureContext(metaclass=abc.ABCMeta):
|
||||
@abc.abstractmethod
|
||||
def update(self, data: bytes) -> None:
|
||||
"""
|
||||
Processes the provided bytes and returns nothing.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def finalize(self) -> bytes:
|
||||
"""
|
||||
Returns the signature as bytes.
|
||||
"""
|
||||
|
||||
|
||||
class AsymmetricVerificationContext(metaclass=abc.ABCMeta):
|
||||
@abc.abstractmethod
|
||||
def update(self, data: bytes) -> None:
|
||||
"""
|
||||
Processes the provided bytes and returns nothing.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def verify(self) -> None:
|
||||
"""
|
||||
Raises an exception if the bytes provided to update do not match the
|
||||
signature or the signature does not match the public key.
|
||||
"""
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,239 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import abc
|
||||
import typing
|
||||
|
||||
from cryptography.hazmat.backends import _get_backend
|
||||
from cryptography.hazmat.backends.interfaces import Backend
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
|
||||
|
||||
_MIN_MODULUS_SIZE = 512
|
||||
|
||||
|
||||
def generate_parameters(
|
||||
generator: int, key_size: int, backend: typing.Optional[Backend] = None
|
||||
) -> "DHParameters":
|
||||
backend = _get_backend(backend)
|
||||
return backend.generate_dh_parameters(generator, key_size)
|
||||
|
||||
|
||||
class DHParameterNumbers(object):
|
||||
def __init__(self, p: int, g: int, q: typing.Optional[int] = None) -> None:
|
||||
if not isinstance(p, int) or not isinstance(g, int):
|
||||
raise TypeError("p and g must be integers")
|
||||
if q is not None and not isinstance(q, int):
|
||||
raise TypeError("q must be integer or None")
|
||||
|
||||
if g < 2:
|
||||
raise ValueError("DH generator must be 2 or greater")
|
||||
|
||||
if p.bit_length() < _MIN_MODULUS_SIZE:
|
||||
raise ValueError(
|
||||
"p (modulus) must be at least {}-bit".format(_MIN_MODULUS_SIZE)
|
||||
)
|
||||
|
||||
self._p = p
|
||||
self._g = g
|
||||
self._q = q
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, DHParameterNumbers):
|
||||
return NotImplemented
|
||||
|
||||
return (
|
||||
self._p == other._p and self._g == other._g and self._q == other._q
|
||||
)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def parameters(
|
||||
self, backend: typing.Optional[Backend] = None
|
||||
) -> "DHParameters":
|
||||
backend = _get_backend(backend)
|
||||
return backend.load_dh_parameter_numbers(self)
|
||||
|
||||
p = property(lambda self: self._p)
|
||||
g = property(lambda self: self._g)
|
||||
q = property(lambda self: self._q)
|
||||
|
||||
|
||||
class DHPublicNumbers(object):
|
||||
def __init__(self, y: int, parameter_numbers: DHParameterNumbers) -> None:
|
||||
if not isinstance(y, int):
|
||||
raise TypeError("y must be an integer.")
|
||||
|
||||
if not isinstance(parameter_numbers, DHParameterNumbers):
|
||||
raise TypeError(
|
||||
"parameters must be an instance of DHParameterNumbers."
|
||||
)
|
||||
|
||||
self._y = y
|
||||
self._parameter_numbers = parameter_numbers
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, DHPublicNumbers):
|
||||
return NotImplemented
|
||||
|
||||
return (
|
||||
self._y == other._y
|
||||
and self._parameter_numbers == other._parameter_numbers
|
||||
)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def public_key(
|
||||
self, backend: typing.Optional[Backend] = None
|
||||
) -> "DHPublicKey":
|
||||
backend = _get_backend(backend)
|
||||
return backend.load_dh_public_numbers(self)
|
||||
|
||||
y = property(lambda self: self._y)
|
||||
parameter_numbers = property(lambda self: self._parameter_numbers)
|
||||
|
||||
|
||||
class DHPrivateNumbers(object):
|
||||
def __init__(self, x: int, public_numbers: DHPublicNumbers) -> None:
|
||||
if not isinstance(x, int):
|
||||
raise TypeError("x must be an integer.")
|
||||
|
||||
if not isinstance(public_numbers, DHPublicNumbers):
|
||||
raise TypeError(
|
||||
"public_numbers must be an instance of " "DHPublicNumbers."
|
||||
)
|
||||
|
||||
self._x = x
|
||||
self._public_numbers = public_numbers
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, DHPrivateNumbers):
|
||||
return NotImplemented
|
||||
|
||||
return (
|
||||
self._x == other._x
|
||||
and self._public_numbers == other._public_numbers
|
||||
)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def private_key(
|
||||
self, backend: typing.Optional[Backend] = None
|
||||
) -> "DHPrivateKey":
|
||||
backend = _get_backend(backend)
|
||||
return backend.load_dh_private_numbers(self)
|
||||
|
||||
public_numbers = property(lambda self: self._public_numbers)
|
||||
x = property(lambda self: self._x)
|
||||
|
||||
|
||||
class DHParameters(metaclass=abc.ABCMeta):
|
||||
@abc.abstractmethod
|
||||
def generate_private_key(self) -> "DHPrivateKey":
|
||||
"""
|
||||
Generates and returns a DHPrivateKey.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def parameter_bytes(
|
||||
self,
|
||||
encoding: "serialization.Encoding",
|
||||
format: "serialization.ParameterFormat",
|
||||
) -> bytes:
|
||||
"""
|
||||
Returns the parameters serialized as bytes.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def parameter_numbers(self) -> DHParameterNumbers:
|
||||
"""
|
||||
Returns a DHParameterNumbers.
|
||||
"""
|
||||
|
||||
|
||||
DHParametersWithSerialization = DHParameters
|
||||
|
||||
|
||||
class DHPublicKey(metaclass=abc.ABCMeta):
|
||||
@abc.abstractproperty
|
||||
def key_size(self) -> int:
|
||||
"""
|
||||
The bit length of the prime modulus.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def parameters(self) -> DHParameters:
|
||||
"""
|
||||
The DHParameters object associated with this public key.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def public_numbers(self) -> DHPublicNumbers:
|
||||
"""
|
||||
Returns a DHPublicNumbers.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def public_bytes(
|
||||
self,
|
||||
encoding: "serialization.Encoding",
|
||||
format: "serialization.PublicFormat",
|
||||
) -> bytes:
|
||||
"""
|
||||
Returns the key serialized as bytes.
|
||||
"""
|
||||
|
||||
|
||||
DHPublicKeyWithSerialization = DHPublicKey
|
||||
|
||||
|
||||
class DHPrivateKey(metaclass=abc.ABCMeta):
|
||||
@abc.abstractproperty
|
||||
def key_size(self) -> int:
|
||||
"""
|
||||
The bit length of the prime modulus.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def public_key(self) -> DHPublicKey:
|
||||
"""
|
||||
The DHPublicKey associated with this private key.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def parameters(self) -> DHParameters:
|
||||
"""
|
||||
The DHParameters object associated with this private key.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def exchange(self, peer_public_key: DHPublicKey) -> bytes:
|
||||
"""
|
||||
Given peer's DHPublicKey, carry out the key exchange and
|
||||
return shared key as bytes.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def private_numbers(self) -> DHPrivateNumbers:
|
||||
"""
|
||||
Returns a DHPrivateNumbers.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def private_bytes(
|
||||
self,
|
||||
encoding: "serialization.Encoding",
|
||||
format: "serialization.PrivateFormat",
|
||||
encryption_algorithm: "serialization.KeySerializationEncryption",
|
||||
) -> bytes:
|
||||
"""
|
||||
Returns the key serialized as bytes.
|
||||
"""
|
||||
|
||||
|
||||
DHPrivateKeyWithSerialization = DHPrivateKey
|
||||
@@ -0,0 +1,297 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import abc
|
||||
import typing
|
||||
|
||||
from cryptography.hazmat.backends import _get_backend
|
||||
from cryptography.hazmat.backends.interfaces import Backend
|
||||
from cryptography.hazmat.primitives import _serialization, hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import (
|
||||
AsymmetricSignatureContext,
|
||||
AsymmetricVerificationContext,
|
||||
utils as asym_utils,
|
||||
)
|
||||
|
||||
|
||||
class DSAParameters(metaclass=abc.ABCMeta):
|
||||
@abc.abstractmethod
|
||||
def generate_private_key(self) -> "DSAPrivateKey":
|
||||
"""
|
||||
Generates and returns a DSAPrivateKey.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def parameter_numbers(self) -> "DSAParameterNumbers":
|
||||
"""
|
||||
Returns a DSAParameterNumbers.
|
||||
"""
|
||||
|
||||
|
||||
DSAParametersWithNumbers = DSAParameters
|
||||
|
||||
|
||||
class DSAPrivateKey(metaclass=abc.ABCMeta):
|
||||
@abc.abstractproperty
|
||||
def key_size(self) -> int:
|
||||
"""
|
||||
The bit length of the prime modulus.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def public_key(self) -> "DSAPublicKey":
|
||||
"""
|
||||
The DSAPublicKey associated with this private key.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def parameters(self) -> DSAParameters:
|
||||
"""
|
||||
The DSAParameters object associated with this private key.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def signer(
|
||||
self,
|
||||
signature_algorithm: hashes.HashAlgorithm,
|
||||
) -> AsymmetricSignatureContext:
|
||||
"""
|
||||
Returns an AsymmetricSignatureContext used for signing data.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def sign(
|
||||
self,
|
||||
data: bytes,
|
||||
algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm],
|
||||
) -> bytes:
|
||||
"""
|
||||
Signs the data
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def private_numbers(self) -> "DSAPrivateNumbers":
|
||||
"""
|
||||
Returns a DSAPrivateNumbers.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def private_bytes(
|
||||
self,
|
||||
encoding: _serialization.Encoding,
|
||||
format: _serialization.PrivateFormat,
|
||||
encryption_algorithm: _serialization.KeySerializationEncryption,
|
||||
) -> bytes:
|
||||
"""
|
||||
Returns the key serialized as bytes.
|
||||
"""
|
||||
|
||||
|
||||
DSAPrivateKeyWithSerialization = DSAPrivateKey
|
||||
|
||||
|
||||
class DSAPublicKey(metaclass=abc.ABCMeta):
|
||||
@abc.abstractproperty
|
||||
def key_size(self) -> int:
|
||||
"""
|
||||
The bit length of the prime modulus.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def parameters(self) -> DSAParameters:
|
||||
"""
|
||||
The DSAParameters object associated with this public key.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def verifier(
|
||||
self,
|
||||
signature: bytes,
|
||||
signature_algorithm: hashes.HashAlgorithm,
|
||||
) -> AsymmetricVerificationContext:
|
||||
"""
|
||||
Returns an AsymmetricVerificationContext used for signing data.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def public_numbers(self) -> "DSAPublicNumbers":
|
||||
"""
|
||||
Returns a DSAPublicNumbers.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def public_bytes(
|
||||
self,
|
||||
encoding: _serialization.Encoding,
|
||||
format: _serialization.PublicFormat,
|
||||
) -> bytes:
|
||||
"""
|
||||
Returns the key serialized as bytes.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def verify(
|
||||
self,
|
||||
signature: bytes,
|
||||
data: bytes,
|
||||
algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm],
|
||||
) -> None:
|
||||
"""
|
||||
Verifies the signature of the data.
|
||||
"""
|
||||
|
||||
|
||||
DSAPublicKeyWithSerialization = DSAPublicKey
|
||||
|
||||
|
||||
class DSAParameterNumbers(object):
|
||||
def __init__(self, p: int, q: int, g: int):
|
||||
if (
|
||||
not isinstance(p, int)
|
||||
or not isinstance(q, int)
|
||||
or not isinstance(g, int)
|
||||
):
|
||||
raise TypeError(
|
||||
"DSAParameterNumbers p, q, and g arguments must be integers."
|
||||
)
|
||||
|
||||
self._p = p
|
||||
self._q = q
|
||||
self._g = g
|
||||
|
||||
p = property(lambda self: self._p)
|
||||
q = property(lambda self: self._q)
|
||||
g = property(lambda self: self._g)
|
||||
|
||||
def parameters(
|
||||
self, backend: typing.Optional[Backend] = None
|
||||
) -> DSAParameters:
|
||||
backend = _get_backend(backend)
|
||||
return backend.load_dsa_parameter_numbers(self)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, DSAParameterNumbers):
|
||||
return NotImplemented
|
||||
|
||||
return self.p == other.p and self.q == other.q and self.g == other.g
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
"<DSAParameterNumbers(p={self.p}, q={self.q}, "
|
||||
"g={self.g})>".format(self=self)
|
||||
)
|
||||
|
||||
|
||||
class DSAPublicNumbers(object):
|
||||
def __init__(self, y: int, parameter_numbers: DSAParameterNumbers):
|
||||
if not isinstance(y, int):
|
||||
raise TypeError("DSAPublicNumbers y argument must be an integer.")
|
||||
|
||||
if not isinstance(parameter_numbers, DSAParameterNumbers):
|
||||
raise TypeError(
|
||||
"parameter_numbers must be a DSAParameterNumbers instance."
|
||||
)
|
||||
|
||||
self._y = y
|
||||
self._parameter_numbers = parameter_numbers
|
||||
|
||||
y = property(lambda self: self._y)
|
||||
parameter_numbers = property(lambda self: self._parameter_numbers)
|
||||
|
||||
def public_key(
|
||||
self, backend: typing.Optional[Backend] = None
|
||||
) -> DSAPublicKey:
|
||||
backend = _get_backend(backend)
|
||||
return backend.load_dsa_public_numbers(self)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, DSAPublicNumbers):
|
||||
return NotImplemented
|
||||
|
||||
return (
|
||||
self.y == other.y
|
||||
and self.parameter_numbers == other.parameter_numbers
|
||||
)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
"<DSAPublicNumbers(y={self.y}, "
|
||||
"parameter_numbers={self.parameter_numbers})>".format(self=self)
|
||||
)
|
||||
|
||||
|
||||
class DSAPrivateNumbers(object):
|
||||
def __init__(self, x: int, public_numbers: DSAPublicNumbers):
|
||||
if not isinstance(x, int):
|
||||
raise TypeError("DSAPrivateNumbers x argument must be an integer.")
|
||||
|
||||
if not isinstance(public_numbers, DSAPublicNumbers):
|
||||
raise TypeError(
|
||||
"public_numbers must be a DSAPublicNumbers instance."
|
||||
)
|
||||
self._public_numbers = public_numbers
|
||||
self._x = x
|
||||
|
||||
x = property(lambda self: self._x)
|
||||
public_numbers = property(lambda self: self._public_numbers)
|
||||
|
||||
def private_key(
|
||||
self, backend: typing.Optional[Backend] = None
|
||||
) -> DSAPrivateKey:
|
||||
backend = _get_backend(backend)
|
||||
return backend.load_dsa_private_numbers(self)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, DSAPrivateNumbers):
|
||||
return NotImplemented
|
||||
|
||||
return (
|
||||
self.x == other.x and self.public_numbers == other.public_numbers
|
||||
)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
|
||||
def generate_parameters(
|
||||
key_size: int, backend: typing.Optional[Backend] = None
|
||||
) -> DSAParameters:
|
||||
backend = _get_backend(backend)
|
||||
return backend.generate_dsa_parameters(key_size)
|
||||
|
||||
|
||||
def generate_private_key(
|
||||
key_size: int, backend: typing.Optional[Backend] = None
|
||||
) -> DSAPrivateKey:
|
||||
backend = _get_backend(backend)
|
||||
return backend.generate_dsa_private_key_and_parameters(key_size)
|
||||
|
||||
|
||||
def _check_dsa_parameters(parameters: DSAParameterNumbers) -> None:
|
||||
if parameters.p.bit_length() not in [1024, 2048, 3072, 4096]:
|
||||
raise ValueError(
|
||||
"p must be exactly 1024, 2048, 3072, or 4096 bits long"
|
||||
)
|
||||
if parameters.q.bit_length() not in [160, 224, 256]:
|
||||
raise ValueError("q must be exactly 160, 224, or 256 bits long")
|
||||
|
||||
if not (1 < parameters.g < parameters.p):
|
||||
raise ValueError("g, p don't satisfy 1 < g < p.")
|
||||
|
||||
|
||||
def _check_dsa_private_numbers(numbers: DSAPrivateNumbers) -> None:
|
||||
parameters = numbers.public_numbers.parameter_numbers
|
||||
_check_dsa_parameters(parameters)
|
||||
if numbers.x <= 0 or numbers.x >= parameters.q:
|
||||
raise ValueError("x must be > 0 and < q.")
|
||||
|
||||
if numbers.public_numbers.y != pow(parameters.g, numbers.x, parameters.p):
|
||||
raise ValueError("y must be equal to (g ** x % p).")
|
||||
@@ -0,0 +1,533 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import abc
|
||||
import typing
|
||||
import warnings
|
||||
|
||||
from cryptography import utils
|
||||
from cryptography.hazmat._oid import ObjectIdentifier
|
||||
from cryptography.hazmat.backends import _get_backend
|
||||
from cryptography.hazmat.backends.interfaces import Backend
|
||||
from cryptography.hazmat.primitives import _serialization, hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import (
|
||||
AsymmetricSignatureContext,
|
||||
AsymmetricVerificationContext,
|
||||
utils as asym_utils,
|
||||
)
|
||||
|
||||
|
||||
class EllipticCurveOID(object):
|
||||
SECP192R1 = ObjectIdentifier("1.2.840.10045.3.1.1")
|
||||
SECP224R1 = ObjectIdentifier("1.3.132.0.33")
|
||||
SECP256K1 = ObjectIdentifier("1.3.132.0.10")
|
||||
SECP256R1 = ObjectIdentifier("1.2.840.10045.3.1.7")
|
||||
SECP384R1 = ObjectIdentifier("1.3.132.0.34")
|
||||
SECP521R1 = ObjectIdentifier("1.3.132.0.35")
|
||||
BRAINPOOLP256R1 = ObjectIdentifier("1.3.36.3.3.2.8.1.1.7")
|
||||
BRAINPOOLP384R1 = ObjectIdentifier("1.3.36.3.3.2.8.1.1.11")
|
||||
BRAINPOOLP512R1 = ObjectIdentifier("1.3.36.3.3.2.8.1.1.13")
|
||||
SECT163K1 = ObjectIdentifier("1.3.132.0.1")
|
||||
SECT163R2 = ObjectIdentifier("1.3.132.0.15")
|
||||
SECT233K1 = ObjectIdentifier("1.3.132.0.26")
|
||||
SECT233R1 = ObjectIdentifier("1.3.132.0.27")
|
||||
SECT283K1 = ObjectIdentifier("1.3.132.0.16")
|
||||
SECT283R1 = ObjectIdentifier("1.3.132.0.17")
|
||||
SECT409K1 = ObjectIdentifier("1.3.132.0.36")
|
||||
SECT409R1 = ObjectIdentifier("1.3.132.0.37")
|
||||
SECT571K1 = ObjectIdentifier("1.3.132.0.38")
|
||||
SECT571R1 = ObjectIdentifier("1.3.132.0.39")
|
||||
|
||||
|
||||
class EllipticCurve(metaclass=abc.ABCMeta):
|
||||
@abc.abstractproperty
|
||||
def name(self) -> str:
|
||||
"""
|
||||
The name of the curve. e.g. secp256r1.
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
def key_size(self) -> int:
|
||||
"""
|
||||
Bit size of a secret scalar for the curve.
|
||||
"""
|
||||
|
||||
|
||||
class EllipticCurveSignatureAlgorithm(metaclass=abc.ABCMeta):
|
||||
@abc.abstractproperty
|
||||
def algorithm(
|
||||
self,
|
||||
) -> typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm]:
|
||||
"""
|
||||
The digest algorithm used with this signature.
|
||||
"""
|
||||
|
||||
|
||||
class EllipticCurvePrivateKey(metaclass=abc.ABCMeta):
|
||||
@abc.abstractmethod
|
||||
def signer(
|
||||
self,
|
||||
signature_algorithm: EllipticCurveSignatureAlgorithm,
|
||||
) -> AsymmetricSignatureContext:
|
||||
"""
|
||||
Returns an AsymmetricSignatureContext used for signing data.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def exchange(
|
||||
self, algorithm: "ECDH", peer_public_key: "EllipticCurvePublicKey"
|
||||
) -> bytes:
|
||||
"""
|
||||
Performs a key exchange operation using the provided algorithm with the
|
||||
provided peer's public key.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def public_key(self) -> "EllipticCurvePublicKey":
|
||||
"""
|
||||
The EllipticCurvePublicKey for this private key.
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
def curve(self) -> EllipticCurve:
|
||||
"""
|
||||
The EllipticCurve that this key is on.
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
def key_size(self) -> int:
|
||||
"""
|
||||
Bit size of a secret scalar for the curve.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def sign(
|
||||
self,
|
||||
data: bytes,
|
||||
signature_algorithm: EllipticCurveSignatureAlgorithm,
|
||||
) -> bytes:
|
||||
"""
|
||||
Signs the data
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def private_numbers(self) -> "EllipticCurvePrivateNumbers":
|
||||
"""
|
||||
Returns an EllipticCurvePrivateNumbers.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def private_bytes(
|
||||
self,
|
||||
encoding: _serialization.Encoding,
|
||||
format: _serialization.PrivateFormat,
|
||||
encryption_algorithm: _serialization.KeySerializationEncryption,
|
||||
) -> bytes:
|
||||
"""
|
||||
Returns the key serialized as bytes.
|
||||
"""
|
||||
|
||||
|
||||
EllipticCurvePrivateKeyWithSerialization = EllipticCurvePrivateKey
|
||||
|
||||
|
||||
class EllipticCurvePublicKey(metaclass=abc.ABCMeta):
|
||||
@abc.abstractmethod
|
||||
def verifier(
|
||||
self,
|
||||
signature: bytes,
|
||||
signature_algorithm: EllipticCurveSignatureAlgorithm,
|
||||
) -> AsymmetricVerificationContext:
|
||||
"""
|
||||
Returns an AsymmetricVerificationContext used for signing data.
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
def curve(self) -> EllipticCurve:
|
||||
"""
|
||||
The EllipticCurve that this key is on.
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
def key_size(self) -> int:
|
||||
"""
|
||||
Bit size of a secret scalar for the curve.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def public_numbers(self) -> "EllipticCurvePublicNumbers":
|
||||
"""
|
||||
Returns an EllipticCurvePublicNumbers.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def public_bytes(
|
||||
self,
|
||||
encoding: _serialization.Encoding,
|
||||
format: _serialization.PublicFormat,
|
||||
) -> bytes:
|
||||
"""
|
||||
Returns the key serialized as bytes.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def verify(
|
||||
self,
|
||||
signature: bytes,
|
||||
data: bytes,
|
||||
signature_algorithm: EllipticCurveSignatureAlgorithm,
|
||||
) -> None:
|
||||
"""
|
||||
Verifies the signature of the data.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def from_encoded_point(
|
||||
cls, curve: EllipticCurve, data: bytes
|
||||
) -> "EllipticCurvePublicKey":
|
||||
utils._check_bytes("data", data)
|
||||
|
||||
if not isinstance(curve, EllipticCurve):
|
||||
raise TypeError("curve must be an EllipticCurve instance")
|
||||
|
||||
if len(data) == 0:
|
||||
raise ValueError("data must not be an empty byte string")
|
||||
|
||||
if data[0] not in [0x02, 0x03, 0x04]:
|
||||
raise ValueError("Unsupported elliptic curve point type")
|
||||
|
||||
from cryptography.hazmat.backends.openssl.backend import backend
|
||||
|
||||
return backend.load_elliptic_curve_public_bytes(curve, data)
|
||||
|
||||
|
||||
EllipticCurvePublicKeyWithSerialization = EllipticCurvePublicKey
|
||||
|
||||
|
||||
class SECT571R1(EllipticCurve):
|
||||
name = "sect571r1"
|
||||
key_size = 570
|
||||
|
||||
|
||||
class SECT409R1(EllipticCurve):
|
||||
name = "sect409r1"
|
||||
key_size = 409
|
||||
|
||||
|
||||
class SECT283R1(EllipticCurve):
|
||||
name = "sect283r1"
|
||||
key_size = 283
|
||||
|
||||
|
||||
class SECT233R1(EllipticCurve):
|
||||
name = "sect233r1"
|
||||
key_size = 233
|
||||
|
||||
|
||||
class SECT163R2(EllipticCurve):
|
||||
name = "sect163r2"
|
||||
key_size = 163
|
||||
|
||||
|
||||
class SECT571K1(EllipticCurve):
|
||||
name = "sect571k1"
|
||||
key_size = 571
|
||||
|
||||
|
||||
class SECT409K1(EllipticCurve):
|
||||
name = "sect409k1"
|
||||
key_size = 409
|
||||
|
||||
|
||||
class SECT283K1(EllipticCurve):
|
||||
name = "sect283k1"
|
||||
key_size = 283
|
||||
|
||||
|
||||
class SECT233K1(EllipticCurve):
|
||||
name = "sect233k1"
|
||||
key_size = 233
|
||||
|
||||
|
||||
class SECT163K1(EllipticCurve):
|
||||
name = "sect163k1"
|
||||
key_size = 163
|
||||
|
||||
|
||||
class SECP521R1(EllipticCurve):
|
||||
name = "secp521r1"
|
||||
key_size = 521
|
||||
|
||||
|
||||
class SECP384R1(EllipticCurve):
|
||||
name = "secp384r1"
|
||||
key_size = 384
|
||||
|
||||
|
||||
class SECP256R1(EllipticCurve):
|
||||
name = "secp256r1"
|
||||
key_size = 256
|
||||
|
||||
|
||||
class SECP256K1(EllipticCurve):
|
||||
name = "secp256k1"
|
||||
key_size = 256
|
||||
|
||||
|
||||
class SECP224R1(EllipticCurve):
|
||||
name = "secp224r1"
|
||||
key_size = 224
|
||||
|
||||
|
||||
class SECP192R1(EllipticCurve):
|
||||
name = "secp192r1"
|
||||
key_size = 192
|
||||
|
||||
|
||||
class BrainpoolP256R1(EllipticCurve):
|
||||
name = "brainpoolP256r1"
|
||||
key_size = 256
|
||||
|
||||
|
||||
class BrainpoolP384R1(EllipticCurve):
|
||||
name = "brainpoolP384r1"
|
||||
key_size = 384
|
||||
|
||||
|
||||
class BrainpoolP512R1(EllipticCurve):
|
||||
name = "brainpoolP512r1"
|
||||
key_size = 512
|
||||
|
||||
|
||||
_CURVE_TYPES: typing.Dict[str, typing.Type[EllipticCurve]] = {
|
||||
"prime192v1": SECP192R1,
|
||||
"prime256v1": SECP256R1,
|
||||
"secp192r1": SECP192R1,
|
||||
"secp224r1": SECP224R1,
|
||||
"secp256r1": SECP256R1,
|
||||
"secp384r1": SECP384R1,
|
||||
"secp521r1": SECP521R1,
|
||||
"secp256k1": SECP256K1,
|
||||
"sect163k1": SECT163K1,
|
||||
"sect233k1": SECT233K1,
|
||||
"sect283k1": SECT283K1,
|
||||
"sect409k1": SECT409K1,
|
||||
"sect571k1": SECT571K1,
|
||||
"sect163r2": SECT163R2,
|
||||
"sect233r1": SECT233R1,
|
||||
"sect283r1": SECT283R1,
|
||||
"sect409r1": SECT409R1,
|
||||
"sect571r1": SECT571R1,
|
||||
"brainpoolP256r1": BrainpoolP256R1,
|
||||
"brainpoolP384r1": BrainpoolP384R1,
|
||||
"brainpoolP512r1": BrainpoolP512R1,
|
||||
}
|
||||
|
||||
|
||||
class ECDSA(EllipticCurveSignatureAlgorithm):
|
||||
def __init__(
|
||||
self,
|
||||
algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm],
|
||||
):
|
||||
self._algorithm = algorithm
|
||||
|
||||
@property
|
||||
def algorithm(
|
||||
self,
|
||||
) -> typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm]:
|
||||
return self._algorithm
|
||||
|
||||
|
||||
def generate_private_key(
|
||||
curve: EllipticCurve, backend: typing.Optional[Backend] = None
|
||||
) -> EllipticCurvePrivateKey:
|
||||
backend = _get_backend(backend)
|
||||
return backend.generate_elliptic_curve_private_key(curve)
|
||||
|
||||
|
||||
def derive_private_key(
|
||||
private_value: int,
|
||||
curve: EllipticCurve,
|
||||
backend: typing.Optional[Backend] = None,
|
||||
) -> EllipticCurvePrivateKey:
|
||||
backend = _get_backend(backend)
|
||||
if not isinstance(private_value, int):
|
||||
raise TypeError("private_value must be an integer type.")
|
||||
|
||||
if private_value <= 0:
|
||||
raise ValueError("private_value must be a positive integer.")
|
||||
|
||||
if not isinstance(curve, EllipticCurve):
|
||||
raise TypeError("curve must provide the EllipticCurve interface.")
|
||||
|
||||
return backend.derive_elliptic_curve_private_key(private_value, curve)
|
||||
|
||||
|
||||
class EllipticCurvePublicNumbers(object):
|
||||
def __init__(self, x: int, y: int, curve: EllipticCurve):
|
||||
if not isinstance(x, int) or not isinstance(y, int):
|
||||
raise TypeError("x and y must be integers.")
|
||||
|
||||
if not isinstance(curve, EllipticCurve):
|
||||
raise TypeError("curve must provide the EllipticCurve interface.")
|
||||
|
||||
self._y = y
|
||||
self._x = x
|
||||
self._curve = curve
|
||||
|
||||
def public_key(
|
||||
self, backend: typing.Optional[Backend] = None
|
||||
) -> EllipticCurvePublicKey:
|
||||
backend = _get_backend(backend)
|
||||
return backend.load_elliptic_curve_public_numbers(self)
|
||||
|
||||
def encode_point(self) -> bytes:
|
||||
warnings.warn(
|
||||
"encode_point has been deprecated on EllipticCurvePublicNumbers"
|
||||
" and will be removed in a future version. Please use "
|
||||
"EllipticCurvePublicKey.public_bytes to obtain both "
|
||||
"compressed and uncompressed point encoding.",
|
||||
utils.PersistentlyDeprecated2019,
|
||||
stacklevel=2,
|
||||
)
|
||||
# key_size is in bits. Convert to bytes and round up
|
||||
byte_length = (self.curve.key_size + 7) // 8
|
||||
return (
|
||||
b"\x04"
|
||||
+ utils.int_to_bytes(self.x, byte_length)
|
||||
+ utils.int_to_bytes(self.y, byte_length)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_encoded_point(
|
||||
cls, curve: EllipticCurve, data: bytes
|
||||
) -> "EllipticCurvePublicNumbers":
|
||||
if not isinstance(curve, EllipticCurve):
|
||||
raise TypeError("curve must be an EllipticCurve instance")
|
||||
|
||||
warnings.warn(
|
||||
"Support for unsafe construction of public numbers from "
|
||||
"encoded data will be removed in a future version. "
|
||||
"Please use EllipticCurvePublicKey.from_encoded_point",
|
||||
utils.PersistentlyDeprecated2019,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
if data.startswith(b"\x04"):
|
||||
# key_size is in bits. Convert to bytes and round up
|
||||
byte_length = (curve.key_size + 7) // 8
|
||||
if len(data) == 2 * byte_length + 1:
|
||||
x = int.from_bytes(data[1 : byte_length + 1], "big")
|
||||
y = int.from_bytes(data[byte_length + 1 :], "big")
|
||||
return cls(x, y, curve)
|
||||
else:
|
||||
raise ValueError("Invalid elliptic curve point data length")
|
||||
else:
|
||||
raise ValueError("Unsupported elliptic curve point type")
|
||||
|
||||
curve = property(lambda self: self._curve)
|
||||
x = property(lambda self: self._x)
|
||||
y = property(lambda self: self._y)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, EllipticCurvePublicNumbers):
|
||||
return NotImplemented
|
||||
|
||||
return (
|
||||
self.x == other.x
|
||||
and self.y == other.y
|
||||
and self.curve.name == other.curve.name
|
||||
and self.curve.key_size == other.curve.key_size
|
||||
)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.x, self.y, self.curve.name, self.curve.key_size))
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
"<EllipticCurvePublicNumbers(curve={0.curve.name}, x={0.x}, "
|
||||
"y={0.y}>".format(self)
|
||||
)
|
||||
|
||||
|
||||
class EllipticCurvePrivateNumbers(object):
|
||||
def __init__(
|
||||
self, private_value: int, public_numbers: EllipticCurvePublicNumbers
|
||||
):
|
||||
if not isinstance(private_value, int):
|
||||
raise TypeError("private_value must be an integer.")
|
||||
|
||||
if not isinstance(public_numbers, EllipticCurvePublicNumbers):
|
||||
raise TypeError(
|
||||
"public_numbers must be an EllipticCurvePublicNumbers "
|
||||
"instance."
|
||||
)
|
||||
|
||||
self._private_value = private_value
|
||||
self._public_numbers = public_numbers
|
||||
|
||||
def private_key(
|
||||
self, backend: typing.Optional[Backend] = None
|
||||
) -> EllipticCurvePrivateKey:
|
||||
backend = _get_backend(backend)
|
||||
return backend.load_elliptic_curve_private_numbers(self)
|
||||
|
||||
private_value = property(lambda self: self._private_value)
|
||||
public_numbers = property(lambda self: self._public_numbers)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, EllipticCurvePrivateNumbers):
|
||||
return NotImplemented
|
||||
|
||||
return (
|
||||
self.private_value == other.private_value
|
||||
and self.public_numbers == other.public_numbers
|
||||
)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.private_value, self.public_numbers))
|
||||
|
||||
|
||||
class ECDH(object):
|
||||
pass
|
||||
|
||||
|
||||
_OID_TO_CURVE = {
|
||||
EllipticCurveOID.SECP192R1: SECP192R1,
|
||||
EllipticCurveOID.SECP224R1: SECP224R1,
|
||||
EllipticCurveOID.SECP256K1: SECP256K1,
|
||||
EllipticCurveOID.SECP256R1: SECP256R1,
|
||||
EllipticCurveOID.SECP384R1: SECP384R1,
|
||||
EllipticCurveOID.SECP521R1: SECP521R1,
|
||||
EllipticCurveOID.BRAINPOOLP256R1: BrainpoolP256R1,
|
||||
EllipticCurveOID.BRAINPOOLP384R1: BrainpoolP384R1,
|
||||
EllipticCurveOID.BRAINPOOLP512R1: BrainpoolP512R1,
|
||||
EllipticCurveOID.SECT163K1: SECT163K1,
|
||||
EllipticCurveOID.SECT163R2: SECT163R2,
|
||||
EllipticCurveOID.SECT233K1: SECT233K1,
|
||||
EllipticCurveOID.SECT233R1: SECT233R1,
|
||||
EllipticCurveOID.SECT283K1: SECT283K1,
|
||||
EllipticCurveOID.SECT283R1: SECT283R1,
|
||||
EllipticCurveOID.SECT409K1: SECT409K1,
|
||||
EllipticCurveOID.SECT409R1: SECT409R1,
|
||||
EllipticCurveOID.SECT571K1: SECT571K1,
|
||||
EllipticCurveOID.SECT571R1: SECT571R1,
|
||||
}
|
||||
|
||||
|
||||
def get_curve_for_oid(oid: ObjectIdentifier) -> typing.Type[EllipticCurve]:
|
||||
try:
|
||||
return _OID_TO_CURVE[oid]
|
||||
except KeyError:
|
||||
raise LookupError(
|
||||
"The provided object identifier has no matching elliptic "
|
||||
"curve class"
|
||||
)
|
||||
@@ -0,0 +1,92 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import abc
|
||||
|
||||
from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
|
||||
from cryptography.hazmat.primitives import _serialization
|
||||
|
||||
|
||||
_ED25519_KEY_SIZE = 32
|
||||
_ED25519_SIG_SIZE = 64
|
||||
|
||||
|
||||
class Ed25519PublicKey(metaclass=abc.ABCMeta):
|
||||
@classmethod
|
||||
def from_public_bytes(cls, data: bytes) -> "Ed25519PublicKey":
|
||||
from cryptography.hazmat.backends.openssl.backend import backend
|
||||
|
||||
if not backend.ed25519_supported():
|
||||
raise UnsupportedAlgorithm(
|
||||
"ed25519 is not supported by this version of OpenSSL.",
|
||||
_Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM,
|
||||
)
|
||||
|
||||
return backend.ed25519_load_public_bytes(data)
|
||||
|
||||
@abc.abstractmethod
|
||||
def public_bytes(
|
||||
self,
|
||||
encoding: _serialization.Encoding,
|
||||
format: _serialization.PublicFormat,
|
||||
) -> bytes:
|
||||
"""
|
||||
The serialized bytes of the public key.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def verify(self, signature: bytes, data: bytes) -> None:
|
||||
"""
|
||||
Verify the signature.
|
||||
"""
|
||||
|
||||
|
||||
class Ed25519PrivateKey(metaclass=abc.ABCMeta):
|
||||
@classmethod
|
||||
def generate(cls) -> "Ed25519PrivateKey":
|
||||
from cryptography.hazmat.backends.openssl.backend import backend
|
||||
|
||||
if not backend.ed25519_supported():
|
||||
raise UnsupportedAlgorithm(
|
||||
"ed25519 is not supported by this version of OpenSSL.",
|
||||
_Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM,
|
||||
)
|
||||
|
||||
return backend.ed25519_generate_key()
|
||||
|
||||
@classmethod
|
||||
def from_private_bytes(cls, data: bytes) -> "Ed25519PrivateKey":
|
||||
from cryptography.hazmat.backends.openssl.backend import backend
|
||||
|
||||
if not backend.ed25519_supported():
|
||||
raise UnsupportedAlgorithm(
|
||||
"ed25519 is not supported by this version of OpenSSL.",
|
||||
_Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM,
|
||||
)
|
||||
|
||||
return backend.ed25519_load_private_bytes(data)
|
||||
|
||||
@abc.abstractmethod
|
||||
def public_key(self) -> Ed25519PublicKey:
|
||||
"""
|
||||
The Ed25519PublicKey derived from the private key.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def private_bytes(
|
||||
self,
|
||||
encoding: _serialization.Encoding,
|
||||
format: _serialization.PrivateFormat,
|
||||
encryption_algorithm: _serialization.KeySerializationEncryption,
|
||||
) -> bytes:
|
||||
"""
|
||||
The serialized bytes of the private key.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def sign(self, data: bytes) -> bytes:
|
||||
"""
|
||||
Signs the data.
|
||||
"""
|
||||
@@ -0,0 +1,87 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import abc
|
||||
|
||||
from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
|
||||
from cryptography.hazmat.primitives import _serialization
|
||||
|
||||
|
||||
class Ed448PublicKey(metaclass=abc.ABCMeta):
|
||||
@classmethod
|
||||
def from_public_bytes(cls, data: bytes) -> "Ed448PublicKey":
|
||||
from cryptography.hazmat.backends.openssl.backend import backend
|
||||
|
||||
if not backend.ed448_supported():
|
||||
raise UnsupportedAlgorithm(
|
||||
"ed448 is not supported by this version of OpenSSL.",
|
||||
_Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM,
|
||||
)
|
||||
|
||||
return backend.ed448_load_public_bytes(data)
|
||||
|
||||
@abc.abstractmethod
|
||||
def public_bytes(
|
||||
self,
|
||||
encoding: _serialization.Encoding,
|
||||
format: _serialization.PublicFormat,
|
||||
) -> bytes:
|
||||
"""
|
||||
The serialized bytes of the public key.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def verify(self, signature: bytes, data: bytes) -> None:
|
||||
"""
|
||||
Verify the signature.
|
||||
"""
|
||||
|
||||
|
||||
class Ed448PrivateKey(metaclass=abc.ABCMeta):
|
||||
@classmethod
|
||||
def generate(cls) -> "Ed448PrivateKey":
|
||||
from cryptography.hazmat.backends.openssl.backend import backend
|
||||
|
||||
if not backend.ed448_supported():
|
||||
raise UnsupportedAlgorithm(
|
||||
"ed448 is not supported by this version of OpenSSL.",
|
||||
_Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM,
|
||||
)
|
||||
return backend.ed448_generate_key()
|
||||
|
||||
@classmethod
|
||||
def from_private_bytes(cls, data: bytes) -> "Ed448PrivateKey":
|
||||
from cryptography.hazmat.backends.openssl.backend import backend
|
||||
|
||||
if not backend.ed448_supported():
|
||||
raise UnsupportedAlgorithm(
|
||||
"ed448 is not supported by this version of OpenSSL.",
|
||||
_Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM,
|
||||
)
|
||||
|
||||
return backend.ed448_load_private_bytes(data)
|
||||
|
||||
@abc.abstractmethod
|
||||
def public_key(self) -> Ed448PublicKey:
|
||||
"""
|
||||
The Ed448PublicKey derived from the private key.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def sign(self, data: bytes) -> bytes:
|
||||
"""
|
||||
Signs the data.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def private_bytes(
|
||||
self,
|
||||
encoding: _serialization.Encoding,
|
||||
format: _serialization.PrivateFormat,
|
||||
encryption_algorithm: _serialization.KeySerializationEncryption,
|
||||
) -> bytes:
|
||||
"""
|
||||
The serialized bytes of the private key.
|
||||
"""
|
||||
@@ -0,0 +1,75 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import typing
|
||||
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives._asymmetric import (
|
||||
AsymmetricPadding as AsymmetricPadding,
|
||||
)
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
|
||||
|
||||
class PKCS1v15(AsymmetricPadding):
|
||||
name = "EMSA-PKCS1-v1_5"
|
||||
|
||||
|
||||
class PSS(AsymmetricPadding):
|
||||
MAX_LENGTH = object()
|
||||
name = "EMSA-PSS"
|
||||
|
||||
def __init__(self, mgf, salt_length):
|
||||
self._mgf = mgf
|
||||
|
||||
if (
|
||||
not isinstance(salt_length, int)
|
||||
and salt_length is not self.MAX_LENGTH
|
||||
):
|
||||
raise TypeError("salt_length must be an integer.")
|
||||
|
||||
if salt_length is not self.MAX_LENGTH and salt_length < 0:
|
||||
raise ValueError("salt_length must be zero or greater.")
|
||||
|
||||
self._salt_length = salt_length
|
||||
|
||||
|
||||
class OAEP(AsymmetricPadding):
|
||||
name = "EME-OAEP"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
mgf: "MGF1",
|
||||
algorithm: hashes.HashAlgorithm,
|
||||
label: typing.Optional[bytes],
|
||||
):
|
||||
if not isinstance(algorithm, hashes.HashAlgorithm):
|
||||
raise TypeError("Expected instance of hashes.HashAlgorithm.")
|
||||
|
||||
self._mgf = mgf
|
||||
self._algorithm = algorithm
|
||||
self._label = label
|
||||
|
||||
|
||||
class MGF1(object):
|
||||
MAX_LENGTH = object()
|
||||
|
||||
def __init__(self, algorithm: hashes.HashAlgorithm):
|
||||
if not isinstance(algorithm, hashes.HashAlgorithm):
|
||||
raise TypeError("Expected instance of hashes.HashAlgorithm.")
|
||||
|
||||
self._algorithm = algorithm
|
||||
|
||||
|
||||
def calculate_max_pss_salt_length(
|
||||
key: typing.Union["rsa.RSAPrivateKey", "rsa.RSAPublicKey"],
|
||||
hash_algorithm: hashes.HashAlgorithm,
|
||||
) -> int:
|
||||
if not isinstance(key, (rsa.RSAPrivateKey, rsa.RSAPublicKey)):
|
||||
raise TypeError("key must be an RSA public or private key")
|
||||
# bit length - 1 per RFC 3447
|
||||
emlen = (key.key_size + 6) // 8
|
||||
salt_length = emlen - hash_algorithm.digest_size - 2
|
||||
assert salt_length >= 0
|
||||
return salt_length
|
||||
@@ -0,0 +1,433 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import abc
|
||||
import typing
|
||||
from math import gcd
|
||||
|
||||
from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
|
||||
from cryptography.hazmat.backends import _get_backend
|
||||
from cryptography.hazmat.backends.interfaces import Backend, RSABackend
|
||||
from cryptography.hazmat.primitives import _serialization, hashes
|
||||
from cryptography.hazmat.primitives._asymmetric import AsymmetricPadding
|
||||
from cryptography.hazmat.primitives.asymmetric import (
|
||||
AsymmetricSignatureContext,
|
||||
AsymmetricVerificationContext,
|
||||
utils as asym_utils,
|
||||
)
|
||||
|
||||
|
||||
class RSAPrivateKey(metaclass=abc.ABCMeta):
|
||||
@abc.abstractmethod
|
||||
def signer(
|
||||
self, padding: AsymmetricPadding, algorithm: hashes.HashAlgorithm
|
||||
) -> AsymmetricSignatureContext:
|
||||
"""
|
||||
Returns an AsymmetricSignatureContext used for signing data.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def decrypt(self, ciphertext: bytes, padding: AsymmetricPadding) -> bytes:
|
||||
"""
|
||||
Decrypts the provided ciphertext.
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
def key_size(self) -> int:
|
||||
"""
|
||||
The bit length of the public modulus.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def public_key(self) -> "RSAPublicKey":
|
||||
"""
|
||||
The RSAPublicKey associated with this private key.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def sign(
|
||||
self,
|
||||
data: bytes,
|
||||
padding: AsymmetricPadding,
|
||||
algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm],
|
||||
) -> bytes:
|
||||
"""
|
||||
Signs the data.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def private_numbers(self) -> "RSAPrivateNumbers":
|
||||
"""
|
||||
Returns an RSAPrivateNumbers.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def private_bytes(
|
||||
self,
|
||||
encoding: _serialization.Encoding,
|
||||
format: _serialization.PrivateFormat,
|
||||
encryption_algorithm: _serialization.KeySerializationEncryption,
|
||||
) -> bytes:
|
||||
"""
|
||||
Returns the key serialized as bytes.
|
||||
"""
|
||||
|
||||
|
||||
RSAPrivateKeyWithSerialization = RSAPrivateKey
|
||||
|
||||
|
||||
class RSAPublicKey(metaclass=abc.ABCMeta):
|
||||
@abc.abstractmethod
|
||||
def verifier(
|
||||
self,
|
||||
signature: bytes,
|
||||
padding: AsymmetricPadding,
|
||||
algorithm: hashes.HashAlgorithm,
|
||||
) -> AsymmetricVerificationContext:
|
||||
"""
|
||||
Returns an AsymmetricVerificationContext used for verifying signatures.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def encrypt(self, plaintext: bytes, padding: AsymmetricPadding) -> bytes:
|
||||
"""
|
||||
Encrypts the given plaintext.
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
def key_size(self) -> int:
|
||||
"""
|
||||
The bit length of the public modulus.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def public_numbers(self) -> "RSAPublicNumbers":
|
||||
"""
|
||||
Returns an RSAPublicNumbers
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def public_bytes(
|
||||
self,
|
||||
encoding: _serialization.Encoding,
|
||||
format: _serialization.PublicFormat,
|
||||
) -> bytes:
|
||||
"""
|
||||
Returns the key serialized as bytes.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def verify(
|
||||
self,
|
||||
signature: bytes,
|
||||
data: bytes,
|
||||
padding: AsymmetricPadding,
|
||||
algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm],
|
||||
) -> None:
|
||||
"""
|
||||
Verifies the signature of the data.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def recover_data_from_signature(
|
||||
self,
|
||||
signature: bytes,
|
||||
padding: AsymmetricPadding,
|
||||
algorithm: typing.Optional[hashes.HashAlgorithm],
|
||||
) -> bytes:
|
||||
"""
|
||||
Recovers the original data from the signature.
|
||||
"""
|
||||
|
||||
|
||||
RSAPublicKeyWithSerialization = RSAPublicKey
|
||||
|
||||
|
||||
def generate_private_key(
|
||||
public_exponent: int,
|
||||
key_size: int,
|
||||
backend: typing.Optional[Backend] = None,
|
||||
) -> RSAPrivateKey:
|
||||
backend = _get_backend(backend)
|
||||
if not isinstance(backend, RSABackend):
|
||||
raise UnsupportedAlgorithm(
|
||||
"Backend object does not implement RSABackend.",
|
||||
_Reasons.BACKEND_MISSING_INTERFACE,
|
||||
)
|
||||
|
||||
_verify_rsa_parameters(public_exponent, key_size)
|
||||
return backend.generate_rsa_private_key(public_exponent, key_size)
|
||||
|
||||
|
||||
def _verify_rsa_parameters(public_exponent: int, key_size: int) -> None:
|
||||
if public_exponent not in (3, 65537):
|
||||
raise ValueError(
|
||||
"public_exponent must be either 3 (for legacy compatibility) or "
|
||||
"65537. Almost everyone should choose 65537 here!"
|
||||
)
|
||||
|
||||
if key_size < 512:
|
||||
raise ValueError("key_size must be at least 512-bits.")
|
||||
|
||||
|
||||
def _check_private_key_components(
|
||||
p: int,
|
||||
q: int,
|
||||
private_exponent: int,
|
||||
dmp1: int,
|
||||
dmq1: int,
|
||||
iqmp: int,
|
||||
public_exponent: int,
|
||||
modulus: int,
|
||||
) -> None:
|
||||
if modulus < 3:
|
||||
raise ValueError("modulus must be >= 3.")
|
||||
|
||||
if p >= modulus:
|
||||
raise ValueError("p must be < modulus.")
|
||||
|
||||
if q >= modulus:
|
||||
raise ValueError("q must be < modulus.")
|
||||
|
||||
if dmp1 >= modulus:
|
||||
raise ValueError("dmp1 must be < modulus.")
|
||||
|
||||
if dmq1 >= modulus:
|
||||
raise ValueError("dmq1 must be < modulus.")
|
||||
|
||||
if iqmp >= modulus:
|
||||
raise ValueError("iqmp must be < modulus.")
|
||||
|
||||
if private_exponent >= modulus:
|
||||
raise ValueError("private_exponent must be < modulus.")
|
||||
|
||||
if public_exponent < 3 or public_exponent >= modulus:
|
||||
raise ValueError("public_exponent must be >= 3 and < modulus.")
|
||||
|
||||
if public_exponent & 1 == 0:
|
||||
raise ValueError("public_exponent must be odd.")
|
||||
|
||||
if dmp1 & 1 == 0:
|
||||
raise ValueError("dmp1 must be odd.")
|
||||
|
||||
if dmq1 & 1 == 0:
|
||||
raise ValueError("dmq1 must be odd.")
|
||||
|
||||
if p * q != modulus:
|
||||
raise ValueError("p*q must equal modulus.")
|
||||
|
||||
|
||||
def _check_public_key_components(e: int, n: int) -> None:
|
||||
if n < 3:
|
||||
raise ValueError("n must be >= 3.")
|
||||
|
||||
if e < 3 or e >= n:
|
||||
raise ValueError("e must be >= 3 and < n.")
|
||||
|
||||
if e & 1 == 0:
|
||||
raise ValueError("e must be odd.")
|
||||
|
||||
|
||||
def _modinv(e: int, m: int) -> int:
|
||||
"""
|
||||
Modular Multiplicative Inverse. Returns x such that: (x*e) mod m == 1
|
||||
"""
|
||||
x1, x2 = 1, 0
|
||||
a, b = e, m
|
||||
while b > 0:
|
||||
q, r = divmod(a, b)
|
||||
xn = x1 - q * x2
|
||||
a, b, x1, x2 = b, r, x2, xn
|
||||
return x1 % m
|
||||
|
||||
|
||||
def rsa_crt_iqmp(p: int, q: int) -> int:
|
||||
"""
|
||||
Compute the CRT (q ** -1) % p value from RSA primes p and q.
|
||||
"""
|
||||
return _modinv(q, p)
|
||||
|
||||
|
||||
def rsa_crt_dmp1(private_exponent: int, p: int) -> int:
|
||||
"""
|
||||
Compute the CRT private_exponent % (p - 1) value from the RSA
|
||||
private_exponent (d) and p.
|
||||
"""
|
||||
return private_exponent % (p - 1)
|
||||
|
||||
|
||||
def rsa_crt_dmq1(private_exponent: int, q: int) -> int:
|
||||
"""
|
||||
Compute the CRT private_exponent % (q - 1) value from the RSA
|
||||
private_exponent (d) and q.
|
||||
"""
|
||||
return private_exponent % (q - 1)
|
||||
|
||||
|
||||
# Controls the number of iterations rsa_recover_prime_factors will perform
|
||||
# to obtain the prime factors. Each iteration increments by 2 so the actual
|
||||
# maximum attempts is half this number.
|
||||
_MAX_RECOVERY_ATTEMPTS = 1000
|
||||
|
||||
|
||||
def rsa_recover_prime_factors(
|
||||
n: int, e: int, d: int
|
||||
) -> typing.Tuple[int, int]:
|
||||
"""
|
||||
Compute factors p and q from the private exponent d. We assume that n has
|
||||
no more than two factors. This function is adapted from code in PyCrypto.
|
||||
"""
|
||||
# See 8.2.2(i) in Handbook of Applied Cryptography.
|
||||
ktot = d * e - 1
|
||||
# The quantity d*e-1 is a multiple of phi(n), even,
|
||||
# and can be represented as t*2^s.
|
||||
t = ktot
|
||||
while t % 2 == 0:
|
||||
t = t // 2
|
||||
# Cycle through all multiplicative inverses in Zn.
|
||||
# The algorithm is non-deterministic, but there is a 50% chance
|
||||
# any candidate a leads to successful factoring.
|
||||
# See "Digitalized Signatures and Public Key Functions as Intractable
|
||||
# as Factorization", M. Rabin, 1979
|
||||
spotted = False
|
||||
a = 2
|
||||
while not spotted and a < _MAX_RECOVERY_ATTEMPTS:
|
||||
k = t
|
||||
# Cycle through all values a^{t*2^i}=a^k
|
||||
while k < ktot:
|
||||
cand = pow(a, k, n)
|
||||
# Check if a^k is a non-trivial root of unity (mod n)
|
||||
if cand != 1 and cand != (n - 1) and pow(cand, 2, n) == 1:
|
||||
# We have found a number such that (cand-1)(cand+1)=0 (mod n).
|
||||
# Either of the terms divides n.
|
||||
p = gcd(cand + 1, n)
|
||||
spotted = True
|
||||
break
|
||||
k *= 2
|
||||
# This value was not any good... let's try another!
|
||||
a += 2
|
||||
if not spotted:
|
||||
raise ValueError("Unable to compute factors p and q from exponent d.")
|
||||
# Found !
|
||||
q, r = divmod(n, p)
|
||||
assert r == 0
|
||||
p, q = sorted((p, q), reverse=True)
|
||||
return (p, q)
|
||||
|
||||
|
||||
class RSAPrivateNumbers(object):
|
||||
def __init__(
|
||||
self,
|
||||
p: int,
|
||||
q: int,
|
||||
d: int,
|
||||
dmp1: int,
|
||||
dmq1: int,
|
||||
iqmp: int,
|
||||
public_numbers: "RSAPublicNumbers",
|
||||
):
|
||||
if (
|
||||
not isinstance(p, int)
|
||||
or not isinstance(q, int)
|
||||
or not isinstance(d, int)
|
||||
or not isinstance(dmp1, int)
|
||||
or not isinstance(dmq1, int)
|
||||
or not isinstance(iqmp, int)
|
||||
):
|
||||
raise TypeError(
|
||||
"RSAPrivateNumbers p, q, d, dmp1, dmq1, iqmp arguments must"
|
||||
" all be an integers."
|
||||
)
|
||||
|
||||
if not isinstance(public_numbers, RSAPublicNumbers):
|
||||
raise TypeError(
|
||||
"RSAPrivateNumbers public_numbers must be an RSAPublicNumbers"
|
||||
" instance."
|
||||
)
|
||||
|
||||
self._p = p
|
||||
self._q = q
|
||||
self._d = d
|
||||
self._dmp1 = dmp1
|
||||
self._dmq1 = dmq1
|
||||
self._iqmp = iqmp
|
||||
self._public_numbers = public_numbers
|
||||
|
||||
p = property(lambda self: self._p)
|
||||
q = property(lambda self: self._q)
|
||||
d = property(lambda self: self._d)
|
||||
dmp1 = property(lambda self: self._dmp1)
|
||||
dmq1 = property(lambda self: self._dmq1)
|
||||
iqmp = property(lambda self: self._iqmp)
|
||||
public_numbers = property(lambda self: self._public_numbers)
|
||||
|
||||
def private_key(
|
||||
self, backend: typing.Optional[Backend] = None
|
||||
) -> RSAPrivateKey:
|
||||
backend = _get_backend(backend)
|
||||
return backend.load_rsa_private_numbers(self)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, RSAPrivateNumbers):
|
||||
return NotImplemented
|
||||
|
||||
return (
|
||||
self.p == other.p
|
||||
and self.q == other.q
|
||||
and self.d == other.d
|
||||
and self.dmp1 == other.dmp1
|
||||
and self.dmq1 == other.dmq1
|
||||
and self.iqmp == other.iqmp
|
||||
and self.public_numbers == other.public_numbers
|
||||
)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def __hash__(self):
|
||||
return hash(
|
||||
(
|
||||
self.p,
|
||||
self.q,
|
||||
self.d,
|
||||
self.dmp1,
|
||||
self.dmq1,
|
||||
self.iqmp,
|
||||
self.public_numbers,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class RSAPublicNumbers(object):
|
||||
def __init__(self, e: int, n: int):
|
||||
if not isinstance(e, int) or not isinstance(n, int):
|
||||
raise TypeError("RSAPublicNumbers arguments must be integers.")
|
||||
|
||||
self._e = e
|
||||
self._n = n
|
||||
|
||||
e = property(lambda self: self._e)
|
||||
n = property(lambda self: self._n)
|
||||
|
||||
def public_key(
|
||||
self, backend: typing.Optional[Backend] = None
|
||||
) -> RSAPublicKey:
|
||||
backend = _get_backend(backend)
|
||||
return backend.load_rsa_public_numbers(self)
|
||||
|
||||
def __repr__(self):
|
||||
return "<RSAPublicNumbers(e={0.e}, n={0.n})>".format(self)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, RSAPublicNumbers):
|
||||
return NotImplemented
|
||||
|
||||
return self.e == other.e and self.n == other.n
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.e, self.n))
|
||||
@@ -0,0 +1,29 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
import typing
|
||||
|
||||
from cryptography.hazmat.primitives.asymmetric import (
|
||||
dsa,
|
||||
ec,
|
||||
ed25519,
|
||||
ed448,
|
||||
rsa,
|
||||
)
|
||||
|
||||
|
||||
PUBLIC_KEY_TYPES = typing.Union[
|
||||
dsa.DSAPublicKey,
|
||||
rsa.RSAPublicKey,
|
||||
ec.EllipticCurvePublicKey,
|
||||
ed25519.Ed25519PublicKey,
|
||||
ed448.Ed448PublicKey,
|
||||
]
|
||||
PRIVATE_KEY_TYPES = typing.Union[
|
||||
ed25519.Ed25519PrivateKey,
|
||||
ed448.Ed448PrivateKey,
|
||||
rsa.RSAPrivateKey,
|
||||
dsa.DSAPrivateKey,
|
||||
ec.EllipticCurvePrivateKey,
|
||||
]
|
||||
@@ -0,0 +1,22 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
from cryptography.hazmat.bindings._rust import asn1
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
|
||||
|
||||
decode_dss_signature = asn1.decode_dss_signature
|
||||
encode_dss_signature = asn1.encode_dss_signature
|
||||
|
||||
|
||||
class Prehashed(object):
|
||||
def __init__(self, algorithm: hashes.HashAlgorithm):
|
||||
if not isinstance(algorithm, hashes.HashAlgorithm):
|
||||
raise TypeError("Expected instance of HashAlgorithm.")
|
||||
|
||||
self._algorithm = algorithm
|
||||
self._digest_size = algorithm.digest_size
|
||||
|
||||
digest_size = property(lambda self: self._digest_size)
|
||||
@@ -0,0 +1,81 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import abc
|
||||
|
||||
from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
|
||||
from cryptography.hazmat.primitives import _serialization
|
||||
|
||||
|
||||
class X25519PublicKey(metaclass=abc.ABCMeta):
|
||||
@classmethod
|
||||
def from_public_bytes(cls, data: bytes) -> "X25519PublicKey":
|
||||
from cryptography.hazmat.backends.openssl.backend import backend
|
||||
|
||||
if not backend.x25519_supported():
|
||||
raise UnsupportedAlgorithm(
|
||||
"X25519 is not supported by this version of OpenSSL.",
|
||||
_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM,
|
||||
)
|
||||
|
||||
return backend.x25519_load_public_bytes(data)
|
||||
|
||||
@abc.abstractmethod
|
||||
def public_bytes(
|
||||
self,
|
||||
encoding: _serialization.Encoding,
|
||||
format: _serialization.PublicFormat,
|
||||
) -> bytes:
|
||||
"""
|
||||
The serialized bytes of the public key.
|
||||
"""
|
||||
|
||||
|
||||
class X25519PrivateKey(metaclass=abc.ABCMeta):
|
||||
@classmethod
|
||||
def generate(cls) -> "X25519PrivateKey":
|
||||
from cryptography.hazmat.backends.openssl.backend import backend
|
||||
|
||||
if not backend.x25519_supported():
|
||||
raise UnsupportedAlgorithm(
|
||||
"X25519 is not supported by this version of OpenSSL.",
|
||||
_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM,
|
||||
)
|
||||
return backend.x25519_generate_key()
|
||||
|
||||
@classmethod
|
||||
def from_private_bytes(cls, data: bytes) -> "X25519PrivateKey":
|
||||
from cryptography.hazmat.backends.openssl.backend import backend
|
||||
|
||||
if not backend.x25519_supported():
|
||||
raise UnsupportedAlgorithm(
|
||||
"X25519 is not supported by this version of OpenSSL.",
|
||||
_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM,
|
||||
)
|
||||
|
||||
return backend.x25519_load_private_bytes(data)
|
||||
|
||||
@abc.abstractmethod
|
||||
def public_key(self) -> X25519PublicKey:
|
||||
"""
|
||||
The serialized bytes of the public key.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def private_bytes(
|
||||
self,
|
||||
encoding: _serialization.Encoding,
|
||||
format: _serialization.PrivateFormat,
|
||||
encryption_algorithm: _serialization.KeySerializationEncryption,
|
||||
) -> bytes:
|
||||
"""
|
||||
The serialized bytes of the private key.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def exchange(self, peer_public_key: X25519PublicKey) -> bytes:
|
||||
"""
|
||||
Performs a key exchange operation using the provided peer's public key.
|
||||
"""
|
||||
@@ -0,0 +1,81 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import abc
|
||||
|
||||
from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
|
||||
from cryptography.hazmat.primitives import _serialization
|
||||
|
||||
|
||||
class X448PublicKey(metaclass=abc.ABCMeta):
|
||||
@classmethod
|
||||
def from_public_bytes(cls, data: bytes) -> "X448PublicKey":
|
||||
from cryptography.hazmat.backends.openssl.backend import backend
|
||||
|
||||
if not backend.x448_supported():
|
||||
raise UnsupportedAlgorithm(
|
||||
"X448 is not supported by this version of OpenSSL.",
|
||||
_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM,
|
||||
)
|
||||
|
||||
return backend.x448_load_public_bytes(data)
|
||||
|
||||
@abc.abstractmethod
|
||||
def public_bytes(
|
||||
self,
|
||||
encoding: _serialization.Encoding,
|
||||
format: _serialization.PublicFormat,
|
||||
) -> bytes:
|
||||
"""
|
||||
The serialized bytes of the public key.
|
||||
"""
|
||||
|
||||
|
||||
class X448PrivateKey(metaclass=abc.ABCMeta):
|
||||
@classmethod
|
||||
def generate(cls) -> "X448PrivateKey":
|
||||
from cryptography.hazmat.backends.openssl.backend import backend
|
||||
|
||||
if not backend.x448_supported():
|
||||
raise UnsupportedAlgorithm(
|
||||
"X448 is not supported by this version of OpenSSL.",
|
||||
_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM,
|
||||
)
|
||||
return backend.x448_generate_key()
|
||||
|
||||
@classmethod
|
||||
def from_private_bytes(cls, data: bytes) -> "X448PrivateKey":
|
||||
from cryptography.hazmat.backends.openssl.backend import backend
|
||||
|
||||
if not backend.x448_supported():
|
||||
raise UnsupportedAlgorithm(
|
||||
"X448 is not supported by this version of OpenSSL.",
|
||||
_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM,
|
||||
)
|
||||
|
||||
return backend.x448_load_private_bytes(data)
|
||||
|
||||
@abc.abstractmethod
|
||||
def public_key(self) -> X448PublicKey:
|
||||
"""
|
||||
The serialized bytes of the public key.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def private_bytes(
|
||||
self,
|
||||
encoding: _serialization.Encoding,
|
||||
format: _serialization.PrivateFormat,
|
||||
encryption_algorithm: _serialization.KeySerializationEncryption,
|
||||
) -> bytes:
|
||||
"""
|
||||
The serialized bytes of the private key.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def exchange(self, peer_public_key: X448PublicKey) -> bytes:
|
||||
"""
|
||||
Performs a key exchange operation using the provided peer's public key.
|
||||
"""
|
||||
@@ -0,0 +1,27 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
from cryptography.hazmat.primitives._cipheralgorithm import (
|
||||
BlockCipherAlgorithm,
|
||||
CipherAlgorithm,
|
||||
)
|
||||
from cryptography.hazmat.primitives.ciphers.base import (
|
||||
AEADCipherContext,
|
||||
AEADDecryptionContext,
|
||||
AEADEncryptionContext,
|
||||
Cipher,
|
||||
CipherContext,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Cipher",
|
||||
"CipherAlgorithm",
|
||||
"BlockCipherAlgorithm",
|
||||
"CipherContext",
|
||||
"AEADCipherContext",
|
||||
"AEADDecryptionContext",
|
||||
"AEADEncryptionContext",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,216 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import os
|
||||
import typing
|
||||
|
||||
from cryptography import exceptions, utils
|
||||
from cryptography.hazmat.backends.openssl import aead
|
||||
from cryptography.hazmat.backends.openssl.backend import backend
|
||||
|
||||
|
||||
class ChaCha20Poly1305(object):
|
||||
_MAX_SIZE = 2 ** 32
|
||||
|
||||
def __init__(self, key: bytes):
|
||||
if not backend.aead_cipher_supported(self):
|
||||
raise exceptions.UnsupportedAlgorithm(
|
||||
"ChaCha20Poly1305 is not supported by this version of OpenSSL",
|
||||
exceptions._Reasons.UNSUPPORTED_CIPHER,
|
||||
)
|
||||
utils._check_byteslike("key", key)
|
||||
|
||||
if len(key) != 32:
|
||||
raise ValueError("ChaCha20Poly1305 key must be 32 bytes.")
|
||||
|
||||
self._key = key
|
||||
|
||||
@classmethod
|
||||
def generate_key(cls) -> bytes:
|
||||
return os.urandom(32)
|
||||
|
||||
def encrypt(
|
||||
self,
|
||||
nonce: bytes,
|
||||
data: bytes,
|
||||
associated_data: typing.Optional[bytes],
|
||||
) -> bytes:
|
||||
if associated_data is None:
|
||||
associated_data = b""
|
||||
|
||||
if len(data) > self._MAX_SIZE or len(associated_data) > self._MAX_SIZE:
|
||||
# This is OverflowError to match what cffi would raise
|
||||
raise OverflowError(
|
||||
"Data or associated data too long. Max 2**32 bytes"
|
||||
)
|
||||
|
||||
self._check_params(nonce, data, associated_data)
|
||||
return aead._encrypt(backend, self, nonce, data, associated_data, 16)
|
||||
|
||||
def decrypt(
|
||||
self,
|
||||
nonce: bytes,
|
||||
data: bytes,
|
||||
associated_data: typing.Optional[bytes],
|
||||
) -> bytes:
|
||||
if associated_data is None:
|
||||
associated_data = b""
|
||||
|
||||
self._check_params(nonce, data, associated_data)
|
||||
return aead._decrypt(backend, self, nonce, data, associated_data, 16)
|
||||
|
||||
def _check_params(
|
||||
self,
|
||||
nonce: bytes,
|
||||
data: bytes,
|
||||
associated_data: bytes,
|
||||
) -> None:
|
||||
utils._check_byteslike("nonce", nonce)
|
||||
utils._check_bytes("data", data)
|
||||
utils._check_bytes("associated_data", associated_data)
|
||||
if len(nonce) != 12:
|
||||
raise ValueError("Nonce must be 12 bytes")
|
||||
|
||||
|
||||
class AESCCM(object):
|
||||
_MAX_SIZE = 2 ** 32
|
||||
|
||||
def __init__(self, key: bytes, tag_length: int = 16):
|
||||
utils._check_byteslike("key", key)
|
||||
if len(key) not in (16, 24, 32):
|
||||
raise ValueError("AESCCM key must be 128, 192, or 256 bits.")
|
||||
|
||||
self._key = key
|
||||
if not isinstance(tag_length, int):
|
||||
raise TypeError("tag_length must be an integer")
|
||||
|
||||
if tag_length not in (4, 6, 8, 10, 12, 14, 16):
|
||||
raise ValueError("Invalid tag_length")
|
||||
|
||||
self._tag_length = tag_length
|
||||
|
||||
@classmethod
|
||||
def generate_key(cls, bit_length: int) -> bytes:
|
||||
if not isinstance(bit_length, int):
|
||||
raise TypeError("bit_length must be an integer")
|
||||
|
||||
if bit_length not in (128, 192, 256):
|
||||
raise ValueError("bit_length must be 128, 192, or 256")
|
||||
|
||||
return os.urandom(bit_length // 8)
|
||||
|
||||
def encrypt(
|
||||
self,
|
||||
nonce: bytes,
|
||||
data: bytes,
|
||||
associated_data: typing.Optional[bytes],
|
||||
) -> bytes:
|
||||
if associated_data is None:
|
||||
associated_data = b""
|
||||
|
||||
if len(data) > self._MAX_SIZE or len(associated_data) > self._MAX_SIZE:
|
||||
# This is OverflowError to match what cffi would raise
|
||||
raise OverflowError(
|
||||
"Data or associated data too long. Max 2**32 bytes"
|
||||
)
|
||||
|
||||
self._check_params(nonce, data, associated_data)
|
||||
self._validate_lengths(nonce, len(data))
|
||||
return aead._encrypt(
|
||||
backend, self, nonce, data, associated_data, self._tag_length
|
||||
)
|
||||
|
||||
def decrypt(
|
||||
self,
|
||||
nonce: bytes,
|
||||
data: bytes,
|
||||
associated_data: typing.Optional[bytes],
|
||||
) -> bytes:
|
||||
if associated_data is None:
|
||||
associated_data = b""
|
||||
|
||||
self._check_params(nonce, data, associated_data)
|
||||
return aead._decrypt(
|
||||
backend, self, nonce, data, associated_data, self._tag_length
|
||||
)
|
||||
|
||||
def _validate_lengths(self, nonce: bytes, data_len: int) -> None:
|
||||
# For information about computing this, see
|
||||
# https://tools.ietf.org/html/rfc3610#section-2.1
|
||||
l_val = 15 - len(nonce)
|
||||
if 2 ** (8 * l_val) < data_len:
|
||||
raise ValueError("Data too long for nonce")
|
||||
|
||||
def _check_params(
|
||||
self, nonce: bytes, data: bytes, associated_data: bytes
|
||||
) -> None:
|
||||
utils._check_byteslike("nonce", nonce)
|
||||
utils._check_bytes("data", data)
|
||||
utils._check_bytes("associated_data", associated_data)
|
||||
if not 7 <= len(nonce) <= 13:
|
||||
raise ValueError("Nonce must be between 7 and 13 bytes")
|
||||
|
||||
|
||||
class AESGCM(object):
|
||||
_MAX_SIZE = 2 ** 32
|
||||
|
||||
def __init__(self, key: bytes):
|
||||
utils._check_byteslike("key", key)
|
||||
if len(key) not in (16, 24, 32):
|
||||
raise ValueError("AESGCM key must be 128, 192, or 256 bits.")
|
||||
|
||||
self._key = key
|
||||
|
||||
@classmethod
|
||||
def generate_key(cls, bit_length: int) -> bytes:
|
||||
if not isinstance(bit_length, int):
|
||||
raise TypeError("bit_length must be an integer")
|
||||
|
||||
if bit_length not in (128, 192, 256):
|
||||
raise ValueError("bit_length must be 128, 192, or 256")
|
||||
|
||||
return os.urandom(bit_length // 8)
|
||||
|
||||
def encrypt(
|
||||
self,
|
||||
nonce: bytes,
|
||||
data: bytes,
|
||||
associated_data: typing.Optional[bytes],
|
||||
) -> bytes:
|
||||
if associated_data is None:
|
||||
associated_data = b""
|
||||
|
||||
if len(data) > self._MAX_SIZE or len(associated_data) > self._MAX_SIZE:
|
||||
# This is OverflowError to match what cffi would raise
|
||||
raise OverflowError(
|
||||
"Data or associated data too long. Max 2**32 bytes"
|
||||
)
|
||||
|
||||
self._check_params(nonce, data, associated_data)
|
||||
return aead._encrypt(backend, self, nonce, data, associated_data, 16)
|
||||
|
||||
def decrypt(
|
||||
self,
|
||||
nonce: bytes,
|
||||
data: bytes,
|
||||
associated_data: typing.Optional[bytes],
|
||||
) -> bytes:
|
||||
if associated_data is None:
|
||||
associated_data = b""
|
||||
|
||||
self._check_params(nonce, data, associated_data)
|
||||
return aead._decrypt(backend, self, nonce, data, associated_data, 16)
|
||||
|
||||
def _check_params(
|
||||
self,
|
||||
nonce: bytes,
|
||||
data: bytes,
|
||||
associated_data: bytes,
|
||||
) -> None:
|
||||
utils._check_byteslike("nonce", nonce)
|
||||
utils._check_bytes("data", data)
|
||||
utils._check_bytes("associated_data", associated_data)
|
||||
if len(nonce) < 8 or len(nonce) > 128:
|
||||
raise ValueError("Nonce must be between 8 and 128 bytes")
|
||||
@@ -0,0 +1,168 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
from cryptography import utils
|
||||
from cryptography.hazmat.primitives.ciphers import (
|
||||
BlockCipherAlgorithm,
|
||||
CipherAlgorithm,
|
||||
)
|
||||
from cryptography.hazmat.primitives.ciphers.modes import ModeWithNonce
|
||||
|
||||
|
||||
def _verify_key_size(algorithm: CipherAlgorithm, key: bytes) -> bytes:
|
||||
# Verify that the key is instance of bytes
|
||||
utils._check_byteslike("key", key)
|
||||
|
||||
# Verify that the key size matches the expected key size
|
||||
if len(key) * 8 not in algorithm.key_sizes:
|
||||
raise ValueError(
|
||||
"Invalid key size ({}) for {}.".format(
|
||||
len(key) * 8, algorithm.name
|
||||
)
|
||||
)
|
||||
return key
|
||||
|
||||
|
||||
class AES(CipherAlgorithm, BlockCipherAlgorithm):
|
||||
name = "AES"
|
||||
block_size = 128
|
||||
# 512 added to support AES-256-XTS, which uses 512-bit keys
|
||||
key_sizes = frozenset([128, 192, 256, 512])
|
||||
|
||||
def __init__(self, key: bytes):
|
||||
self.key = _verify_key_size(self, key)
|
||||
|
||||
@property
|
||||
def key_size(self) -> int:
|
||||
return len(self.key) * 8
|
||||
|
||||
|
||||
class Camellia(CipherAlgorithm, BlockCipherAlgorithm):
|
||||
name = "camellia"
|
||||
block_size = 128
|
||||
key_sizes = frozenset([128, 192, 256])
|
||||
|
||||
def __init__(self, key: bytes):
|
||||
self.key = _verify_key_size(self, key)
|
||||
|
||||
@property
|
||||
def key_size(self) -> int:
|
||||
return len(self.key) * 8
|
||||
|
||||
|
||||
class TripleDES(CipherAlgorithm, BlockCipherAlgorithm):
|
||||
name = "3DES"
|
||||
block_size = 64
|
||||
key_sizes = frozenset([64, 128, 192])
|
||||
|
||||
def __init__(self, key: bytes):
|
||||
if len(key) == 8:
|
||||
key += key + key
|
||||
elif len(key) == 16:
|
||||
key += key[:8]
|
||||
self.key = _verify_key_size(self, key)
|
||||
|
||||
@property
|
||||
def key_size(self) -> int:
|
||||
return len(self.key) * 8
|
||||
|
||||
|
||||
class Blowfish(CipherAlgorithm, BlockCipherAlgorithm):
|
||||
name = "Blowfish"
|
||||
block_size = 64
|
||||
key_sizes = frozenset(range(32, 449, 8))
|
||||
|
||||
def __init__(self, key: bytes):
|
||||
self.key = _verify_key_size(self, key)
|
||||
|
||||
@property
|
||||
def key_size(self) -> int:
|
||||
return len(self.key) * 8
|
||||
|
||||
|
||||
class CAST5(CipherAlgorithm, BlockCipherAlgorithm):
|
||||
name = "CAST5"
|
||||
block_size = 64
|
||||
key_sizes = frozenset(range(40, 129, 8))
|
||||
|
||||
def __init__(self, key: bytes):
|
||||
self.key = _verify_key_size(self, key)
|
||||
|
||||
@property
|
||||
def key_size(self) -> int:
|
||||
return len(self.key) * 8
|
||||
|
||||
|
||||
class ARC4(CipherAlgorithm):
|
||||
name = "RC4"
|
||||
key_sizes = frozenset([40, 56, 64, 80, 128, 160, 192, 256])
|
||||
|
||||
def __init__(self, key: bytes):
|
||||
self.key = _verify_key_size(self, key)
|
||||
|
||||
@property
|
||||
def key_size(self) -> int:
|
||||
return len(self.key) * 8
|
||||
|
||||
|
||||
class IDEA(CipherAlgorithm):
|
||||
name = "IDEA"
|
||||
block_size = 64
|
||||
key_sizes = frozenset([128])
|
||||
|
||||
def __init__(self, key: bytes):
|
||||
self.key = _verify_key_size(self, key)
|
||||
|
||||
@property
|
||||
def key_size(self) -> int:
|
||||
return len(self.key) * 8
|
||||
|
||||
|
||||
class SEED(CipherAlgorithm, BlockCipherAlgorithm):
|
||||
name = "SEED"
|
||||
block_size = 128
|
||||
key_sizes = frozenset([128])
|
||||
|
||||
def __init__(self, key: bytes):
|
||||
self.key = _verify_key_size(self, key)
|
||||
|
||||
@property
|
||||
def key_size(self) -> int:
|
||||
return len(self.key) * 8
|
||||
|
||||
|
||||
class ChaCha20(CipherAlgorithm, ModeWithNonce):
|
||||
name = "ChaCha20"
|
||||
key_sizes = frozenset([256])
|
||||
|
||||
def __init__(self, key: bytes, nonce: bytes):
|
||||
self.key = _verify_key_size(self, key)
|
||||
utils._check_byteslike("nonce", nonce)
|
||||
|
||||
if len(nonce) != 16:
|
||||
raise ValueError("nonce must be 128-bits (16 bytes)")
|
||||
|
||||
self._nonce = nonce
|
||||
|
||||
@property
|
||||
def nonce(self) -> bytes:
|
||||
return self._nonce
|
||||
|
||||
@property
|
||||
def key_size(self) -> int:
|
||||
return len(self.key) * 8
|
||||
|
||||
|
||||
class SM4(CipherAlgorithm, BlockCipherAlgorithm):
|
||||
name = "SM4"
|
||||
block_size = 128
|
||||
key_sizes = frozenset([128])
|
||||
|
||||
def __init__(self, key: bytes):
|
||||
self.key = _verify_key_size(self, key)
|
||||
|
||||
@property
|
||||
def key_size(self) -> int:
|
||||
return len(self.key) * 8
|
||||
@@ -0,0 +1,218 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import abc
|
||||
import typing
|
||||
|
||||
from cryptography import utils
|
||||
from cryptography.exceptions import (
|
||||
AlreadyFinalized,
|
||||
AlreadyUpdated,
|
||||
NotYetFinalized,
|
||||
UnsupportedAlgorithm,
|
||||
_Reasons,
|
||||
)
|
||||
from cryptography.hazmat.backends import _get_backend
|
||||
from cryptography.hazmat.backends.interfaces import Backend, CipherBackend
|
||||
from cryptography.hazmat.primitives._cipheralgorithm import CipherAlgorithm
|
||||
from cryptography.hazmat.primitives.ciphers import modes
|
||||
|
||||
|
||||
class CipherContext(metaclass=abc.ABCMeta):
|
||||
@abc.abstractmethod
|
||||
def update(self, data: bytes) -> bytes:
|
||||
"""
|
||||
Processes the provided bytes through the cipher and returns the results
|
||||
as bytes.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_into(self, data: bytes, buf) -> int:
|
||||
"""
|
||||
Processes the provided bytes and writes the resulting data into the
|
||||
provided buffer. Returns the number of bytes written.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def finalize(self) -> bytes:
|
||||
"""
|
||||
Returns the results of processing the final block as bytes.
|
||||
"""
|
||||
|
||||
|
||||
class AEADCipherContext(metaclass=abc.ABCMeta):
|
||||
@abc.abstractmethod
|
||||
def authenticate_additional_data(self, data: bytes) -> None:
|
||||
"""
|
||||
Authenticates the provided bytes.
|
||||
"""
|
||||
|
||||
|
||||
class AEADDecryptionContext(metaclass=abc.ABCMeta):
|
||||
@abc.abstractmethod
|
||||
def finalize_with_tag(self, tag: bytes) -> bytes:
|
||||
"""
|
||||
Returns the results of processing the final block as bytes and allows
|
||||
delayed passing of the authentication tag.
|
||||
"""
|
||||
|
||||
|
||||
class AEADEncryptionContext(metaclass=abc.ABCMeta):
|
||||
@abc.abstractproperty
|
||||
def tag(self) -> bytes:
|
||||
"""
|
||||
Returns tag bytes. This is only available after encryption is
|
||||
finalized.
|
||||
"""
|
||||
|
||||
|
||||
class Cipher(object):
|
||||
def __init__(
|
||||
self,
|
||||
algorithm: CipherAlgorithm,
|
||||
mode: typing.Optional[modes.Mode],
|
||||
backend: typing.Optional[Backend] = None,
|
||||
):
|
||||
backend = _get_backend(backend)
|
||||
if not isinstance(backend, CipherBackend):
|
||||
raise UnsupportedAlgorithm(
|
||||
"Backend object does not implement CipherBackend.",
|
||||
_Reasons.BACKEND_MISSING_INTERFACE,
|
||||
)
|
||||
|
||||
if not isinstance(algorithm, CipherAlgorithm):
|
||||
raise TypeError("Expected interface of CipherAlgorithm.")
|
||||
|
||||
if mode is not None:
|
||||
mode.validate_for_algorithm(algorithm)
|
||||
|
||||
self.algorithm = algorithm
|
||||
self.mode = mode
|
||||
self._backend = backend
|
||||
|
||||
def encryptor(self):
|
||||
if isinstance(self.mode, modes.ModeWithAuthenticationTag):
|
||||
if self.mode.tag is not None:
|
||||
raise ValueError(
|
||||
"Authentication tag must be None when encrypting."
|
||||
)
|
||||
ctx = self._backend.create_symmetric_encryption_ctx(
|
||||
self.algorithm, self.mode
|
||||
)
|
||||
return self._wrap_ctx(ctx, encrypt=True)
|
||||
|
||||
def decryptor(self):
|
||||
ctx = self._backend.create_symmetric_decryption_ctx(
|
||||
self.algorithm, self.mode
|
||||
)
|
||||
return self._wrap_ctx(ctx, encrypt=False)
|
||||
|
||||
def _wrap_ctx(self, ctx, encrypt):
|
||||
if isinstance(self.mode, modes.ModeWithAuthenticationTag):
|
||||
if encrypt:
|
||||
return _AEADEncryptionContext(ctx)
|
||||
else:
|
||||
return _AEADCipherContext(ctx)
|
||||
else:
|
||||
return _CipherContext(ctx)
|
||||
|
||||
|
||||
@utils.register_interface(CipherContext)
|
||||
class _CipherContext(object):
|
||||
def __init__(self, ctx):
|
||||
self._ctx = ctx
|
||||
|
||||
def update(self, data: bytes) -> bytes:
|
||||
if self._ctx is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
return self._ctx.update(data)
|
||||
|
||||
def update_into(self, data: bytes, buf) -> int:
|
||||
if self._ctx is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
return self._ctx.update_into(data, buf)
|
||||
|
||||
def finalize(self) -> bytes:
|
||||
if self._ctx is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
data = self._ctx.finalize()
|
||||
self._ctx = None
|
||||
return data
|
||||
|
||||
|
||||
@utils.register_interface(AEADCipherContext)
|
||||
@utils.register_interface(CipherContext)
|
||||
@utils.register_interface(AEADDecryptionContext)
|
||||
class _AEADCipherContext(object):
|
||||
def __init__(self, ctx):
|
||||
self._ctx = ctx
|
||||
self._bytes_processed = 0
|
||||
self._aad_bytes_processed = 0
|
||||
self._tag = None
|
||||
self._updated = False
|
||||
|
||||
def _check_limit(self, data_size: int) -> None:
|
||||
if self._ctx is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
self._updated = True
|
||||
self._bytes_processed += data_size
|
||||
if self._bytes_processed > self._ctx._mode._MAX_ENCRYPTED_BYTES:
|
||||
raise ValueError(
|
||||
"{} has a maximum encrypted byte limit of {}".format(
|
||||
self._ctx._mode.name, self._ctx._mode._MAX_ENCRYPTED_BYTES
|
||||
)
|
||||
)
|
||||
|
||||
def update(self, data: bytes) -> bytes:
|
||||
self._check_limit(len(data))
|
||||
return self._ctx.update(data)
|
||||
|
||||
def update_into(self, data: bytes, buf) -> int:
|
||||
self._check_limit(len(data))
|
||||
return self._ctx.update_into(data, buf)
|
||||
|
||||
def finalize(self) -> bytes:
|
||||
if self._ctx is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
data = self._ctx.finalize()
|
||||
self._tag = self._ctx.tag
|
||||
self._ctx = None
|
||||
return data
|
||||
|
||||
def finalize_with_tag(self, tag: bytes) -> bytes:
|
||||
if self._ctx is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
data = self._ctx.finalize_with_tag(tag)
|
||||
self._tag = self._ctx.tag
|
||||
self._ctx = None
|
||||
return data
|
||||
|
||||
def authenticate_additional_data(self, data: bytes) -> None:
|
||||
if self._ctx is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
if self._updated:
|
||||
raise AlreadyUpdated("Update has been called on this context.")
|
||||
|
||||
self._aad_bytes_processed += len(data)
|
||||
if self._aad_bytes_processed > self._ctx._mode._MAX_AAD_BYTES:
|
||||
raise ValueError(
|
||||
"{} has a maximum AAD byte limit of {}".format(
|
||||
self._ctx._mode.name, self._ctx._mode._MAX_AAD_BYTES
|
||||
)
|
||||
)
|
||||
|
||||
self._ctx.authenticate_additional_data(data)
|
||||
|
||||
|
||||
@utils.register_interface(AEADEncryptionContext)
|
||||
class _AEADEncryptionContext(_AEADCipherContext):
|
||||
@property
|
||||
def tag(self) -> bytes:
|
||||
if self._ctx is not None:
|
||||
raise NotYetFinalized(
|
||||
"You must finalize encryption before " "getting the tag."
|
||||
)
|
||||
assert self._tag is not None
|
||||
return self._tag
|
||||
@@ -0,0 +1,247 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import abc
|
||||
import typing
|
||||
|
||||
from cryptography import utils
|
||||
from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
|
||||
from cryptography.hazmat.primitives._cipheralgorithm import (
|
||||
BlockCipherAlgorithm,
|
||||
CipherAlgorithm,
|
||||
)
|
||||
|
||||
|
||||
class Mode(metaclass=abc.ABCMeta):
|
||||
@abc.abstractproperty
|
||||
def name(self) -> str:
|
||||
"""
|
||||
A string naming this mode (e.g. "ECB", "CBC").
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None:
|
||||
"""
|
||||
Checks that all the necessary invariants of this (mode, algorithm)
|
||||
combination are met.
|
||||
"""
|
||||
|
||||
|
||||
class ModeWithInitializationVector(metaclass=abc.ABCMeta):
|
||||
@abc.abstractproperty
|
||||
def initialization_vector(self) -> bytes:
|
||||
"""
|
||||
The value of the initialization vector for this mode as bytes.
|
||||
"""
|
||||
|
||||
|
||||
class ModeWithTweak(metaclass=abc.ABCMeta):
|
||||
@abc.abstractproperty
|
||||
def tweak(self) -> bytes:
|
||||
"""
|
||||
The value of the tweak for this mode as bytes.
|
||||
"""
|
||||
|
||||
|
||||
class ModeWithNonce(metaclass=abc.ABCMeta):
|
||||
@abc.abstractproperty
|
||||
def nonce(self) -> bytes:
|
||||
"""
|
||||
The value of the nonce for this mode as bytes.
|
||||
"""
|
||||
|
||||
|
||||
class ModeWithAuthenticationTag(metaclass=abc.ABCMeta):
|
||||
@abc.abstractproperty
|
||||
def tag(self) -> typing.Optional[bytes]:
|
||||
"""
|
||||
The value of the tag supplied to the constructor of this mode.
|
||||
"""
|
||||
|
||||
|
||||
def _check_aes_key_length(self, algorithm):
|
||||
if algorithm.key_size > 256 and algorithm.name == "AES":
|
||||
raise ValueError(
|
||||
"Only 128, 192, and 256 bit keys are allowed for this AES mode"
|
||||
)
|
||||
|
||||
|
||||
def _check_iv_length(self, algorithm):
|
||||
if len(self.initialization_vector) * 8 != algorithm.block_size:
|
||||
raise ValueError(
|
||||
"Invalid IV size ({}) for {}.".format(
|
||||
len(self.initialization_vector), self.name
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _check_nonce_length(nonce: bytes, name: str, algorithm) -> None:
|
||||
if len(nonce) * 8 != algorithm.block_size:
|
||||
raise ValueError(
|
||||
"Invalid nonce size ({}) for {}.".format(len(nonce), name)
|
||||
)
|
||||
|
||||
|
||||
def _check_iv_and_key_length(self, algorithm):
|
||||
_check_aes_key_length(self, algorithm)
|
||||
_check_iv_length(self, algorithm)
|
||||
|
||||
|
||||
class CBC(Mode, ModeWithInitializationVector):
|
||||
name = "CBC"
|
||||
|
||||
def __init__(self, initialization_vector: bytes):
|
||||
utils._check_byteslike("initialization_vector", initialization_vector)
|
||||
self._initialization_vector = initialization_vector
|
||||
|
||||
@property
|
||||
def initialization_vector(self) -> bytes:
|
||||
return self._initialization_vector
|
||||
|
||||
validate_for_algorithm = _check_iv_and_key_length
|
||||
|
||||
|
||||
class XTS(Mode, ModeWithTweak):
|
||||
name = "XTS"
|
||||
|
||||
def __init__(self, tweak: bytes):
|
||||
utils._check_byteslike("tweak", tweak)
|
||||
|
||||
if len(tweak) != 16:
|
||||
raise ValueError("tweak must be 128-bits (16 bytes)")
|
||||
|
||||
self._tweak = tweak
|
||||
|
||||
@property
|
||||
def tweak(self) -> bytes:
|
||||
return self._tweak
|
||||
|
||||
def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None:
|
||||
if algorithm.key_size not in (256, 512):
|
||||
raise ValueError(
|
||||
"The XTS specification requires a 256-bit key for AES-128-XTS"
|
||||
" and 512-bit key for AES-256-XTS"
|
||||
)
|
||||
|
||||
|
||||
class ECB(Mode):
|
||||
name = "ECB"
|
||||
|
||||
validate_for_algorithm = _check_aes_key_length
|
||||
|
||||
|
||||
class OFB(Mode, ModeWithInitializationVector):
|
||||
name = "OFB"
|
||||
|
||||
def __init__(self, initialization_vector: bytes):
|
||||
utils._check_byteslike("initialization_vector", initialization_vector)
|
||||
self._initialization_vector = initialization_vector
|
||||
|
||||
@property
|
||||
def initialization_vector(self) -> bytes:
|
||||
return self._initialization_vector
|
||||
|
||||
validate_for_algorithm = _check_iv_and_key_length
|
||||
|
||||
|
||||
class CFB(Mode, ModeWithInitializationVector):
|
||||
name = "CFB"
|
||||
|
||||
def __init__(self, initialization_vector: bytes):
|
||||
utils._check_byteslike("initialization_vector", initialization_vector)
|
||||
self._initialization_vector = initialization_vector
|
||||
|
||||
@property
|
||||
def initialization_vector(self) -> bytes:
|
||||
return self._initialization_vector
|
||||
|
||||
validate_for_algorithm = _check_iv_and_key_length
|
||||
|
||||
|
||||
class CFB8(Mode, ModeWithInitializationVector):
|
||||
name = "CFB8"
|
||||
|
||||
def __init__(self, initialization_vector: bytes):
|
||||
utils._check_byteslike("initialization_vector", initialization_vector)
|
||||
self._initialization_vector = initialization_vector
|
||||
|
||||
@property
|
||||
def initialization_vector(self) -> bytes:
|
||||
return self._initialization_vector
|
||||
|
||||
validate_for_algorithm = _check_iv_and_key_length
|
||||
|
||||
|
||||
class CTR(Mode, ModeWithNonce):
|
||||
name = "CTR"
|
||||
|
||||
def __init__(self, nonce: bytes):
|
||||
utils._check_byteslike("nonce", nonce)
|
||||
self._nonce = nonce
|
||||
|
||||
@property
|
||||
def nonce(self) -> bytes:
|
||||
return self._nonce
|
||||
|
||||
def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None:
|
||||
_check_aes_key_length(self, algorithm)
|
||||
_check_nonce_length(self.nonce, self.name, algorithm)
|
||||
|
||||
|
||||
class GCM(Mode, ModeWithInitializationVector, ModeWithAuthenticationTag):
|
||||
name = "GCM"
|
||||
_MAX_ENCRYPTED_BYTES = (2 ** 39 - 256) // 8
|
||||
_MAX_AAD_BYTES = (2 ** 64) // 8
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
initialization_vector: bytes,
|
||||
tag: typing.Optional[bytes] = None,
|
||||
min_tag_length: int = 16,
|
||||
):
|
||||
# OpenSSL 3.0.0 constrains GCM IVs to [64, 1024] bits inclusive
|
||||
# This is a sane limit anyway so we'll enforce it here.
|
||||
utils._check_byteslike("initialization_vector", initialization_vector)
|
||||
if len(initialization_vector) < 8 or len(initialization_vector) > 128:
|
||||
raise ValueError(
|
||||
"initialization_vector must be between 8 and 128 bytes (64 "
|
||||
"and 1024 bits)."
|
||||
)
|
||||
self._initialization_vector = initialization_vector
|
||||
if tag is not None:
|
||||
utils._check_bytes("tag", tag)
|
||||
if min_tag_length < 4:
|
||||
raise ValueError("min_tag_length must be >= 4")
|
||||
if len(tag) < min_tag_length:
|
||||
raise ValueError(
|
||||
"Authentication tag must be {} bytes or longer.".format(
|
||||
min_tag_length
|
||||
)
|
||||
)
|
||||
self._tag = tag
|
||||
self._min_tag_length = min_tag_length
|
||||
|
||||
@property
|
||||
def tag(self) -> typing.Optional[bytes]:
|
||||
return self._tag
|
||||
|
||||
@property
|
||||
def initialization_vector(self) -> bytes:
|
||||
return self._initialization_vector
|
||||
|
||||
def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None:
|
||||
_check_aes_key_length(self, algorithm)
|
||||
if not isinstance(algorithm, BlockCipherAlgorithm):
|
||||
raise UnsupportedAlgorithm(
|
||||
"GCM requires a block cipher algorithm",
|
||||
_Reasons.UNSUPPORTED_CIPHER,
|
||||
)
|
||||
block_size_bytes = algorithm.block_size // 8
|
||||
if self._tag is not None and len(self._tag) > block_size_bytes:
|
||||
raise ValueError(
|
||||
"Authentication tag cannot be more than {} bytes.".format(
|
||||
block_size_bytes
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,70 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import typing
|
||||
|
||||
from cryptography import utils
|
||||
from cryptography.exceptions import (
|
||||
AlreadyFinalized,
|
||||
UnsupportedAlgorithm,
|
||||
_Reasons,
|
||||
)
|
||||
from cryptography.hazmat.backends import _get_backend
|
||||
from cryptography.hazmat.backends.interfaces import Backend, CMACBackend
|
||||
from cryptography.hazmat.primitives import ciphers
|
||||
|
||||
|
||||
class CMAC(object):
|
||||
def __init__(
|
||||
self,
|
||||
algorithm: ciphers.BlockCipherAlgorithm,
|
||||
backend: typing.Optional[Backend] = None,
|
||||
ctx=None,
|
||||
):
|
||||
backend = _get_backend(backend)
|
||||
if not isinstance(backend, CMACBackend):
|
||||
raise UnsupportedAlgorithm(
|
||||
"Backend object does not implement CMACBackend.",
|
||||
_Reasons.BACKEND_MISSING_INTERFACE,
|
||||
)
|
||||
|
||||
if not isinstance(algorithm, ciphers.BlockCipherAlgorithm):
|
||||
raise TypeError("Expected instance of BlockCipherAlgorithm.")
|
||||
self._algorithm = algorithm
|
||||
|
||||
self._backend = backend
|
||||
if ctx is None:
|
||||
self._ctx = self._backend.create_cmac_ctx(self._algorithm)
|
||||
else:
|
||||
self._ctx = ctx
|
||||
|
||||
def update(self, data: bytes) -> None:
|
||||
if self._ctx is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
|
||||
utils._check_bytes("data", data)
|
||||
self._ctx.update(data)
|
||||
|
||||
def finalize(self) -> bytes:
|
||||
if self._ctx is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
digest = self._ctx.finalize()
|
||||
self._ctx = None
|
||||
return digest
|
||||
|
||||
def verify(self, signature: bytes) -> None:
|
||||
utils._check_bytes("signature", signature)
|
||||
if self._ctx is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
|
||||
ctx, self._ctx = self._ctx, None
|
||||
ctx.verify(signature)
|
||||
|
||||
def copy(self) -> "CMAC":
|
||||
if self._ctx is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
return CMAC(
|
||||
self._algorithm, backend=self._backend, ctx=self._ctx.copy()
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import hmac
|
||||
|
||||
|
||||
def bytes_eq(a: bytes, b: bytes) -> bool:
|
||||
if not isinstance(a, bytes) or not isinstance(b, bytes):
|
||||
raise TypeError("a and b must be bytes.")
|
||||
|
||||
return hmac.compare_digest(a, b)
|
||||
@@ -0,0 +1,268 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
import abc
|
||||
import typing
|
||||
|
||||
from cryptography import utils
|
||||
from cryptography.exceptions import (
|
||||
AlreadyFinalized,
|
||||
UnsupportedAlgorithm,
|
||||
_Reasons,
|
||||
)
|
||||
from cryptography.hazmat.backends import _get_backend
|
||||
from cryptography.hazmat.backends.interfaces import Backend, HashBackend
|
||||
|
||||
|
||||
class HashAlgorithm(metaclass=abc.ABCMeta):
|
||||
@abc.abstractproperty
|
||||
def name(self) -> str:
|
||||
"""
|
||||
A string naming this algorithm (e.g. "sha256", "md5").
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
def digest_size(self) -> int:
|
||||
"""
|
||||
The size of the resulting digest in bytes.
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
def block_size(self) -> typing.Optional[int]:
|
||||
"""
|
||||
The internal block size of the hash function, or None if the hash
|
||||
function does not use blocks internally (e.g. SHA3).
|
||||
"""
|
||||
|
||||
|
||||
class HashContext(metaclass=abc.ABCMeta):
|
||||
@abc.abstractproperty
|
||||
def algorithm(self) -> HashAlgorithm:
|
||||
"""
|
||||
A HashAlgorithm that will be used by this context.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def update(self, data: bytes) -> None:
|
||||
"""
|
||||
Processes the provided bytes through the hash.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def finalize(self) -> bytes:
|
||||
"""
|
||||
Finalizes the hash context and returns the hash digest as bytes.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def copy(self) -> "HashContext":
|
||||
"""
|
||||
Return a HashContext that is a copy of the current context.
|
||||
"""
|
||||
|
||||
|
||||
class ExtendableOutputFunction(metaclass=abc.ABCMeta):
|
||||
"""
|
||||
An interface for extendable output functions.
|
||||
"""
|
||||
|
||||
|
||||
class Hash(HashContext):
|
||||
def __init__(
|
||||
self,
|
||||
algorithm: HashAlgorithm,
|
||||
backend: typing.Optional[Backend] = None,
|
||||
ctx: typing.Optional["HashContext"] = None,
|
||||
):
|
||||
backend = _get_backend(backend)
|
||||
if not isinstance(backend, HashBackend):
|
||||
raise UnsupportedAlgorithm(
|
||||
"Backend object does not implement HashBackend.",
|
||||
_Reasons.BACKEND_MISSING_INTERFACE,
|
||||
)
|
||||
|
||||
if not isinstance(algorithm, HashAlgorithm):
|
||||
raise TypeError("Expected instance of hashes.HashAlgorithm.")
|
||||
self._algorithm = algorithm
|
||||
|
||||
self._backend = backend
|
||||
|
||||
if ctx is None:
|
||||
self._ctx = self._backend.create_hash_ctx(self.algorithm)
|
||||
else:
|
||||
self._ctx = ctx
|
||||
|
||||
@property
|
||||
def algorithm(self) -> HashAlgorithm:
|
||||
return self._algorithm
|
||||
|
||||
def update(self, data: bytes) -> None:
|
||||
if self._ctx is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
utils._check_byteslike("data", data)
|
||||
self._ctx.update(data)
|
||||
|
||||
def copy(self) -> "Hash":
|
||||
if self._ctx is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
return Hash(
|
||||
self.algorithm, backend=self._backend, ctx=self._ctx.copy()
|
||||
)
|
||||
|
||||
def finalize(self) -> bytes:
|
||||
if self._ctx is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
digest = self._ctx.finalize()
|
||||
self._ctx = None
|
||||
return digest
|
||||
|
||||
|
||||
class SHA1(HashAlgorithm):
|
||||
name = "sha1"
|
||||
digest_size = 20
|
||||
block_size = 64
|
||||
|
||||
|
||||
class SHA512_224(HashAlgorithm): # noqa: N801
|
||||
name = "sha512-224"
|
||||
digest_size = 28
|
||||
block_size = 128
|
||||
|
||||
|
||||
class SHA512_256(HashAlgorithm): # noqa: N801
|
||||
name = "sha512-256"
|
||||
digest_size = 32
|
||||
block_size = 128
|
||||
|
||||
|
||||
class SHA224(HashAlgorithm):
|
||||
name = "sha224"
|
||||
digest_size = 28
|
||||
block_size = 64
|
||||
|
||||
|
||||
class SHA256(HashAlgorithm):
|
||||
name = "sha256"
|
||||
digest_size = 32
|
||||
block_size = 64
|
||||
|
||||
|
||||
class SHA384(HashAlgorithm):
|
||||
name = "sha384"
|
||||
digest_size = 48
|
||||
block_size = 128
|
||||
|
||||
|
||||
class SHA512(HashAlgorithm):
|
||||
name = "sha512"
|
||||
digest_size = 64
|
||||
block_size = 128
|
||||
|
||||
|
||||
class SHA3_224(HashAlgorithm): # noqa: N801
|
||||
name = "sha3-224"
|
||||
digest_size = 28
|
||||
block_size = None
|
||||
|
||||
|
||||
class SHA3_256(HashAlgorithm): # noqa: N801
|
||||
name = "sha3-256"
|
||||
digest_size = 32
|
||||
block_size = None
|
||||
|
||||
|
||||
class SHA3_384(HashAlgorithm): # noqa: N801
|
||||
name = "sha3-384"
|
||||
digest_size = 48
|
||||
block_size = None
|
||||
|
||||
|
||||
class SHA3_512(HashAlgorithm): # noqa: N801
|
||||
name = "sha3-512"
|
||||
digest_size = 64
|
||||
block_size = None
|
||||
|
||||
|
||||
class SHAKE128(HashAlgorithm, ExtendableOutputFunction):
|
||||
name = "shake128"
|
||||
block_size = None
|
||||
|
||||
def __init__(self, digest_size: int):
|
||||
if not isinstance(digest_size, int):
|
||||
raise TypeError("digest_size must be an integer")
|
||||
|
||||
if digest_size < 1:
|
||||
raise ValueError("digest_size must be a positive integer")
|
||||
|
||||
self._digest_size = digest_size
|
||||
|
||||
@property
|
||||
def digest_size(self) -> int:
|
||||
return self._digest_size
|
||||
|
||||
|
||||
class SHAKE256(HashAlgorithm, ExtendableOutputFunction):
|
||||
name = "shake256"
|
||||
block_size = None
|
||||
|
||||
def __init__(self, digest_size: int):
|
||||
if not isinstance(digest_size, int):
|
||||
raise TypeError("digest_size must be an integer")
|
||||
|
||||
if digest_size < 1:
|
||||
raise ValueError("digest_size must be a positive integer")
|
||||
|
||||
self._digest_size = digest_size
|
||||
|
||||
@property
|
||||
def digest_size(self) -> int:
|
||||
return self._digest_size
|
||||
|
||||
|
||||
class MD5(HashAlgorithm):
|
||||
name = "md5"
|
||||
digest_size = 16
|
||||
block_size = 64
|
||||
|
||||
|
||||
class BLAKE2b(HashAlgorithm):
|
||||
name = "blake2b"
|
||||
_max_digest_size = 64
|
||||
_min_digest_size = 1
|
||||
block_size = 128
|
||||
|
||||
def __init__(self, digest_size: int):
|
||||
|
||||
if digest_size != 64:
|
||||
raise ValueError("Digest size must be 64")
|
||||
|
||||
self._digest_size = digest_size
|
||||
|
||||
@property
|
||||
def digest_size(self) -> int:
|
||||
return self._digest_size
|
||||
|
||||
|
||||
class BLAKE2s(HashAlgorithm):
|
||||
name = "blake2s"
|
||||
block_size = 64
|
||||
_max_digest_size = 32
|
||||
_min_digest_size = 1
|
||||
|
||||
def __init__(self, digest_size: int):
|
||||
|
||||
if digest_size != 32:
|
||||
raise ValueError("Digest size must be 32")
|
||||
|
||||
self._digest_size = digest_size
|
||||
|
||||
@property
|
||||
def digest_size(self) -> int:
|
||||
return self._digest_size
|
||||
|
||||
|
||||
class SM3(HashAlgorithm):
|
||||
name = "sm3"
|
||||
digest_size = 32
|
||||
block_size = 64
|
||||
@@ -0,0 +1,78 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import typing
|
||||
|
||||
from cryptography import utils
|
||||
from cryptography.exceptions import (
|
||||
AlreadyFinalized,
|
||||
UnsupportedAlgorithm,
|
||||
_Reasons,
|
||||
)
|
||||
from cryptography.hazmat.backends import _get_backend
|
||||
from cryptography.hazmat.backends.interfaces import Backend, HMACBackend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
|
||||
|
||||
class HMAC(hashes.HashContext):
|
||||
def __init__(
|
||||
self,
|
||||
key: bytes,
|
||||
algorithm: hashes.HashAlgorithm,
|
||||
backend: typing.Optional[Backend] = None,
|
||||
ctx=None,
|
||||
):
|
||||
backend = _get_backend(backend)
|
||||
if not isinstance(backend, HMACBackend):
|
||||
raise UnsupportedAlgorithm(
|
||||
"Backend object does not implement HMACBackend.",
|
||||
_Reasons.BACKEND_MISSING_INTERFACE,
|
||||
)
|
||||
|
||||
if not isinstance(algorithm, hashes.HashAlgorithm):
|
||||
raise TypeError("Expected instance of hashes.HashAlgorithm.")
|
||||
self._algorithm = algorithm
|
||||
|
||||
self._backend = backend
|
||||
self._key = key
|
||||
if ctx is None:
|
||||
self._ctx = self._backend.create_hmac_ctx(key, self.algorithm)
|
||||
else:
|
||||
self._ctx = ctx
|
||||
|
||||
@property
|
||||
def algorithm(self) -> hashes.HashAlgorithm:
|
||||
return self._algorithm
|
||||
|
||||
def update(self, data: bytes) -> None:
|
||||
if self._ctx is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
utils._check_byteslike("data", data)
|
||||
self._ctx.update(data)
|
||||
|
||||
def copy(self) -> "HMAC":
|
||||
if self._ctx is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
return HMAC(
|
||||
self._key,
|
||||
self.algorithm,
|
||||
backend=self._backend,
|
||||
ctx=self._ctx.copy(),
|
||||
)
|
||||
|
||||
def finalize(self) -> bytes:
|
||||
if self._ctx is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
digest = self._ctx.finalize()
|
||||
self._ctx = None
|
||||
return digest
|
||||
|
||||
def verify(self, signature: bytes) -> None:
|
||||
utils._check_bytes("signature", signature)
|
||||
if self._ctx is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
|
||||
ctx, self._ctx = self._ctx, None
|
||||
ctx.verify(signature)
|
||||
@@ -0,0 +1,22 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import abc
|
||||
|
||||
|
||||
class KeyDerivationFunction(metaclass=abc.ABCMeta):
|
||||
@abc.abstractmethod
|
||||
def derive(self, key_material: bytes) -> bytes:
|
||||
"""
|
||||
Deterministically generates and returns a new key based on the existing
|
||||
key material.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def verify(self, key_material: bytes, expected_key: bytes) -> None:
|
||||
"""
|
||||
Checks whether the key generated by the key material matches the
|
||||
expected derived key. Raises an exception if they do not match.
|
||||
"""
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,155 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import struct
|
||||
import typing
|
||||
|
||||
from cryptography import utils
|
||||
from cryptography.exceptions import (
|
||||
AlreadyFinalized,
|
||||
InvalidKey,
|
||||
UnsupportedAlgorithm,
|
||||
_Reasons,
|
||||
)
|
||||
from cryptography.hazmat.backends import _get_backend
|
||||
from cryptography.hazmat.backends.interfaces import (
|
||||
Backend,
|
||||
HMACBackend,
|
||||
HashBackend,
|
||||
)
|
||||
from cryptography.hazmat.primitives import constant_time, hashes, hmac
|
||||
from cryptography.hazmat.primitives.kdf import KeyDerivationFunction
|
||||
|
||||
|
||||
def _int_to_u32be(n: int) -> bytes:
|
||||
return struct.pack(">I", n)
|
||||
|
||||
|
||||
def _common_args_checks(
|
||||
algorithm: hashes.HashAlgorithm,
|
||||
length: int,
|
||||
otherinfo: typing.Optional[bytes],
|
||||
) -> None:
|
||||
max_length = algorithm.digest_size * (2 ** 32 - 1)
|
||||
if length > max_length:
|
||||
raise ValueError(
|
||||
"Cannot derive keys larger than {} bits.".format(max_length)
|
||||
)
|
||||
if otherinfo is not None:
|
||||
utils._check_bytes("otherinfo", otherinfo)
|
||||
|
||||
|
||||
def _concatkdf_derive(
|
||||
key_material: bytes,
|
||||
length: int,
|
||||
auxfn: typing.Callable[[], hashes.HashContext],
|
||||
otherinfo: bytes,
|
||||
) -> bytes:
|
||||
utils._check_byteslike("key_material", key_material)
|
||||
output = [b""]
|
||||
outlen = 0
|
||||
counter = 1
|
||||
|
||||
while length > outlen:
|
||||
h = auxfn()
|
||||
h.update(_int_to_u32be(counter))
|
||||
h.update(key_material)
|
||||
h.update(otherinfo)
|
||||
output.append(h.finalize())
|
||||
outlen += len(output[-1])
|
||||
counter += 1
|
||||
|
||||
return b"".join(output)[:length]
|
||||
|
||||
|
||||
class ConcatKDFHash(KeyDerivationFunction):
|
||||
def __init__(
|
||||
self,
|
||||
algorithm: hashes.HashAlgorithm,
|
||||
length: int,
|
||||
otherinfo: typing.Optional[bytes],
|
||||
backend: typing.Optional[Backend] = None,
|
||||
):
|
||||
backend = _get_backend(backend)
|
||||
|
||||
_common_args_checks(algorithm, length, otherinfo)
|
||||
self._algorithm = algorithm
|
||||
self._length = length
|
||||
self._otherinfo: bytes = otherinfo if otherinfo is not None else b""
|
||||
|
||||
if not isinstance(backend, HashBackend):
|
||||
raise UnsupportedAlgorithm(
|
||||
"Backend object does not implement HashBackend.",
|
||||
_Reasons.BACKEND_MISSING_INTERFACE,
|
||||
)
|
||||
self._backend = backend
|
||||
self._used = False
|
||||
|
||||
def _hash(self) -> hashes.Hash:
|
||||
return hashes.Hash(self._algorithm, self._backend)
|
||||
|
||||
def derive(self, key_material: bytes) -> bytes:
|
||||
if self._used:
|
||||
raise AlreadyFinalized
|
||||
self._used = True
|
||||
return _concatkdf_derive(
|
||||
key_material, self._length, self._hash, self._otherinfo
|
||||
)
|
||||
|
||||
def verify(self, key_material: bytes, expected_key: bytes) -> None:
|
||||
if not constant_time.bytes_eq(self.derive(key_material), expected_key):
|
||||
raise InvalidKey
|
||||
|
||||
|
||||
class ConcatKDFHMAC(KeyDerivationFunction):
|
||||
def __init__(
|
||||
self,
|
||||
algorithm: hashes.HashAlgorithm,
|
||||
length: int,
|
||||
salt: typing.Optional[bytes],
|
||||
otherinfo: typing.Optional[bytes],
|
||||
backend: typing.Optional[Backend] = None,
|
||||
):
|
||||
backend = _get_backend(backend)
|
||||
|
||||
_common_args_checks(algorithm, length, otherinfo)
|
||||
self._algorithm = algorithm
|
||||
self._length = length
|
||||
self._otherinfo: bytes = otherinfo if otherinfo is not None else b""
|
||||
|
||||
if algorithm.block_size is None:
|
||||
raise TypeError(
|
||||
"{} is unsupported for ConcatKDF".format(algorithm.name)
|
||||
)
|
||||
|
||||
if salt is None:
|
||||
salt = b"\x00" * algorithm.block_size
|
||||
else:
|
||||
utils._check_bytes("salt", salt)
|
||||
|
||||
self._salt = salt
|
||||
|
||||
if not isinstance(backend, HMACBackend):
|
||||
raise UnsupportedAlgorithm(
|
||||
"Backend object does not implement HMACBackend.",
|
||||
_Reasons.BACKEND_MISSING_INTERFACE,
|
||||
)
|
||||
self._backend = backend
|
||||
self._used = False
|
||||
|
||||
def _hmac(self) -> hmac.HMAC:
|
||||
return hmac.HMAC(self._salt, self._algorithm, self._backend)
|
||||
|
||||
def derive(self, key_material: bytes) -> bytes:
|
||||
if self._used:
|
||||
raise AlreadyFinalized
|
||||
self._used = True
|
||||
return _concatkdf_derive(
|
||||
key_material, self._length, self._hmac, self._otherinfo
|
||||
)
|
||||
|
||||
def verify(self, key_material: bytes, expected_key: bytes) -> None:
|
||||
if not constant_time.bytes_eq(self.derive(key_material), expected_key):
|
||||
raise InvalidKey
|
||||
@@ -0,0 +1,125 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import typing
|
||||
|
||||
from cryptography import utils
|
||||
from cryptography.exceptions import (
|
||||
AlreadyFinalized,
|
||||
InvalidKey,
|
||||
UnsupportedAlgorithm,
|
||||
_Reasons,
|
||||
)
|
||||
from cryptography.hazmat.backends import _get_backend
|
||||
from cryptography.hazmat.backends.interfaces import Backend, HMACBackend
|
||||
from cryptography.hazmat.primitives import constant_time, hashes, hmac
|
||||
from cryptography.hazmat.primitives.kdf import KeyDerivationFunction
|
||||
|
||||
|
||||
class HKDF(KeyDerivationFunction):
|
||||
def __init__(
|
||||
self,
|
||||
algorithm: hashes.HashAlgorithm,
|
||||
length: int,
|
||||
salt: typing.Optional[bytes],
|
||||
info: typing.Optional[bytes],
|
||||
backend: typing.Optional[Backend] = None,
|
||||
):
|
||||
backend = _get_backend(backend)
|
||||
if not isinstance(backend, HMACBackend):
|
||||
raise UnsupportedAlgorithm(
|
||||
"Backend object does not implement HMACBackend.",
|
||||
_Reasons.BACKEND_MISSING_INTERFACE,
|
||||
)
|
||||
|
||||
self._algorithm = algorithm
|
||||
|
||||
if salt is None:
|
||||
salt = b"\x00" * self._algorithm.digest_size
|
||||
else:
|
||||
utils._check_bytes("salt", salt)
|
||||
|
||||
self._salt = salt
|
||||
|
||||
self._backend = backend
|
||||
|
||||
self._hkdf_expand = HKDFExpand(self._algorithm, length, info, backend)
|
||||
|
||||
def _extract(self, key_material: bytes) -> bytes:
|
||||
h = hmac.HMAC(self._salt, self._algorithm, backend=self._backend)
|
||||
h.update(key_material)
|
||||
return h.finalize()
|
||||
|
||||
def derive(self, key_material: bytes) -> bytes:
|
||||
utils._check_byteslike("key_material", key_material)
|
||||
return self._hkdf_expand.derive(self._extract(key_material))
|
||||
|
||||
def verify(self, key_material: bytes, expected_key: bytes) -> None:
|
||||
if not constant_time.bytes_eq(self.derive(key_material), expected_key):
|
||||
raise InvalidKey
|
||||
|
||||
|
||||
class HKDFExpand(KeyDerivationFunction):
|
||||
def __init__(
|
||||
self,
|
||||
algorithm: hashes.HashAlgorithm,
|
||||
length: int,
|
||||
info: typing.Optional[bytes],
|
||||
backend: typing.Optional[Backend] = None,
|
||||
):
|
||||
backend = _get_backend(backend)
|
||||
if not isinstance(backend, HMACBackend):
|
||||
raise UnsupportedAlgorithm(
|
||||
"Backend object does not implement HMACBackend.",
|
||||
_Reasons.BACKEND_MISSING_INTERFACE,
|
||||
)
|
||||
|
||||
self._algorithm = algorithm
|
||||
|
||||
self._backend = backend
|
||||
|
||||
max_length = 255 * algorithm.digest_size
|
||||
|
||||
if length > max_length:
|
||||
raise ValueError(
|
||||
"Cannot derive keys larger than {} octets.".format(max_length)
|
||||
)
|
||||
|
||||
self._length = length
|
||||
|
||||
if info is None:
|
||||
info = b""
|
||||
else:
|
||||
utils._check_bytes("info", info)
|
||||
|
||||
self._info = info
|
||||
|
||||
self._used = False
|
||||
|
||||
def _expand(self, key_material: bytes) -> bytes:
|
||||
output = [b""]
|
||||
counter = 1
|
||||
|
||||
while self._algorithm.digest_size * (len(output) - 1) < self._length:
|
||||
h = hmac.HMAC(key_material, self._algorithm, backend=self._backend)
|
||||
h.update(output[-1])
|
||||
h.update(self._info)
|
||||
h.update(bytes([counter]))
|
||||
output.append(h.finalize())
|
||||
counter += 1
|
||||
|
||||
return b"".join(output)[: self._length]
|
||||
|
||||
def derive(self, key_material: bytes) -> bytes:
|
||||
utils._check_byteslike("key_material", key_material)
|
||||
if self._used:
|
||||
raise AlreadyFinalized
|
||||
|
||||
self._used = True
|
||||
return self._expand(key_material)
|
||||
|
||||
def verify(self, key_material: bytes, expected_key: bytes) -> None:
|
||||
if not constant_time.bytes_eq(self.derive(key_material), expected_key):
|
||||
raise InvalidKey
|
||||
@@ -0,0 +1,272 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
import typing
|
||||
|
||||
from cryptography import utils
|
||||
from cryptography.exceptions import (
|
||||
AlreadyFinalized,
|
||||
InvalidKey,
|
||||
UnsupportedAlgorithm,
|
||||
_Reasons,
|
||||
)
|
||||
from cryptography.hazmat.backends import _get_backend
|
||||
from cryptography.hazmat.backends.interfaces import (
|
||||
Backend,
|
||||
CMACBackend,
|
||||
HMACBackend,
|
||||
)
|
||||
from cryptography.hazmat.primitives import (
|
||||
ciphers,
|
||||
cmac,
|
||||
constant_time,
|
||||
hashes,
|
||||
hmac,
|
||||
)
|
||||
from cryptography.hazmat.primitives.kdf import KeyDerivationFunction
|
||||
|
||||
|
||||
class Mode(utils.Enum):
|
||||
CounterMode = "ctr"
|
||||
|
||||
|
||||
class CounterLocation(utils.Enum):
|
||||
BeforeFixed = "before_fixed"
|
||||
AfterFixed = "after_fixed"
|
||||
|
||||
|
||||
class _KBKDFDeriver:
|
||||
def __init__(
|
||||
self,
|
||||
prf: typing.Callable,
|
||||
mode: Mode,
|
||||
length: int,
|
||||
rlen: int,
|
||||
llen: typing.Optional[int],
|
||||
location: CounterLocation,
|
||||
label: typing.Optional[bytes],
|
||||
context: typing.Optional[bytes],
|
||||
fixed: typing.Optional[bytes],
|
||||
):
|
||||
assert callable(prf)
|
||||
|
||||
if not isinstance(mode, Mode):
|
||||
raise TypeError("mode must be of type Mode")
|
||||
|
||||
if not isinstance(location, CounterLocation):
|
||||
raise TypeError("location must be of type CounterLocation")
|
||||
|
||||
if (label or context) and fixed:
|
||||
raise ValueError(
|
||||
"When supplying fixed data, " "label and context are ignored."
|
||||
)
|
||||
|
||||
if rlen is None or not self._valid_byte_length(rlen):
|
||||
raise ValueError("rlen must be between 1 and 4")
|
||||
|
||||
if llen is None and fixed is None:
|
||||
raise ValueError("Please specify an llen")
|
||||
|
||||
if llen is not None and not isinstance(llen, int):
|
||||
raise TypeError("llen must be an integer")
|
||||
|
||||
if label is None:
|
||||
label = b""
|
||||
|
||||
if context is None:
|
||||
context = b""
|
||||
|
||||
utils._check_bytes("label", label)
|
||||
utils._check_bytes("context", context)
|
||||
self._prf = prf
|
||||
self._mode = mode
|
||||
self._length = length
|
||||
self._rlen = rlen
|
||||
self._llen = llen
|
||||
self._location = location
|
||||
self._label = label
|
||||
self._context = context
|
||||
self._used = False
|
||||
self._fixed_data = fixed
|
||||
|
||||
@staticmethod
|
||||
def _valid_byte_length(value: int) -> bool:
|
||||
if not isinstance(value, int):
|
||||
raise TypeError("value must be of type int")
|
||||
|
||||
value_bin = utils.int_to_bytes(1, value)
|
||||
if not 1 <= len(value_bin) <= 4:
|
||||
return False
|
||||
return True
|
||||
|
||||
def derive(self, key_material: bytes, prf_output_size: int) -> bytes:
|
||||
if self._used:
|
||||
raise AlreadyFinalized
|
||||
|
||||
utils._check_byteslike("key_material", key_material)
|
||||
self._used = True
|
||||
|
||||
# inverse floor division (equivalent to ceiling)
|
||||
rounds = -(-self._length // prf_output_size)
|
||||
|
||||
output = [b""]
|
||||
|
||||
# For counter mode, the number of iterations shall not be
|
||||
# larger than 2^r-1, where r <= 32 is the binary length of the counter
|
||||
# This ensures that the counter values used as an input to the
|
||||
# PRF will not repeat during a particular call to the KDF function.
|
||||
r_bin = utils.int_to_bytes(1, self._rlen)
|
||||
if rounds > pow(2, len(r_bin) * 8) - 1:
|
||||
raise ValueError("There are too many iterations.")
|
||||
|
||||
for i in range(1, rounds + 1):
|
||||
h = self._prf(key_material)
|
||||
|
||||
counter = utils.int_to_bytes(i, self._rlen)
|
||||
if self._location == CounterLocation.BeforeFixed:
|
||||
h.update(counter)
|
||||
|
||||
h.update(self._generate_fixed_input())
|
||||
|
||||
if self._location == CounterLocation.AfterFixed:
|
||||
h.update(counter)
|
||||
|
||||
output.append(h.finalize())
|
||||
|
||||
return b"".join(output)[: self._length]
|
||||
|
||||
def _generate_fixed_input(self) -> bytes:
|
||||
if self._fixed_data and isinstance(self._fixed_data, bytes):
|
||||
return self._fixed_data
|
||||
|
||||
l_val = utils.int_to_bytes(self._length * 8, self._llen)
|
||||
|
||||
return b"".join([self._label, b"\x00", self._context, l_val])
|
||||
|
||||
|
||||
class KBKDFHMAC(KeyDerivationFunction):
|
||||
def __init__(
|
||||
self,
|
||||
algorithm: hashes.HashAlgorithm,
|
||||
mode: Mode,
|
||||
length: int,
|
||||
rlen: int,
|
||||
llen: typing.Optional[int],
|
||||
location: CounterLocation,
|
||||
label: typing.Optional[bytes],
|
||||
context: typing.Optional[bytes],
|
||||
fixed: typing.Optional[bytes],
|
||||
backend: typing.Optional[Backend] = None,
|
||||
):
|
||||
backend = _get_backend(backend)
|
||||
if not isinstance(backend, HMACBackend):
|
||||
raise UnsupportedAlgorithm(
|
||||
"Backend object does not implement HMACBackend.",
|
||||
_Reasons.BACKEND_MISSING_INTERFACE,
|
||||
)
|
||||
|
||||
if not isinstance(algorithm, hashes.HashAlgorithm):
|
||||
raise UnsupportedAlgorithm(
|
||||
"Algorithm supplied is not a supported hash algorithm.",
|
||||
_Reasons.UNSUPPORTED_HASH,
|
||||
)
|
||||
|
||||
if not backend.hmac_supported(algorithm):
|
||||
raise UnsupportedAlgorithm(
|
||||
"Algorithm supplied is not a supported hmac algorithm.",
|
||||
_Reasons.UNSUPPORTED_HASH,
|
||||
)
|
||||
|
||||
self._algorithm = algorithm
|
||||
self._backend = backend
|
||||
|
||||
self._deriver = _KBKDFDeriver(
|
||||
self._prf,
|
||||
mode,
|
||||
length,
|
||||
rlen,
|
||||
llen,
|
||||
location,
|
||||
label,
|
||||
context,
|
||||
fixed,
|
||||
)
|
||||
|
||||
def _prf(self, key_material: bytes):
|
||||
return hmac.HMAC(key_material, self._algorithm, backend=self._backend)
|
||||
|
||||
def derive(self, key_material) -> bytes:
|
||||
return self._deriver.derive(key_material, self._algorithm.digest_size)
|
||||
|
||||
def verify(self, key_material: bytes, expected_key: bytes) -> None:
|
||||
if not constant_time.bytes_eq(self.derive(key_material), expected_key):
|
||||
raise InvalidKey
|
||||
|
||||
|
||||
class KBKDFCMAC(KeyDerivationFunction):
|
||||
def __init__(
|
||||
self,
|
||||
algorithm,
|
||||
mode: Mode,
|
||||
length: int,
|
||||
rlen: int,
|
||||
llen: typing.Optional[int],
|
||||
location: CounterLocation,
|
||||
label: typing.Optional[bytes],
|
||||
context: typing.Optional[bytes],
|
||||
fixed: typing.Optional[bytes],
|
||||
backend: typing.Optional[Backend] = None,
|
||||
):
|
||||
backend = _get_backend(backend)
|
||||
if not isinstance(backend, CMACBackend):
|
||||
raise UnsupportedAlgorithm(
|
||||
"Backend object does not implement CMACBackend.",
|
||||
_Reasons.BACKEND_MISSING_INTERFACE,
|
||||
)
|
||||
|
||||
if not issubclass(
|
||||
algorithm, ciphers.BlockCipherAlgorithm
|
||||
) or not issubclass(algorithm, ciphers.CipherAlgorithm):
|
||||
raise UnsupportedAlgorithm(
|
||||
"Algorithm supplied is not a supported cipher algorithm.",
|
||||
_Reasons.UNSUPPORTED_CIPHER,
|
||||
)
|
||||
|
||||
self._algorithm = algorithm
|
||||
self._backend = backend
|
||||
self._cipher: typing.Optional[ciphers.BlockCipherAlgorithm] = None
|
||||
|
||||
self._deriver = _KBKDFDeriver(
|
||||
self._prf,
|
||||
mode,
|
||||
length,
|
||||
rlen,
|
||||
llen,
|
||||
location,
|
||||
label,
|
||||
context,
|
||||
fixed,
|
||||
)
|
||||
|
||||
def _prf(self, _: bytes):
|
||||
assert self._cipher is not None
|
||||
|
||||
return cmac.CMAC(self._cipher, backend=self._backend)
|
||||
|
||||
def derive(self, key_material: bytes) -> bytes:
|
||||
self._cipher = self._algorithm(key_material)
|
||||
|
||||
assert self._cipher is not None
|
||||
|
||||
if not self._backend.cmac_algorithm_supported(self._cipher):
|
||||
raise UnsupportedAlgorithm(
|
||||
"Algorithm supplied is not a supported cipher algorithm.",
|
||||
_Reasons.UNSUPPORTED_CIPHER,
|
||||
)
|
||||
|
||||
return self._deriver.derive(key_material, self._cipher.block_size // 8)
|
||||
|
||||
def verify(self, key_material: bytes, expected_key: bytes) -> None:
|
||||
if not constant_time.bytes_eq(self.derive(key_material), expected_key):
|
||||
raise InvalidKey
|
||||
@@ -0,0 +1,69 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import typing
|
||||
|
||||
from cryptography import utils
|
||||
from cryptography.exceptions import (
|
||||
AlreadyFinalized,
|
||||
InvalidKey,
|
||||
UnsupportedAlgorithm,
|
||||
_Reasons,
|
||||
)
|
||||
from cryptography.hazmat.backends import _get_backend
|
||||
from cryptography.hazmat.backends.interfaces import Backend, PBKDF2HMACBackend
|
||||
from cryptography.hazmat.primitives import constant_time, hashes
|
||||
from cryptography.hazmat.primitives.kdf import KeyDerivationFunction
|
||||
|
||||
|
||||
class PBKDF2HMAC(KeyDerivationFunction):
|
||||
def __init__(
|
||||
self,
|
||||
algorithm: hashes.HashAlgorithm,
|
||||
length: int,
|
||||
salt: bytes,
|
||||
iterations: int,
|
||||
backend: typing.Optional[Backend] = None,
|
||||
):
|
||||
backend = _get_backend(backend)
|
||||
if not isinstance(backend, PBKDF2HMACBackend):
|
||||
raise UnsupportedAlgorithm(
|
||||
"Backend object does not implement PBKDF2HMACBackend.",
|
||||
_Reasons.BACKEND_MISSING_INTERFACE,
|
||||
)
|
||||
|
||||
if not backend.pbkdf2_hmac_supported(algorithm):
|
||||
raise UnsupportedAlgorithm(
|
||||
"{} is not supported for PBKDF2 by this backend.".format(
|
||||
algorithm.name
|
||||
),
|
||||
_Reasons.UNSUPPORTED_HASH,
|
||||
)
|
||||
self._used = False
|
||||
self._algorithm = algorithm
|
||||
self._length = length
|
||||
utils._check_bytes("salt", salt)
|
||||
self._salt = salt
|
||||
self._iterations = iterations
|
||||
self._backend = backend
|
||||
|
||||
def derive(self, key_material: bytes) -> bytes:
|
||||
if self._used:
|
||||
raise AlreadyFinalized("PBKDF2 instances can only be used once.")
|
||||
self._used = True
|
||||
|
||||
utils._check_byteslike("key_material", key_material)
|
||||
return self._backend.derive_pbkdf2_hmac(
|
||||
self._algorithm,
|
||||
self._length,
|
||||
self._salt,
|
||||
self._iterations,
|
||||
key_material,
|
||||
)
|
||||
|
||||
def verify(self, key_material: bytes, expected_key: bytes) -> None:
|
||||
derived_key = self.derive(key_material)
|
||||
if not constant_time.bytes_eq(derived_key, expected_key):
|
||||
raise InvalidKey("Keys do not match.")
|
||||
@@ -0,0 +1,75 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import sys
|
||||
import typing
|
||||
|
||||
from cryptography import utils
|
||||
from cryptography.exceptions import (
|
||||
AlreadyFinalized,
|
||||
InvalidKey,
|
||||
UnsupportedAlgorithm,
|
||||
_Reasons,
|
||||
)
|
||||
from cryptography.hazmat.backends import _get_backend
|
||||
from cryptography.hazmat.backends.interfaces import Backend, ScryptBackend
|
||||
from cryptography.hazmat.primitives import constant_time
|
||||
from cryptography.hazmat.primitives.kdf import KeyDerivationFunction
|
||||
|
||||
|
||||
# This is used by the scrypt tests to skip tests that require more memory
|
||||
# than the MEM_LIMIT
|
||||
_MEM_LIMIT = sys.maxsize // 2
|
||||
|
||||
|
||||
class Scrypt(KeyDerivationFunction):
|
||||
def __init__(
|
||||
self,
|
||||
salt: bytes,
|
||||
length: int,
|
||||
n: int,
|
||||
r: int,
|
||||
p: int,
|
||||
backend: typing.Optional[Backend] = None,
|
||||
):
|
||||
backend = _get_backend(backend)
|
||||
if not isinstance(backend, ScryptBackend):
|
||||
raise UnsupportedAlgorithm(
|
||||
"Backend object does not implement ScryptBackend.",
|
||||
_Reasons.BACKEND_MISSING_INTERFACE,
|
||||
)
|
||||
|
||||
self._length = length
|
||||
utils._check_bytes("salt", salt)
|
||||
if n < 2 or (n & (n - 1)) != 0:
|
||||
raise ValueError("n must be greater than 1 and be a power of 2.")
|
||||
|
||||
if r < 1:
|
||||
raise ValueError("r must be greater than or equal to 1.")
|
||||
|
||||
if p < 1:
|
||||
raise ValueError("p must be greater than or equal to 1.")
|
||||
|
||||
self._used = False
|
||||
self._salt = salt
|
||||
self._n = n
|
||||
self._r = r
|
||||
self._p = p
|
||||
self._backend = backend
|
||||
|
||||
def derive(self, key_material: bytes) -> bytes:
|
||||
if self._used:
|
||||
raise AlreadyFinalized("Scrypt instances can only be used once.")
|
||||
self._used = True
|
||||
|
||||
utils._check_byteslike("key_material", key_material)
|
||||
return self._backend.derive_scrypt(
|
||||
key_material, self._salt, self._length, self._n, self._r, self._p
|
||||
)
|
||||
|
||||
def verify(self, key_material: bytes, expected_key: bytes) -> None:
|
||||
derived_key = self.derive(key_material)
|
||||
if not constant_time.bytes_eq(derived_key, expected_key):
|
||||
raise InvalidKey("Keys do not match.")
|
||||
@@ -0,0 +1,79 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import struct
|
||||
import typing
|
||||
|
||||
from cryptography import utils
|
||||
from cryptography.exceptions import (
|
||||
AlreadyFinalized,
|
||||
InvalidKey,
|
||||
UnsupportedAlgorithm,
|
||||
_Reasons,
|
||||
)
|
||||
from cryptography.hazmat.backends import _get_backend
|
||||
from cryptography.hazmat.backends.interfaces import Backend, HashBackend
|
||||
from cryptography.hazmat.primitives import constant_time, hashes
|
||||
from cryptography.hazmat.primitives.kdf import KeyDerivationFunction
|
||||
|
||||
|
||||
def _int_to_u32be(n):
|
||||
return struct.pack(">I", n)
|
||||
|
||||
|
||||
class X963KDF(KeyDerivationFunction):
|
||||
def __init__(
|
||||
self,
|
||||
algorithm: hashes.HashAlgorithm,
|
||||
length: int,
|
||||
sharedinfo: typing.Optional[bytes],
|
||||
backend: typing.Optional[Backend] = None,
|
||||
):
|
||||
backend = _get_backend(backend)
|
||||
|
||||
max_len = algorithm.digest_size * (2 ** 32 - 1)
|
||||
if length > max_len:
|
||||
raise ValueError(
|
||||
"Cannot derive keys larger than {} bits.".format(max_len)
|
||||
)
|
||||
if sharedinfo is not None:
|
||||
utils._check_bytes("sharedinfo", sharedinfo)
|
||||
|
||||
self._algorithm = algorithm
|
||||
self._length = length
|
||||
self._sharedinfo = sharedinfo
|
||||
|
||||
if not isinstance(backend, HashBackend):
|
||||
raise UnsupportedAlgorithm(
|
||||
"Backend object does not implement HashBackend.",
|
||||
_Reasons.BACKEND_MISSING_INTERFACE,
|
||||
)
|
||||
self._backend = backend
|
||||
self._used = False
|
||||
|
||||
def derive(self, key_material: bytes) -> bytes:
|
||||
if self._used:
|
||||
raise AlreadyFinalized
|
||||
self._used = True
|
||||
utils._check_byteslike("key_material", key_material)
|
||||
output = [b""]
|
||||
outlen = 0
|
||||
counter = 1
|
||||
|
||||
while self._length > outlen:
|
||||
h = hashes.Hash(self._algorithm, self._backend)
|
||||
h.update(key_material)
|
||||
h.update(_int_to_u32be(counter))
|
||||
if self._sharedinfo is not None:
|
||||
h.update(self._sharedinfo)
|
||||
output.append(h.finalize())
|
||||
outlen += len(output[-1])
|
||||
counter += 1
|
||||
|
||||
return b"".join(output)[: self._length]
|
||||
|
||||
def verify(self, key_material: bytes, expected_key: bytes) -> None:
|
||||
if not constant_time.bytes_eq(self.derive(key_material), expected_key):
|
||||
raise InvalidKey
|
||||
@@ -0,0 +1,188 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import struct
|
||||
import typing
|
||||
|
||||
from cryptography.hazmat.backends import _get_backend
|
||||
from cryptography.hazmat.backends.interfaces import Backend
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher
|
||||
from cryptography.hazmat.primitives.ciphers.algorithms import AES
|
||||
from cryptography.hazmat.primitives.ciphers.modes import ECB
|
||||
from cryptography.hazmat.primitives.constant_time import bytes_eq
|
||||
|
||||
|
||||
def _wrap_core(
|
||||
wrapping_key: bytes,
|
||||
a: bytes,
|
||||
r: typing.List[bytes],
|
||||
backend: Backend,
|
||||
) -> bytes:
|
||||
# RFC 3394 Key Wrap - 2.2.1 (index method)
|
||||
encryptor = Cipher(AES(wrapping_key), ECB(), backend).encryptor()
|
||||
n = len(r)
|
||||
for j in range(6):
|
||||
for i in range(n):
|
||||
# every encryption operation is a discrete 16 byte chunk (because
|
||||
# AES has a 128-bit block size) and since we're using ECB it is
|
||||
# safe to reuse the encryptor for the entire operation
|
||||
b = encryptor.update(a + r[i])
|
||||
# pack/unpack are safe as these are always 64-bit chunks
|
||||
a = struct.pack(
|
||||
">Q", struct.unpack(">Q", b[:8])[0] ^ ((n * j) + i + 1)
|
||||
)
|
||||
r[i] = b[-8:]
|
||||
|
||||
assert encryptor.finalize() == b""
|
||||
|
||||
return a + b"".join(r)
|
||||
|
||||
|
||||
def aes_key_wrap(
|
||||
wrapping_key: bytes,
|
||||
key_to_wrap: bytes,
|
||||
backend: typing.Optional[Backend] = None,
|
||||
) -> bytes:
|
||||
backend = _get_backend(backend)
|
||||
if len(wrapping_key) not in [16, 24, 32]:
|
||||
raise ValueError("The wrapping key must be a valid AES key length")
|
||||
|
||||
if len(key_to_wrap) < 16:
|
||||
raise ValueError("The key to wrap must be at least 16 bytes")
|
||||
|
||||
if len(key_to_wrap) % 8 != 0:
|
||||
raise ValueError("The key to wrap must be a multiple of 8 bytes")
|
||||
|
||||
a = b"\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6"
|
||||
r = [key_to_wrap[i : i + 8] for i in range(0, len(key_to_wrap), 8)]
|
||||
return _wrap_core(wrapping_key, a, r, backend)
|
||||
|
||||
|
||||
def _unwrap_core(
|
||||
wrapping_key: bytes,
|
||||
a: bytes,
|
||||
r: typing.List[bytes],
|
||||
backend: Backend,
|
||||
) -> typing.Tuple[bytes, typing.List[bytes]]:
|
||||
# Implement RFC 3394 Key Unwrap - 2.2.2 (index method)
|
||||
decryptor = Cipher(AES(wrapping_key), ECB(), backend).decryptor()
|
||||
n = len(r)
|
||||
for j in reversed(range(6)):
|
||||
for i in reversed(range(n)):
|
||||
# pack/unpack are safe as these are always 64-bit chunks
|
||||
atr = (
|
||||
struct.pack(
|
||||
">Q", struct.unpack(">Q", a)[0] ^ ((n * j) + i + 1)
|
||||
)
|
||||
+ r[i]
|
||||
)
|
||||
# every decryption operation is a discrete 16 byte chunk so
|
||||
# it is safe to reuse the decryptor for the entire operation
|
||||
b = decryptor.update(atr)
|
||||
a = b[:8]
|
||||
r[i] = b[-8:]
|
||||
|
||||
assert decryptor.finalize() == b""
|
||||
return a, r
|
||||
|
||||
|
||||
def aes_key_wrap_with_padding(
|
||||
wrapping_key: bytes,
|
||||
key_to_wrap: bytes,
|
||||
backend: typing.Optional[Backend] = None,
|
||||
) -> bytes:
|
||||
backend = _get_backend(backend)
|
||||
if len(wrapping_key) not in [16, 24, 32]:
|
||||
raise ValueError("The wrapping key must be a valid AES key length")
|
||||
|
||||
aiv = b"\xA6\x59\x59\xA6" + struct.pack(">i", len(key_to_wrap))
|
||||
# pad the key to wrap if necessary
|
||||
pad = (8 - (len(key_to_wrap) % 8)) % 8
|
||||
key_to_wrap = key_to_wrap + b"\x00" * pad
|
||||
if len(key_to_wrap) == 8:
|
||||
# RFC 5649 - 4.1 - exactly 8 octets after padding
|
||||
encryptor = Cipher(AES(wrapping_key), ECB(), backend).encryptor()
|
||||
b = encryptor.update(aiv + key_to_wrap)
|
||||
assert encryptor.finalize() == b""
|
||||
return b
|
||||
else:
|
||||
r = [key_to_wrap[i : i + 8] for i in range(0, len(key_to_wrap), 8)]
|
||||
return _wrap_core(wrapping_key, aiv, r, backend)
|
||||
|
||||
|
||||
def aes_key_unwrap_with_padding(
|
||||
wrapping_key: bytes,
|
||||
wrapped_key: bytes,
|
||||
backend: typing.Optional[Backend] = None,
|
||||
) -> bytes:
|
||||
backend = _get_backend(backend)
|
||||
if len(wrapped_key) < 16:
|
||||
raise InvalidUnwrap("Must be at least 16 bytes")
|
||||
|
||||
if len(wrapping_key) not in [16, 24, 32]:
|
||||
raise ValueError("The wrapping key must be a valid AES key length")
|
||||
|
||||
if len(wrapped_key) == 16:
|
||||
# RFC 5649 - 4.2 - exactly two 64-bit blocks
|
||||
decryptor = Cipher(AES(wrapping_key), ECB(), backend).decryptor()
|
||||
b = decryptor.update(wrapped_key)
|
||||
assert decryptor.finalize() == b""
|
||||
a = b[:8]
|
||||
data = b[8:]
|
||||
n = 1
|
||||
else:
|
||||
r = [wrapped_key[i : i + 8] for i in range(0, len(wrapped_key), 8)]
|
||||
encrypted_aiv = r.pop(0)
|
||||
n = len(r)
|
||||
a, r = _unwrap_core(wrapping_key, encrypted_aiv, r, backend)
|
||||
data = b"".join(r)
|
||||
|
||||
# 1) Check that MSB(32,A) = A65959A6.
|
||||
# 2) Check that 8*(n-1) < LSB(32,A) <= 8*n. If so, let
|
||||
# MLI = LSB(32,A).
|
||||
# 3) Let b = (8*n)-MLI, and then check that the rightmost b octets of
|
||||
# the output data are zero.
|
||||
(mli,) = struct.unpack(">I", a[4:])
|
||||
b = (8 * n) - mli
|
||||
if (
|
||||
not bytes_eq(a[:4], b"\xa6\x59\x59\xa6")
|
||||
or not 8 * (n - 1) < mli <= 8 * n
|
||||
or (b != 0 and not bytes_eq(data[-b:], b"\x00" * b))
|
||||
):
|
||||
raise InvalidUnwrap()
|
||||
|
||||
if b == 0:
|
||||
return data
|
||||
else:
|
||||
return data[:-b]
|
||||
|
||||
|
||||
def aes_key_unwrap(
|
||||
wrapping_key: bytes,
|
||||
wrapped_key: bytes,
|
||||
backend: typing.Optional[Backend] = None,
|
||||
) -> bytes:
|
||||
backend = _get_backend(backend)
|
||||
if len(wrapped_key) < 24:
|
||||
raise InvalidUnwrap("Must be at least 24 bytes")
|
||||
|
||||
if len(wrapped_key) % 8 != 0:
|
||||
raise InvalidUnwrap("The wrapped key must be a multiple of 8 bytes")
|
||||
|
||||
if len(wrapping_key) not in [16, 24, 32]:
|
||||
raise ValueError("The wrapping key must be a valid AES key length")
|
||||
|
||||
aiv = b"\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6"
|
||||
r = [wrapped_key[i : i + 8] for i in range(0, len(wrapped_key), 8)]
|
||||
a = r.pop(0)
|
||||
a, r = _unwrap_core(wrapping_key, a, r, backend)
|
||||
if not bytes_eq(a, aiv):
|
||||
raise InvalidUnwrap()
|
||||
|
||||
return b"".join(r)
|
||||
|
||||
|
||||
class InvalidUnwrap(Exception):
|
||||
pass
|
||||
@@ -0,0 +1,224 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import abc
|
||||
import typing
|
||||
|
||||
from cryptography import utils
|
||||
from cryptography.exceptions import AlreadyFinalized
|
||||
from cryptography.hazmat.bindings._rust import (
|
||||
check_ansix923_padding,
|
||||
check_pkcs7_padding,
|
||||
)
|
||||
|
||||
|
||||
class PaddingContext(metaclass=abc.ABCMeta):
|
||||
@abc.abstractmethod
|
||||
def update(self, data: bytes) -> bytes:
|
||||
"""
|
||||
Pads the provided bytes and returns any available data as bytes.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def finalize(self) -> bytes:
|
||||
"""
|
||||
Finalize the padding, returns bytes.
|
||||
"""
|
||||
|
||||
|
||||
def _byte_padding_check(block_size: int) -> None:
|
||||
if not (0 <= block_size <= 2040):
|
||||
raise ValueError("block_size must be in range(0, 2041).")
|
||||
|
||||
if block_size % 8 != 0:
|
||||
raise ValueError("block_size must be a multiple of 8.")
|
||||
|
||||
|
||||
def _byte_padding_update(
|
||||
buffer_: typing.Optional[bytes], data: bytes, block_size: int
|
||||
) -> typing.Tuple[bytes, bytes]:
|
||||
if buffer_ is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
|
||||
utils._check_byteslike("data", data)
|
||||
|
||||
buffer_ += bytes(data)
|
||||
|
||||
finished_blocks = len(buffer_) // (block_size // 8)
|
||||
|
||||
result = buffer_[: finished_blocks * (block_size // 8)]
|
||||
buffer_ = buffer_[finished_blocks * (block_size // 8) :]
|
||||
|
||||
return buffer_, result
|
||||
|
||||
|
||||
def _byte_padding_pad(
|
||||
buffer_: typing.Optional[bytes],
|
||||
block_size: int,
|
||||
paddingfn: typing.Callable[[int], bytes],
|
||||
) -> bytes:
|
||||
if buffer_ is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
|
||||
pad_size = block_size // 8 - len(buffer_)
|
||||
return buffer_ + paddingfn(pad_size)
|
||||
|
||||
|
||||
def _byte_unpadding_update(
|
||||
buffer_: typing.Optional[bytes], data: bytes, block_size: int
|
||||
) -> typing.Tuple[bytes, bytes]:
|
||||
if buffer_ is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
|
||||
utils._check_byteslike("data", data)
|
||||
|
||||
buffer_ += bytes(data)
|
||||
|
||||
finished_blocks = max(len(buffer_) // (block_size // 8) - 1, 0)
|
||||
|
||||
result = buffer_[: finished_blocks * (block_size // 8)]
|
||||
buffer_ = buffer_[finished_blocks * (block_size // 8) :]
|
||||
|
||||
return buffer_, result
|
||||
|
||||
|
||||
def _byte_unpadding_check(
|
||||
buffer_: typing.Optional[bytes],
|
||||
block_size: int,
|
||||
checkfn: typing.Callable[[bytes], int],
|
||||
) -> bytes:
|
||||
if buffer_ is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
|
||||
if len(buffer_) != block_size // 8:
|
||||
raise ValueError("Invalid padding bytes.")
|
||||
|
||||
valid = checkfn(buffer_)
|
||||
|
||||
if not valid:
|
||||
raise ValueError("Invalid padding bytes.")
|
||||
|
||||
pad_size = buffer_[-1]
|
||||
return buffer_[:-pad_size]
|
||||
|
||||
|
||||
class PKCS7(object):
|
||||
def __init__(self, block_size: int):
|
||||
_byte_padding_check(block_size)
|
||||
self.block_size = block_size
|
||||
|
||||
def padder(self):
|
||||
return _PKCS7PaddingContext(self.block_size)
|
||||
|
||||
def unpadder(self):
|
||||
return _PKCS7UnpaddingContext(self.block_size)
|
||||
|
||||
|
||||
class _PKCS7PaddingContext(PaddingContext):
|
||||
_buffer: typing.Optional[bytes]
|
||||
|
||||
def __init__(self, block_size: int):
|
||||
self.block_size = block_size
|
||||
# TODO: more copies than necessary, we should use zero-buffer (#193)
|
||||
self._buffer = b""
|
||||
|
||||
def update(self, data: bytes) -> bytes:
|
||||
self._buffer, result = _byte_padding_update(
|
||||
self._buffer, data, self.block_size
|
||||
)
|
||||
return result
|
||||
|
||||
def _padding(self, size: int) -> bytes:
|
||||
return bytes([size]) * size
|
||||
|
||||
def finalize(self) -> bytes:
|
||||
result = _byte_padding_pad(
|
||||
self._buffer, self.block_size, self._padding
|
||||
)
|
||||
self._buffer = None
|
||||
return result
|
||||
|
||||
|
||||
class _PKCS7UnpaddingContext(PaddingContext):
|
||||
_buffer: typing.Optional[bytes]
|
||||
|
||||
def __init__(self, block_size: int):
|
||||
self.block_size = block_size
|
||||
# TODO: more copies than necessary, we should use zero-buffer (#193)
|
||||
self._buffer = b""
|
||||
|
||||
def update(self, data: bytes) -> bytes:
|
||||
self._buffer, result = _byte_unpadding_update(
|
||||
self._buffer, data, self.block_size
|
||||
)
|
||||
return result
|
||||
|
||||
def finalize(self) -> bytes:
|
||||
result = _byte_unpadding_check(
|
||||
self._buffer, self.block_size, check_pkcs7_padding
|
||||
)
|
||||
self._buffer = None
|
||||
return result
|
||||
|
||||
|
||||
class ANSIX923(object):
|
||||
def __init__(self, block_size: int):
|
||||
_byte_padding_check(block_size)
|
||||
self.block_size = block_size
|
||||
|
||||
def padder(self) -> PaddingContext:
|
||||
return _ANSIX923PaddingContext(self.block_size)
|
||||
|
||||
def unpadder(self) -> PaddingContext:
|
||||
return _ANSIX923UnpaddingContext(self.block_size)
|
||||
|
||||
|
||||
class _ANSIX923PaddingContext(PaddingContext):
|
||||
_buffer: typing.Optional[bytes]
|
||||
|
||||
def __init__(self, block_size: int):
|
||||
self.block_size = block_size
|
||||
# TODO: more copies than necessary, we should use zero-buffer (#193)
|
||||
self._buffer = b""
|
||||
|
||||
def update(self, data: bytes) -> bytes:
|
||||
self._buffer, result = _byte_padding_update(
|
||||
self._buffer, data, self.block_size
|
||||
)
|
||||
return result
|
||||
|
||||
def _padding(self, size: int) -> bytes:
|
||||
return bytes([0]) * (size - 1) + bytes([size])
|
||||
|
||||
def finalize(self) -> bytes:
|
||||
result = _byte_padding_pad(
|
||||
self._buffer, self.block_size, self._padding
|
||||
)
|
||||
self._buffer = None
|
||||
return result
|
||||
|
||||
|
||||
class _ANSIX923UnpaddingContext(PaddingContext):
|
||||
_buffer: typing.Optional[bytes]
|
||||
|
||||
def __init__(self, block_size: int):
|
||||
self.block_size = block_size
|
||||
# TODO: more copies than necessary, we should use zero-buffer (#193)
|
||||
self._buffer = b""
|
||||
|
||||
def update(self, data: bytes) -> bytes:
|
||||
self._buffer, result = _byte_unpadding_update(
|
||||
self._buffer, data, self.block_size
|
||||
)
|
||||
return result
|
||||
|
||||
def finalize(self) -> bytes:
|
||||
result = _byte_unpadding_check(
|
||||
self._buffer,
|
||||
self.block_size,
|
||||
check_ansix923_padding,
|
||||
)
|
||||
self._buffer = None
|
||||
return result
|
||||
@@ -0,0 +1,56 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
from cryptography import utils
|
||||
from cryptography.exceptions import (
|
||||
AlreadyFinalized,
|
||||
UnsupportedAlgorithm,
|
||||
_Reasons,
|
||||
)
|
||||
|
||||
|
||||
class Poly1305(object):
|
||||
def __init__(self, key: bytes):
|
||||
from cryptography.hazmat.backends.openssl.backend import backend
|
||||
|
||||
if not backend.poly1305_supported():
|
||||
raise UnsupportedAlgorithm(
|
||||
"poly1305 is not supported by this version of OpenSSL.",
|
||||
_Reasons.UNSUPPORTED_MAC,
|
||||
)
|
||||
self._ctx = backend.create_poly1305_ctx(key)
|
||||
|
||||
def update(self, data: bytes) -> None:
|
||||
if self._ctx is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
utils._check_byteslike("data", data)
|
||||
self._ctx.update(data)
|
||||
|
||||
def finalize(self) -> bytes:
|
||||
if self._ctx is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
mac = self._ctx.finalize()
|
||||
self._ctx = None
|
||||
return mac
|
||||
|
||||
def verify(self, tag: bytes) -> None:
|
||||
utils._check_bytes("tag", tag)
|
||||
if self._ctx is None:
|
||||
raise AlreadyFinalized("Context was already finalized.")
|
||||
|
||||
ctx, self._ctx = self._ctx, None
|
||||
ctx.verify(tag)
|
||||
|
||||
@classmethod
|
||||
def generate_tag(cls, key: bytes, data: bytes) -> bytes:
|
||||
p = Poly1305(key)
|
||||
p.update(data)
|
||||
return p.finalize()
|
||||
|
||||
@classmethod
|
||||
def verify_tag(cls, key: bytes, data: bytes, tag: bytes) -> None:
|
||||
p = Poly1305(key)
|
||||
p.update(data)
|
||||
p.verify(tag)
|
||||
@@ -0,0 +1,45 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
from cryptography.hazmat.primitives._serialization import (
|
||||
BestAvailableEncryption,
|
||||
Encoding,
|
||||
KeySerializationEncryption,
|
||||
NoEncryption,
|
||||
ParameterFormat,
|
||||
PrivateFormat,
|
||||
PublicFormat,
|
||||
)
|
||||
from cryptography.hazmat.primitives.serialization.base import (
|
||||
load_der_parameters,
|
||||
load_der_private_key,
|
||||
load_der_public_key,
|
||||
load_pem_parameters,
|
||||
load_pem_private_key,
|
||||
load_pem_public_key,
|
||||
)
|
||||
from cryptography.hazmat.primitives.serialization.ssh import (
|
||||
load_ssh_private_key,
|
||||
load_ssh_public_key,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"load_der_parameters",
|
||||
"load_der_private_key",
|
||||
"load_der_public_key",
|
||||
"load_pem_parameters",
|
||||
"load_pem_private_key",
|
||||
"load_pem_public_key",
|
||||
"load_ssh_private_key",
|
||||
"load_ssh_public_key",
|
||||
"Encoding",
|
||||
"PrivateFormat",
|
||||
"PublicFormat",
|
||||
"ParameterFormat",
|
||||
"KeySerializationEncryption",
|
||||
"BestAvailableEncryption",
|
||||
"NoEncryption",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,60 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import typing
|
||||
|
||||
from cryptography.hazmat.backends import _get_backend
|
||||
from cryptography.hazmat.backends.interfaces import Backend
|
||||
from cryptography.hazmat.primitives.asymmetric import dh
|
||||
from cryptography.hazmat.primitives.asymmetric.types import (
|
||||
PRIVATE_KEY_TYPES,
|
||||
PUBLIC_KEY_TYPES,
|
||||
)
|
||||
|
||||
|
||||
def load_pem_private_key(
|
||||
data: bytes,
|
||||
password: typing.Optional[bytes],
|
||||
backend: typing.Optional[Backend] = None,
|
||||
) -> PRIVATE_KEY_TYPES:
|
||||
backend = _get_backend(backend)
|
||||
return backend.load_pem_private_key(data, password)
|
||||
|
||||
|
||||
def load_pem_public_key(
|
||||
data: bytes, backend: typing.Optional[Backend] = None
|
||||
) -> PUBLIC_KEY_TYPES:
|
||||
backend = _get_backend(backend)
|
||||
return backend.load_pem_public_key(data)
|
||||
|
||||
|
||||
def load_pem_parameters(
|
||||
data: bytes, backend: typing.Optional[Backend] = None
|
||||
) -> "dh.DHParameters":
|
||||
backend = _get_backend(backend)
|
||||
return backend.load_pem_parameters(data)
|
||||
|
||||
|
||||
def load_der_private_key(
|
||||
data: bytes,
|
||||
password: typing.Optional[bytes],
|
||||
backend: typing.Optional[Backend] = None,
|
||||
) -> PRIVATE_KEY_TYPES:
|
||||
backend = _get_backend(backend)
|
||||
return backend.load_der_private_key(data, password)
|
||||
|
||||
|
||||
def load_der_public_key(
|
||||
data: bytes, backend: typing.Optional[Backend] = None
|
||||
) -> PUBLIC_KEY_TYPES:
|
||||
backend = _get_backend(backend)
|
||||
return backend.load_der_public_key(data)
|
||||
|
||||
|
||||
def load_der_parameters(
|
||||
data: bytes, backend: typing.Optional[Backend] = None
|
||||
) -> "dh.DHParameters":
|
||||
backend = _get_backend(backend)
|
||||
return backend.load_der_parameters(data)
|
||||
@@ -0,0 +1,72 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
import typing
|
||||
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import _get_backend
|
||||
from cryptography.hazmat.backends.interfaces import Backend
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa
|
||||
|
||||
|
||||
_ALLOWED_PKCS12_TYPES = typing.Union[
|
||||
rsa.RSAPrivateKey,
|
||||
dsa.DSAPrivateKey,
|
||||
ec.EllipticCurvePrivateKey,
|
||||
]
|
||||
|
||||
|
||||
def load_key_and_certificates(
|
||||
data: bytes,
|
||||
password: typing.Optional[bytes],
|
||||
backend: typing.Optional[Backend] = None,
|
||||
) -> typing.Tuple[
|
||||
typing.Optional[_ALLOWED_PKCS12_TYPES],
|
||||
typing.Optional[x509.Certificate],
|
||||
typing.List[x509.Certificate],
|
||||
]:
|
||||
backend = _get_backend(backend)
|
||||
return backend.load_key_and_certificates_from_pkcs12(data, password)
|
||||
|
||||
|
||||
def serialize_key_and_certificates(
|
||||
name: typing.Optional[bytes],
|
||||
key: typing.Optional[_ALLOWED_PKCS12_TYPES],
|
||||
cert: typing.Optional[x509.Certificate],
|
||||
cas: typing.Optional[typing.Iterable[x509.Certificate]],
|
||||
encryption_algorithm: serialization.KeySerializationEncryption,
|
||||
) -> bytes:
|
||||
if key is not None and not isinstance(
|
||||
key,
|
||||
(
|
||||
rsa.RSAPrivateKey,
|
||||
dsa.DSAPrivateKey,
|
||||
ec.EllipticCurvePrivateKey,
|
||||
),
|
||||
):
|
||||
raise TypeError("Key must be RSA, DSA, or EllipticCurve private key.")
|
||||
if cert is not None and not isinstance(cert, x509.Certificate):
|
||||
raise TypeError("cert must be a certificate")
|
||||
|
||||
if cas is not None:
|
||||
cas = list(cas)
|
||||
if not all(isinstance(val, x509.Certificate) for val in cas):
|
||||
raise TypeError("all values in cas must be certificates")
|
||||
|
||||
if not isinstance(
|
||||
encryption_algorithm, serialization.KeySerializationEncryption
|
||||
):
|
||||
raise TypeError(
|
||||
"Key encryption algorithm must be a "
|
||||
"KeySerializationEncryption instance"
|
||||
)
|
||||
|
||||
if key is None and cert is None and not cas:
|
||||
raise ValueError("You must supply at least one of key, cert, or cas")
|
||||
|
||||
backend = _get_backend(None)
|
||||
return backend.serialize_key_and_certificates_to_pkcs12(
|
||||
name, key, cert, cas, encryption_algorithm
|
||||
)
|
||||
@@ -0,0 +1,157 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
import typing
|
||||
|
||||
from cryptography import utils
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import _get_backend
|
||||
from cryptography.hazmat.backends.interfaces import Backend
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import ec, rsa
|
||||
from cryptography.utils import _check_byteslike
|
||||
|
||||
|
||||
def load_pem_pkcs7_certificates(data: bytes) -> typing.List[x509.Certificate]:
|
||||
backend = _get_backend(None)
|
||||
return backend.load_pem_pkcs7_certificates(data)
|
||||
|
||||
|
||||
def load_der_pkcs7_certificates(data: bytes) -> typing.List[x509.Certificate]:
|
||||
backend = _get_backend(None)
|
||||
return backend.load_der_pkcs7_certificates(data)
|
||||
|
||||
|
||||
_ALLOWED_PKCS7_HASH_TYPES = typing.Union[
|
||||
hashes.SHA1,
|
||||
hashes.SHA224,
|
||||
hashes.SHA256,
|
||||
hashes.SHA384,
|
||||
hashes.SHA512,
|
||||
]
|
||||
|
||||
_ALLOWED_PRIVATE_KEY_TYPES = typing.Union[
|
||||
rsa.RSAPrivateKey, ec.EllipticCurvePrivateKey
|
||||
]
|
||||
|
||||
|
||||
class PKCS7Options(utils.Enum):
|
||||
Text = "Add text/plain MIME type"
|
||||
Binary = "Don't translate input data into canonical MIME format"
|
||||
DetachedSignature = "Don't embed data in the PKCS7 structure"
|
||||
NoCapabilities = "Don't embed SMIME capabilities"
|
||||
NoAttributes = "Don't embed authenticatedAttributes"
|
||||
NoCerts = "Don't embed signer certificate"
|
||||
|
||||
|
||||
class PKCS7SignatureBuilder(object):
|
||||
def __init__(self, data=None, signers=[], additional_certs=[]):
|
||||
self._data = data
|
||||
self._signers = signers
|
||||
self._additional_certs = additional_certs
|
||||
|
||||
def set_data(self, data: bytes) -> "PKCS7SignatureBuilder":
|
||||
_check_byteslike("data", data)
|
||||
if self._data is not None:
|
||||
raise ValueError("data may only be set once")
|
||||
|
||||
return PKCS7SignatureBuilder(data, self._signers)
|
||||
|
||||
def add_signer(
|
||||
self,
|
||||
certificate: x509.Certificate,
|
||||
private_key: _ALLOWED_PRIVATE_KEY_TYPES,
|
||||
hash_algorithm: _ALLOWED_PKCS7_HASH_TYPES,
|
||||
) -> "PKCS7SignatureBuilder":
|
||||
if not isinstance(
|
||||
hash_algorithm,
|
||||
(
|
||||
hashes.SHA1,
|
||||
hashes.SHA224,
|
||||
hashes.SHA256,
|
||||
hashes.SHA384,
|
||||
hashes.SHA512,
|
||||
),
|
||||
):
|
||||
raise TypeError(
|
||||
"hash_algorithm must be one of hashes.SHA1, SHA224, "
|
||||
"SHA256, SHA384, or SHA512"
|
||||
)
|
||||
if not isinstance(certificate, x509.Certificate):
|
||||
raise TypeError("certificate must be a x509.Certificate")
|
||||
|
||||
if not isinstance(
|
||||
private_key, (rsa.RSAPrivateKey, ec.EllipticCurvePrivateKey)
|
||||
):
|
||||
raise TypeError("Only RSA & EC keys are supported at this time.")
|
||||
|
||||
return PKCS7SignatureBuilder(
|
||||
self._data,
|
||||
self._signers + [(certificate, private_key, hash_algorithm)],
|
||||
)
|
||||
|
||||
def add_certificate(
|
||||
self, certificate: x509.Certificate
|
||||
) -> "PKCS7SignatureBuilder":
|
||||
if not isinstance(certificate, x509.Certificate):
|
||||
raise TypeError("certificate must be a x509.Certificate")
|
||||
|
||||
return PKCS7SignatureBuilder(
|
||||
self._data, self._signers, self._additional_certs + [certificate]
|
||||
)
|
||||
|
||||
def sign(
|
||||
self,
|
||||
encoding: serialization.Encoding,
|
||||
options: typing.Iterable[PKCS7Options],
|
||||
backend: typing.Optional[Backend] = None,
|
||||
) -> bytes:
|
||||
if len(self._signers) == 0:
|
||||
raise ValueError("Must have at least one signer")
|
||||
if self._data is None:
|
||||
raise ValueError("You must add data to sign")
|
||||
options = list(options)
|
||||
if not all(isinstance(x, PKCS7Options) for x in options):
|
||||
raise ValueError("options must be from the PKCS7Options enum")
|
||||
if encoding not in (
|
||||
serialization.Encoding.PEM,
|
||||
serialization.Encoding.DER,
|
||||
serialization.Encoding.SMIME,
|
||||
):
|
||||
raise ValueError(
|
||||
"Must be PEM, DER, or SMIME from the Encoding enum"
|
||||
)
|
||||
|
||||
# Text is a meaningless option unless it is accompanied by
|
||||
# DetachedSignature
|
||||
if (
|
||||
PKCS7Options.Text in options
|
||||
and PKCS7Options.DetachedSignature not in options
|
||||
):
|
||||
raise ValueError(
|
||||
"When passing the Text option you must also pass "
|
||||
"DetachedSignature"
|
||||
)
|
||||
|
||||
if PKCS7Options.Text in options and encoding in (
|
||||
serialization.Encoding.DER,
|
||||
serialization.Encoding.PEM,
|
||||
):
|
||||
raise ValueError(
|
||||
"The Text option is only available for SMIME serialization"
|
||||
)
|
||||
|
||||
# No attributes implies no capabilities so we'll error if you try to
|
||||
# pass both.
|
||||
if (
|
||||
PKCS7Options.NoAttributes in options
|
||||
and PKCS7Options.NoCapabilities in options
|
||||
):
|
||||
raise ValueError(
|
||||
"NoAttributes is a superset of NoCapabilities. Do not pass "
|
||||
"both values."
|
||||
)
|
||||
|
||||
backend = _get_backend(backend)
|
||||
return backend.pkcs7_sign(self, encoding, options)
|
||||
@@ -0,0 +1,712 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import binascii
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import typing
|
||||
from base64 import encodebytes as _base64_encode
|
||||
|
||||
from cryptography import utils
|
||||
from cryptography.exceptions import UnsupportedAlgorithm
|
||||
from cryptography.hazmat.backends import _get_backend
|
||||
from cryptography.hazmat.backends.interfaces import Backend
|
||||
from cryptography.hazmat.primitives.asymmetric import dsa, ec, ed25519, rsa
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
from cryptography.hazmat.primitives.serialization import (
|
||||
Encoding,
|
||||
NoEncryption,
|
||||
PrivateFormat,
|
||||
PublicFormat,
|
||||
)
|
||||
|
||||
try:
|
||||
from bcrypt import kdf as _bcrypt_kdf
|
||||
|
||||
_bcrypt_supported = True
|
||||
except ImportError:
|
||||
_bcrypt_supported = False
|
||||
|
||||
def _bcrypt_kdf(
|
||||
password: bytes,
|
||||
salt: bytes,
|
||||
desired_key_bytes: int,
|
||||
rounds: int,
|
||||
ignore_few_rounds: bool = False,
|
||||
) -> bytes:
|
||||
raise UnsupportedAlgorithm("Need bcrypt module")
|
||||
|
||||
|
||||
_SSH_ED25519 = b"ssh-ed25519"
|
||||
_SSH_RSA = b"ssh-rsa"
|
||||
_SSH_DSA = b"ssh-dss"
|
||||
_ECDSA_NISTP256 = b"ecdsa-sha2-nistp256"
|
||||
_ECDSA_NISTP384 = b"ecdsa-sha2-nistp384"
|
||||
_ECDSA_NISTP521 = b"ecdsa-sha2-nistp521"
|
||||
_CERT_SUFFIX = b"-cert-v01@openssh.com"
|
||||
|
||||
_SSH_PUBKEY_RC = re.compile(br"\A(\S+)[ \t]+(\S+)")
|
||||
_SK_MAGIC = b"openssh-key-v1\0"
|
||||
_SK_START = b"-----BEGIN OPENSSH PRIVATE KEY-----"
|
||||
_SK_END = b"-----END OPENSSH PRIVATE KEY-----"
|
||||
_BCRYPT = b"bcrypt"
|
||||
_NONE = b"none"
|
||||
_DEFAULT_CIPHER = b"aes256-ctr"
|
||||
_DEFAULT_ROUNDS = 16
|
||||
_MAX_PASSWORD = 72
|
||||
|
||||
# re is only way to work on bytes-like data
|
||||
_PEM_RC = re.compile(_SK_START + b"(.*?)" + _SK_END, re.DOTALL)
|
||||
|
||||
# padding for max blocksize
|
||||
_PADDING = memoryview(bytearray(range(1, 1 + 16)))
|
||||
|
||||
# ciphers that are actually used in key wrapping
|
||||
_SSH_CIPHERS = {
|
||||
b"aes256-ctr": (algorithms.AES, 32, modes.CTR, 16),
|
||||
b"aes256-cbc": (algorithms.AES, 32, modes.CBC, 16),
|
||||
}
|
||||
|
||||
# map local curve name to key type
|
||||
_ECDSA_KEY_TYPE = {
|
||||
"secp256r1": _ECDSA_NISTP256,
|
||||
"secp384r1": _ECDSA_NISTP384,
|
||||
"secp521r1": _ECDSA_NISTP521,
|
||||
}
|
||||
|
||||
_U32 = struct.Struct(b">I")
|
||||
_U64 = struct.Struct(b">Q")
|
||||
|
||||
|
||||
def _ecdsa_key_type(public_key):
|
||||
"""Return SSH key_type and curve_name for private key."""
|
||||
curve = public_key.curve
|
||||
if curve.name not in _ECDSA_KEY_TYPE:
|
||||
raise ValueError(
|
||||
"Unsupported curve for ssh private key: %r" % curve.name
|
||||
)
|
||||
return _ECDSA_KEY_TYPE[curve.name]
|
||||
|
||||
|
||||
def _ssh_pem_encode(data, prefix=_SK_START + b"\n", suffix=_SK_END + b"\n"):
|
||||
return b"".join([prefix, _base64_encode(data), suffix])
|
||||
|
||||
|
||||
def _check_block_size(data, block_len):
|
||||
"""Require data to be full blocks"""
|
||||
if not data or len(data) % block_len != 0:
|
||||
raise ValueError("Corrupt data: missing padding")
|
||||
|
||||
|
||||
def _check_empty(data):
|
||||
"""All data should have been parsed."""
|
||||
if data:
|
||||
raise ValueError("Corrupt data: unparsed data")
|
||||
|
||||
|
||||
def _init_cipher(ciphername, password, salt, rounds, backend):
|
||||
"""Generate key + iv and return cipher."""
|
||||
if not password:
|
||||
raise ValueError("Key is password-protected.")
|
||||
|
||||
algo, key_len, mode, iv_len = _SSH_CIPHERS[ciphername]
|
||||
seed = _bcrypt_kdf(password, salt, key_len + iv_len, rounds, True)
|
||||
return Cipher(algo(seed[:key_len]), mode(seed[key_len:]), backend)
|
||||
|
||||
|
||||
def _get_u32(data):
|
||||
"""Uint32"""
|
||||
if len(data) < 4:
|
||||
raise ValueError("Invalid data")
|
||||
return _U32.unpack(data[:4])[0], data[4:]
|
||||
|
||||
|
||||
def _get_u64(data):
|
||||
"""Uint64"""
|
||||
if len(data) < 8:
|
||||
raise ValueError("Invalid data")
|
||||
return _U64.unpack(data[:8])[0], data[8:]
|
||||
|
||||
|
||||
def _get_sshstr(data):
|
||||
"""Bytes with u32 length prefix"""
|
||||
n, data = _get_u32(data)
|
||||
if n > len(data):
|
||||
raise ValueError("Invalid data")
|
||||
return data[:n], data[n:]
|
||||
|
||||
|
||||
def _get_mpint(data):
|
||||
"""Big integer."""
|
||||
val, data = _get_sshstr(data)
|
||||
if val and val[0] > 0x7F:
|
||||
raise ValueError("Invalid data")
|
||||
return int.from_bytes(val, "big"), data
|
||||
|
||||
|
||||
def _to_mpint(val):
|
||||
"""Storage format for signed bigint."""
|
||||
if val < 0:
|
||||
raise ValueError("negative mpint not allowed")
|
||||
if not val:
|
||||
return b""
|
||||
nbytes = (val.bit_length() + 8) // 8
|
||||
return utils.int_to_bytes(val, nbytes)
|
||||
|
||||
|
||||
class _FragList(object):
|
||||
"""Build recursive structure without data copy."""
|
||||
|
||||
def __init__(self, init=None):
|
||||
self.flist = []
|
||||
if init:
|
||||
self.flist.extend(init)
|
||||
|
||||
def put_raw(self, val):
|
||||
"""Add plain bytes"""
|
||||
self.flist.append(val)
|
||||
|
||||
def put_u32(self, val):
|
||||
"""Big-endian uint32"""
|
||||
self.flist.append(_U32.pack(val))
|
||||
|
||||
def put_sshstr(self, val):
|
||||
"""Bytes prefixed with u32 length"""
|
||||
if isinstance(val, (bytes, memoryview, bytearray)):
|
||||
self.put_u32(len(val))
|
||||
self.flist.append(val)
|
||||
else:
|
||||
self.put_u32(val.size())
|
||||
self.flist.extend(val.flist)
|
||||
|
||||
def put_mpint(self, val):
|
||||
"""Big-endian bigint prefixed with u32 length"""
|
||||
self.put_sshstr(_to_mpint(val))
|
||||
|
||||
def size(self):
|
||||
"""Current number of bytes"""
|
||||
return sum(map(len, self.flist))
|
||||
|
||||
def render(self, dstbuf, pos=0):
|
||||
"""Write into bytearray"""
|
||||
for frag in self.flist:
|
||||
flen = len(frag)
|
||||
start, pos = pos, pos + flen
|
||||
dstbuf[start:pos] = frag
|
||||
return pos
|
||||
|
||||
def tobytes(self):
|
||||
"""Return as bytes"""
|
||||
buf = memoryview(bytearray(self.size()))
|
||||
self.render(buf)
|
||||
return buf.tobytes()
|
||||
|
||||
|
||||
class _SSHFormatRSA(object):
|
||||
"""Format for RSA keys.
|
||||
|
||||
Public:
|
||||
mpint e, n
|
||||
Private:
|
||||
mpint n, e, d, iqmp, p, q
|
||||
"""
|
||||
|
||||
def get_public(self, data):
|
||||
"""RSA public fields"""
|
||||
e, data = _get_mpint(data)
|
||||
n, data = _get_mpint(data)
|
||||
return (e, n), data
|
||||
|
||||
def load_public(self, key_type, data, backend):
|
||||
"""Make RSA public key from data."""
|
||||
(e, n), data = self.get_public(data)
|
||||
public_numbers = rsa.RSAPublicNumbers(e, n)
|
||||
public_key = public_numbers.public_key(backend)
|
||||
return public_key, data
|
||||
|
||||
def load_private(self, data, pubfields, backend):
|
||||
"""Make RSA private key from data."""
|
||||
n, data = _get_mpint(data)
|
||||
e, data = _get_mpint(data)
|
||||
d, data = _get_mpint(data)
|
||||
iqmp, data = _get_mpint(data)
|
||||
p, data = _get_mpint(data)
|
||||
q, data = _get_mpint(data)
|
||||
|
||||
if (e, n) != pubfields:
|
||||
raise ValueError("Corrupt data: rsa field mismatch")
|
||||
dmp1 = rsa.rsa_crt_dmp1(d, p)
|
||||
dmq1 = rsa.rsa_crt_dmq1(d, q)
|
||||
public_numbers = rsa.RSAPublicNumbers(e, n)
|
||||
private_numbers = rsa.RSAPrivateNumbers(
|
||||
p, q, d, dmp1, dmq1, iqmp, public_numbers
|
||||
)
|
||||
private_key = private_numbers.private_key(backend)
|
||||
return private_key, data
|
||||
|
||||
def encode_public(self, public_key, f_pub):
|
||||
"""Write RSA public key"""
|
||||
pubn = public_key.public_numbers()
|
||||
f_pub.put_mpint(pubn.e)
|
||||
f_pub.put_mpint(pubn.n)
|
||||
|
||||
def encode_private(self, private_key, f_priv):
|
||||
"""Write RSA private key"""
|
||||
private_numbers = private_key.private_numbers()
|
||||
public_numbers = private_numbers.public_numbers
|
||||
|
||||
f_priv.put_mpint(public_numbers.n)
|
||||
f_priv.put_mpint(public_numbers.e)
|
||||
|
||||
f_priv.put_mpint(private_numbers.d)
|
||||
f_priv.put_mpint(private_numbers.iqmp)
|
||||
f_priv.put_mpint(private_numbers.p)
|
||||
f_priv.put_mpint(private_numbers.q)
|
||||
|
||||
|
||||
class _SSHFormatDSA(object):
|
||||
"""Format for DSA keys.
|
||||
|
||||
Public:
|
||||
mpint p, q, g, y
|
||||
Private:
|
||||
mpint p, q, g, y, x
|
||||
"""
|
||||
|
||||
def get_public(self, data):
|
||||
"""DSA public fields"""
|
||||
p, data = _get_mpint(data)
|
||||
q, data = _get_mpint(data)
|
||||
g, data = _get_mpint(data)
|
||||
y, data = _get_mpint(data)
|
||||
return (p, q, g, y), data
|
||||
|
||||
def load_public(self, key_type, data, backend):
|
||||
"""Make DSA public key from data."""
|
||||
(p, q, g, y), data = self.get_public(data)
|
||||
parameter_numbers = dsa.DSAParameterNumbers(p, q, g)
|
||||
public_numbers = dsa.DSAPublicNumbers(y, parameter_numbers)
|
||||
self._validate(public_numbers)
|
||||
public_key = public_numbers.public_key(backend)
|
||||
return public_key, data
|
||||
|
||||
def load_private(self, data, pubfields, backend):
|
||||
"""Make DSA private key from data."""
|
||||
(p, q, g, y), data = self.get_public(data)
|
||||
x, data = _get_mpint(data)
|
||||
|
||||
if (p, q, g, y) != pubfields:
|
||||
raise ValueError("Corrupt data: dsa field mismatch")
|
||||
parameter_numbers = dsa.DSAParameterNumbers(p, q, g)
|
||||
public_numbers = dsa.DSAPublicNumbers(y, parameter_numbers)
|
||||
self._validate(public_numbers)
|
||||
private_numbers = dsa.DSAPrivateNumbers(x, public_numbers)
|
||||
private_key = private_numbers.private_key(backend)
|
||||
return private_key, data
|
||||
|
||||
def encode_public(self, public_key, f_pub):
|
||||
"""Write DSA public key"""
|
||||
public_numbers = public_key.public_numbers()
|
||||
parameter_numbers = public_numbers.parameter_numbers
|
||||
self._validate(public_numbers)
|
||||
|
||||
f_pub.put_mpint(parameter_numbers.p)
|
||||
f_pub.put_mpint(parameter_numbers.q)
|
||||
f_pub.put_mpint(parameter_numbers.g)
|
||||
f_pub.put_mpint(public_numbers.y)
|
||||
|
||||
def encode_private(self, private_key, f_priv):
|
||||
"""Write DSA private key"""
|
||||
self.encode_public(private_key.public_key(), f_priv)
|
||||
f_priv.put_mpint(private_key.private_numbers().x)
|
||||
|
||||
def _validate(self, public_numbers):
|
||||
parameter_numbers = public_numbers.parameter_numbers
|
||||
if parameter_numbers.p.bit_length() != 1024:
|
||||
raise ValueError("SSH supports only 1024 bit DSA keys")
|
||||
|
||||
|
||||
class _SSHFormatECDSA(object):
|
||||
"""Format for ECDSA keys.
|
||||
|
||||
Public:
|
||||
str curve
|
||||
bytes point
|
||||
Private:
|
||||
str curve
|
||||
bytes point
|
||||
mpint secret
|
||||
"""
|
||||
|
||||
def __init__(self, ssh_curve_name, curve):
|
||||
self.ssh_curve_name = ssh_curve_name
|
||||
self.curve = curve
|
||||
|
||||
def get_public(self, data):
|
||||
"""ECDSA public fields"""
|
||||
curve, data = _get_sshstr(data)
|
||||
point, data = _get_sshstr(data)
|
||||
if curve != self.ssh_curve_name:
|
||||
raise ValueError("Curve name mismatch")
|
||||
if point[0] != 4:
|
||||
raise NotImplementedError("Need uncompressed point")
|
||||
return (curve, point), data
|
||||
|
||||
def load_public(self, key_type, data, backend):
|
||||
"""Make ECDSA public key from data."""
|
||||
(curve_name, point), data = self.get_public(data)
|
||||
public_key = ec.EllipticCurvePublicKey.from_encoded_point(
|
||||
self.curve, point.tobytes()
|
||||
)
|
||||
return public_key, data
|
||||
|
||||
def load_private(self, data, pubfields, backend):
|
||||
"""Make ECDSA private key from data."""
|
||||
(curve_name, point), data = self.get_public(data)
|
||||
secret, data = _get_mpint(data)
|
||||
|
||||
if (curve_name, point) != pubfields:
|
||||
raise ValueError("Corrupt data: ecdsa field mismatch")
|
||||
private_key = ec.derive_private_key(secret, self.curve, backend)
|
||||
return private_key, data
|
||||
|
||||
def encode_public(self, public_key, f_pub):
|
||||
"""Write ECDSA public key"""
|
||||
point = public_key.public_bytes(
|
||||
Encoding.X962, PublicFormat.UncompressedPoint
|
||||
)
|
||||
f_pub.put_sshstr(self.ssh_curve_name)
|
||||
f_pub.put_sshstr(point)
|
||||
|
||||
def encode_private(self, private_key, f_priv):
|
||||
"""Write ECDSA private key"""
|
||||
public_key = private_key.public_key()
|
||||
private_numbers = private_key.private_numbers()
|
||||
|
||||
self.encode_public(public_key, f_priv)
|
||||
f_priv.put_mpint(private_numbers.private_value)
|
||||
|
||||
|
||||
class _SSHFormatEd25519(object):
|
||||
"""Format for Ed25519 keys.
|
||||
|
||||
Public:
|
||||
bytes point
|
||||
Private:
|
||||
bytes point
|
||||
bytes secret_and_point
|
||||
"""
|
||||
|
||||
def get_public(self, data):
|
||||
"""Ed25519 public fields"""
|
||||
point, data = _get_sshstr(data)
|
||||
return (point,), data
|
||||
|
||||
def load_public(self, key_type, data, backend):
|
||||
"""Make Ed25519 public key from data."""
|
||||
(point,), data = self.get_public(data)
|
||||
public_key = ed25519.Ed25519PublicKey.from_public_bytes(
|
||||
point.tobytes()
|
||||
)
|
||||
return public_key, data
|
||||
|
||||
def load_private(self, data, pubfields, backend):
|
||||
"""Make Ed25519 private key from data."""
|
||||
(point,), data = self.get_public(data)
|
||||
keypair, data = _get_sshstr(data)
|
||||
|
||||
secret = keypair[:32]
|
||||
point2 = keypair[32:]
|
||||
if point != point2 or (point,) != pubfields:
|
||||
raise ValueError("Corrupt data: ed25519 field mismatch")
|
||||
private_key = ed25519.Ed25519PrivateKey.from_private_bytes(secret)
|
||||
return private_key, data
|
||||
|
||||
def encode_public(self, public_key, f_pub):
|
||||
"""Write Ed25519 public key"""
|
||||
raw_public_key = public_key.public_bytes(
|
||||
Encoding.Raw, PublicFormat.Raw
|
||||
)
|
||||
f_pub.put_sshstr(raw_public_key)
|
||||
|
||||
def encode_private(self, private_key, f_priv):
|
||||
"""Write Ed25519 private key"""
|
||||
public_key = private_key.public_key()
|
||||
raw_private_key = private_key.private_bytes(
|
||||
Encoding.Raw, PrivateFormat.Raw, NoEncryption()
|
||||
)
|
||||
raw_public_key = public_key.public_bytes(
|
||||
Encoding.Raw, PublicFormat.Raw
|
||||
)
|
||||
f_keypair = _FragList([raw_private_key, raw_public_key])
|
||||
|
||||
self.encode_public(public_key, f_priv)
|
||||
f_priv.put_sshstr(f_keypair)
|
||||
|
||||
|
||||
_KEY_FORMATS = {
|
||||
_SSH_RSA: _SSHFormatRSA(),
|
||||
_SSH_DSA: _SSHFormatDSA(),
|
||||
_SSH_ED25519: _SSHFormatEd25519(),
|
||||
_ECDSA_NISTP256: _SSHFormatECDSA(b"nistp256", ec.SECP256R1()),
|
||||
_ECDSA_NISTP384: _SSHFormatECDSA(b"nistp384", ec.SECP384R1()),
|
||||
_ECDSA_NISTP521: _SSHFormatECDSA(b"nistp521", ec.SECP521R1()),
|
||||
}
|
||||
|
||||
|
||||
def _lookup_kformat(key_type):
|
||||
"""Return valid format or throw error"""
|
||||
if not isinstance(key_type, bytes):
|
||||
key_type = memoryview(key_type).tobytes()
|
||||
if key_type in _KEY_FORMATS:
|
||||
return _KEY_FORMATS[key_type]
|
||||
raise UnsupportedAlgorithm("Unsupported key type: %r" % key_type)
|
||||
|
||||
|
||||
_SSH_PRIVATE_KEY_TYPES = typing.Union[
|
||||
ec.EllipticCurvePrivateKey,
|
||||
rsa.RSAPrivateKey,
|
||||
dsa.DSAPrivateKey,
|
||||
ed25519.Ed25519PrivateKey,
|
||||
]
|
||||
|
||||
|
||||
def load_ssh_private_key(
|
||||
data: bytes,
|
||||
password: typing.Optional[bytes],
|
||||
backend: typing.Optional[Backend] = None,
|
||||
) -> _SSH_PRIVATE_KEY_TYPES:
|
||||
"""Load private key from OpenSSH custom encoding."""
|
||||
utils._check_byteslike("data", data)
|
||||
backend = _get_backend(backend)
|
||||
if password is not None:
|
||||
utils._check_bytes("password", password)
|
||||
|
||||
m = _PEM_RC.search(data)
|
||||
if not m:
|
||||
raise ValueError("Not OpenSSH private key format")
|
||||
p1 = m.start(1)
|
||||
p2 = m.end(1)
|
||||
data = binascii.a2b_base64(memoryview(data)[p1:p2])
|
||||
if not data.startswith(_SK_MAGIC):
|
||||
raise ValueError("Not OpenSSH private key format")
|
||||
data = memoryview(data)[len(_SK_MAGIC) :]
|
||||
|
||||
# parse header
|
||||
ciphername, data = _get_sshstr(data)
|
||||
kdfname, data = _get_sshstr(data)
|
||||
kdfoptions, data = _get_sshstr(data)
|
||||
nkeys, data = _get_u32(data)
|
||||
if nkeys != 1:
|
||||
raise ValueError("Only one key supported")
|
||||
|
||||
# load public key data
|
||||
pubdata, data = _get_sshstr(data)
|
||||
pub_key_type, pubdata = _get_sshstr(pubdata)
|
||||
kformat = _lookup_kformat(pub_key_type)
|
||||
pubfields, pubdata = kformat.get_public(pubdata)
|
||||
_check_empty(pubdata)
|
||||
|
||||
# load secret data
|
||||
edata, data = _get_sshstr(data)
|
||||
_check_empty(data)
|
||||
|
||||
if (ciphername, kdfname) != (_NONE, _NONE):
|
||||
ciphername = ciphername.tobytes()
|
||||
if ciphername not in _SSH_CIPHERS:
|
||||
raise UnsupportedAlgorithm("Unsupported cipher: %r" % ciphername)
|
||||
if kdfname != _BCRYPT:
|
||||
raise UnsupportedAlgorithm("Unsupported KDF: %r" % kdfname)
|
||||
blklen = _SSH_CIPHERS[ciphername][3]
|
||||
_check_block_size(edata, blklen)
|
||||
salt, kbuf = _get_sshstr(kdfoptions)
|
||||
rounds, kbuf = _get_u32(kbuf)
|
||||
_check_empty(kbuf)
|
||||
ciph = _init_cipher(
|
||||
ciphername, password, salt.tobytes(), rounds, backend
|
||||
)
|
||||
edata = memoryview(ciph.decryptor().update(edata))
|
||||
else:
|
||||
blklen = 8
|
||||
_check_block_size(edata, blklen)
|
||||
ck1, edata = _get_u32(edata)
|
||||
ck2, edata = _get_u32(edata)
|
||||
if ck1 != ck2:
|
||||
raise ValueError("Corrupt data: broken checksum")
|
||||
|
||||
# load per-key struct
|
||||
key_type, edata = _get_sshstr(edata)
|
||||
if key_type != pub_key_type:
|
||||
raise ValueError("Corrupt data: key type mismatch")
|
||||
private_key, edata = kformat.load_private(edata, pubfields, backend)
|
||||
comment, edata = _get_sshstr(edata)
|
||||
|
||||
# yes, SSH does padding check *after* all other parsing is done.
|
||||
# need to follow as it writes zero-byte padding too.
|
||||
if edata != _PADDING[: len(edata)]:
|
||||
raise ValueError("Corrupt data: invalid padding")
|
||||
|
||||
return private_key
|
||||
|
||||
|
||||
def serialize_ssh_private_key(
|
||||
private_key: _SSH_PRIVATE_KEY_TYPES,
|
||||
password: typing.Optional[bytes] = None,
|
||||
) -> bytes:
|
||||
"""Serialize private key with OpenSSH custom encoding."""
|
||||
if password is not None:
|
||||
utils._check_bytes("password", password)
|
||||
if password and len(password) > _MAX_PASSWORD:
|
||||
raise ValueError(
|
||||
"Passwords longer than 72 bytes are not supported by "
|
||||
"OpenSSH private key format"
|
||||
)
|
||||
|
||||
if isinstance(private_key, ec.EllipticCurvePrivateKey):
|
||||
key_type = _ecdsa_key_type(private_key.public_key())
|
||||
elif isinstance(private_key, rsa.RSAPrivateKey):
|
||||
key_type = _SSH_RSA
|
||||
elif isinstance(private_key, dsa.DSAPrivateKey):
|
||||
key_type = _SSH_DSA
|
||||
elif isinstance(private_key, ed25519.Ed25519PrivateKey):
|
||||
key_type = _SSH_ED25519
|
||||
else:
|
||||
raise ValueError("Unsupported key type")
|
||||
kformat = _lookup_kformat(key_type)
|
||||
|
||||
# setup parameters
|
||||
f_kdfoptions = _FragList()
|
||||
if password:
|
||||
ciphername = _DEFAULT_CIPHER
|
||||
blklen = _SSH_CIPHERS[ciphername][3]
|
||||
kdfname = _BCRYPT
|
||||
rounds = _DEFAULT_ROUNDS
|
||||
salt = os.urandom(16)
|
||||
f_kdfoptions.put_sshstr(salt)
|
||||
f_kdfoptions.put_u32(rounds)
|
||||
backend: Backend = _get_backend(None)
|
||||
ciph = _init_cipher(ciphername, password, salt, rounds, backend)
|
||||
else:
|
||||
ciphername = kdfname = _NONE
|
||||
blklen = 8
|
||||
ciph = None
|
||||
nkeys = 1
|
||||
checkval = os.urandom(4)
|
||||
comment = b""
|
||||
|
||||
# encode public and private parts together
|
||||
f_public_key = _FragList()
|
||||
f_public_key.put_sshstr(key_type)
|
||||
kformat.encode_public(private_key.public_key(), f_public_key)
|
||||
|
||||
f_secrets = _FragList([checkval, checkval])
|
||||
f_secrets.put_sshstr(key_type)
|
||||
kformat.encode_private(private_key, f_secrets)
|
||||
f_secrets.put_sshstr(comment)
|
||||
f_secrets.put_raw(_PADDING[: blklen - (f_secrets.size() % blklen)])
|
||||
|
||||
# top-level structure
|
||||
f_main = _FragList()
|
||||
f_main.put_raw(_SK_MAGIC)
|
||||
f_main.put_sshstr(ciphername)
|
||||
f_main.put_sshstr(kdfname)
|
||||
f_main.put_sshstr(f_kdfoptions)
|
||||
f_main.put_u32(nkeys)
|
||||
f_main.put_sshstr(f_public_key)
|
||||
f_main.put_sshstr(f_secrets)
|
||||
|
||||
# copy result info bytearray
|
||||
slen = f_secrets.size()
|
||||
mlen = f_main.size()
|
||||
buf = memoryview(bytearray(mlen + blklen))
|
||||
f_main.render(buf)
|
||||
ofs = mlen - slen
|
||||
|
||||
# encrypt in-place
|
||||
if ciph is not None:
|
||||
ciph.encryptor().update_into(buf[ofs:mlen], buf[ofs:])
|
||||
|
||||
txt = _ssh_pem_encode(buf[:mlen])
|
||||
# Ignore the following type because mypy wants
|
||||
# Sequence[bytes] but what we're passing is fine.
|
||||
# https://github.com/python/mypy/issues/9999
|
||||
buf[ofs:mlen] = bytearray(slen) # type: ignore
|
||||
return txt
|
||||
|
||||
|
||||
_SSH_PUBLIC_KEY_TYPES = typing.Union[
|
||||
ec.EllipticCurvePublicKey,
|
||||
rsa.RSAPublicKey,
|
||||
dsa.DSAPublicKey,
|
||||
ed25519.Ed25519PublicKey,
|
||||
]
|
||||
|
||||
|
||||
def load_ssh_public_key(
|
||||
data: bytes, backend: typing.Optional[Backend] = None
|
||||
) -> _SSH_PUBLIC_KEY_TYPES:
|
||||
"""Load public key from OpenSSH one-line format."""
|
||||
backend = _get_backend(backend)
|
||||
utils._check_byteslike("data", data)
|
||||
|
||||
m = _SSH_PUBKEY_RC.match(data)
|
||||
if not m:
|
||||
raise ValueError("Invalid line format")
|
||||
key_type = orig_key_type = m.group(1)
|
||||
key_body = m.group(2)
|
||||
with_cert = False
|
||||
if _CERT_SUFFIX == key_type[-len(_CERT_SUFFIX) :]:
|
||||
with_cert = True
|
||||
key_type = key_type[: -len(_CERT_SUFFIX)]
|
||||
kformat = _lookup_kformat(key_type)
|
||||
|
||||
try:
|
||||
data = memoryview(binascii.a2b_base64(key_body))
|
||||
except (TypeError, binascii.Error):
|
||||
raise ValueError("Invalid key format")
|
||||
|
||||
inner_key_type, data = _get_sshstr(data)
|
||||
if inner_key_type != orig_key_type:
|
||||
raise ValueError("Invalid key format")
|
||||
if with_cert:
|
||||
nonce, data = _get_sshstr(data)
|
||||
public_key, data = kformat.load_public(key_type, data, backend)
|
||||
if with_cert:
|
||||
serial, data = _get_u64(data)
|
||||
cctype, data = _get_u32(data)
|
||||
key_id, data = _get_sshstr(data)
|
||||
principals, data = _get_sshstr(data)
|
||||
valid_after, data = _get_u64(data)
|
||||
valid_before, data = _get_u64(data)
|
||||
crit_options, data = _get_sshstr(data)
|
||||
extensions, data = _get_sshstr(data)
|
||||
reserved, data = _get_sshstr(data)
|
||||
sig_key, data = _get_sshstr(data)
|
||||
signature, data = _get_sshstr(data)
|
||||
_check_empty(data)
|
||||
return public_key
|
||||
|
||||
|
||||
def serialize_ssh_public_key(public_key: _SSH_PUBLIC_KEY_TYPES) -> bytes:
|
||||
"""One-line public key format for OpenSSH"""
|
||||
if isinstance(public_key, ec.EllipticCurvePublicKey):
|
||||
key_type = _ecdsa_key_type(public_key)
|
||||
elif isinstance(public_key, rsa.RSAPublicKey):
|
||||
key_type = _SSH_RSA
|
||||
elif isinstance(public_key, dsa.DSAPublicKey):
|
||||
key_type = _SSH_DSA
|
||||
elif isinstance(public_key, ed25519.Ed25519PublicKey):
|
||||
key_type = _SSH_ED25519
|
||||
else:
|
||||
raise ValueError("Unsupported key type")
|
||||
kformat = _lookup_kformat(key_type)
|
||||
|
||||
f_pub = _FragList()
|
||||
f_pub.put_sshstr(key_type)
|
||||
kformat.encode_public(public_key, f_pub)
|
||||
|
||||
pub = binascii.b2a_base64(f_pub.tobytes()).strip()
|
||||
return b"".join([key_type, b" ", pub])
|
||||
@@ -0,0 +1,7 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
class InvalidToken(Exception):
|
||||
pass
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,108 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
|
||||
import base64
|
||||
import struct
|
||||
import typing
|
||||
from urllib.parse import quote, urlencode
|
||||
|
||||
from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
|
||||
from cryptography.hazmat.backends import _get_backend
|
||||
from cryptography.hazmat.backends.interfaces import Backend, HMACBackend
|
||||
from cryptography.hazmat.primitives import constant_time, hmac
|
||||
from cryptography.hazmat.primitives.hashes import SHA1, SHA256, SHA512
|
||||
from cryptography.hazmat.primitives.twofactor import InvalidToken
|
||||
|
||||
|
||||
_ALLOWED_HASH_TYPES = typing.Union[SHA1, SHA256, SHA512]
|
||||
|
||||
|
||||
def _generate_uri(
|
||||
hotp: "HOTP",
|
||||
type_name: str,
|
||||
account_name: str,
|
||||
issuer: typing.Optional[str],
|
||||
extra_parameters: typing.List[typing.Tuple[str, int]],
|
||||
) -> str:
|
||||
parameters = [
|
||||
("digits", hotp._length),
|
||||
("secret", base64.b32encode(hotp._key)),
|
||||
("algorithm", hotp._algorithm.name.upper()),
|
||||
]
|
||||
|
||||
if issuer is not None:
|
||||
parameters.append(("issuer", issuer))
|
||||
|
||||
parameters.extend(extra_parameters)
|
||||
|
||||
uriparts = {
|
||||
"type": type_name,
|
||||
"label": (
|
||||
"%s:%s" % (quote(issuer), quote(account_name))
|
||||
if issuer
|
||||
else quote(account_name)
|
||||
),
|
||||
"parameters": urlencode(parameters),
|
||||
}
|
||||
return "otpauth://{type}/{label}?{parameters}".format(**uriparts)
|
||||
|
||||
|
||||
class HOTP(object):
|
||||
def __init__(
|
||||
self,
|
||||
key: bytes,
|
||||
length: int,
|
||||
algorithm: _ALLOWED_HASH_TYPES,
|
||||
backend: typing.Optional[Backend] = None,
|
||||
enforce_key_length: bool = True,
|
||||
) -> None:
|
||||
backend = _get_backend(backend)
|
||||
if not isinstance(backend, HMACBackend):
|
||||
raise UnsupportedAlgorithm(
|
||||
"Backend object does not implement HMACBackend.",
|
||||
_Reasons.BACKEND_MISSING_INTERFACE,
|
||||
)
|
||||
|
||||
if len(key) < 16 and enforce_key_length is True:
|
||||
raise ValueError("Key length has to be at least 128 bits.")
|
||||
|
||||
if not isinstance(length, int):
|
||||
raise TypeError("Length parameter must be an integer type.")
|
||||
|
||||
if length < 6 or length > 8:
|
||||
raise ValueError("Length of HOTP has to be between 6 to 8.")
|
||||
|
||||
if not isinstance(algorithm, (SHA1, SHA256, SHA512)):
|
||||
raise TypeError("Algorithm must be SHA1, SHA256 or SHA512.")
|
||||
|
||||
self._key = key
|
||||
self._length = length
|
||||
self._algorithm = algorithm
|
||||
self._backend = backend
|
||||
|
||||
def generate(self, counter: int) -> bytes:
|
||||
truncated_value = self._dynamic_truncate(counter)
|
||||
hotp = truncated_value % (10 ** self._length)
|
||||
return "{0:0{1}}".format(hotp, self._length).encode()
|
||||
|
||||
def verify(self, hotp: bytes, counter: int) -> None:
|
||||
if not constant_time.bytes_eq(self.generate(counter), hotp):
|
||||
raise InvalidToken("Supplied HOTP value does not match.")
|
||||
|
||||
def _dynamic_truncate(self, counter: int) -> int:
|
||||
ctx = hmac.HMAC(self._key, self._algorithm, self._backend)
|
||||
ctx.update(struct.pack(">Q", counter))
|
||||
hmac_value = ctx.finalize()
|
||||
|
||||
offset = hmac_value[len(hmac_value) - 1] & 0b1111
|
||||
p = hmac_value[offset : offset + 4]
|
||||
return struct.unpack(">I", p)[0] & 0x7FFFFFFF
|
||||
|
||||
def get_provisioning_uri(
|
||||
self, account_name: str, counter: int, issuer: typing.Optional[str]
|
||||
) -> str:
|
||||
return _generate_uri(
|
||||
self, "hotp", account_name, issuer, [("counter", int(counter))]
|
||||
)
|
||||
@@ -0,0 +1,56 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
import typing
|
||||
|
||||
from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
|
||||
from cryptography.hazmat.backends import _get_backend
|
||||
from cryptography.hazmat.backends.interfaces import Backend, HMACBackend
|
||||
from cryptography.hazmat.primitives import constant_time
|
||||
from cryptography.hazmat.primitives.twofactor import InvalidToken
|
||||
from cryptography.hazmat.primitives.twofactor.hotp import (
|
||||
HOTP,
|
||||
_ALLOWED_HASH_TYPES,
|
||||
_generate_uri,
|
||||
)
|
||||
|
||||
|
||||
class TOTP(object):
|
||||
def __init__(
|
||||
self,
|
||||
key: bytes,
|
||||
length: int,
|
||||
algorithm: _ALLOWED_HASH_TYPES,
|
||||
time_step: int,
|
||||
backend: typing.Optional[Backend] = None,
|
||||
enforce_key_length: bool = True,
|
||||
):
|
||||
backend = _get_backend(backend)
|
||||
if not isinstance(backend, HMACBackend):
|
||||
raise UnsupportedAlgorithm(
|
||||
"Backend object does not implement HMACBackend.",
|
||||
_Reasons.BACKEND_MISSING_INTERFACE,
|
||||
)
|
||||
|
||||
self._time_step = time_step
|
||||
self._hotp = HOTP(key, length, algorithm, backend, enforce_key_length)
|
||||
|
||||
def generate(self, time: typing.Union[int, float]) -> bytes:
|
||||
counter = int(time / self._time_step)
|
||||
return self._hotp.generate(counter)
|
||||
|
||||
def verify(self, totp: bytes, time: int) -> None:
|
||||
if not constant_time.bytes_eq(self.generate(time), totp):
|
||||
raise InvalidToken("Supplied TOTP value does not match.")
|
||||
|
||||
def get_provisioning_uri(
|
||||
self, account_name: str, issuer: typing.Optional[str]
|
||||
) -> str:
|
||||
return _generate_uri(
|
||||
self._hotp,
|
||||
"totp",
|
||||
account_name,
|
||||
issuer,
|
||||
[("period", int(self._time_step))],
|
||||
)
|
||||
Reference in New Issue
Block a user