second commit

This commit is contained in:
Александр Геннадьевич Сальный
2022-10-15 21:01:12 +03:00
commit 7caeeaaff5
1329 changed files with 489315 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
# 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.
__all__ = [
"__version__",
"__author__",
"__copyright__",
]
__version__ = "37.0.2"
__author__ = "The Python Cryptographic Authority and individual contributors"
__copyright__ = "Copyright 2013-2021 {}".format(__author__)

View File

@@ -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 sys
import warnings
from cryptography.__about__ import (
__author__,
__copyright__,
__version__,
)
from cryptography.utils import CryptographyDeprecationWarning
__all__ = [
"__version__",
"__author__",
"__copyright__",
]
if sys.version_info[:2] == (3, 6):
warnings.warn(
"Python 3.6 is no longer supported by the Python core team. "
"Therefore, support for it is deprecated in cryptography and will be"
" removed in a future release.",
CryptographyDeprecationWarning,
stacklevel=2,
)

View File

@@ -0,0 +1,68 @@
# 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
if typing.TYPE_CHECKING:
from cryptography.hazmat.bindings.openssl.binding import (
_OpenSSLErrorWithText,
)
class _Reasons(utils.Enum):
BACKEND_MISSING_INTERFACE = 0
UNSUPPORTED_HASH = 1
UNSUPPORTED_CIPHER = 2
UNSUPPORTED_PADDING = 3
UNSUPPORTED_MGF = 4
UNSUPPORTED_PUBLIC_KEY_ALGORITHM = 5
UNSUPPORTED_ELLIPTIC_CURVE = 6
UNSUPPORTED_SERIALIZATION = 7
UNSUPPORTED_X509 = 8
UNSUPPORTED_EXCHANGE_ALGORITHM = 9
UNSUPPORTED_DIFFIE_HELLMAN = 10
UNSUPPORTED_MAC = 11
class UnsupportedAlgorithm(Exception):
def __init__(
self, message: str, reason: typing.Optional[_Reasons] = None
) -> None:
super(UnsupportedAlgorithm, self).__init__(message)
self._reason = reason
class AlreadyFinalized(Exception):
pass
class AlreadyUpdated(Exception):
pass
class NotYetFinalized(Exception):
pass
class InvalidTag(Exception):
pass
class InvalidSignature(Exception):
pass
class InternalError(Exception):
def __init__(
self, msg: str, err_code: typing.List["_OpenSSLErrorWithText"]
) -> None:
super(InternalError, self).__init__(msg)
self.err_code = err_code
class InvalidKey(Exception):
pass

View File

@@ -0,0 +1,212 @@
# 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 binascii
import os
import time
import typing
from cryptography import utils
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives import hashes, padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.hmac import HMAC
class InvalidToken(Exception):
pass
_MAX_CLOCK_SKEW = 60
class Fernet:
def __init__(
self,
key: typing.Union[bytes, str],
backend: typing.Any = None,
):
try:
key = base64.urlsafe_b64decode(key)
except binascii.Error as exc:
raise ValueError(
"Fernet key must be 32 url-safe base64-encoded bytes."
) from exc
if len(key) != 32:
raise ValueError(
"Fernet key must be 32 url-safe base64-encoded bytes."
)
self._signing_key = key[:16]
self._encryption_key = key[16:]
@classmethod
def generate_key(cls) -> bytes:
return base64.urlsafe_b64encode(os.urandom(32))
def encrypt(self, data: bytes) -> bytes:
return self.encrypt_at_time(data, int(time.time()))
def encrypt_at_time(self, data: bytes, current_time: int) -> bytes:
iv = os.urandom(16)
return self._encrypt_from_parts(data, current_time, iv)
def _encrypt_from_parts(
self, data: bytes, current_time: int, iv: bytes
) -> bytes:
utils._check_bytes("data", data)
padder = padding.PKCS7(algorithms.AES.block_size).padder()
padded_data = padder.update(data) + padder.finalize()
encryptor = Cipher(
algorithms.AES(self._encryption_key),
modes.CBC(iv),
).encryptor()
ciphertext = encryptor.update(padded_data) + encryptor.finalize()
basic_parts = (
b"\x80"
+ current_time.to_bytes(length=8, byteorder="big")
+ iv
+ ciphertext
)
h = HMAC(self._signing_key, hashes.SHA256())
h.update(basic_parts)
hmac = h.finalize()
return base64.urlsafe_b64encode(basic_parts + hmac)
def decrypt(self, token: bytes, ttl: typing.Optional[int] = None) -> bytes:
timestamp, data = Fernet._get_unverified_token_data(token)
if ttl is None:
time_info = None
else:
time_info = (ttl, int(time.time()))
return self._decrypt_data(data, timestamp, time_info)
def decrypt_at_time(
self, token: bytes, ttl: int, current_time: int
) -> bytes:
if ttl is None:
raise ValueError(
"decrypt_at_time() can only be used with a non-None ttl"
)
timestamp, data = Fernet._get_unverified_token_data(token)
return self._decrypt_data(data, timestamp, (ttl, current_time))
def extract_timestamp(self, token: bytes) -> int:
timestamp, data = Fernet._get_unverified_token_data(token)
# Verify the token was not tampered with.
self._verify_signature(data)
return timestamp
@staticmethod
def _get_unverified_token_data(token: bytes) -> typing.Tuple[int, bytes]:
utils._check_bytes("token", token)
try:
data = base64.urlsafe_b64decode(token)
except (TypeError, binascii.Error):
raise InvalidToken
if not data or data[0] != 0x80:
raise InvalidToken
if len(data) < 9:
raise InvalidToken
timestamp = int.from_bytes(data[1:9], byteorder="big")
return timestamp, data
def _verify_signature(self, data: bytes) -> None:
h = HMAC(self._signing_key, hashes.SHA256())
h.update(data[:-32])
try:
h.verify(data[-32:])
except InvalidSignature:
raise InvalidToken
def _decrypt_data(
self,
data: bytes,
timestamp: int,
time_info: typing.Optional[typing.Tuple[int, int]],
) -> bytes:
if time_info is not None:
ttl, current_time = time_info
if timestamp + ttl < current_time:
raise InvalidToken
if current_time + _MAX_CLOCK_SKEW < timestamp:
raise InvalidToken
self._verify_signature(data)
iv = data[9:25]
ciphertext = data[25:-32]
decryptor = Cipher(
algorithms.AES(self._encryption_key), modes.CBC(iv)
).decryptor()
plaintext_padded = decryptor.update(ciphertext)
try:
plaintext_padded += decryptor.finalize()
except ValueError:
raise InvalidToken
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
unpadded = unpadder.update(plaintext_padded)
try:
unpadded += unpadder.finalize()
except ValueError:
raise InvalidToken
return unpadded
class MultiFernet:
def __init__(self, fernets: typing.Iterable[Fernet]):
fernets = list(fernets)
if not fernets:
raise ValueError(
"MultiFernet requires at least one Fernet instance"
)
self._fernets = fernets
def encrypt(self, msg: bytes) -> bytes:
return self.encrypt_at_time(msg, int(time.time()))
def encrypt_at_time(self, msg: bytes, current_time: int) -> bytes:
return self._fernets[0].encrypt_at_time(msg, current_time)
def rotate(self, msg: bytes) -> bytes:
timestamp, data = Fernet._get_unverified_token_data(msg)
for f in self._fernets:
try:
p = f._decrypt_data(data, timestamp, None)
break
except InvalidToken:
pass
else:
raise InvalidToken
iv = os.urandom(16)
return self._fernets[0]._encrypt_from_parts(p, timestamp, iv)
def decrypt(self, msg: bytes, ttl: typing.Optional[int] = None) -> bytes:
for f in self._fernets:
try:
return f.decrypt(msg, ttl)
except InvalidToken:
pass
raise InvalidToken
def decrypt_at_time(
self, msg: bytes, ttl: int, current_time: int
) -> bytes:
for f in self._fernets:
try:
return f.decrypt_at_time(msg, ttl, current_time)
except InvalidToken:
pass
raise InvalidToken

View File

@@ -0,0 +1,10 @@
# 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.
"""
Hazardous Materials
This is a "Hazardous Materials" module. You should ONLY use it if you're
100% absolutely sure that you know what you're doing because this module
is full of land mines, dragons, and dinosaurs with laser guns.
"""

View File

@@ -0,0 +1,345 @@
# 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
class ObjectIdentifier:
def __init__(self, dotted_string: str) -> None:
self._dotted_string = dotted_string
nodes = self._dotted_string.split(".")
intnodes = []
# There must be at least 2 nodes, the first node must be 0..2, and
# if less than 2, the second node cannot have a value outside the
# range 0..39. All nodes must be integers.
for node in nodes:
try:
node_value = int(node, 10)
except ValueError:
raise ValueError(
f"Malformed OID: {dotted_string} (non-integer nodes)"
)
if node_value < 0:
raise ValueError(
f"Malformed OID: {dotted_string} (negative-integer nodes)"
)
intnodes.append(node_value)
if len(nodes) < 2:
raise ValueError(
f"Malformed OID: {dotted_string} "
"(insufficient number of nodes)"
)
if intnodes[0] > 2:
raise ValueError(
f"Malformed OID: {dotted_string} "
"(first node outside valid range)"
)
if intnodes[0] < 2 and intnodes[1] >= 40:
raise ValueError(
f"Malformed OID: {dotted_string} "
"(second node outside valid range)"
)
def __eq__(self, other: object) -> bool:
if not isinstance(other, ObjectIdentifier):
return NotImplemented
return self.dotted_string == other.dotted_string
def __repr__(self) -> str:
return "<ObjectIdentifier(oid={}, name={})>".format(
self.dotted_string, self._name
)
def __hash__(self) -> int:
return hash(self.dotted_string)
@property
def _name(self) -> str:
return _OID_NAMES.get(self, "Unknown OID")
@property
def dotted_string(self) -> str:
return self._dotted_string
class ExtensionOID:
SUBJECT_DIRECTORY_ATTRIBUTES = ObjectIdentifier("2.5.29.9")
SUBJECT_KEY_IDENTIFIER = ObjectIdentifier("2.5.29.14")
KEY_USAGE = ObjectIdentifier("2.5.29.15")
SUBJECT_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.17")
ISSUER_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.18")
BASIC_CONSTRAINTS = ObjectIdentifier("2.5.29.19")
NAME_CONSTRAINTS = ObjectIdentifier("2.5.29.30")
CRL_DISTRIBUTION_POINTS = ObjectIdentifier("2.5.29.31")
CERTIFICATE_POLICIES = ObjectIdentifier("2.5.29.32")
POLICY_MAPPINGS = ObjectIdentifier("2.5.29.33")
AUTHORITY_KEY_IDENTIFIER = ObjectIdentifier("2.5.29.35")
POLICY_CONSTRAINTS = ObjectIdentifier("2.5.29.36")
EXTENDED_KEY_USAGE = ObjectIdentifier("2.5.29.37")
FRESHEST_CRL = ObjectIdentifier("2.5.29.46")
INHIBIT_ANY_POLICY = ObjectIdentifier("2.5.29.54")
ISSUING_DISTRIBUTION_POINT = ObjectIdentifier("2.5.29.28")
AUTHORITY_INFORMATION_ACCESS = ObjectIdentifier("1.3.6.1.5.5.7.1.1")
SUBJECT_INFORMATION_ACCESS = ObjectIdentifier("1.3.6.1.5.5.7.1.11")
OCSP_NO_CHECK = ObjectIdentifier("1.3.6.1.5.5.7.48.1.5")
TLS_FEATURE = ObjectIdentifier("1.3.6.1.5.5.7.1.24")
CRL_NUMBER = ObjectIdentifier("2.5.29.20")
DELTA_CRL_INDICATOR = ObjectIdentifier("2.5.29.27")
PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS = ObjectIdentifier(
"1.3.6.1.4.1.11129.2.4.2"
)
PRECERT_POISON = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.3")
SIGNED_CERTIFICATE_TIMESTAMPS = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.5")
class OCSPExtensionOID:
NONCE = ObjectIdentifier("1.3.6.1.5.5.7.48.1.2")
class CRLEntryExtensionOID:
CERTIFICATE_ISSUER = ObjectIdentifier("2.5.29.29")
CRL_REASON = ObjectIdentifier("2.5.29.21")
INVALIDITY_DATE = ObjectIdentifier("2.5.29.24")
class NameOID:
COMMON_NAME = ObjectIdentifier("2.5.4.3")
COUNTRY_NAME = ObjectIdentifier("2.5.4.6")
LOCALITY_NAME = ObjectIdentifier("2.5.4.7")
STATE_OR_PROVINCE_NAME = ObjectIdentifier("2.5.4.8")
STREET_ADDRESS = ObjectIdentifier("2.5.4.9")
ORGANIZATION_NAME = ObjectIdentifier("2.5.4.10")
ORGANIZATIONAL_UNIT_NAME = ObjectIdentifier("2.5.4.11")
SERIAL_NUMBER = ObjectIdentifier("2.5.4.5")
SURNAME = ObjectIdentifier("2.5.4.4")
GIVEN_NAME = ObjectIdentifier("2.5.4.42")
TITLE = ObjectIdentifier("2.5.4.12")
GENERATION_QUALIFIER = ObjectIdentifier("2.5.4.44")
X500_UNIQUE_IDENTIFIER = ObjectIdentifier("2.5.4.45")
DN_QUALIFIER = ObjectIdentifier("2.5.4.46")
PSEUDONYM = ObjectIdentifier("2.5.4.65")
USER_ID = ObjectIdentifier("0.9.2342.19200300.100.1.1")
DOMAIN_COMPONENT = ObjectIdentifier("0.9.2342.19200300.100.1.25")
EMAIL_ADDRESS = ObjectIdentifier("1.2.840.113549.1.9.1")
JURISDICTION_COUNTRY_NAME = ObjectIdentifier("1.3.6.1.4.1.311.60.2.1.3")
JURISDICTION_LOCALITY_NAME = ObjectIdentifier("1.3.6.1.4.1.311.60.2.1.1")
JURISDICTION_STATE_OR_PROVINCE_NAME = ObjectIdentifier(
"1.3.6.1.4.1.311.60.2.1.2"
)
BUSINESS_CATEGORY = ObjectIdentifier("2.5.4.15")
POSTAL_ADDRESS = ObjectIdentifier("2.5.4.16")
POSTAL_CODE = ObjectIdentifier("2.5.4.17")
INN = ObjectIdentifier("1.2.643.3.131.1.1")
OGRN = ObjectIdentifier("1.2.643.100.1")
SNILS = ObjectIdentifier("1.2.643.100.3")
UNSTRUCTURED_NAME = ObjectIdentifier("1.2.840.113549.1.9.2")
class SignatureAlgorithmOID:
RSA_WITH_MD5 = ObjectIdentifier("1.2.840.113549.1.1.4")
RSA_WITH_SHA1 = ObjectIdentifier("1.2.840.113549.1.1.5")
# This is an alternate OID for RSA with SHA1 that is occasionally seen
_RSA_WITH_SHA1 = ObjectIdentifier("1.3.14.3.2.29")
RSA_WITH_SHA224 = ObjectIdentifier("1.2.840.113549.1.1.14")
RSA_WITH_SHA256 = ObjectIdentifier("1.2.840.113549.1.1.11")
RSA_WITH_SHA384 = ObjectIdentifier("1.2.840.113549.1.1.12")
RSA_WITH_SHA512 = ObjectIdentifier("1.2.840.113549.1.1.13")
RSA_WITH_SHA3_224 = ObjectIdentifier("2.16.840.1.101.3.4.3.13")
RSA_WITH_SHA3_256 = ObjectIdentifier("2.16.840.1.101.3.4.3.14")
RSA_WITH_SHA3_384 = ObjectIdentifier("2.16.840.1.101.3.4.3.15")
RSA_WITH_SHA3_512 = ObjectIdentifier("2.16.840.1.101.3.4.3.16")
RSASSA_PSS = ObjectIdentifier("1.2.840.113549.1.1.10")
ECDSA_WITH_SHA1 = ObjectIdentifier("1.2.840.10045.4.1")
ECDSA_WITH_SHA224 = ObjectIdentifier("1.2.840.10045.4.3.1")
ECDSA_WITH_SHA256 = ObjectIdentifier("1.2.840.10045.4.3.2")
ECDSA_WITH_SHA384 = ObjectIdentifier("1.2.840.10045.4.3.3")
ECDSA_WITH_SHA512 = ObjectIdentifier("1.2.840.10045.4.3.4")
ECDSA_WITH_SHA3_224 = ObjectIdentifier("2.16.840.1.101.3.4.3.9")
ECDSA_WITH_SHA3_256 = ObjectIdentifier("2.16.840.1.101.3.4.3.10")
ECDSA_WITH_SHA3_384 = ObjectIdentifier("2.16.840.1.101.3.4.3.11")
ECDSA_WITH_SHA3_512 = ObjectIdentifier("2.16.840.1.101.3.4.3.12")
DSA_WITH_SHA1 = ObjectIdentifier("1.2.840.10040.4.3")
DSA_WITH_SHA224 = ObjectIdentifier("2.16.840.1.101.3.4.3.1")
DSA_WITH_SHA256 = ObjectIdentifier("2.16.840.1.101.3.4.3.2")
DSA_WITH_SHA384 = ObjectIdentifier("2.16.840.1.101.3.4.3.3")
DSA_WITH_SHA512 = ObjectIdentifier("2.16.840.1.101.3.4.3.4")
ED25519 = ObjectIdentifier("1.3.101.112")
ED448 = ObjectIdentifier("1.3.101.113")
GOSTR3411_94_WITH_3410_2001 = ObjectIdentifier("1.2.643.2.2.3")
GOSTR3410_2012_WITH_3411_2012_256 = ObjectIdentifier("1.2.643.7.1.1.3.2")
GOSTR3410_2012_WITH_3411_2012_512 = ObjectIdentifier("1.2.643.7.1.1.3.3")
_SIG_OIDS_TO_HASH: typing.Dict[
ObjectIdentifier, typing.Optional[hashes.HashAlgorithm]
] = {
SignatureAlgorithmOID.RSA_WITH_MD5: hashes.MD5(),
SignatureAlgorithmOID.RSA_WITH_SHA1: hashes.SHA1(),
SignatureAlgorithmOID._RSA_WITH_SHA1: hashes.SHA1(),
SignatureAlgorithmOID.RSA_WITH_SHA224: hashes.SHA224(),
SignatureAlgorithmOID.RSA_WITH_SHA256: hashes.SHA256(),
SignatureAlgorithmOID.RSA_WITH_SHA384: hashes.SHA384(),
SignatureAlgorithmOID.RSA_WITH_SHA512: hashes.SHA512(),
SignatureAlgorithmOID.ECDSA_WITH_SHA1: hashes.SHA1(),
SignatureAlgorithmOID.ECDSA_WITH_SHA224: hashes.SHA224(),
SignatureAlgorithmOID.ECDSA_WITH_SHA256: hashes.SHA256(),
SignatureAlgorithmOID.ECDSA_WITH_SHA384: hashes.SHA384(),
SignatureAlgorithmOID.ECDSA_WITH_SHA512: hashes.SHA512(),
SignatureAlgorithmOID.DSA_WITH_SHA1: hashes.SHA1(),
SignatureAlgorithmOID.DSA_WITH_SHA224: hashes.SHA224(),
SignatureAlgorithmOID.DSA_WITH_SHA256: hashes.SHA256(),
SignatureAlgorithmOID.ED25519: None,
SignatureAlgorithmOID.ED448: None,
SignatureAlgorithmOID.GOSTR3411_94_WITH_3410_2001: None,
SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_256: None,
SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_512: None,
}
class ExtendedKeyUsageOID:
SERVER_AUTH = ObjectIdentifier("1.3.6.1.5.5.7.3.1")
CLIENT_AUTH = ObjectIdentifier("1.3.6.1.5.5.7.3.2")
CODE_SIGNING = ObjectIdentifier("1.3.6.1.5.5.7.3.3")
EMAIL_PROTECTION = ObjectIdentifier("1.3.6.1.5.5.7.3.4")
TIME_STAMPING = ObjectIdentifier("1.3.6.1.5.5.7.3.8")
OCSP_SIGNING = ObjectIdentifier("1.3.6.1.5.5.7.3.9")
ANY_EXTENDED_KEY_USAGE = ObjectIdentifier("2.5.29.37.0")
SMARTCARD_LOGON = ObjectIdentifier("1.3.6.1.4.1.311.20.2.2")
KERBEROS_PKINIT_KDC = ObjectIdentifier("1.3.6.1.5.2.3.5")
IPSEC_IKE = ObjectIdentifier("1.3.6.1.5.5.7.3.17")
class AuthorityInformationAccessOID:
CA_ISSUERS = ObjectIdentifier("1.3.6.1.5.5.7.48.2")
OCSP = ObjectIdentifier("1.3.6.1.5.5.7.48.1")
class SubjectInformationAccessOID:
CA_REPOSITORY = ObjectIdentifier("1.3.6.1.5.5.7.48.5")
class CertificatePoliciesOID:
CPS_QUALIFIER = ObjectIdentifier("1.3.6.1.5.5.7.2.1")
CPS_USER_NOTICE = ObjectIdentifier("1.3.6.1.5.5.7.2.2")
ANY_POLICY = ObjectIdentifier("2.5.29.32.0")
class AttributeOID:
CHALLENGE_PASSWORD = ObjectIdentifier("1.2.840.113549.1.9.7")
UNSTRUCTURED_NAME = ObjectIdentifier("1.2.840.113549.1.9.2")
_OID_NAMES = {
NameOID.COMMON_NAME: "commonName",
NameOID.COUNTRY_NAME: "countryName",
NameOID.LOCALITY_NAME: "localityName",
NameOID.STATE_OR_PROVINCE_NAME: "stateOrProvinceName",
NameOID.STREET_ADDRESS: "streetAddress",
NameOID.ORGANIZATION_NAME: "organizationName",
NameOID.ORGANIZATIONAL_UNIT_NAME: "organizationalUnitName",
NameOID.SERIAL_NUMBER: "serialNumber",
NameOID.SURNAME: "surname",
NameOID.GIVEN_NAME: "givenName",
NameOID.TITLE: "title",
NameOID.GENERATION_QUALIFIER: "generationQualifier",
NameOID.X500_UNIQUE_IDENTIFIER: "x500UniqueIdentifier",
NameOID.DN_QUALIFIER: "dnQualifier",
NameOID.PSEUDONYM: "pseudonym",
NameOID.USER_ID: "userID",
NameOID.DOMAIN_COMPONENT: "domainComponent",
NameOID.EMAIL_ADDRESS: "emailAddress",
NameOID.JURISDICTION_COUNTRY_NAME: "jurisdictionCountryName",
NameOID.JURISDICTION_LOCALITY_NAME: "jurisdictionLocalityName",
NameOID.JURISDICTION_STATE_OR_PROVINCE_NAME: (
"jurisdictionStateOrProvinceName"
),
NameOID.BUSINESS_CATEGORY: "businessCategory",
NameOID.POSTAL_ADDRESS: "postalAddress",
NameOID.POSTAL_CODE: "postalCode",
NameOID.INN: "INN",
NameOID.OGRN: "OGRN",
NameOID.SNILS: "SNILS",
NameOID.UNSTRUCTURED_NAME: "unstructuredName",
SignatureAlgorithmOID.RSA_WITH_MD5: "md5WithRSAEncryption",
SignatureAlgorithmOID.RSA_WITH_SHA1: "sha1WithRSAEncryption",
SignatureAlgorithmOID.RSA_WITH_SHA224: "sha224WithRSAEncryption",
SignatureAlgorithmOID.RSA_WITH_SHA256: "sha256WithRSAEncryption",
SignatureAlgorithmOID.RSA_WITH_SHA384: "sha384WithRSAEncryption",
SignatureAlgorithmOID.RSA_WITH_SHA512: "sha512WithRSAEncryption",
SignatureAlgorithmOID.RSASSA_PSS: "RSASSA-PSS",
SignatureAlgorithmOID.ECDSA_WITH_SHA1: "ecdsa-with-SHA1",
SignatureAlgorithmOID.ECDSA_WITH_SHA224: "ecdsa-with-SHA224",
SignatureAlgorithmOID.ECDSA_WITH_SHA256: "ecdsa-with-SHA256",
SignatureAlgorithmOID.ECDSA_WITH_SHA384: "ecdsa-with-SHA384",
SignatureAlgorithmOID.ECDSA_WITH_SHA512: "ecdsa-with-SHA512",
SignatureAlgorithmOID.DSA_WITH_SHA1: "dsa-with-sha1",
SignatureAlgorithmOID.DSA_WITH_SHA224: "dsa-with-sha224",
SignatureAlgorithmOID.DSA_WITH_SHA256: "dsa-with-sha256",
SignatureAlgorithmOID.ED25519: "ed25519",
SignatureAlgorithmOID.ED448: "ed448",
SignatureAlgorithmOID.GOSTR3411_94_WITH_3410_2001: (
"GOST R 34.11-94 with GOST R 34.10-2001"
),
SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_256: (
"GOST R 34.10-2012 with GOST R 34.11-2012 (256 bit)"
),
SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_512: (
"GOST R 34.10-2012 with GOST R 34.11-2012 (512 bit)"
),
ExtendedKeyUsageOID.SERVER_AUTH: "serverAuth",
ExtendedKeyUsageOID.CLIENT_AUTH: "clientAuth",
ExtendedKeyUsageOID.CODE_SIGNING: "codeSigning",
ExtendedKeyUsageOID.EMAIL_PROTECTION: "emailProtection",
ExtendedKeyUsageOID.TIME_STAMPING: "timeStamping",
ExtendedKeyUsageOID.OCSP_SIGNING: "OCSPSigning",
ExtendedKeyUsageOID.SMARTCARD_LOGON: "msSmartcardLogin",
ExtendedKeyUsageOID.KERBEROS_PKINIT_KDC: "pkInitKDC",
ExtensionOID.SUBJECT_DIRECTORY_ATTRIBUTES: "subjectDirectoryAttributes",
ExtensionOID.SUBJECT_KEY_IDENTIFIER: "subjectKeyIdentifier",
ExtensionOID.KEY_USAGE: "keyUsage",
ExtensionOID.SUBJECT_ALTERNATIVE_NAME: "subjectAltName",
ExtensionOID.ISSUER_ALTERNATIVE_NAME: "issuerAltName",
ExtensionOID.BASIC_CONSTRAINTS: "basicConstraints",
ExtensionOID.PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS: (
"signedCertificateTimestampList"
),
ExtensionOID.SIGNED_CERTIFICATE_TIMESTAMPS: (
"signedCertificateTimestampList"
),
ExtensionOID.PRECERT_POISON: "ctPoison",
CRLEntryExtensionOID.CRL_REASON: "cRLReason",
CRLEntryExtensionOID.INVALIDITY_DATE: "invalidityDate",
CRLEntryExtensionOID.CERTIFICATE_ISSUER: "certificateIssuer",
ExtensionOID.NAME_CONSTRAINTS: "nameConstraints",
ExtensionOID.CRL_DISTRIBUTION_POINTS: "cRLDistributionPoints",
ExtensionOID.CERTIFICATE_POLICIES: "certificatePolicies",
ExtensionOID.POLICY_MAPPINGS: "policyMappings",
ExtensionOID.AUTHORITY_KEY_IDENTIFIER: "authorityKeyIdentifier",
ExtensionOID.POLICY_CONSTRAINTS: "policyConstraints",
ExtensionOID.EXTENDED_KEY_USAGE: "extendedKeyUsage",
ExtensionOID.FRESHEST_CRL: "freshestCRL",
ExtensionOID.INHIBIT_ANY_POLICY: "inhibitAnyPolicy",
ExtensionOID.ISSUING_DISTRIBUTION_POINT: ("issuingDistributionPoint"),
ExtensionOID.AUTHORITY_INFORMATION_ACCESS: "authorityInfoAccess",
ExtensionOID.SUBJECT_INFORMATION_ACCESS: "subjectInfoAccess",
ExtensionOID.OCSP_NO_CHECK: "OCSPNoCheck",
ExtensionOID.CRL_NUMBER: "cRLNumber",
ExtensionOID.DELTA_CRL_INDICATOR: "deltaCRLIndicator",
ExtensionOID.TLS_FEATURE: "TLSFeature",
AuthorityInformationAccessOID.OCSP: "OCSP",
AuthorityInformationAccessOID.CA_ISSUERS: "caIssuers",
SubjectInformationAccessOID.CA_REPOSITORY: "caRepository",
CertificatePoliciesOID.CPS_QUALIFIER: "id-qt-cps",
CertificatePoliciesOID.CPS_USER_NOTICE: "id-qt-unotice",
OCSPExtensionOID.NONCE: "OCSPNonce",
AttributeOID.CHALLENGE_PASSWORD: "challengePassword",
}

View File

@@ -0,0 +1,10 @@
# 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 typing import Any
def default_backend() -> Any:
from cryptography.hazmat.backends.openssl.backend import backend
return backend

View File

@@ -0,0 +1,9 @@
# 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.backends.openssl.backend import backend
__all__ = ["backend"]

View File

@@ -0,0 +1,251 @@
# 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 InvalidTag
if typing.TYPE_CHECKING:
from cryptography.hazmat.backends.openssl.backend import Backend
from cryptography.hazmat.primitives.ciphers.aead import (
AESCCM,
AESGCM,
AESOCB3,
AESSIV,
ChaCha20Poly1305,
)
_AEAD_TYPES = typing.Union[
AESCCM, AESGCM, AESOCB3, AESSIV, ChaCha20Poly1305
]
_ENCRYPT = 1
_DECRYPT = 0
def _aead_cipher_name(cipher: "_AEAD_TYPES") -> bytes:
from cryptography.hazmat.primitives.ciphers.aead import (
AESCCM,
AESGCM,
AESOCB3,
AESSIV,
ChaCha20Poly1305,
)
if isinstance(cipher, ChaCha20Poly1305):
return b"chacha20-poly1305"
elif isinstance(cipher, AESCCM):
return f"aes-{len(cipher._key) * 8}-ccm".encode("ascii")
elif isinstance(cipher, AESOCB3):
return f"aes-{len(cipher._key) * 8}-ocb".encode("ascii")
elif isinstance(cipher, AESSIV):
return f"aes-{len(cipher._key) * 8 // 2}-siv".encode("ascii")
else:
assert isinstance(cipher, AESGCM)
return f"aes-{len(cipher._key) * 8}-gcm".encode("ascii")
def _evp_cipher(cipher_name: bytes, backend: "Backend"):
if cipher_name.endswith(b"-siv"):
evp_cipher = backend._lib.EVP_CIPHER_fetch(
backend._ffi.NULL,
cipher_name,
backend._ffi.NULL,
)
backend.openssl_assert(evp_cipher != backend._ffi.NULL)
evp_cipher = backend._ffi.gc(evp_cipher, backend._lib.EVP_CIPHER_free)
else:
evp_cipher = backend._lib.EVP_get_cipherbyname(cipher_name)
backend.openssl_assert(evp_cipher != backend._ffi.NULL)
return evp_cipher
def _aead_setup(
backend: "Backend",
cipher_name: bytes,
key: bytes,
nonce: bytes,
tag: typing.Optional[bytes],
tag_len: int,
operation: int,
):
evp_cipher = _evp_cipher(cipher_name, backend)
ctx = backend._lib.EVP_CIPHER_CTX_new()
ctx = backend._ffi.gc(ctx, backend._lib.EVP_CIPHER_CTX_free)
res = backend._lib.EVP_CipherInit_ex(
ctx,
evp_cipher,
backend._ffi.NULL,
backend._ffi.NULL,
backend._ffi.NULL,
int(operation == _ENCRYPT),
)
backend.openssl_assert(res != 0)
res = backend._lib.EVP_CIPHER_CTX_set_key_length(ctx, len(key))
backend.openssl_assert(res != 0)
res = backend._lib.EVP_CIPHER_CTX_ctrl(
ctx,
backend._lib.EVP_CTRL_AEAD_SET_IVLEN,
len(nonce),
backend._ffi.NULL,
)
backend.openssl_assert(res != 0)
if operation == _DECRYPT:
assert tag is not None
res = backend._lib.EVP_CIPHER_CTX_ctrl(
ctx, backend._lib.EVP_CTRL_AEAD_SET_TAG, len(tag), tag
)
backend.openssl_assert(res != 0)
elif cipher_name.endswith(b"-ccm"):
res = backend._lib.EVP_CIPHER_CTX_ctrl(
ctx, backend._lib.EVP_CTRL_AEAD_SET_TAG, tag_len, backend._ffi.NULL
)
backend.openssl_assert(res != 0)
nonce_ptr = backend._ffi.from_buffer(nonce)
key_ptr = backend._ffi.from_buffer(key)
res = backend._lib.EVP_CipherInit_ex(
ctx,
backend._ffi.NULL,
backend._ffi.NULL,
key_ptr,
nonce_ptr,
int(operation == _ENCRYPT),
)
backend.openssl_assert(res != 0)
return ctx
def _set_length(backend: "Backend", ctx, data_len: int) -> None:
intptr = backend._ffi.new("int *")
res = backend._lib.EVP_CipherUpdate(
ctx, backend._ffi.NULL, intptr, backend._ffi.NULL, data_len
)
backend.openssl_assert(res != 0)
def _process_aad(backend: "Backend", ctx, associated_data: bytes) -> None:
outlen = backend._ffi.new("int *")
res = backend._lib.EVP_CipherUpdate(
ctx, backend._ffi.NULL, outlen, associated_data, len(associated_data)
)
backend.openssl_assert(res != 0)
def _process_data(backend: "Backend", ctx, data: bytes) -> bytes:
outlen = backend._ffi.new("int *")
buf = backend._ffi.new("unsigned char[]", len(data))
res = backend._lib.EVP_CipherUpdate(ctx, buf, outlen, data, len(data))
if res == 0:
# AES SIV can error here if the data is invalid on decrypt
backend._consume_errors()
raise InvalidTag
return backend._ffi.buffer(buf, outlen[0])[:]
def _encrypt(
backend: "Backend",
cipher: "_AEAD_TYPES",
nonce: bytes,
data: bytes,
associated_data: typing.List[bytes],
tag_length: int,
) -> bytes:
from cryptography.hazmat.primitives.ciphers.aead import AESCCM, AESSIV
cipher_name = _aead_cipher_name(cipher)
ctx = _aead_setup(
backend, cipher_name, cipher._key, nonce, None, tag_length, _ENCRYPT
)
# CCM requires us to pass the length of the data before processing anything
# However calling this with any other AEAD results in an error
if isinstance(cipher, AESCCM):
_set_length(backend, ctx, len(data))
for ad in associated_data:
_process_aad(backend, ctx, ad)
processed_data = _process_data(backend, ctx, data)
outlen = backend._ffi.new("int *")
# All AEADs we support besides OCB are streaming so they return nothing
# in finalization. OCB can return up to (16 byte block - 1) bytes so
# we need a buffer here too.
buf = backend._ffi.new("unsigned char[]", 16)
res = backend._lib.EVP_CipherFinal_ex(ctx, buf, outlen)
backend.openssl_assert(res != 0)
processed_data += backend._ffi.buffer(buf, outlen[0])[:]
tag_buf = backend._ffi.new("unsigned char[]", tag_length)
res = backend._lib.EVP_CIPHER_CTX_ctrl(
ctx, backend._lib.EVP_CTRL_AEAD_GET_TAG, tag_length, tag_buf
)
backend.openssl_assert(res != 0)
tag = backend._ffi.buffer(tag_buf)[:]
if isinstance(cipher, AESSIV):
# RFC 5297 defines the output as IV || C, where the tag we generate is
# the "IV" and C is the ciphertext. This is the opposite of our
# other AEADs, which are Ciphertext || Tag
backend.openssl_assert(len(tag) == 16)
return tag + processed_data
else:
return processed_data + tag
def _decrypt(
backend: "Backend",
cipher: "_AEAD_TYPES",
nonce: bytes,
data: bytes,
associated_data: typing.List[bytes],
tag_length: int,
) -> bytes:
from cryptography.hazmat.primitives.ciphers.aead import AESCCM, AESSIV
if len(data) < tag_length:
raise InvalidTag
if isinstance(cipher, AESSIV):
# RFC 5297 defines the output as IV || C, where the tag we generate is
# the "IV" and C is the ciphertext. This is the opposite of our
# other AEADs, which are Ciphertext || Tag
tag = data[:tag_length]
data = data[tag_length:]
else:
tag = data[-tag_length:]
data = data[:-tag_length]
cipher_name = _aead_cipher_name(cipher)
ctx = _aead_setup(
backend, cipher_name, cipher._key, nonce, tag, tag_length, _DECRYPT
)
# CCM requires us to pass the length of the data before processing anything
# However calling this with any other AEAD results in an error
if isinstance(cipher, AESCCM):
_set_length(backend, ctx, len(data))
for ad in associated_data:
_process_aad(backend, ctx, ad)
# CCM has a different error path if the tag doesn't match. Errors are
# raised in Update and Final is irrelevant.
if isinstance(cipher, AESCCM):
outlen = backend._ffi.new("int *")
buf = backend._ffi.new("unsigned char[]", len(data))
res = backend._lib.EVP_CipherUpdate(ctx, buf, outlen, data, len(data))
if res != 1:
backend._consume_errors()
raise InvalidTag
processed_data = backend._ffi.buffer(buf, outlen[0])[:]
else:
processed_data = _process_data(backend, ctx, data)
outlen = backend._ffi.new("int *")
# OCB can return up to 15 bytes (16 byte block - 1) in finalization
buf = backend._ffi.new("unsigned char[]", 16)
res = backend._lib.EVP_CipherFinal_ex(ctx, buf, outlen)
processed_data += backend._ffi.buffer(buf, outlen[0])[:]
if res == 0:
backend._consume_errors()
raise InvalidTag
return processed_data

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,282 @@
# 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 InvalidTag, UnsupportedAlgorithm, _Reasons
from cryptography.hazmat.primitives import ciphers
from cryptography.hazmat.primitives.ciphers import algorithms, modes
if typing.TYPE_CHECKING:
from cryptography.hazmat.backends.openssl.backend import Backend
class _CipherContext:
_ENCRYPT = 1
_DECRYPT = 0
_MAX_CHUNK_SIZE = 2**30 - 1
def __init__(
self, backend: "Backend", cipher, mode, operation: int
) -> None:
self._backend = backend
self._cipher = cipher
self._mode = mode
self._operation = operation
self._tag: typing.Optional[bytes] = None
if isinstance(self._cipher, ciphers.BlockCipherAlgorithm):
self._block_size_bytes = self._cipher.block_size // 8
else:
self._block_size_bytes = 1
ctx = self._backend._lib.EVP_CIPHER_CTX_new()
ctx = self._backend._ffi.gc(
ctx, self._backend._lib.EVP_CIPHER_CTX_free
)
registry = self._backend._cipher_registry
try:
adapter = registry[type(cipher), type(mode)]
except KeyError:
raise UnsupportedAlgorithm(
"cipher {} in {} mode is not supported "
"by this backend.".format(
cipher.name, mode.name if mode else mode
),
_Reasons.UNSUPPORTED_CIPHER,
)
evp_cipher = adapter(self._backend, cipher, mode)
if evp_cipher == self._backend._ffi.NULL:
msg = "cipher {0.name} ".format(cipher)
if mode is not None:
msg += "in {0.name} mode ".format(mode)
msg += (
"is not supported by this backend (Your version of OpenSSL "
"may be too old. Current version: {}.)"
).format(self._backend.openssl_version_text())
raise UnsupportedAlgorithm(msg, _Reasons.UNSUPPORTED_CIPHER)
if isinstance(mode, modes.ModeWithInitializationVector):
iv_nonce = self._backend._ffi.from_buffer(
mode.initialization_vector
)
elif isinstance(mode, modes.ModeWithTweak):
iv_nonce = self._backend._ffi.from_buffer(mode.tweak)
elif isinstance(mode, modes.ModeWithNonce):
iv_nonce = self._backend._ffi.from_buffer(mode.nonce)
elif isinstance(cipher, algorithms.ChaCha20):
iv_nonce = self._backend._ffi.from_buffer(cipher.nonce)
else:
iv_nonce = self._backend._ffi.NULL
# begin init with cipher and operation type
res = self._backend._lib.EVP_CipherInit_ex(
ctx,
evp_cipher,
self._backend._ffi.NULL,
self._backend._ffi.NULL,
self._backend._ffi.NULL,
operation,
)
self._backend.openssl_assert(res != 0)
# set the key length to handle variable key ciphers
res = self._backend._lib.EVP_CIPHER_CTX_set_key_length(
ctx, len(cipher.key)
)
self._backend.openssl_assert(res != 0)
if isinstance(mode, modes.GCM):
res = self._backend._lib.EVP_CIPHER_CTX_ctrl(
ctx,
self._backend._lib.EVP_CTRL_AEAD_SET_IVLEN,
len(iv_nonce),
self._backend._ffi.NULL,
)
self._backend.openssl_assert(res != 0)
if mode.tag is not None:
res = self._backend._lib.EVP_CIPHER_CTX_ctrl(
ctx,
self._backend._lib.EVP_CTRL_AEAD_SET_TAG,
len(mode.tag),
mode.tag,
)
self._backend.openssl_assert(res != 0)
self._tag = mode.tag
# pass key/iv
res = self._backend._lib.EVP_CipherInit_ex(
ctx,
self._backend._ffi.NULL,
self._backend._ffi.NULL,
self._backend._ffi.from_buffer(cipher.key),
iv_nonce,
operation,
)
# Check for XTS mode duplicate keys error
errors = self._backend._consume_errors()
lib = self._backend._lib
if res == 0 and (
(
lib.CRYPTOGRAPHY_OPENSSL_111D_OR_GREATER
and errors[0]._lib_reason_match(
lib.ERR_LIB_EVP, lib.EVP_R_XTS_DUPLICATED_KEYS
)
)
or (
lib.Cryptography_HAS_PROVIDERS
and errors[0]._lib_reason_match(
lib.ERR_LIB_PROV, lib.PROV_R_XTS_DUPLICATED_KEYS
)
)
):
raise ValueError("In XTS mode duplicated keys are not allowed")
self._backend.openssl_assert(res != 0, errors=errors)
# We purposely disable padding here as it's handled higher up in the
# API.
self._backend._lib.EVP_CIPHER_CTX_set_padding(ctx, 0)
self._ctx = ctx
def update(self, data: bytes) -> bytes:
buf = bytearray(len(data) + self._block_size_bytes - 1)
n = self.update_into(data, buf)
return bytes(buf[:n])
def update_into(self, data: bytes, buf: bytes) -> int:
total_data_len = len(data)
if len(buf) < (total_data_len + self._block_size_bytes - 1):
raise ValueError(
"buffer must be at least {} bytes for this "
"payload".format(len(data) + self._block_size_bytes - 1)
)
data_processed = 0
total_out = 0
outlen = self._backend._ffi.new("int *")
baseoutbuf = self._backend._ffi.from_buffer(buf)
baseinbuf = self._backend._ffi.from_buffer(data)
while data_processed != total_data_len:
outbuf = baseoutbuf + total_out
inbuf = baseinbuf + data_processed
inlen = min(self._MAX_CHUNK_SIZE, total_data_len - data_processed)
res = self._backend._lib.EVP_CipherUpdate(
self._ctx, outbuf, outlen, inbuf, inlen
)
if res == 0 and isinstance(self._mode, modes.XTS):
self._backend._consume_errors()
raise ValueError(
"In XTS mode you must supply at least a full block in the "
"first update call. For AES this is 16 bytes."
)
else:
self._backend.openssl_assert(res != 0)
data_processed += inlen
total_out += outlen[0]
return total_out
def finalize(self) -> bytes:
if (
self._operation == self._DECRYPT
and isinstance(self._mode, modes.ModeWithAuthenticationTag)
and self.tag is None
):
raise ValueError(
"Authentication tag must be provided when decrypting."
)
buf = self._backend._ffi.new("unsigned char[]", self._block_size_bytes)
outlen = self._backend._ffi.new("int *")
res = self._backend._lib.EVP_CipherFinal_ex(self._ctx, buf, outlen)
if res == 0:
errors = self._backend._consume_errors()
if not errors and isinstance(self._mode, modes.GCM):
raise InvalidTag
lib = self._backend._lib
self._backend.openssl_assert(
errors[0]._lib_reason_match(
lib.ERR_LIB_EVP,
lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH,
)
or (
lib.Cryptography_HAS_PROVIDERS
and errors[0]._lib_reason_match(
lib.ERR_LIB_PROV,
lib.PROV_R_WRONG_FINAL_BLOCK_LENGTH,
)
)
or (
lib.CRYPTOGRAPHY_IS_BORINGSSL
and errors[0].reason
== lib.CIPHER_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH
),
errors=errors,
)
raise ValueError(
"The length of the provided data is not a multiple of "
"the block length."
)
if (
isinstance(self._mode, modes.GCM)
and self._operation == self._ENCRYPT
):
tag_buf = self._backend._ffi.new(
"unsigned char[]", self._block_size_bytes
)
res = self._backend._lib.EVP_CIPHER_CTX_ctrl(
self._ctx,
self._backend._lib.EVP_CTRL_AEAD_GET_TAG,
self._block_size_bytes,
tag_buf,
)
self._backend.openssl_assert(res != 0)
self._tag = self._backend._ffi.buffer(tag_buf)[:]
res = self._backend._lib.EVP_CIPHER_CTX_reset(self._ctx)
self._backend.openssl_assert(res == 1)
return self._backend._ffi.buffer(buf)[: outlen[0]]
def finalize_with_tag(self, tag: bytes) -> bytes:
tag_len = len(tag)
if tag_len < self._mode._min_tag_length:
raise ValueError(
"Authentication tag must be {} bytes or longer.".format(
self._mode._min_tag_length
)
)
elif tag_len > self._block_size_bytes:
raise ValueError(
"Authentication tag cannot be more than {} bytes.".format(
self._block_size_bytes
)
)
res = self._backend._lib.EVP_CIPHER_CTX_ctrl(
self._ctx, self._backend._lib.EVP_CTRL_AEAD_SET_TAG, len(tag), tag
)
self._backend.openssl_assert(res != 0)
self._tag = tag
return self.finalize()
def authenticate_additional_data(self, data: bytes) -> None:
outlen = self._backend._ffi.new("int *")
res = self._backend._lib.EVP_CipherUpdate(
self._ctx,
self._backend._ffi.NULL,
outlen,
self._backend._ffi.from_buffer(data),
len(data),
)
self._backend.openssl_assert(res != 0)
@property
def tag(self) -> typing.Optional[bytes]:
return self._tag

View File

@@ -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 typing
from cryptography.exceptions import (
InvalidSignature,
UnsupportedAlgorithm,
_Reasons,
)
from cryptography.hazmat.primitives import constant_time
from cryptography.hazmat.primitives.ciphers.modes import CBC
if typing.TYPE_CHECKING:
from cryptography.hazmat.primitives import ciphers
from cryptography.hazmat.backends.openssl.backend import Backend
class _CMACContext:
def __init__(
self,
backend: "Backend",
algorithm: "ciphers.BlockCipherAlgorithm",
ctx=None,
) -> None:
if not backend.cmac_algorithm_supported(algorithm):
raise UnsupportedAlgorithm(
"This backend does not support CMAC.",
_Reasons.UNSUPPORTED_CIPHER,
)
self._backend = backend
self._key = algorithm.key
self._algorithm = algorithm
self._output_length = algorithm.block_size // 8
if ctx is None:
registry = self._backend._cipher_registry
adapter = registry[type(algorithm), CBC]
evp_cipher = adapter(self._backend, algorithm, CBC)
ctx = self._backend._lib.CMAC_CTX_new()
self._backend.openssl_assert(ctx != self._backend._ffi.NULL)
ctx = self._backend._ffi.gc(ctx, self._backend._lib.CMAC_CTX_free)
key_ptr = self._backend._ffi.from_buffer(self._key)
res = self._backend._lib.CMAC_Init(
ctx,
key_ptr,
len(self._key),
evp_cipher,
self._backend._ffi.NULL,
)
self._backend.openssl_assert(res == 1)
self._ctx = ctx
def update(self, data: bytes) -> None:
res = self._backend._lib.CMAC_Update(self._ctx, data, len(data))
self._backend.openssl_assert(res == 1)
def finalize(self) -> bytes:
buf = self._backend._ffi.new("unsigned char[]", self._output_length)
length = self._backend._ffi.new("size_t *", self._output_length)
res = self._backend._lib.CMAC_Final(self._ctx, buf, length)
self._backend.openssl_assert(res == 1)
self._ctx = None
return self._backend._ffi.buffer(buf)[:]
def copy(self) -> "_CMACContext":
copied_ctx = self._backend._lib.CMAC_CTX_new()
copied_ctx = self._backend._ffi.gc(
copied_ctx, self._backend._lib.CMAC_CTX_free
)
res = self._backend._lib.CMAC_CTX_copy(copied_ctx, self._ctx)
self._backend.openssl_assert(res == 1)
return _CMACContext(self._backend, self._algorithm, ctx=copied_ctx)
def verify(self, signature: bytes) -> None:
digest = self.finalize()
if not constant_time.bytes_eq(digest, signature):
raise InvalidSignature("Signature did not match digest.")

View File

@@ -0,0 +1,31 @@
# 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 x509
# CRLReason ::= ENUMERATED {
# unspecified (0),
# keyCompromise (1),
# cACompromise (2),
# affiliationChanged (3),
# superseded (4),
# cessationOfOperation (5),
# certificateHold (6),
# -- value 7 is not used
# removeFromCRL (8),
# privilegeWithdrawn (9),
# aACompromise (10) }
_CRL_ENTRY_REASON_ENUM_TO_CODE = {
x509.ReasonFlags.unspecified: 0,
x509.ReasonFlags.key_compromise: 1,
x509.ReasonFlags.ca_compromise: 2,
x509.ReasonFlags.affiliation_changed: 3,
x509.ReasonFlags.superseded: 4,
x509.ReasonFlags.cessation_of_operation: 5,
x509.ReasonFlags.certificate_hold: 6,
x509.ReasonFlags.remove_from_crl: 8,
x509.ReasonFlags.privilege_withdrawn: 9,
x509.ReasonFlags.aa_compromise: 10,
}

View File

@@ -0,0 +1,318 @@
# 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.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import dh
if typing.TYPE_CHECKING:
from cryptography.hazmat.backends.openssl.backend import Backend
def _dh_params_dup(dh_cdata, backend: "Backend"):
lib = backend._lib
ffi = backend._ffi
param_cdata = lib.DHparams_dup(dh_cdata)
backend.openssl_assert(param_cdata != ffi.NULL)
param_cdata = ffi.gc(param_cdata, lib.DH_free)
if lib.CRYPTOGRAPHY_IS_LIBRESSL:
# In libressl DHparams_dup don't copy q
q = ffi.new("BIGNUM **")
lib.DH_get0_pqg(dh_cdata, ffi.NULL, q, ffi.NULL)
q_dup = lib.BN_dup(q[0])
res = lib.DH_set0_pqg(param_cdata, ffi.NULL, q_dup, ffi.NULL)
backend.openssl_assert(res == 1)
return param_cdata
def _dh_cdata_to_parameters(dh_cdata, backend: "Backend") -> "_DHParameters":
param_cdata = _dh_params_dup(dh_cdata, backend)
return _DHParameters(backend, param_cdata)
class _DHParameters(dh.DHParameters):
def __init__(self, backend: "Backend", dh_cdata):
self._backend = backend
self._dh_cdata = dh_cdata
def parameter_numbers(self) -> dh.DHParameterNumbers:
p = self._backend._ffi.new("BIGNUM **")
g = self._backend._ffi.new("BIGNUM **")
q = self._backend._ffi.new("BIGNUM **")
self._backend._lib.DH_get0_pqg(self._dh_cdata, p, q, g)
self._backend.openssl_assert(p[0] != self._backend._ffi.NULL)
self._backend.openssl_assert(g[0] != self._backend._ffi.NULL)
q_val: typing.Optional[int]
if q[0] == self._backend._ffi.NULL:
q_val = None
else:
q_val = self._backend._bn_to_int(q[0])
return dh.DHParameterNumbers(
p=self._backend._bn_to_int(p[0]),
g=self._backend._bn_to_int(g[0]),
q=q_val,
)
def generate_private_key(self) -> dh.DHPrivateKey:
return self._backend.generate_dh_private_key(self)
def parameter_bytes(
self,
encoding: serialization.Encoding,
format: serialization.ParameterFormat,
) -> bytes:
if encoding is serialization.Encoding.OpenSSH:
raise TypeError("OpenSSH encoding is not supported")
if format is not serialization.ParameterFormat.PKCS3:
raise ValueError("Only PKCS3 serialization is supported")
q = self._backend._ffi.new("BIGNUM **")
self._backend._lib.DH_get0_pqg(
self._dh_cdata, self._backend._ffi.NULL, q, self._backend._ffi.NULL
)
if (
q[0] != self._backend._ffi.NULL
and not self._backend._lib.Cryptography_HAS_EVP_PKEY_DHX
):
raise UnsupportedAlgorithm(
"DH X9.42 serialization is not supported",
_Reasons.UNSUPPORTED_SERIALIZATION,
)
if encoding is serialization.Encoding.PEM:
if q[0] != self._backend._ffi.NULL:
write_bio = self._backend._lib.PEM_write_bio_DHxparams
else:
write_bio = self._backend._lib.PEM_write_bio_DHparams
elif encoding is serialization.Encoding.DER:
if q[0] != self._backend._ffi.NULL:
write_bio = self._backend._lib.Cryptography_i2d_DHxparams_bio
else:
write_bio = self._backend._lib.i2d_DHparams_bio
else:
raise TypeError("encoding must be an item from the Encoding enum")
bio = self._backend._create_mem_bio_gc()
res = write_bio(bio, self._dh_cdata)
self._backend.openssl_assert(res == 1)
return self._backend._read_mem_bio(bio)
def _get_dh_num_bits(backend, dh_cdata) -> int:
p = backend._ffi.new("BIGNUM **")
backend._lib.DH_get0_pqg(dh_cdata, p, backend._ffi.NULL, backend._ffi.NULL)
backend.openssl_assert(p[0] != backend._ffi.NULL)
return backend._lib.BN_num_bits(p[0])
class _DHPrivateKey(dh.DHPrivateKey):
def __init__(self, backend: "Backend", dh_cdata, evp_pkey):
self._backend = backend
self._dh_cdata = dh_cdata
self._evp_pkey = evp_pkey
self._key_size_bytes = self._backend._lib.DH_size(dh_cdata)
@property
def key_size(self) -> int:
return _get_dh_num_bits(self._backend, self._dh_cdata)
def private_numbers(self) -> dh.DHPrivateNumbers:
p = self._backend._ffi.new("BIGNUM **")
g = self._backend._ffi.new("BIGNUM **")
q = self._backend._ffi.new("BIGNUM **")
self._backend._lib.DH_get0_pqg(self._dh_cdata, p, q, g)
self._backend.openssl_assert(p[0] != self._backend._ffi.NULL)
self._backend.openssl_assert(g[0] != self._backend._ffi.NULL)
if q[0] == self._backend._ffi.NULL:
q_val = None
else:
q_val = self._backend._bn_to_int(q[0])
pub_key = self._backend._ffi.new("BIGNUM **")
priv_key = self._backend._ffi.new("BIGNUM **")
self._backend._lib.DH_get0_key(self._dh_cdata, pub_key, priv_key)
self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL)
self._backend.openssl_assert(priv_key[0] != self._backend._ffi.NULL)
return dh.DHPrivateNumbers(
public_numbers=dh.DHPublicNumbers(
parameter_numbers=dh.DHParameterNumbers(
p=self._backend._bn_to_int(p[0]),
g=self._backend._bn_to_int(g[0]),
q=q_val,
),
y=self._backend._bn_to_int(pub_key[0]),
),
x=self._backend._bn_to_int(priv_key[0]),
)
def exchange(self, peer_public_key: dh.DHPublicKey) -> bytes:
if not isinstance(peer_public_key, _DHPublicKey):
raise TypeError("peer_public_key must be a DHPublicKey")
ctx = self._backend._lib.EVP_PKEY_CTX_new(
self._evp_pkey, self._backend._ffi.NULL
)
self._backend.openssl_assert(ctx != self._backend._ffi.NULL)
ctx = self._backend._ffi.gc(ctx, self._backend._lib.EVP_PKEY_CTX_free)
res = self._backend._lib.EVP_PKEY_derive_init(ctx)
self._backend.openssl_assert(res == 1)
res = self._backend._lib.EVP_PKEY_derive_set_peer(
ctx, peer_public_key._evp_pkey
)
# Invalid kex errors here in OpenSSL 3.0 because checks were moved
# to EVP_PKEY_derive_set_peer
self._exchange_assert(res == 1)
keylen = self._backend._ffi.new("size_t *")
res = self._backend._lib.EVP_PKEY_derive(
ctx, self._backend._ffi.NULL, keylen
)
# Invalid kex errors here in OpenSSL < 3
self._exchange_assert(res == 1)
self._backend.openssl_assert(keylen[0] > 0)
buf = self._backend._ffi.new("unsigned char[]", keylen[0])
res = self._backend._lib.EVP_PKEY_derive(ctx, buf, keylen)
self._backend.openssl_assert(res == 1)
key = self._backend._ffi.buffer(buf, keylen[0])[:]
pad = self._key_size_bytes - len(key)
if pad > 0:
key = (b"\x00" * pad) + key
return key
def _exchange_assert(self, ok: bool) -> None:
if not ok:
errors_with_text = self._backend._consume_errors_with_text()
raise ValueError(
"Error computing shared key.",
errors_with_text,
)
def public_key(self) -> dh.DHPublicKey:
dh_cdata = _dh_params_dup(self._dh_cdata, self._backend)
pub_key = self._backend._ffi.new("BIGNUM **")
self._backend._lib.DH_get0_key(
self._dh_cdata, pub_key, self._backend._ffi.NULL
)
self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL)
pub_key_dup = self._backend._lib.BN_dup(pub_key[0])
self._backend.openssl_assert(pub_key_dup != self._backend._ffi.NULL)
res = self._backend._lib.DH_set0_key(
dh_cdata, pub_key_dup, self._backend._ffi.NULL
)
self._backend.openssl_assert(res == 1)
evp_pkey = self._backend._dh_cdata_to_evp_pkey(dh_cdata)
return _DHPublicKey(self._backend, dh_cdata, evp_pkey)
def parameters(self) -> dh.DHParameters:
return _dh_cdata_to_parameters(self._dh_cdata, self._backend)
def private_bytes(
self,
encoding: serialization.Encoding,
format: serialization.PrivateFormat,
encryption_algorithm: serialization.KeySerializationEncryption,
) -> bytes:
if format is not serialization.PrivateFormat.PKCS8:
raise ValueError(
"DH private keys support only PKCS8 serialization"
)
if not self._backend._lib.Cryptography_HAS_EVP_PKEY_DHX:
q = self._backend._ffi.new("BIGNUM **")
self._backend._lib.DH_get0_pqg(
self._dh_cdata,
self._backend._ffi.NULL,
q,
self._backend._ffi.NULL,
)
if q[0] != self._backend._ffi.NULL:
raise UnsupportedAlgorithm(
"DH X9.42 serialization is not supported",
_Reasons.UNSUPPORTED_SERIALIZATION,
)
return self._backend._private_key_bytes(
encoding,
format,
encryption_algorithm,
self,
self._evp_pkey,
self._dh_cdata,
)
class _DHPublicKey(dh.DHPublicKey):
def __init__(self, backend: "Backend", dh_cdata, evp_pkey):
self._backend = backend
self._dh_cdata = dh_cdata
self._evp_pkey = evp_pkey
self._key_size_bits = _get_dh_num_bits(self._backend, self._dh_cdata)
@property
def key_size(self) -> int:
return self._key_size_bits
def public_numbers(self) -> dh.DHPublicNumbers:
p = self._backend._ffi.new("BIGNUM **")
g = self._backend._ffi.new("BIGNUM **")
q = self._backend._ffi.new("BIGNUM **")
self._backend._lib.DH_get0_pqg(self._dh_cdata, p, q, g)
self._backend.openssl_assert(p[0] != self._backend._ffi.NULL)
self._backend.openssl_assert(g[0] != self._backend._ffi.NULL)
if q[0] == self._backend._ffi.NULL:
q_val = None
else:
q_val = self._backend._bn_to_int(q[0])
pub_key = self._backend._ffi.new("BIGNUM **")
self._backend._lib.DH_get0_key(
self._dh_cdata, pub_key, self._backend._ffi.NULL
)
self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL)
return dh.DHPublicNumbers(
parameter_numbers=dh.DHParameterNumbers(
p=self._backend._bn_to_int(p[0]),
g=self._backend._bn_to_int(g[0]),
q=q_val,
),
y=self._backend._bn_to_int(pub_key[0]),
)
def parameters(self) -> dh.DHParameters:
return _dh_cdata_to_parameters(self._dh_cdata, self._backend)
def public_bytes(
self,
encoding: serialization.Encoding,
format: serialization.PublicFormat,
) -> bytes:
if format is not serialization.PublicFormat.SubjectPublicKeyInfo:
raise ValueError(
"DH public keys support only "
"SubjectPublicKeyInfo serialization"
)
if not self._backend._lib.Cryptography_HAS_EVP_PKEY_DHX:
q = self._backend._ffi.new("BIGNUM **")
self._backend._lib.DH_get0_pqg(
self._dh_cdata,
self._backend._ffi.NULL,
q,
self._backend._ffi.NULL,
)
if q[0] != self._backend._ffi.NULL:
raise UnsupportedAlgorithm(
"DH X9.42 serialization is not supported",
_Reasons.UNSUPPORTED_SERIALIZATION,
)
return self._backend._public_key_bytes(
encoding, format, self, self._evp_pkey, None
)

View File

@@ -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 typing
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.backends.openssl.utils import (
_calculate_digest_and_algorithm,
)
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import (
dsa,
utils as asym_utils,
)
if typing.TYPE_CHECKING:
from cryptography.hazmat.backends.openssl.backend import Backend
def _dsa_sig_sign(
backend: "Backend", private_key: "_DSAPrivateKey", data: bytes
) -> bytes:
sig_buf_len = backend._lib.DSA_size(private_key._dsa_cdata)
sig_buf = backend._ffi.new("unsigned char[]", sig_buf_len)
buflen = backend._ffi.new("unsigned int *")
# The first parameter passed to DSA_sign is unused by OpenSSL but
# must be an integer.
res = backend._lib.DSA_sign(
0, data, len(data), sig_buf, buflen, private_key._dsa_cdata
)
backend.openssl_assert(res == 1)
backend.openssl_assert(buflen[0])
return backend._ffi.buffer(sig_buf)[: buflen[0]]
def _dsa_sig_verify(
backend: "Backend",
public_key: "_DSAPublicKey",
signature: bytes,
data: bytes,
) -> None:
# The first parameter passed to DSA_verify is unused by OpenSSL but
# must be an integer.
res = backend._lib.DSA_verify(
0, data, len(data), signature, len(signature), public_key._dsa_cdata
)
if res != 1:
backend._consume_errors()
raise InvalidSignature
class _DSAParameters(dsa.DSAParameters):
def __init__(self, backend: "Backend", dsa_cdata):
self._backend = backend
self._dsa_cdata = dsa_cdata
def parameter_numbers(self) -> dsa.DSAParameterNumbers:
p = self._backend._ffi.new("BIGNUM **")
q = self._backend._ffi.new("BIGNUM **")
g = self._backend._ffi.new("BIGNUM **")
self._backend._lib.DSA_get0_pqg(self._dsa_cdata, p, q, g)
self._backend.openssl_assert(p[0] != self._backend._ffi.NULL)
self._backend.openssl_assert(q[0] != self._backend._ffi.NULL)
self._backend.openssl_assert(g[0] != self._backend._ffi.NULL)
return dsa.DSAParameterNumbers(
p=self._backend._bn_to_int(p[0]),
q=self._backend._bn_to_int(q[0]),
g=self._backend._bn_to_int(g[0]),
)
def generate_private_key(self) -> dsa.DSAPrivateKey:
return self._backend.generate_dsa_private_key(self)
class _DSAPrivateKey(dsa.DSAPrivateKey):
_key_size: int
def __init__(self, backend: "Backend", dsa_cdata, evp_pkey):
self._backend = backend
self._dsa_cdata = dsa_cdata
self._evp_pkey = evp_pkey
p = self._backend._ffi.new("BIGNUM **")
self._backend._lib.DSA_get0_pqg(
dsa_cdata, p, self._backend._ffi.NULL, self._backend._ffi.NULL
)
self._backend.openssl_assert(p[0] != backend._ffi.NULL)
self._key_size = self._backend._lib.BN_num_bits(p[0])
@property
def key_size(self) -> int:
return self._key_size
def private_numbers(self) -> dsa.DSAPrivateNumbers:
p = self._backend._ffi.new("BIGNUM **")
q = self._backend._ffi.new("BIGNUM **")
g = self._backend._ffi.new("BIGNUM **")
pub_key = self._backend._ffi.new("BIGNUM **")
priv_key = self._backend._ffi.new("BIGNUM **")
self._backend._lib.DSA_get0_pqg(self._dsa_cdata, p, q, g)
self._backend.openssl_assert(p[0] != self._backend._ffi.NULL)
self._backend.openssl_assert(q[0] != self._backend._ffi.NULL)
self._backend.openssl_assert(g[0] != self._backend._ffi.NULL)
self._backend._lib.DSA_get0_key(self._dsa_cdata, pub_key, priv_key)
self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL)
self._backend.openssl_assert(priv_key[0] != self._backend._ffi.NULL)
return dsa.DSAPrivateNumbers(
public_numbers=dsa.DSAPublicNumbers(
parameter_numbers=dsa.DSAParameterNumbers(
p=self._backend._bn_to_int(p[0]),
q=self._backend._bn_to_int(q[0]),
g=self._backend._bn_to_int(g[0]),
),
y=self._backend._bn_to_int(pub_key[0]),
),
x=self._backend._bn_to_int(priv_key[0]),
)
def public_key(self) -> dsa.DSAPublicKey:
dsa_cdata = self._backend._lib.DSAparams_dup(self._dsa_cdata)
self._backend.openssl_assert(dsa_cdata != self._backend._ffi.NULL)
dsa_cdata = self._backend._ffi.gc(
dsa_cdata, self._backend._lib.DSA_free
)
pub_key = self._backend._ffi.new("BIGNUM **")
self._backend._lib.DSA_get0_key(
self._dsa_cdata, pub_key, self._backend._ffi.NULL
)
self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL)
pub_key_dup = self._backend._lib.BN_dup(pub_key[0])
res = self._backend._lib.DSA_set0_key(
dsa_cdata, pub_key_dup, self._backend._ffi.NULL
)
self._backend.openssl_assert(res == 1)
evp_pkey = self._backend._dsa_cdata_to_evp_pkey(dsa_cdata)
return _DSAPublicKey(self._backend, dsa_cdata, evp_pkey)
def parameters(self) -> dsa.DSAParameters:
dsa_cdata = self._backend._lib.DSAparams_dup(self._dsa_cdata)
self._backend.openssl_assert(dsa_cdata != self._backend._ffi.NULL)
dsa_cdata = self._backend._ffi.gc(
dsa_cdata, self._backend._lib.DSA_free
)
return _DSAParameters(self._backend, dsa_cdata)
def private_bytes(
self,
encoding: serialization.Encoding,
format: serialization.PrivateFormat,
encryption_algorithm: serialization.KeySerializationEncryption,
) -> bytes:
return self._backend._private_key_bytes(
encoding,
format,
encryption_algorithm,
self,
self._evp_pkey,
self._dsa_cdata,
)
def sign(
self,
data: bytes,
algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm],
) -> bytes:
data, _ = _calculate_digest_and_algorithm(data, algorithm)
return _dsa_sig_sign(self._backend, self, data)
class _DSAPublicKey(dsa.DSAPublicKey):
_key_size: int
def __init__(self, backend: "Backend", dsa_cdata, evp_pkey):
self._backend = backend
self._dsa_cdata = dsa_cdata
self._evp_pkey = evp_pkey
p = self._backend._ffi.new("BIGNUM **")
self._backend._lib.DSA_get0_pqg(
dsa_cdata, p, self._backend._ffi.NULL, self._backend._ffi.NULL
)
self._backend.openssl_assert(p[0] != backend._ffi.NULL)
self._key_size = self._backend._lib.BN_num_bits(p[0])
@property
def key_size(self) -> int:
return self._key_size
def public_numbers(self) -> dsa.DSAPublicNumbers:
p = self._backend._ffi.new("BIGNUM **")
q = self._backend._ffi.new("BIGNUM **")
g = self._backend._ffi.new("BIGNUM **")
pub_key = self._backend._ffi.new("BIGNUM **")
self._backend._lib.DSA_get0_pqg(self._dsa_cdata, p, q, g)
self._backend.openssl_assert(p[0] != self._backend._ffi.NULL)
self._backend.openssl_assert(q[0] != self._backend._ffi.NULL)
self._backend.openssl_assert(g[0] != self._backend._ffi.NULL)
self._backend._lib.DSA_get0_key(
self._dsa_cdata, pub_key, self._backend._ffi.NULL
)
self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL)
return dsa.DSAPublicNumbers(
parameter_numbers=dsa.DSAParameterNumbers(
p=self._backend._bn_to_int(p[0]),
q=self._backend._bn_to_int(q[0]),
g=self._backend._bn_to_int(g[0]),
),
y=self._backend._bn_to_int(pub_key[0]),
)
def parameters(self) -> dsa.DSAParameters:
dsa_cdata = self._backend._lib.DSAparams_dup(self._dsa_cdata)
dsa_cdata = self._backend._ffi.gc(
dsa_cdata, self._backend._lib.DSA_free
)
return _DSAParameters(self._backend, dsa_cdata)
def public_bytes(
self,
encoding: serialization.Encoding,
format: serialization.PublicFormat,
) -> bytes:
return self._backend._public_key_bytes(
encoding, format, self, self._evp_pkey, None
)
def verify(
self,
signature: bytes,
data: bytes,
algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm],
) -> None:
data, _ = _calculate_digest_and_algorithm(data, algorithm)
return _dsa_sig_verify(self._backend, self, signature, data)

View File

@@ -0,0 +1,315 @@
# 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 (
InvalidSignature,
UnsupportedAlgorithm,
_Reasons,
)
from cryptography.hazmat.backends.openssl.utils import (
_calculate_digest_and_algorithm,
_evp_pkey_derive,
)
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
if typing.TYPE_CHECKING:
from cryptography.hazmat.backends.openssl.backend import Backend
def _check_signature_algorithm(
signature_algorithm: ec.EllipticCurveSignatureAlgorithm,
) -> None:
if not isinstance(signature_algorithm, ec.ECDSA):
raise UnsupportedAlgorithm(
"Unsupported elliptic curve signature algorithm.",
_Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM,
)
def _ec_key_curve_sn(backend: "Backend", ec_key) -> str:
group = backend._lib.EC_KEY_get0_group(ec_key)
backend.openssl_assert(group != backend._ffi.NULL)
nid = backend._lib.EC_GROUP_get_curve_name(group)
# The following check is to find EC keys with unnamed curves and raise
# an error for now.
if nid == backend._lib.NID_undef:
raise ValueError(
"ECDSA keys with explicit parameters are unsupported at this time"
)
# This is like the above check, but it also catches the case where you
# explicitly encoded a curve with the same parameters as a named curve.
# Don't do that.
if (
not backend._lib.CRYPTOGRAPHY_IS_LIBRESSL
and backend._lib.EC_GROUP_get_asn1_flag(group) == 0
):
raise ValueError(
"ECDSA keys with explicit parameters are unsupported at this time"
)
curve_name = backend._lib.OBJ_nid2sn(nid)
backend.openssl_assert(curve_name != backend._ffi.NULL)
sn = backend._ffi.string(curve_name).decode("ascii")
return sn
def _mark_asn1_named_ec_curve(backend: "Backend", ec_cdata):
"""
Set the named curve flag on the EC_KEY. This causes OpenSSL to
serialize EC keys along with their curve OID which makes
deserialization easier.
"""
backend._lib.EC_KEY_set_asn1_flag(
ec_cdata, backend._lib.OPENSSL_EC_NAMED_CURVE
)
def _check_key_infinity(backend: "Backend", ec_cdata) -> None:
point = backend._lib.EC_KEY_get0_public_key(ec_cdata)
backend.openssl_assert(point != backend._ffi.NULL)
group = backend._lib.EC_KEY_get0_group(ec_cdata)
backend.openssl_assert(group != backend._ffi.NULL)
if backend._lib.EC_POINT_is_at_infinity(group, point):
raise ValueError(
"Cannot load an EC public key where the point is at infinity"
)
def _sn_to_elliptic_curve(backend: "Backend", sn: str) -> ec.EllipticCurve:
try:
return ec._CURVE_TYPES[sn]()
except KeyError:
raise UnsupportedAlgorithm(
"{} is not a supported elliptic curve".format(sn),
_Reasons.UNSUPPORTED_ELLIPTIC_CURVE,
)
def _ecdsa_sig_sign(
backend: "Backend", private_key: "_EllipticCurvePrivateKey", data: bytes
) -> bytes:
max_size = backend._lib.ECDSA_size(private_key._ec_key)
backend.openssl_assert(max_size > 0)
sigbuf = backend._ffi.new("unsigned char[]", max_size)
siglen_ptr = backend._ffi.new("unsigned int[]", 1)
res = backend._lib.ECDSA_sign(
0, data, len(data), sigbuf, siglen_ptr, private_key._ec_key
)
backend.openssl_assert(res == 1)
return backend._ffi.buffer(sigbuf)[: siglen_ptr[0]]
def _ecdsa_sig_verify(
backend: "Backend",
public_key: "_EllipticCurvePublicKey",
signature: bytes,
data: bytes,
) -> None:
res = backend._lib.ECDSA_verify(
0, data, len(data), signature, len(signature), public_key._ec_key
)
if res != 1:
backend._consume_errors()
raise InvalidSignature
class _EllipticCurvePrivateKey(ec.EllipticCurvePrivateKey):
def __init__(self, backend: "Backend", ec_key_cdata, evp_pkey):
self._backend = backend
self._ec_key = ec_key_cdata
self._evp_pkey = evp_pkey
sn = _ec_key_curve_sn(backend, ec_key_cdata)
self._curve = _sn_to_elliptic_curve(backend, sn)
_mark_asn1_named_ec_curve(backend, ec_key_cdata)
_check_key_infinity(backend, ec_key_cdata)
@property
def curve(self) -> ec.EllipticCurve:
return self._curve
@property
def key_size(self) -> int:
return self.curve.key_size
def exchange(
self, algorithm: ec.ECDH, peer_public_key: ec.EllipticCurvePublicKey
) -> bytes:
if not (
self._backend.elliptic_curve_exchange_algorithm_supported(
algorithm, self.curve
)
):
raise UnsupportedAlgorithm(
"This backend does not support the ECDH algorithm.",
_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM,
)
if peer_public_key.curve.name != self.curve.name:
raise ValueError(
"peer_public_key and self are not on the same curve"
)
return _evp_pkey_derive(self._backend, self._evp_pkey, peer_public_key)
def public_key(self) -> ec.EllipticCurvePublicKey:
group = self._backend._lib.EC_KEY_get0_group(self._ec_key)
self._backend.openssl_assert(group != self._backend._ffi.NULL)
curve_nid = self._backend._lib.EC_GROUP_get_curve_name(group)
public_ec_key = self._backend._ec_key_new_by_curve_nid(curve_nid)
point = self._backend._lib.EC_KEY_get0_public_key(self._ec_key)
self._backend.openssl_assert(point != self._backend._ffi.NULL)
res = self._backend._lib.EC_KEY_set_public_key(public_ec_key, point)
self._backend.openssl_assert(res == 1)
evp_pkey = self._backend._ec_cdata_to_evp_pkey(public_ec_key)
return _EllipticCurvePublicKey(self._backend, public_ec_key, evp_pkey)
def private_numbers(self) -> ec.EllipticCurvePrivateNumbers:
bn = self._backend._lib.EC_KEY_get0_private_key(self._ec_key)
private_value = self._backend._bn_to_int(bn)
return ec.EllipticCurvePrivateNumbers(
private_value=private_value,
public_numbers=self.public_key().public_numbers(),
)
def private_bytes(
self,
encoding: serialization.Encoding,
format: serialization.PrivateFormat,
encryption_algorithm: serialization.KeySerializationEncryption,
) -> bytes:
return self._backend._private_key_bytes(
encoding,
format,
encryption_algorithm,
self,
self._evp_pkey,
self._ec_key,
)
def sign(
self,
data: bytes,
signature_algorithm: ec.EllipticCurveSignatureAlgorithm,
) -> bytes:
_check_signature_algorithm(signature_algorithm)
data, _ = _calculate_digest_and_algorithm(
data,
signature_algorithm.algorithm,
)
return _ecdsa_sig_sign(self._backend, self, data)
class _EllipticCurvePublicKey(ec.EllipticCurvePublicKey):
def __init__(self, backend: "Backend", ec_key_cdata, evp_pkey):
self._backend = backend
self._ec_key = ec_key_cdata
self._evp_pkey = evp_pkey
sn = _ec_key_curve_sn(backend, ec_key_cdata)
self._curve = _sn_to_elliptic_curve(backend, sn)
_mark_asn1_named_ec_curve(backend, ec_key_cdata)
_check_key_infinity(backend, ec_key_cdata)
@property
def curve(self) -> ec.EllipticCurve:
return self._curve
@property
def key_size(self) -> int:
return self.curve.key_size
def public_numbers(self) -> ec.EllipticCurvePublicNumbers:
get_func, group = self._backend._ec_key_determine_group_get_func(
self._ec_key
)
point = self._backend._lib.EC_KEY_get0_public_key(self._ec_key)
self._backend.openssl_assert(point != self._backend._ffi.NULL)
with self._backend._tmp_bn_ctx() as bn_ctx:
bn_x = self._backend._lib.BN_CTX_get(bn_ctx)
bn_y = self._backend._lib.BN_CTX_get(bn_ctx)
res = get_func(group, point, bn_x, bn_y, bn_ctx)
self._backend.openssl_assert(res == 1)
x = self._backend._bn_to_int(bn_x)
y = self._backend._bn_to_int(bn_y)
return ec.EllipticCurvePublicNumbers(x=x, y=y, curve=self._curve)
def _encode_point(self, format: serialization.PublicFormat) -> bytes:
if format is serialization.PublicFormat.CompressedPoint:
conversion = self._backend._lib.POINT_CONVERSION_COMPRESSED
else:
assert format is serialization.PublicFormat.UncompressedPoint
conversion = self._backend._lib.POINT_CONVERSION_UNCOMPRESSED
group = self._backend._lib.EC_KEY_get0_group(self._ec_key)
self._backend.openssl_assert(group != self._backend._ffi.NULL)
point = self._backend._lib.EC_KEY_get0_public_key(self._ec_key)
self._backend.openssl_assert(point != self._backend._ffi.NULL)
with self._backend._tmp_bn_ctx() as bn_ctx:
buflen = self._backend._lib.EC_POINT_point2oct(
group, point, conversion, self._backend._ffi.NULL, 0, bn_ctx
)
self._backend.openssl_assert(buflen > 0)
buf = self._backend._ffi.new("char[]", buflen)
res = self._backend._lib.EC_POINT_point2oct(
group, point, conversion, buf, buflen, bn_ctx
)
self._backend.openssl_assert(buflen == res)
return self._backend._ffi.buffer(buf)[:]
def public_bytes(
self,
encoding: serialization.Encoding,
format: serialization.PublicFormat,
) -> bytes:
if (
encoding is serialization.Encoding.X962
or format is serialization.PublicFormat.CompressedPoint
or format is serialization.PublicFormat.UncompressedPoint
):
if encoding is not serialization.Encoding.X962 or format not in (
serialization.PublicFormat.CompressedPoint,
serialization.PublicFormat.UncompressedPoint,
):
raise ValueError(
"X962 encoding must be used with CompressedPoint or "
"UncompressedPoint format"
)
return self._encode_point(format)
else:
return self._backend._public_key_bytes(
encoding, format, self, self._evp_pkey, None
)
def verify(
self,
signature: bytes,
data: bytes,
signature_algorithm: ec.EllipticCurveSignatureAlgorithm,
) -> None:
_check_signature_algorithm(signature_algorithm)
data, _ = _calculate_digest_and_algorithm(
data,
signature_algorithm.algorithm,
)
_ecdsa_sig_verify(self._backend, self, signature, data)

View File

@@ -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 typing
from cryptography import exceptions
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PrivateKey,
Ed25519PublicKey,
_ED25519_KEY_SIZE,
_ED25519_SIG_SIZE,
)
if typing.TYPE_CHECKING:
from cryptography.hazmat.backends.openssl.backend import Backend
class _Ed25519PublicKey(Ed25519PublicKey):
def __init__(self, backend: "Backend", evp_pkey):
self._backend = backend
self._evp_pkey = evp_pkey
def public_bytes(
self,
encoding: serialization.Encoding,
format: serialization.PublicFormat,
) -> bytes:
if (
encoding is serialization.Encoding.Raw
or format is serialization.PublicFormat.Raw
):
if (
encoding is not serialization.Encoding.Raw
or format is not serialization.PublicFormat.Raw
):
raise ValueError(
"When using Raw both encoding and format must be Raw"
)
return self._raw_public_bytes()
return self._backend._public_key_bytes(
encoding, format, self, self._evp_pkey, None
)
def _raw_public_bytes(self) -> bytes:
buf = self._backend._ffi.new("unsigned char []", _ED25519_KEY_SIZE)
buflen = self._backend._ffi.new("size_t *", _ED25519_KEY_SIZE)
res = self._backend._lib.EVP_PKEY_get_raw_public_key(
self._evp_pkey, buf, buflen
)
self._backend.openssl_assert(res == 1)
self._backend.openssl_assert(buflen[0] == _ED25519_KEY_SIZE)
return self._backend._ffi.buffer(buf, _ED25519_KEY_SIZE)[:]
def verify(self, signature: bytes, data: bytes) -> None:
evp_md_ctx = self._backend._lib.EVP_MD_CTX_new()
self._backend.openssl_assert(evp_md_ctx != self._backend._ffi.NULL)
evp_md_ctx = self._backend._ffi.gc(
evp_md_ctx, self._backend._lib.EVP_MD_CTX_free
)
res = self._backend._lib.EVP_DigestVerifyInit(
evp_md_ctx,
self._backend._ffi.NULL,
self._backend._ffi.NULL,
self._backend._ffi.NULL,
self._evp_pkey,
)
self._backend.openssl_assert(res == 1)
res = self._backend._lib.EVP_DigestVerify(
evp_md_ctx, signature, len(signature), data, len(data)
)
if res != 1:
self._backend._consume_errors()
raise exceptions.InvalidSignature
class _Ed25519PrivateKey(Ed25519PrivateKey):
def __init__(self, backend: "Backend", evp_pkey):
self._backend = backend
self._evp_pkey = evp_pkey
def public_key(self) -> Ed25519PublicKey:
buf = self._backend._ffi.new("unsigned char []", _ED25519_KEY_SIZE)
buflen = self._backend._ffi.new("size_t *", _ED25519_KEY_SIZE)
res = self._backend._lib.EVP_PKEY_get_raw_public_key(
self._evp_pkey, buf, buflen
)
self._backend.openssl_assert(res == 1)
self._backend.openssl_assert(buflen[0] == _ED25519_KEY_SIZE)
public_bytes = self._backend._ffi.buffer(buf)[:]
return self._backend.ed25519_load_public_bytes(public_bytes)
def sign(self, data: bytes) -> bytes:
evp_md_ctx = self._backend._lib.EVP_MD_CTX_new()
self._backend.openssl_assert(evp_md_ctx != self._backend._ffi.NULL)
evp_md_ctx = self._backend._ffi.gc(
evp_md_ctx, self._backend._lib.EVP_MD_CTX_free
)
res = self._backend._lib.EVP_DigestSignInit(
evp_md_ctx,
self._backend._ffi.NULL,
self._backend._ffi.NULL,
self._backend._ffi.NULL,
self._evp_pkey,
)
self._backend.openssl_assert(res == 1)
buf = self._backend._ffi.new("unsigned char[]", _ED25519_SIG_SIZE)
buflen = self._backend._ffi.new("size_t *", len(buf))
res = self._backend._lib.EVP_DigestSign(
evp_md_ctx, buf, buflen, data, len(data)
)
self._backend.openssl_assert(res == 1)
self._backend.openssl_assert(buflen[0] == _ED25519_SIG_SIZE)
return self._backend._ffi.buffer(buf, buflen[0])[:]
def private_bytes(
self,
encoding: serialization.Encoding,
format: serialization.PrivateFormat,
encryption_algorithm: serialization.KeySerializationEncryption,
) -> bytes:
if (
encoding is serialization.Encoding.Raw
or format is serialization.PublicFormat.Raw
):
if (
format is not serialization.PrivateFormat.Raw
or encoding is not serialization.Encoding.Raw
or not isinstance(
encryption_algorithm, serialization.NoEncryption
)
):
raise ValueError(
"When using Raw both encoding and format must be Raw "
"and encryption_algorithm must be NoEncryption()"
)
return self._raw_private_bytes()
return self._backend._private_key_bytes(
encoding, format, encryption_algorithm, self, self._evp_pkey, None
)
def _raw_private_bytes(self) -> bytes:
buf = self._backend._ffi.new("unsigned char []", _ED25519_KEY_SIZE)
buflen = self._backend._ffi.new("size_t *", _ED25519_KEY_SIZE)
res = self._backend._lib.EVP_PKEY_get_raw_private_key(
self._evp_pkey, buf, buflen
)
self._backend.openssl_assert(res == 1)
self._backend.openssl_assert(buflen[0] == _ED25519_KEY_SIZE)
return self._backend._ffi.buffer(buf, _ED25519_KEY_SIZE)[:]

View File

@@ -0,0 +1,156 @@
# 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 exceptions
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed448 import (
Ed448PrivateKey,
Ed448PublicKey,
)
if typing.TYPE_CHECKING:
from cryptography.hazmat.backends.openssl.backend import Backend
_ED448_KEY_SIZE = 57
_ED448_SIG_SIZE = 114
class _Ed448PublicKey(Ed448PublicKey):
def __init__(self, backend: "Backend", evp_pkey):
self._backend = backend
self._evp_pkey = evp_pkey
def public_bytes(
self,
encoding: serialization.Encoding,
format: serialization.PublicFormat,
) -> bytes:
if (
encoding is serialization.Encoding.Raw
or format is serialization.PublicFormat.Raw
):
if (
encoding is not serialization.Encoding.Raw
or format is not serialization.PublicFormat.Raw
):
raise ValueError(
"When using Raw both encoding and format must be Raw"
)
return self._raw_public_bytes()
return self._backend._public_key_bytes(
encoding, format, self, self._evp_pkey, None
)
def _raw_public_bytes(self) -> bytes:
buf = self._backend._ffi.new("unsigned char []", _ED448_KEY_SIZE)
buflen = self._backend._ffi.new("size_t *", _ED448_KEY_SIZE)
res = self._backend._lib.EVP_PKEY_get_raw_public_key(
self._evp_pkey, buf, buflen
)
self._backend.openssl_assert(res == 1)
self._backend.openssl_assert(buflen[0] == _ED448_KEY_SIZE)
return self._backend._ffi.buffer(buf, _ED448_KEY_SIZE)[:]
def verify(self, signature: bytes, data: bytes) -> None:
evp_md_ctx = self._backend._lib.EVP_MD_CTX_new()
self._backend.openssl_assert(evp_md_ctx != self._backend._ffi.NULL)
evp_md_ctx = self._backend._ffi.gc(
evp_md_ctx, self._backend._lib.EVP_MD_CTX_free
)
res = self._backend._lib.EVP_DigestVerifyInit(
evp_md_ctx,
self._backend._ffi.NULL,
self._backend._ffi.NULL,
self._backend._ffi.NULL,
self._evp_pkey,
)
self._backend.openssl_assert(res == 1)
res = self._backend._lib.EVP_DigestVerify(
evp_md_ctx, signature, len(signature), data, len(data)
)
if res != 1:
self._backend._consume_errors()
raise exceptions.InvalidSignature
class _Ed448PrivateKey(Ed448PrivateKey):
def __init__(self, backend: "Backend", evp_pkey):
self._backend = backend
self._evp_pkey = evp_pkey
def public_key(self) -> Ed448PublicKey:
buf = self._backend._ffi.new("unsigned char []", _ED448_KEY_SIZE)
buflen = self._backend._ffi.new("size_t *", _ED448_KEY_SIZE)
res = self._backend._lib.EVP_PKEY_get_raw_public_key(
self._evp_pkey, buf, buflen
)
self._backend.openssl_assert(res == 1)
self._backend.openssl_assert(buflen[0] == _ED448_KEY_SIZE)
public_bytes = self._backend._ffi.buffer(buf)[:]
return self._backend.ed448_load_public_bytes(public_bytes)
def sign(self, data: bytes) -> bytes:
evp_md_ctx = self._backend._lib.EVP_MD_CTX_new()
self._backend.openssl_assert(evp_md_ctx != self._backend._ffi.NULL)
evp_md_ctx = self._backend._ffi.gc(
evp_md_ctx, self._backend._lib.EVP_MD_CTX_free
)
res = self._backend._lib.EVP_DigestSignInit(
evp_md_ctx,
self._backend._ffi.NULL,
self._backend._ffi.NULL,
self._backend._ffi.NULL,
self._evp_pkey,
)
self._backend.openssl_assert(res == 1)
buf = self._backend._ffi.new("unsigned char[]", _ED448_SIG_SIZE)
buflen = self._backend._ffi.new("size_t *", len(buf))
res = self._backend._lib.EVP_DigestSign(
evp_md_ctx, buf, buflen, data, len(data)
)
self._backend.openssl_assert(res == 1)
self._backend.openssl_assert(buflen[0] == _ED448_SIG_SIZE)
return self._backend._ffi.buffer(buf, buflen[0])[:]
def private_bytes(
self,
encoding: serialization.Encoding,
format: serialization.PrivateFormat,
encryption_algorithm: serialization.KeySerializationEncryption,
) -> bytes:
if (
encoding is serialization.Encoding.Raw
or format is serialization.PublicFormat.Raw
):
if (
format is not serialization.PrivateFormat.Raw
or encoding is not serialization.Encoding.Raw
or not isinstance(
encryption_algorithm, serialization.NoEncryption
)
):
raise ValueError(
"When using Raw both encoding and format must be Raw "
"and encryption_algorithm must be NoEncryption()"
)
return self._raw_private_bytes()
return self._backend._private_key_bytes(
encoding, format, encryption_algorithm, self, self._evp_pkey, None
)
def _raw_private_bytes(self) -> bytes:
buf = self._backend._ffi.new("unsigned char []", _ED448_KEY_SIZE)
buflen = self._backend._ffi.new("size_t *", _ED448_KEY_SIZE)
res = self._backend._lib.EVP_PKEY_get_raw_private_key(
self._evp_pkey, buf, buflen
)
self._backend.openssl_assert(res == 1)
self._backend.openssl_assert(buflen[0] == _ED448_KEY_SIZE)
return self._backend._ffi.buffer(buf, _ED448_KEY_SIZE)[:]

View File

@@ -0,0 +1,18 @@
# 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 x509
_CRLREASONFLAGS = {
x509.ReasonFlags.key_compromise: 1,
x509.ReasonFlags.ca_compromise: 2,
x509.ReasonFlags.affiliation_changed: 3,
x509.ReasonFlags.superseded: 4,
x509.ReasonFlags.cessation_of_operation: 5,
x509.ReasonFlags.certificate_hold: 6,
x509.ReasonFlags.privilege_withdrawn: 7,
x509.ReasonFlags.aa_compromise: 8,
}

View File

@@ -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 typing
from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
from cryptography.hazmat.primitives import hashes
if typing.TYPE_CHECKING:
from cryptography.hazmat.backends.openssl.backend import Backend
class _HashContext(hashes.HashContext):
def __init__(
self, backend: "Backend", algorithm: hashes.HashAlgorithm, ctx=None
) -> None:
self._algorithm = algorithm
self._backend = backend
if ctx is None:
ctx = self._backend._lib.EVP_MD_CTX_new()
ctx = self._backend._ffi.gc(
ctx, self._backend._lib.EVP_MD_CTX_free
)
evp_md = self._backend._evp_md_from_algorithm(algorithm)
if evp_md == self._backend._ffi.NULL:
raise UnsupportedAlgorithm(
"{} is not a supported hash on this backend.".format(
algorithm.name
),
_Reasons.UNSUPPORTED_HASH,
)
res = self._backend._lib.EVP_DigestInit_ex(
ctx, evp_md, self._backend._ffi.NULL
)
self._backend.openssl_assert(res != 0)
self._ctx = ctx
@property
def algorithm(self) -> hashes.HashAlgorithm:
return self._algorithm
def copy(self) -> "_HashContext":
copied_ctx = self._backend._lib.EVP_MD_CTX_new()
copied_ctx = self._backend._ffi.gc(
copied_ctx, self._backend._lib.EVP_MD_CTX_free
)
res = self._backend._lib.EVP_MD_CTX_copy_ex(copied_ctx, self._ctx)
self._backend.openssl_assert(res != 0)
return _HashContext(self._backend, self.algorithm, ctx=copied_ctx)
def update(self, data: bytes) -> None:
data_ptr = self._backend._ffi.from_buffer(data)
res = self._backend._lib.EVP_DigestUpdate(
self._ctx, data_ptr, len(data)
)
self._backend.openssl_assert(res != 0)
def finalize(self) -> bytes:
if isinstance(self.algorithm, hashes.ExtendableOutputFunction):
# extendable output functions use a different finalize
return self._finalize_xof()
else:
buf = self._backend._ffi.new(
"unsigned char[]", self._backend._lib.EVP_MAX_MD_SIZE
)
outlen = self._backend._ffi.new("unsigned int *")
res = self._backend._lib.EVP_DigestFinal_ex(self._ctx, buf, outlen)
self._backend.openssl_assert(res != 0)
self._backend.openssl_assert(
outlen[0] == self.algorithm.digest_size
)
return self._backend._ffi.buffer(buf)[: outlen[0]]
def _finalize_xof(self) -> bytes:
buf = self._backend._ffi.new(
"unsigned char[]", self.algorithm.digest_size
)
res = self._backend._lib.EVP_DigestFinalXOF(
self._ctx, buf, self.algorithm.digest_size
)
self._backend.openssl_assert(res != 0)
return self._backend._ffi.buffer(buf)[: self.algorithm.digest_size]

View File

@@ -0,0 +1,85 @@
# 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 (
InvalidSignature,
UnsupportedAlgorithm,
_Reasons,
)
from cryptography.hazmat.primitives import constant_time, hashes
if typing.TYPE_CHECKING:
from cryptography.hazmat.backends.openssl.backend import Backend
class _HMACContext(hashes.HashContext):
def __init__(
self,
backend: "Backend",
key: bytes,
algorithm: hashes.HashAlgorithm,
ctx=None,
):
self._algorithm = algorithm
self._backend = backend
if ctx is None:
ctx = self._backend._lib.HMAC_CTX_new()
self._backend.openssl_assert(ctx != self._backend._ffi.NULL)
ctx = self._backend._ffi.gc(ctx, self._backend._lib.HMAC_CTX_free)
evp_md = self._backend._evp_md_from_algorithm(algorithm)
if evp_md == self._backend._ffi.NULL:
raise UnsupportedAlgorithm(
"{} is not a supported hash on this backend".format(
algorithm.name
),
_Reasons.UNSUPPORTED_HASH,
)
key_ptr = self._backend._ffi.from_buffer(key)
res = self._backend._lib.HMAC_Init_ex(
ctx, key_ptr, len(key), evp_md, self._backend._ffi.NULL
)
self._backend.openssl_assert(res != 0)
self._ctx = ctx
self._key = key
@property
def algorithm(self) -> hashes.HashAlgorithm:
return self._algorithm
def copy(self) -> "_HMACContext":
copied_ctx = self._backend._lib.HMAC_CTX_new()
self._backend.openssl_assert(copied_ctx != self._backend._ffi.NULL)
copied_ctx = self._backend._ffi.gc(
copied_ctx, self._backend._lib.HMAC_CTX_free
)
res = self._backend._lib.HMAC_CTX_copy(copied_ctx, self._ctx)
self._backend.openssl_assert(res != 0)
return _HMACContext(
self._backend, self._key, self.algorithm, ctx=copied_ctx
)
def update(self, data: bytes) -> None:
data_ptr = self._backend._ffi.from_buffer(data)
res = self._backend._lib.HMAC_Update(self._ctx, data_ptr, len(data))
self._backend.openssl_assert(res != 0)
def finalize(self) -> bytes:
buf = self._backend._ffi.new(
"unsigned char[]", self._backend._lib.EVP_MAX_MD_SIZE
)
outlen = self._backend._ffi.new("unsigned int *")
res = self._backend._lib.HMAC_Final(self._ctx, buf, outlen)
self._backend.openssl_assert(res != 0)
self._backend.openssl_assert(outlen[0] == self.algorithm.digest_size)
return self._backend._ffi.buffer(buf)[: outlen[0]]
def verify(self, signature: bytes) -> None:
digest = self.finalize()
if not constant_time.bytes_eq(digest, signature):
raise InvalidSignature("Signature did not match digest.")

View File

@@ -0,0 +1,68 @@
# 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 InvalidSignature
from cryptography.hazmat.primitives import constant_time
_POLY1305_TAG_SIZE = 16
_POLY1305_KEY_SIZE = 32
if typing.TYPE_CHECKING:
from cryptography.hazmat.backends.openssl.backend import Backend
class _Poly1305Context:
def __init__(self, backend: "Backend", key: bytes) -> None:
self._backend = backend
key_ptr = self._backend._ffi.from_buffer(key)
# This function copies the key into OpenSSL-owned memory so we don't
# need to retain it ourselves
evp_pkey = self._backend._lib.EVP_PKEY_new_raw_private_key(
self._backend._lib.NID_poly1305,
self._backend._ffi.NULL,
key_ptr,
len(key),
)
self._backend.openssl_assert(evp_pkey != self._backend._ffi.NULL)
self._evp_pkey = self._backend._ffi.gc(
evp_pkey, self._backend._lib.EVP_PKEY_free
)
ctx = self._backend._lib.EVP_MD_CTX_new()
self._backend.openssl_assert(ctx != self._backend._ffi.NULL)
self._ctx = self._backend._ffi.gc(
ctx, self._backend._lib.EVP_MD_CTX_free
)
res = self._backend._lib.EVP_DigestSignInit(
self._ctx,
self._backend._ffi.NULL,
self._backend._ffi.NULL,
self._backend._ffi.NULL,
self._evp_pkey,
)
self._backend.openssl_assert(res == 1)
def update(self, data: bytes) -> None:
data_ptr = self._backend._ffi.from_buffer(data)
res = self._backend._lib.EVP_DigestSignUpdate(
self._ctx, data_ptr, len(data)
)
self._backend.openssl_assert(res != 0)
def finalize(self) -> bytes:
buf = self._backend._ffi.new("unsigned char[]", _POLY1305_TAG_SIZE)
outlen = self._backend._ffi.new("size_t *", _POLY1305_TAG_SIZE)
res = self._backend._lib.EVP_DigestSignFinal(self._ctx, buf, outlen)
self._backend.openssl_assert(res != 0)
self._backend.openssl_assert(outlen[0] == _POLY1305_TAG_SIZE)
return self._backend._ffi.buffer(buf)[: outlen[0]]
def verify(self, tag: bytes) -> None:
mac = self.finalize()
if not constant_time.bytes_eq(mac, tag):
raise InvalidSignature("Value did not match computed tag.")

View File

@@ -0,0 +1,567 @@
# 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 (
InvalidSignature,
UnsupportedAlgorithm,
_Reasons,
)
from cryptography.hazmat.backends.openssl.utils import (
_calculate_digest_and_algorithm,
)
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import (
utils as asym_utils,
)
from cryptography.hazmat.primitives.asymmetric.padding import (
AsymmetricPadding,
MGF1,
OAEP,
PKCS1v15,
PSS,
_Auto,
_DigestLength,
_MaxLength,
calculate_max_pss_salt_length,
)
from cryptography.hazmat.primitives.asymmetric.rsa import (
RSAPrivateKey,
RSAPrivateNumbers,
RSAPublicKey,
RSAPublicNumbers,
)
if typing.TYPE_CHECKING:
from cryptography.hazmat.backends.openssl.backend import Backend
def _get_rsa_pss_salt_length(
backend: "Backend",
pss: PSS,
key: typing.Union[RSAPrivateKey, RSAPublicKey],
hash_algorithm: hashes.HashAlgorithm,
) -> int:
salt = pss._salt_length
if isinstance(salt, _MaxLength):
return calculate_max_pss_salt_length(key, hash_algorithm)
elif isinstance(salt, _DigestLength):
return hash_algorithm.digest_size
elif isinstance(salt, _Auto):
if isinstance(key, RSAPrivateKey):
raise ValueError(
"PSS salt length can only be set to AUTO when verifying"
)
return backend._lib.RSA_PSS_SALTLEN_AUTO
else:
return salt
def _enc_dec_rsa(
backend: "Backend",
key: typing.Union["_RSAPrivateKey", "_RSAPublicKey"],
data: bytes,
padding: AsymmetricPadding,
) -> bytes:
if not isinstance(padding, AsymmetricPadding):
raise TypeError("Padding must be an instance of AsymmetricPadding.")
if isinstance(padding, PKCS1v15):
padding_enum = backend._lib.RSA_PKCS1_PADDING
elif isinstance(padding, OAEP):
padding_enum = backend._lib.RSA_PKCS1_OAEP_PADDING
if not isinstance(padding._mgf, MGF1):
raise UnsupportedAlgorithm(
"Only MGF1 is supported by this backend.",
_Reasons.UNSUPPORTED_MGF,
)
if not backend.rsa_padding_supported(padding):
raise UnsupportedAlgorithm(
"This combination of padding and hash algorithm is not "
"supported by this backend.",
_Reasons.UNSUPPORTED_PADDING,
)
else:
raise UnsupportedAlgorithm(
"{} is not supported by this backend.".format(padding.name),
_Reasons.UNSUPPORTED_PADDING,
)
return _enc_dec_rsa_pkey_ctx(backend, key, data, padding_enum, padding)
def _enc_dec_rsa_pkey_ctx(
backend: "Backend",
key: typing.Union["_RSAPrivateKey", "_RSAPublicKey"],
data: bytes,
padding_enum: int,
padding: AsymmetricPadding,
) -> bytes:
init: typing.Callable[[typing.Any], int]
crypt: typing.Callable[[typing.Any, typing.Any, int, bytes, int], int]
if isinstance(key, _RSAPublicKey):
init = backend._lib.EVP_PKEY_encrypt_init
crypt = backend._lib.EVP_PKEY_encrypt
else:
init = backend._lib.EVP_PKEY_decrypt_init
crypt = backend._lib.EVP_PKEY_decrypt
pkey_ctx = backend._lib.EVP_PKEY_CTX_new(key._evp_pkey, backend._ffi.NULL)
backend.openssl_assert(pkey_ctx != backend._ffi.NULL)
pkey_ctx = backend._ffi.gc(pkey_ctx, backend._lib.EVP_PKEY_CTX_free)
res = init(pkey_ctx)
backend.openssl_assert(res == 1)
res = backend._lib.EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, padding_enum)
backend.openssl_assert(res > 0)
buf_size = backend._lib.EVP_PKEY_size(key._evp_pkey)
backend.openssl_assert(buf_size > 0)
if isinstance(padding, OAEP):
mgf1_md = backend._evp_md_non_null_from_algorithm(
padding._mgf._algorithm
)
res = backend._lib.EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, mgf1_md)
backend.openssl_assert(res > 0)
oaep_md = backend._evp_md_non_null_from_algorithm(padding._algorithm)
res = backend._lib.EVP_PKEY_CTX_set_rsa_oaep_md(pkey_ctx, oaep_md)
backend.openssl_assert(res > 0)
if (
isinstance(padding, OAEP)
and padding._label is not None
and len(padding._label) > 0
):
# set0_rsa_oaep_label takes ownership of the char * so we need to
# copy it into some new memory
labelptr = backend._lib.OPENSSL_malloc(len(padding._label))
backend.openssl_assert(labelptr != backend._ffi.NULL)
backend._ffi.memmove(labelptr, padding._label, len(padding._label))
res = backend._lib.EVP_PKEY_CTX_set0_rsa_oaep_label(
pkey_ctx, labelptr, len(padding._label)
)
backend.openssl_assert(res == 1)
outlen = backend._ffi.new("size_t *", buf_size)
buf = backend._ffi.new("unsigned char[]", buf_size)
# Everything from this line onwards is written with the goal of being as
# constant-time as is practical given the constraints of Python and our
# API. See Bleichenbacher's '98 attack on RSA, and its many many variants.
# As such, you should not attempt to change this (particularly to "clean it
# up") without understanding why it was written this way (see
# Chesterton's Fence), and without measuring to verify you have not
# introduced observable time differences.
res = crypt(pkey_ctx, buf, outlen, data, len(data))
resbuf = backend._ffi.buffer(buf)[: outlen[0]]
backend._lib.ERR_clear_error()
if res <= 0:
raise ValueError("Encryption/decryption failed.")
return resbuf
def _rsa_sig_determine_padding(
backend: "Backend",
key: typing.Union["_RSAPrivateKey", "_RSAPublicKey"],
padding: AsymmetricPadding,
algorithm: typing.Optional[hashes.HashAlgorithm],
) -> int:
if not isinstance(padding, AsymmetricPadding):
raise TypeError("Expected provider of AsymmetricPadding.")
pkey_size = backend._lib.EVP_PKEY_size(key._evp_pkey)
backend.openssl_assert(pkey_size > 0)
if isinstance(padding, PKCS1v15):
# Hash algorithm is ignored for PKCS1v15-padding, may be None.
padding_enum = backend._lib.RSA_PKCS1_PADDING
elif isinstance(padding, PSS):
if not isinstance(padding._mgf, MGF1):
raise UnsupportedAlgorithm(
"Only MGF1 is supported by this backend.",
_Reasons.UNSUPPORTED_MGF,
)
# PSS padding requires a hash algorithm
if not isinstance(algorithm, hashes.HashAlgorithm):
raise TypeError("Expected instance of hashes.HashAlgorithm.")
# Size of key in bytes - 2 is the maximum
# PSS signature length (salt length is checked later)
if pkey_size - algorithm.digest_size - 2 < 0:
raise ValueError(
"Digest too large for key size. Use a larger "
"key or different digest."
)
padding_enum = backend._lib.RSA_PKCS1_PSS_PADDING
else:
raise UnsupportedAlgorithm(
"{} is not supported by this backend.".format(padding.name),
_Reasons.UNSUPPORTED_PADDING,
)
return padding_enum
# Hash algorithm can be absent (None) to initialize the context without setting
# any message digest algorithm. This is currently only valid for the PKCS1v15
# padding type, where it means that the signature data is encoded/decoded
# as provided, without being wrapped in a DigestInfo structure.
def _rsa_sig_setup(
backend: "Backend",
padding: AsymmetricPadding,
algorithm: typing.Optional[hashes.HashAlgorithm],
key: typing.Union["_RSAPublicKey", "_RSAPrivateKey"],
init_func: typing.Callable[[typing.Any], int],
):
padding_enum = _rsa_sig_determine_padding(backend, key, padding, algorithm)
pkey_ctx = backend._lib.EVP_PKEY_CTX_new(key._evp_pkey, backend._ffi.NULL)
backend.openssl_assert(pkey_ctx != backend._ffi.NULL)
pkey_ctx = backend._ffi.gc(pkey_ctx, backend._lib.EVP_PKEY_CTX_free)
res = init_func(pkey_ctx)
if res != 1:
errors = backend._consume_errors()
raise ValueError("Unable to sign/verify with this key", errors)
if algorithm is not None:
evp_md = backend._evp_md_non_null_from_algorithm(algorithm)
res = backend._lib.EVP_PKEY_CTX_set_signature_md(pkey_ctx, evp_md)
if res <= 0:
backend._consume_errors()
raise UnsupportedAlgorithm(
"{} is not supported by this backend for RSA signing.".format(
algorithm.name
),
_Reasons.UNSUPPORTED_HASH,
)
res = backend._lib.EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, padding_enum)
if res <= 0:
backend._consume_errors()
raise UnsupportedAlgorithm(
"{} is not supported for the RSA signature operation.".format(
padding.name
),
_Reasons.UNSUPPORTED_PADDING,
)
if isinstance(padding, PSS):
assert isinstance(algorithm, hashes.HashAlgorithm)
res = backend._lib.EVP_PKEY_CTX_set_rsa_pss_saltlen(
pkey_ctx,
_get_rsa_pss_salt_length(backend, padding, key, algorithm),
)
backend.openssl_assert(res > 0)
mgf1_md = backend._evp_md_non_null_from_algorithm(
padding._mgf._algorithm
)
res = backend._lib.EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, mgf1_md)
backend.openssl_assert(res > 0)
return pkey_ctx
def _rsa_sig_sign(
backend: "Backend",
padding: AsymmetricPadding,
algorithm: hashes.HashAlgorithm,
private_key: "_RSAPrivateKey",
data: bytes,
) -> bytes:
pkey_ctx = _rsa_sig_setup(
backend,
padding,
algorithm,
private_key,
backend._lib.EVP_PKEY_sign_init,
)
buflen = backend._ffi.new("size_t *")
res = backend._lib.EVP_PKEY_sign(
pkey_ctx, backend._ffi.NULL, buflen, data, len(data)
)
backend.openssl_assert(res == 1)
buf = backend._ffi.new("unsigned char[]", buflen[0])
res = backend._lib.EVP_PKEY_sign(pkey_ctx, buf, buflen, data, len(data))
if res != 1:
errors = backend._consume_errors_with_text()
raise ValueError(
"Digest or salt length too long for key size. Use a larger key "
"or shorter salt length if you are specifying a PSS salt",
errors,
)
return backend._ffi.buffer(buf)[:]
def _rsa_sig_verify(
backend: "Backend",
padding: AsymmetricPadding,
algorithm: hashes.HashAlgorithm,
public_key: "_RSAPublicKey",
signature: bytes,
data: bytes,
) -> None:
pkey_ctx = _rsa_sig_setup(
backend,
padding,
algorithm,
public_key,
backend._lib.EVP_PKEY_verify_init,
)
res = backend._lib.EVP_PKEY_verify(
pkey_ctx, signature, len(signature), data, len(data)
)
# The previous call can return negative numbers in the event of an
# error. This is not a signature failure but we need to fail if it
# occurs.
backend.openssl_assert(res >= 0)
if res == 0:
backend._consume_errors()
raise InvalidSignature
def _rsa_sig_recover(
backend: "Backend",
padding: AsymmetricPadding,
algorithm: typing.Optional[hashes.HashAlgorithm],
public_key: "_RSAPublicKey",
signature: bytes,
) -> bytes:
pkey_ctx = _rsa_sig_setup(
backend,
padding,
algorithm,
public_key,
backend._lib.EVP_PKEY_verify_recover_init,
)
# Attempt to keep the rest of the code in this function as constant/time
# as possible. See the comment in _enc_dec_rsa_pkey_ctx. Note that the
# buflen parameter is used even though its value may be undefined in the
# error case. Due to the tolerant nature of Python slicing this does not
# trigger any exceptions.
maxlen = backend._lib.EVP_PKEY_size(public_key._evp_pkey)
backend.openssl_assert(maxlen > 0)
buf = backend._ffi.new("unsigned char[]", maxlen)
buflen = backend._ffi.new("size_t *", maxlen)
res = backend._lib.EVP_PKEY_verify_recover(
pkey_ctx, buf, buflen, signature, len(signature)
)
resbuf = backend._ffi.buffer(buf)[: buflen[0]]
backend._lib.ERR_clear_error()
# Assume that all parameter errors are handled during the setup phase and
# any error here is due to invalid signature.
if res != 1:
raise InvalidSignature
return resbuf
class _RSAPrivateKey(RSAPrivateKey):
_evp_pkey: object
_rsa_cdata: object
_key_size: int
def __init__(
self, backend: "Backend", rsa_cdata, evp_pkey, _skip_check_key: bool
):
res: int
# RSA_check_key is slower in OpenSSL 3.0.0 due to improved
# primality checking. In normal use this is unlikely to be a problem
# since users don't load new keys constantly, but for TESTING we've
# added an init arg that allows skipping the checks. You should not
# use this in production code unless you understand the consequences.
if not _skip_check_key:
res = backend._lib.RSA_check_key(rsa_cdata)
if res != 1:
errors = backend._consume_errors_with_text()
raise ValueError("Invalid private key", errors)
# 2 is prime and passes an RSA key check, so we also check
# if p and q are odd just to be safe.
p = backend._ffi.new("BIGNUM **")
q = backend._ffi.new("BIGNUM **")
backend._lib.RSA_get0_factors(rsa_cdata, p, q)
backend.openssl_assert(p[0] != backend._ffi.NULL)
backend.openssl_assert(q[0] != backend._ffi.NULL)
p_odd = backend._lib.BN_is_odd(p[0])
q_odd = backend._lib.BN_is_odd(q[0])
if p_odd != 1 or q_odd != 1:
errors = backend._consume_errors_with_text()
raise ValueError("Invalid private key", errors)
# Blinding is on by default in many versions of OpenSSL, but let's
# just be conservative here.
res = backend._lib.RSA_blinding_on(rsa_cdata, backend._ffi.NULL)
backend.openssl_assert(res == 1)
self._backend = backend
self._rsa_cdata = rsa_cdata
self._evp_pkey = evp_pkey
n = self._backend._ffi.new("BIGNUM **")
self._backend._lib.RSA_get0_key(
self._rsa_cdata,
n,
self._backend._ffi.NULL,
self._backend._ffi.NULL,
)
self._backend.openssl_assert(n[0] != self._backend._ffi.NULL)
self._key_size = self._backend._lib.BN_num_bits(n[0])
@property
def key_size(self) -> int:
return self._key_size
def decrypt(self, ciphertext: bytes, padding: AsymmetricPadding) -> bytes:
key_size_bytes = (self.key_size + 7) // 8
if key_size_bytes != len(ciphertext):
raise ValueError("Ciphertext length must be equal to key size.")
return _enc_dec_rsa(self._backend, self, ciphertext, padding)
def public_key(self) -> RSAPublicKey:
ctx = self._backend._lib.RSAPublicKey_dup(self._rsa_cdata)
self._backend.openssl_assert(ctx != self._backend._ffi.NULL)
ctx = self._backend._ffi.gc(ctx, self._backend._lib.RSA_free)
evp_pkey = self._backend._rsa_cdata_to_evp_pkey(ctx)
return _RSAPublicKey(self._backend, ctx, evp_pkey)
def private_numbers(self) -> RSAPrivateNumbers:
n = self._backend._ffi.new("BIGNUM **")
e = self._backend._ffi.new("BIGNUM **")
d = self._backend._ffi.new("BIGNUM **")
p = self._backend._ffi.new("BIGNUM **")
q = self._backend._ffi.new("BIGNUM **")
dmp1 = self._backend._ffi.new("BIGNUM **")
dmq1 = self._backend._ffi.new("BIGNUM **")
iqmp = self._backend._ffi.new("BIGNUM **")
self._backend._lib.RSA_get0_key(self._rsa_cdata, n, e, d)
self._backend.openssl_assert(n[0] != self._backend._ffi.NULL)
self._backend.openssl_assert(e[0] != self._backend._ffi.NULL)
self._backend.openssl_assert(d[0] != self._backend._ffi.NULL)
self._backend._lib.RSA_get0_factors(self._rsa_cdata, p, q)
self._backend.openssl_assert(p[0] != self._backend._ffi.NULL)
self._backend.openssl_assert(q[0] != self._backend._ffi.NULL)
self._backend._lib.RSA_get0_crt_params(
self._rsa_cdata, dmp1, dmq1, iqmp
)
self._backend.openssl_assert(dmp1[0] != self._backend._ffi.NULL)
self._backend.openssl_assert(dmq1[0] != self._backend._ffi.NULL)
self._backend.openssl_assert(iqmp[0] != self._backend._ffi.NULL)
return RSAPrivateNumbers(
p=self._backend._bn_to_int(p[0]),
q=self._backend._bn_to_int(q[0]),
d=self._backend._bn_to_int(d[0]),
dmp1=self._backend._bn_to_int(dmp1[0]),
dmq1=self._backend._bn_to_int(dmq1[0]),
iqmp=self._backend._bn_to_int(iqmp[0]),
public_numbers=RSAPublicNumbers(
e=self._backend._bn_to_int(e[0]),
n=self._backend._bn_to_int(n[0]),
),
)
def private_bytes(
self,
encoding: serialization.Encoding,
format: serialization.PrivateFormat,
encryption_algorithm: serialization.KeySerializationEncryption,
) -> bytes:
return self._backend._private_key_bytes(
encoding,
format,
encryption_algorithm,
self,
self._evp_pkey,
self._rsa_cdata,
)
def sign(
self,
data: bytes,
padding: AsymmetricPadding,
algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm],
) -> bytes:
data, algorithm = _calculate_digest_and_algorithm(data, algorithm)
return _rsa_sig_sign(self._backend, padding, algorithm, self, data)
class _RSAPublicKey(RSAPublicKey):
_evp_pkey: object
_rsa_cdata: object
_key_size: int
def __init__(self, backend: "Backend", rsa_cdata, evp_pkey):
self._backend = backend
self._rsa_cdata = rsa_cdata
self._evp_pkey = evp_pkey
n = self._backend._ffi.new("BIGNUM **")
self._backend._lib.RSA_get0_key(
self._rsa_cdata,
n,
self._backend._ffi.NULL,
self._backend._ffi.NULL,
)
self._backend.openssl_assert(n[0] != self._backend._ffi.NULL)
self._key_size = self._backend._lib.BN_num_bits(n[0])
@property
def key_size(self) -> int:
return self._key_size
def encrypt(self, plaintext: bytes, padding: AsymmetricPadding) -> bytes:
return _enc_dec_rsa(self._backend, self, plaintext, padding)
def public_numbers(self) -> RSAPublicNumbers:
n = self._backend._ffi.new("BIGNUM **")
e = self._backend._ffi.new("BIGNUM **")
self._backend._lib.RSA_get0_key(
self._rsa_cdata, n, e, self._backend._ffi.NULL
)
self._backend.openssl_assert(n[0] != self._backend._ffi.NULL)
self._backend.openssl_assert(e[0] != self._backend._ffi.NULL)
return RSAPublicNumbers(
e=self._backend._bn_to_int(e[0]),
n=self._backend._bn_to_int(n[0]),
)
def public_bytes(
self,
encoding: serialization.Encoding,
format: serialization.PublicFormat,
) -> bytes:
return self._backend._public_key_bytes(
encoding, format, self, self._evp_pkey, self._rsa_cdata
)
def verify(
self,
signature: bytes,
data: bytes,
padding: AsymmetricPadding,
algorithm: typing.Union[asym_utils.Prehashed, hashes.HashAlgorithm],
) -> None:
data, algorithm = _calculate_digest_and_algorithm(data, algorithm)
_rsa_sig_verify(
self._backend, padding, algorithm, self, signature, data
)
def recover_data_from_signature(
self,
signature: bytes,
padding: AsymmetricPadding,
algorithm: typing.Optional[hashes.HashAlgorithm],
) -> bytes:
if isinstance(algorithm, asym_utils.Prehashed):
raise TypeError(
"Prehashed is only supported in the sign and verify methods. "
"It cannot be used with recover_data_from_signature."
)
return _rsa_sig_recover(
self._backend, padding, algorithm, self, signature
)

View File

@@ -0,0 +1,52 @@
# 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.utils import Prehashed
if typing.TYPE_CHECKING:
from cryptography.hazmat.backends.openssl.backend import Backend
def _evp_pkey_derive(backend: "Backend", evp_pkey, peer_public_key) -> bytes:
ctx = backend._lib.EVP_PKEY_CTX_new(evp_pkey, backend._ffi.NULL)
backend.openssl_assert(ctx != backend._ffi.NULL)
ctx = backend._ffi.gc(ctx, backend._lib.EVP_PKEY_CTX_free)
res = backend._lib.EVP_PKEY_derive_init(ctx)
backend.openssl_assert(res == 1)
res = backend._lib.EVP_PKEY_derive_set_peer(ctx, peer_public_key._evp_pkey)
backend.openssl_assert(res == 1)
keylen = backend._ffi.new("size_t *")
res = backend._lib.EVP_PKEY_derive(ctx, backend._ffi.NULL, keylen)
backend.openssl_assert(res == 1)
backend.openssl_assert(keylen[0] > 0)
buf = backend._ffi.new("unsigned char[]", keylen[0])
res = backend._lib.EVP_PKEY_derive(ctx, buf, keylen)
if res != 1:
errors_with_text = backend._consume_errors_with_text()
raise ValueError("Error computing shared key.", errors_with_text)
return backend._ffi.buffer(buf, keylen[0])[:]
def _calculate_digest_and_algorithm(
data: bytes,
algorithm: typing.Union[Prehashed, hashes.HashAlgorithm],
) -> typing.Tuple[bytes, hashes.HashAlgorithm]:
if not isinstance(algorithm, Prehashed):
hash_ctx = hashes.Hash(algorithm)
hash_ctx.update(data)
data = hash_ctx.finalize()
else:
algorithm = algorithm._algorithm
if len(data) != algorithm.digest_size:
raise ValueError(
"The provided data must be the same length as the hash "
"algorithm's digest size."
)
return (data, algorithm)

View File

@@ -0,0 +1,132 @@
# 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.openssl.utils import _evp_pkey_derive
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.x25519 import (
X25519PrivateKey,
X25519PublicKey,
)
if typing.TYPE_CHECKING:
from cryptography.hazmat.backends.openssl.backend import Backend
_X25519_KEY_SIZE = 32
class _X25519PublicKey(X25519PublicKey):
def __init__(self, backend: "Backend", evp_pkey):
self._backend = backend
self._evp_pkey = evp_pkey
def public_bytes(
self,
encoding: serialization.Encoding,
format: serialization.PublicFormat,
) -> bytes:
if (
encoding is serialization.Encoding.Raw
or format is serialization.PublicFormat.Raw
):
if (
encoding is not serialization.Encoding.Raw
or format is not serialization.PublicFormat.Raw
):
raise ValueError(
"When using Raw both encoding and format must be Raw"
)
return self._raw_public_bytes()
return self._backend._public_key_bytes(
encoding, format, self, self._evp_pkey, None
)
def _raw_public_bytes(self) -> bytes:
ucharpp = self._backend._ffi.new("unsigned char **")
res = self._backend._lib.EVP_PKEY_get1_tls_encodedpoint(
self._evp_pkey, ucharpp
)
self._backend.openssl_assert(res == 32)
self._backend.openssl_assert(ucharpp[0] != self._backend._ffi.NULL)
data = self._backend._ffi.gc(
ucharpp[0], self._backend._lib.OPENSSL_free
)
return self._backend._ffi.buffer(data, res)[:]
class _X25519PrivateKey(X25519PrivateKey):
def __init__(self, backend: "Backend", evp_pkey):
self._backend = backend
self._evp_pkey = evp_pkey
def public_key(self) -> X25519PublicKey:
bio = self._backend._create_mem_bio_gc()
res = self._backend._lib.i2d_PUBKEY_bio(bio, self._evp_pkey)
self._backend.openssl_assert(res == 1)
evp_pkey = self._backend._lib.d2i_PUBKEY_bio(
bio, self._backend._ffi.NULL
)
self._backend.openssl_assert(evp_pkey != self._backend._ffi.NULL)
evp_pkey = self._backend._ffi.gc(
evp_pkey, self._backend._lib.EVP_PKEY_free
)
return _X25519PublicKey(self._backend, evp_pkey)
def exchange(self, peer_public_key: X25519PublicKey) -> bytes:
if not isinstance(peer_public_key, X25519PublicKey):
raise TypeError("peer_public_key must be X25519PublicKey.")
return _evp_pkey_derive(self._backend, self._evp_pkey, peer_public_key)
def private_bytes(
self,
encoding: serialization.Encoding,
format: serialization.PrivateFormat,
encryption_algorithm: serialization.KeySerializationEncryption,
) -> bytes:
if (
encoding is serialization.Encoding.Raw
or format is serialization.PublicFormat.Raw
):
if (
format is not serialization.PrivateFormat.Raw
or encoding is not serialization.Encoding.Raw
or not isinstance(
encryption_algorithm, serialization.NoEncryption
)
):
raise ValueError(
"When using Raw both encoding and format must be Raw "
"and encryption_algorithm must be NoEncryption()"
)
return self._raw_private_bytes()
return self._backend._private_key_bytes(
encoding, format, encryption_algorithm, self, self._evp_pkey, None
)
def _raw_private_bytes(self) -> bytes:
# When we drop support for CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 we can
# switch this to EVP_PKEY_new_raw_private_key
# The trick we use here is serializing to a PKCS8 key and just
# using the last 32 bytes, which is the key itself.
bio = self._backend._create_mem_bio_gc()
res = self._backend._lib.i2d_PKCS8PrivateKey_bio(
bio,
self._evp_pkey,
self._backend._ffi.NULL,
self._backend._ffi.NULL,
0,
self._backend._ffi.NULL,
self._backend._ffi.NULL,
)
self._backend.openssl_assert(res == 1)
pkcs8 = self._backend._read_mem_bio(bio)
self._backend.openssl_assert(len(pkcs8) == 48)
return pkcs8[-_X25519_KEY_SIZE:]

View File

@@ -0,0 +1,117 @@
# 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.openssl.utils import _evp_pkey_derive
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.x448 import (
X448PrivateKey,
X448PublicKey,
)
if typing.TYPE_CHECKING:
from cryptography.hazmat.backends.openssl.backend import Backend
_X448_KEY_SIZE = 56
class _X448PublicKey(X448PublicKey):
def __init__(self, backend: "Backend", evp_pkey):
self._backend = backend
self._evp_pkey = evp_pkey
def public_bytes(
self,
encoding: serialization.Encoding,
format: serialization.PublicFormat,
) -> bytes:
if (
encoding is serialization.Encoding.Raw
or format is serialization.PublicFormat.Raw
):
if (
encoding is not serialization.Encoding.Raw
or format is not serialization.PublicFormat.Raw
):
raise ValueError(
"When using Raw both encoding and format must be Raw"
)
return self._raw_public_bytes()
return self._backend._public_key_bytes(
encoding, format, self, self._evp_pkey, None
)
def _raw_public_bytes(self) -> bytes:
buf = self._backend._ffi.new("unsigned char []", _X448_KEY_SIZE)
buflen = self._backend._ffi.new("size_t *", _X448_KEY_SIZE)
res = self._backend._lib.EVP_PKEY_get_raw_public_key(
self._evp_pkey, buf, buflen
)
self._backend.openssl_assert(res == 1)
self._backend.openssl_assert(buflen[0] == _X448_KEY_SIZE)
return self._backend._ffi.buffer(buf, _X448_KEY_SIZE)[:]
class _X448PrivateKey(X448PrivateKey):
def __init__(self, backend: "Backend", evp_pkey):
self._backend = backend
self._evp_pkey = evp_pkey
def public_key(self) -> X448PublicKey:
buf = self._backend._ffi.new("unsigned char []", _X448_KEY_SIZE)
buflen = self._backend._ffi.new("size_t *", _X448_KEY_SIZE)
res = self._backend._lib.EVP_PKEY_get_raw_public_key(
self._evp_pkey, buf, buflen
)
self._backend.openssl_assert(res == 1)
self._backend.openssl_assert(buflen[0] == _X448_KEY_SIZE)
public_bytes = self._backend._ffi.buffer(buf)[:]
return self._backend.x448_load_public_bytes(public_bytes)
def exchange(self, peer_public_key: X448PublicKey) -> bytes:
if not isinstance(peer_public_key, X448PublicKey):
raise TypeError("peer_public_key must be X448PublicKey.")
return _evp_pkey_derive(self._backend, self._evp_pkey, peer_public_key)
def private_bytes(
self,
encoding: serialization.Encoding,
format: serialization.PrivateFormat,
encryption_algorithm: serialization.KeySerializationEncryption,
) -> bytes:
if (
encoding is serialization.Encoding.Raw
or format is serialization.PublicFormat.Raw
):
if (
format is not serialization.PrivateFormat.Raw
or encoding is not serialization.Encoding.Raw
or not isinstance(
encryption_algorithm, serialization.NoEncryption
)
):
raise ValueError(
"When using Raw both encoding and format must be Raw "
"and encryption_algorithm must be NoEncryption()"
)
return self._raw_private_bytes()
return self._backend._private_key_bytes(
encoding, format, encryption_algorithm, self, self._evp_pkey, None
)
def _raw_private_bytes(self) -> bytes:
buf = self._backend._ffi.new("unsigned char []", _X448_KEY_SIZE)
buflen = self._backend._ffi.new("size_t *", _X448_KEY_SIZE)
res = self._backend._lib.EVP_PKEY_get_raw_private_key(
self._evp_pkey, buf, buflen
)
self._backend.openssl_assert(res == 1)
self._backend.openssl_assert(buflen[0] == _X448_KEY_SIZE)
return self._backend._ffi.buffer(buf, _X448_KEY_SIZE)[:]

View File

@@ -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.
import warnings
from cryptography import utils, x509
# This exists for pyOpenSSL compatibility and SHOULD NOT BE USED
# WE WILL REMOVE THIS VERY SOON.
def _Certificate(backend, x509) -> x509.Certificate: # noqa: N802
warnings.warn(
"This version of cryptography contains a temporary pyOpenSSL "
"fallback path. Upgrade pyOpenSSL now.",
utils.DeprecatedIn35,
)
return backend._ossl2cert(x509)
# This exists for pyOpenSSL compatibility and SHOULD NOT BE USED
# WE WILL REMOVE THIS VERY SOON.
def _CertificateSigningRequest( # noqa: N802
backend, x509_req
) -> x509.CertificateSigningRequest:
warnings.warn(
"This version of cryptography contains a temporary pyOpenSSL "
"fallback path. Upgrade pyOpenSSL now.",
utils.DeprecatedIn35,
)
return backend._ossl2csr(x509_req)
# This exists for pyOpenSSL compatibility and SHOULD NOT BE USED
# WE WILL REMOVE THIS VERY SOON.
def _CertificateRevocationList( # noqa: N802
backend, x509_crl
) -> x509.CertificateRevocationList:
warnings.warn(
"This version of cryptography contains a temporary pyOpenSSL "
"fallback path. Upgrade pyOpenSSL now.",
utils.DeprecatedIn35,
)
return backend._ossl2crl(x509_crl)

View File

@@ -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.

View File

@@ -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.

View File

@@ -0,0 +1,367 @@
# 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
def cryptography_has_ec2m() -> typing.List[str]:
return [
"EC_POINT_get_affine_coordinates_GF2m",
]
def cryptography_has_ssl3_method() -> typing.List[str]:
return [
"SSLv3_method",
"SSLv3_client_method",
"SSLv3_server_method",
]
def cryptography_has_110_verification_params() -> typing.List[str]:
return ["X509_CHECK_FLAG_NEVER_CHECK_SUBJECT"]
def cryptography_has_set_cert_cb() -> typing.List[str]:
return [
"SSL_CTX_set_cert_cb",
"SSL_set_cert_cb",
]
def cryptography_has_ssl_st() -> typing.List[str]:
return [
"SSL_ST_BEFORE",
"SSL_ST_OK",
"SSL_ST_INIT",
"SSL_ST_RENEGOTIATE",
]
def cryptography_has_tls_st() -> typing.List[str]:
return [
"TLS_ST_BEFORE",
"TLS_ST_OK",
]
def cryptography_has_scrypt() -> typing.List[str]:
return [
"EVP_PBE_scrypt",
]
def cryptography_has_evp_pkey_dhx() -> typing.List[str]:
return [
"EVP_PKEY_DHX",
]
def cryptography_has_mem_functions() -> typing.List[str]:
return [
"Cryptography_CRYPTO_set_mem_functions",
]
def cryptography_has_x509_store_ctx_get_issuer() -> typing.List[str]:
return [
"X509_STORE_get_get_issuer",
"X509_STORE_set_get_issuer",
]
def cryptography_has_ed448() -> typing.List[str]:
return [
"EVP_PKEY_ED448",
"NID_ED448",
]
def cryptography_has_ed25519() -> typing.List[str]:
return [
"NID_ED25519",
"EVP_PKEY_ED25519",
]
def cryptography_has_poly1305() -> typing.List[str]:
return [
"NID_poly1305",
"EVP_PKEY_POLY1305",
]
def cryptography_has_oneshot_evp_digest_sign_verify() -> typing.List[str]:
return [
"EVP_DigestSign",
"EVP_DigestVerify",
]
def cryptography_has_evp_digestfinal_xof() -> typing.List[str]:
return [
"EVP_DigestFinalXOF",
]
def cryptography_has_evp_pkey_get_set_tls_encodedpoint() -> typing.List[str]:
return [
"EVP_PKEY_get1_tls_encodedpoint",
"EVP_PKEY_set1_tls_encodedpoint",
]
def cryptography_has_fips() -> typing.List[str]:
return [
"FIPS_mode_set",
"FIPS_mode",
]
def cryptography_has_psk() -> typing.List[str]:
return [
"SSL_CTX_use_psk_identity_hint",
"SSL_CTX_set_psk_server_callback",
"SSL_CTX_set_psk_client_callback",
]
def cryptography_has_psk_tlsv13() -> typing.List[str]:
return [
"SSL_CTX_set_psk_find_session_callback",
"SSL_CTX_set_psk_use_session_callback",
"Cryptography_SSL_SESSION_new",
"SSL_CIPHER_find",
"SSL_SESSION_set1_master_key",
"SSL_SESSION_set_cipher",
"SSL_SESSION_set_protocol_version",
]
def cryptography_has_custom_ext() -> typing.List[str]:
return [
"SSL_CTX_add_client_custom_ext",
"SSL_CTX_add_server_custom_ext",
"SSL_extension_supported",
]
def cryptography_has_openssl_cleanup() -> typing.List[str]:
return [
"OPENSSL_cleanup",
]
def cryptography_has_tlsv13() -> typing.List[str]:
return [
"TLS1_3_VERSION",
"SSL_OP_NO_TLSv1_3",
]
def cryptography_has_tlsv13_functions() -> typing.List[str]:
return [
"SSL_VERIFY_POST_HANDSHAKE",
"SSL_CTX_set_ciphersuites",
"SSL_verify_client_post_handshake",
"SSL_CTX_set_post_handshake_auth",
"SSL_set_post_handshake_auth",
"SSL_SESSION_get_max_early_data",
"SSL_write_early_data",
"SSL_read_early_data",
"SSL_CTX_set_max_early_data",
]
def cryptography_has_keylog() -> typing.List[str]:
return [
"SSL_CTX_set_keylog_callback",
"SSL_CTX_get_keylog_callback",
]
def cryptography_has_raw_key() -> typing.List[str]:
return [
"EVP_PKEY_new_raw_private_key",
"EVP_PKEY_new_raw_public_key",
"EVP_PKEY_get_raw_private_key",
"EVP_PKEY_get_raw_public_key",
]
def cryptography_has_engine() -> typing.List[str]:
return [
"ENGINE_by_id",
"ENGINE_init",
"ENGINE_finish",
"ENGINE_get_default_RAND",
"ENGINE_set_default_RAND",
"ENGINE_unregister_RAND",
"ENGINE_ctrl_cmd",
"ENGINE_free",
"ENGINE_get_name",
"Cryptography_add_osrandom_engine",
"ENGINE_ctrl_cmd_string",
"ENGINE_load_builtin_engines",
"ENGINE_load_private_key",
"ENGINE_load_public_key",
"SSL_CTX_set_client_cert_engine",
]
def cryptography_has_verified_chain() -> typing.List[str]:
return [
"SSL_get0_verified_chain",
]
def cryptography_has_srtp() -> typing.List[str]:
return [
"SSL_CTX_set_tlsext_use_srtp",
"SSL_set_tlsext_use_srtp",
"SSL_get_selected_srtp_profile",
]
def cryptography_has_get_proto_version() -> typing.List[str]:
return [
"SSL_CTX_get_min_proto_version",
"SSL_CTX_get_max_proto_version",
"SSL_get_min_proto_version",
"SSL_get_max_proto_version",
]
def cryptography_has_providers() -> typing.List[str]:
return [
"OSSL_PROVIDER_load",
"OSSL_PROVIDER_unload",
"ERR_LIB_PROV",
"PROV_R_WRONG_FINAL_BLOCK_LENGTH",
"PROV_R_BAD_DECRYPT",
]
def cryptography_has_op_no_renegotiation() -> typing.List[str]:
return [
"SSL_OP_NO_RENEGOTIATION",
]
def cryptography_has_dtls_get_data_mtu() -> typing.List[str]:
return [
"DTLS_get_data_mtu",
]
def cryptography_has_300_fips() -> typing.List[str]:
return [
"EVP_default_properties_is_fips_enabled",
"EVP_default_properties_enable_fips",
]
def cryptography_has_ssl_cookie() -> typing.List[str]:
return [
"SSL_OP_COOKIE_EXCHANGE",
"DTLSv1_listen",
"SSL_CTX_set_cookie_generate_cb",
"SSL_CTX_set_cookie_verify_cb",
]
def cryptography_has_pkcs7_funcs() -> typing.List[str]:
return [
"SMIME_write_PKCS7",
"PEM_write_bio_PKCS7_stream",
"PKCS7_sign_add_signer",
"PKCS7_final",
"PKCS7_verify",
"SMIME_read_PKCS7",
"PKCS7_get0_signers",
]
def cryptography_has_bn_flags() -> typing.List[str]:
return [
"BN_FLG_CONSTTIME",
"BN_set_flags",
"BN_prime_checks_for_size",
]
def cryptography_has_evp_pkey_dh() -> typing.List[str]:
return [
"EVP_PKEY_set1_DH",
]
def cryptography_has_300_evp_cipher() -> typing.List[str]:
return ["EVP_CIPHER_fetch", "EVP_CIPHER_free"]
def cryptography_has_unexpected_eof_while_reading() -> typing.List[str]:
return ["SSL_R_UNEXPECTED_EOF_WHILE_READING"]
# This is a mapping of
# {condition: function-returning-names-dependent-on-that-condition} so we can
# loop over them and delete unsupported names at runtime. It will be removed
# when cffi supports #if in cdef. We use functions instead of just a dict of
# lists so we can use coverage to measure which are used.
CONDITIONAL_NAMES = {
"Cryptography_HAS_EC2M": cryptography_has_ec2m,
"Cryptography_HAS_SSL3_METHOD": cryptography_has_ssl3_method,
"Cryptography_HAS_110_VERIFICATION_PARAMS": (
cryptography_has_110_verification_params
),
"Cryptography_HAS_SET_CERT_CB": cryptography_has_set_cert_cb,
"Cryptography_HAS_SSL_ST": cryptography_has_ssl_st,
"Cryptography_HAS_TLS_ST": cryptography_has_tls_st,
"Cryptography_HAS_SCRYPT": cryptography_has_scrypt,
"Cryptography_HAS_EVP_PKEY_DHX": cryptography_has_evp_pkey_dhx,
"Cryptography_HAS_MEM_FUNCTIONS": cryptography_has_mem_functions,
"Cryptography_HAS_X509_STORE_CTX_GET_ISSUER": (
cryptography_has_x509_store_ctx_get_issuer
),
"Cryptography_HAS_ED448": cryptography_has_ed448,
"Cryptography_HAS_ED25519": cryptography_has_ed25519,
"Cryptography_HAS_POLY1305": cryptography_has_poly1305,
"Cryptography_HAS_ONESHOT_EVP_DIGEST_SIGN_VERIFY": (
cryptography_has_oneshot_evp_digest_sign_verify
),
"Cryptography_HAS_EVP_PKEY_get_set_tls_encodedpoint": (
cryptography_has_evp_pkey_get_set_tls_encodedpoint
),
"Cryptography_HAS_FIPS": cryptography_has_fips,
"Cryptography_HAS_PSK": cryptography_has_psk,
"Cryptography_HAS_PSK_TLSv1_3": cryptography_has_psk_tlsv13,
"Cryptography_HAS_CUSTOM_EXT": cryptography_has_custom_ext,
"Cryptography_HAS_OPENSSL_CLEANUP": cryptography_has_openssl_cleanup,
"Cryptography_HAS_TLSv1_3": cryptography_has_tlsv13,
"Cryptography_HAS_TLSv1_3_FUNCTIONS": cryptography_has_tlsv13_functions,
"Cryptography_HAS_KEYLOG": cryptography_has_keylog,
"Cryptography_HAS_RAW_KEY": cryptography_has_raw_key,
"Cryptography_HAS_EVP_DIGESTFINAL_XOF": (
cryptography_has_evp_digestfinal_xof
),
"Cryptography_HAS_ENGINE": cryptography_has_engine,
"Cryptography_HAS_VERIFIED_CHAIN": cryptography_has_verified_chain,
"Cryptography_HAS_SRTP": cryptography_has_srtp,
"Cryptography_HAS_GET_PROTO_VERSION": cryptography_has_get_proto_version,
"Cryptography_HAS_PROVIDERS": cryptography_has_providers,
"Cryptography_HAS_OP_NO_RENEGOTIATION": (
cryptography_has_op_no_renegotiation
),
"Cryptography_HAS_DTLS_GET_DATA_MTU": cryptography_has_dtls_get_data_mtu,
"Cryptography_HAS_300_FIPS": cryptography_has_300_fips,
"Cryptography_HAS_SSL_COOKIE": cryptography_has_ssl_cookie,
"Cryptography_HAS_PKCS7_FUNCS": cryptography_has_pkcs7_funcs,
"Cryptography_HAS_BN_FLAGS": cryptography_has_bn_flags,
"Cryptography_HAS_EVP_PKEY_DH": cryptography_has_evp_pkey_dh,
"Cryptography_HAS_300_EVP_CIPHER": cryptography_has_300_evp_cipher,
"Cryptography_HAS_UNEXPECTED_EOF_WHILE_READING": (
cryptography_has_unexpected_eof_while_reading
),
}

View File

@@ -0,0 +1,230 @@
# 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 threading
import types
import typing
import warnings
import cryptography
from cryptography import utils
from cryptography.exceptions import InternalError
from cryptography.hazmat.bindings._openssl import ffi, lib
from cryptography.hazmat.bindings.openssl._conditional import CONDITIONAL_NAMES
_OpenSSLErrorWithText = typing.NamedTuple(
"_OpenSSLErrorWithText",
[("code", int), ("lib", int), ("reason", int), ("reason_text", bytes)],
)
class _OpenSSLError:
def __init__(self, code: int, lib: int, reason: int):
self._code = code
self._lib = lib
self._reason = reason
def _lib_reason_match(self, lib: int, reason: int) -> bool:
return lib == self.lib and reason == self.reason
@property
def code(self) -> int:
return self._code
@property
def lib(self) -> int:
return self._lib
@property
def reason(self) -> int:
return self._reason
def _consume_errors(lib) -> typing.List[_OpenSSLError]:
errors = []
while True:
code: int = lib.ERR_get_error()
if code == 0:
break
err_lib: int = lib.ERR_GET_LIB(code)
err_reason: int = lib.ERR_GET_REASON(code)
errors.append(_OpenSSLError(code, err_lib, err_reason))
return errors
def _errors_with_text(
errors: typing.List[_OpenSSLError],
) -> typing.List[_OpenSSLErrorWithText]:
errors_with_text = []
for err in errors:
buf = ffi.new("char[]", 256)
lib.ERR_error_string_n(err.code, buf, len(buf))
err_text_reason: bytes = ffi.string(buf)
errors_with_text.append(
_OpenSSLErrorWithText(
err.code, err.lib, err.reason, err_text_reason
)
)
return errors_with_text
def _consume_errors_with_text(lib):
return _errors_with_text(_consume_errors(lib))
def _openssl_assert(
lib, ok: bool, errors: typing.Optional[typing.List[_OpenSSLError]] = None
) -> None:
if not ok:
if errors is None:
errors = _consume_errors(lib)
errors_with_text = _errors_with_text(errors)
raise InternalError(
"Unknown OpenSSL error. This error is commonly encountered when "
"another library is not cleaning up the OpenSSL error stack. If "
"you are using cryptography with another library that uses "
"OpenSSL try disabling it before reporting a bug. Otherwise "
"please file an issue at https://github.com/pyca/cryptography/"
"issues with information on how to reproduce "
"this. ({0!r})".format(errors_with_text),
errors_with_text,
)
def build_conditional_library(lib, conditional_names):
conditional_lib = types.ModuleType("lib")
conditional_lib._original_lib = lib # type: ignore[attr-defined]
excluded_names = set()
for condition, names_cb in conditional_names.items():
if not getattr(lib, condition):
excluded_names.update(names_cb())
for attr in dir(lib):
if attr not in excluded_names:
setattr(conditional_lib, attr, getattr(lib, attr))
return conditional_lib
class Binding:
"""
OpenSSL API wrapper.
"""
lib: typing.ClassVar = None
ffi = ffi
_lib_loaded = False
_init_lock = threading.Lock()
_legacy_provider: typing.Any = None
_default_provider: typing.Any = None
def __init__(self):
self._ensure_ffi_initialized()
def _enable_fips(self) -> None:
# This function enables FIPS mode for OpenSSL 3.0.0 on installs that
# have the FIPS provider installed properly.
_openssl_assert(self.lib, self.lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)
self._base_provider = self.lib.OSSL_PROVIDER_load(
self.ffi.NULL, b"base"
)
_openssl_assert(self.lib, self._base_provider != self.ffi.NULL)
self.lib._fips_provider = self.lib.OSSL_PROVIDER_load(
self.ffi.NULL, b"fips"
)
_openssl_assert(self.lib, self.lib._fips_provider != self.ffi.NULL)
res = self.lib.EVP_default_properties_enable_fips(self.ffi.NULL, 1)
_openssl_assert(self.lib, res == 1)
@classmethod
def _register_osrandom_engine(cls):
# Clear any errors extant in the queue before we start. In many
# scenarios other things may be interacting with OpenSSL in the same
# process space and it has proven untenable to assume that they will
# reliably clear the error queue. Once we clear it here we will
# error on any subsequent unexpected item in the stack.
cls.lib.ERR_clear_error()
if cls.lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE:
result = cls.lib.Cryptography_add_osrandom_engine()
_openssl_assert(cls.lib, result in (1, 2))
@classmethod
def _ensure_ffi_initialized(cls):
with cls._init_lock:
if not cls._lib_loaded:
cls.lib = build_conditional_library(lib, CONDITIONAL_NAMES)
cls._lib_loaded = True
cls._register_osrandom_engine()
# As of OpenSSL 3.0.0 we must register a legacy cipher provider
# to get RC2 (needed for junk asymmetric private key
# serialization), RC4, Blowfish, IDEA, SEED, etc. These things
# are ugly legacy, but we aren't going to get rid of them
# any time soon.
if cls.lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER:
cls._legacy_provider = cls.lib.OSSL_PROVIDER_load(
cls.ffi.NULL, b"legacy"
)
_openssl_assert(
cls.lib, cls._legacy_provider != cls.ffi.NULL
)
cls._default_provider = cls.lib.OSSL_PROVIDER_load(
cls.ffi.NULL, b"default"
)
_openssl_assert(
cls.lib, cls._default_provider != cls.ffi.NULL
)
@classmethod
def init_static_locks(cls):
cls._ensure_ffi_initialized()
def _verify_openssl_version(lib):
if (
lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111
and not lib.CRYPTOGRAPHY_IS_LIBRESSL
and not lib.CRYPTOGRAPHY_IS_BORINGSSL
):
warnings.warn(
"OpenSSL version 1.1.0 is no longer supported by the OpenSSL "
"project, please upgrade. The next release of cryptography will "
"be the last to support compiling with OpenSSL 1.1.0.",
utils.DeprecatedIn37,
)
def _verify_package_version(version):
# Occasionally we run into situations where the version of the Python
# package does not match the version of the shared object that is loaded.
# This may occur in environments where multiple versions of cryptography
# are installed and available in the python path. To avoid errors cropping
# up later this code checks that the currently imported package and the
# shared object that were loaded have the same version and raise an
# ImportError if they do not
so_package_version = ffi.string(lib.CRYPTOGRAPHY_PACKAGE_VERSION)
if version.encode("ascii") != so_package_version:
raise ImportError(
"The version of cryptography does not match the loaded "
"shared object. This can happen if you have multiple copies of "
"cryptography installed in your Python path. Please try creating "
"a new virtual environment to resolve this issue. "
"Loaded python version: {}, shared object version: {}".format(
version, so_package_version
)
)
_verify_package_version(cryptography.__version__)
Binding.init_static_locks()
_verify_openssl_version(Binding.lib)

View File

@@ -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.

View File

@@ -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").
"""

View File

@@ -0,0 +1,40 @@
# 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):
key: bytes
@abc.abstractproperty
def block_size(self) -> int:
"""
The size of a block as an integer in bits (e.g. 64, 128).
"""

View File

@@ -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

View File

@@ -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.

View File

@@ -0,0 +1,250 @@
# 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.primitives import _serialization
_MIN_MODULUS_SIZE = 512
def generate_parameters(
generator: int, key_size: int, backend: typing.Any = None
) -> "DHParameters":
from cryptography.hazmat.backends.openssl.backend import backend as ossl
return ossl.generate_dh_parameters(generator, key_size)
class DHParameterNumbers:
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: object) -> bool:
if not isinstance(other, DHParameterNumbers):
return NotImplemented
return (
self._p == other._p and self._g == other._g and self._q == other._q
)
def parameters(self, backend: typing.Any = None) -> "DHParameters":
from cryptography.hazmat.backends.openssl.backend import (
backend as ossl,
)
return ossl.load_dh_parameter_numbers(self)
@property
def p(self) -> int:
return self._p
@property
def g(self) -> int:
return self._g
@property
def q(self) -> typing.Optional[int]:
return self._q
class DHPublicNumbers:
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: object) -> bool:
if not isinstance(other, DHPublicNumbers):
return NotImplemented
return (
self._y == other._y
and self._parameter_numbers == other._parameter_numbers
)
def public_key(self, backend: typing.Any = None) -> "DHPublicKey":
from cryptography.hazmat.backends.openssl.backend import (
backend as ossl,
)
return ossl.load_dh_public_numbers(self)
@property
def y(self) -> int:
return self._y
@property
def parameter_numbers(self) -> DHParameterNumbers:
return self._parameter_numbers
class DHPrivateNumbers:
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: object) -> bool:
if not isinstance(other, DHPrivateNumbers):
return NotImplemented
return (
self._x == other._x
and self._public_numbers == other._public_numbers
)
def private_key(self, backend: typing.Any = None) -> "DHPrivateKey":
from cryptography.hazmat.backends.openssl.backend import (
backend as ossl,
)
return ossl.load_dh_private_numbers(self)
@property
def public_numbers(self) -> DHPublicNumbers:
return self._public_numbers
@property
def x(self) -> int:
return 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

View File

@@ -0,0 +1,288 @@
# 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.primitives import _serialization, hashes
from cryptography.hazmat.primitives.asymmetric import (
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 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 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:
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
@property
def p(self) -> int:
return self._p
@property
def q(self) -> int:
return self._q
@property
def g(self) -> int:
return self._g
def parameters(self, backend: typing.Any = None) -> DSAParameters:
from cryptography.hazmat.backends.openssl.backend import (
backend as ossl,
)
return ossl.load_dsa_parameter_numbers(self)
def __eq__(self, other: object) -> bool:
if not isinstance(other, DSAParameterNumbers):
return NotImplemented
return self.p == other.p and self.q == other.q and self.g == other.g
def __repr__(self) -> str:
return (
"<DSAParameterNumbers(p={self.p}, q={self.q}, "
"g={self.g})>".format(self=self)
)
class DSAPublicNumbers:
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
@property
def y(self) -> int:
return self._y
@property
def parameter_numbers(self) -> DSAParameterNumbers:
return self._parameter_numbers
def public_key(self, backend: typing.Any = None) -> DSAPublicKey:
from cryptography.hazmat.backends.openssl.backend import (
backend as ossl,
)
return ossl.load_dsa_public_numbers(self)
def __eq__(self, other: object) -> bool:
if not isinstance(other, DSAPublicNumbers):
return NotImplemented
return (
self.y == other.y
and self.parameter_numbers == other.parameter_numbers
)
def __repr__(self) -> str:
return (
"<DSAPublicNumbers(y={self.y}, "
"parameter_numbers={self.parameter_numbers})>".format(self=self)
)
class DSAPrivateNumbers:
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
@property
def x(self) -> int:
return self._x
@property
def public_numbers(self) -> DSAPublicNumbers:
return self._public_numbers
def private_key(self, backend: typing.Any = None) -> DSAPrivateKey:
from cryptography.hazmat.backends.openssl.backend import (
backend as ossl,
)
return ossl.load_dsa_private_numbers(self)
def __eq__(self, other: object) -> bool:
if not isinstance(other, DSAPrivateNumbers):
return NotImplemented
return (
self.x == other.x and self.public_numbers == other.public_numbers
)
def generate_parameters(
key_size: int, backend: typing.Any = None
) -> DSAParameters:
from cryptography.hazmat.backends.openssl.backend import backend as ossl
return ossl.generate_dsa_parameters(key_size)
def generate_private_key(
key_size: int, backend: typing.Any = None
) -> DSAPrivateKey:
from cryptography.hazmat.backends.openssl.backend import backend as ossl
return ossl.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).")

View File

@@ -0,0 +1,523 @@
# 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.primitives import _serialization, hashes
from cryptography.hazmat.primitives.asymmetric import (
utils as asym_utils,
)
class EllipticCurveOID:
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 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.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.Any = None
) -> EllipticCurvePrivateKey:
from cryptography.hazmat.backends.openssl.backend import backend as ossl
return ossl.generate_elliptic_curve_private_key(curve)
def derive_private_key(
private_value: int,
curve: EllipticCurve,
backend: typing.Any = None,
) -> EllipticCurvePrivateKey:
from cryptography.hazmat.backends.openssl.backend import backend as ossl
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 ossl.derive_elliptic_curve_private_key(private_value, curve)
class EllipticCurvePublicNumbers:
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.Any = None) -> EllipticCurvePublicKey:
from cryptography.hazmat.backends.openssl.backend import (
backend as ossl,
)
return ossl.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")
@property
def curve(self) -> EllipticCurve:
return self._curve
@property
def x(self) -> int:
return self._x
@property
def y(self) -> int:
return self._y
def __eq__(self, other: object) -> bool:
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 __hash__(self) -> int:
return hash((self.x, self.y, self.curve.name, self.curve.key_size))
def __repr__(self) -> str:
return (
"<EllipticCurvePublicNumbers(curve={0.curve.name}, x={0.x}, "
"y={0.y}>".format(self)
)
class EllipticCurvePrivateNumbers:
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.Any = None
) -> EllipticCurvePrivateKey:
from cryptography.hazmat.backends.openssl.backend import (
backend as ossl,
)
return ossl.load_elliptic_curve_private_numbers(self)
@property
def private_value(self) -> int:
return self._private_value
@property
def public_numbers(self) -> EllipticCurvePublicNumbers:
return self._public_numbers
def __eq__(self, other: object) -> bool:
if not isinstance(other, EllipticCurvePrivateNumbers):
return NotImplemented
return (
self.private_value == other.private_value
and self.public_numbers == other.public_numbers
)
def __hash__(self) -> int:
return hash((self.private_value, self.public_numbers))
class ECDH:
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"
)

View File

@@ -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.
"""

View File

@@ -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.
"""

View File

@@ -0,0 +1,101 @@
# 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.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 _MaxLength:
"Sentinel value for `MAX_LENGTH`."
class _Auto:
"Sentinel value for `AUTO`."
class _DigestLength:
"Sentinel value for `DIGEST_LENGTH`."
class PSS(AsymmetricPadding):
MAX_LENGTH = _MaxLength()
AUTO = _Auto()
DIGEST_LENGTH = _DigestLength()
name = "EMSA-PSS"
_salt_length: typing.Union[int, _MaxLength, _Auto, _DigestLength]
def __init__(
self,
mgf: "MGF",
salt_length: typing.Union[int, _MaxLength, _Auto, _DigestLength],
) -> None:
self._mgf = mgf
if not isinstance(
salt_length, (int, _MaxLength, _Auto, _DigestLength)
):
raise TypeError(
"salt_length must be an integer, MAX_LENGTH, "
"DIGEST_LENGTH, or AUTO"
)
if isinstance(salt_length, int) 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: "MGF",
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 MGF(metaclass=abc.ABCMeta):
_algorithm: hashes.HashAlgorithm
class MGF1(MGF):
MAX_LENGTH = _MaxLength()
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

View File

@@ -0,0 +1,425 @@
# 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.hazmat.primitives import _serialization, hashes
from cryptography.hazmat.primitives._asymmetric import AsymmetricPadding
from cryptography.hazmat.primitives.asymmetric import (
utils as asym_utils,
)
class RSAPrivateKey(metaclass=abc.ABCMeta):
@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 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.Any = None,
) -> RSAPrivateKey:
from cryptography.hazmat.backends.openssl.backend import backend as ossl
_verify_rsa_parameters(public_exponent, key_size)
return ossl.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:
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
@property
def p(self) -> int:
return self._p
@property
def q(self) -> int:
return self._q
@property
def d(self) -> int:
return self._d
@property
def dmp1(self) -> int:
return self._dmp1
@property
def dmq1(self) -> int:
return self._dmq1
@property
def iqmp(self) -> int:
return self._iqmp
@property
def public_numbers(self) -> "RSAPublicNumbers":
return self._public_numbers
def private_key(self, backend: typing.Any = None) -> RSAPrivateKey:
from cryptography.hazmat.backends.openssl.backend import (
backend as ossl,
)
return ossl.load_rsa_private_numbers(self)
def __eq__(self, other: object) -> bool:
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 __hash__(self) -> int:
return hash(
(
self.p,
self.q,
self.d,
self.dmp1,
self.dmq1,
self.iqmp,
self.public_numbers,
)
)
class RSAPublicNumbers:
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
@property
def e(self) -> int:
return self._e
@property
def n(self) -> int:
return self._n
def public_key(self, backend: typing.Any = None) -> RSAPublicKey:
from cryptography.hazmat.backends.openssl.backend import (
backend as ossl,
)
return ossl.load_rsa_public_numbers(self)
def __repr__(self) -> str:
return "<RSAPublicNumbers(e={0.e}, n={0.n})>".format(self)
def __eq__(self, other: object) -> bool:
if not isinstance(other, RSAPublicNumbers):
return NotImplemented
return self.e == other.e and self.n == other.n
def __hash__(self) -> int:
return hash((self.e, self.n))

View File

@@ -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.hazmat.primitives.asymmetric import (
dh,
dsa,
ec,
ed25519,
ed448,
rsa,
x25519,
x448,
)
# Every asymmetric key type
PUBLIC_KEY_TYPES = typing.Union[
dh.DHPublicKey,
dsa.DSAPublicKey,
rsa.RSAPublicKey,
ec.EllipticCurvePublicKey,
ed25519.Ed25519PublicKey,
ed448.Ed448PublicKey,
x25519.X25519PublicKey,
x448.X448PublicKey,
]
# Every asymmetric key type
PRIVATE_KEY_TYPES = typing.Union[
dh.DHPrivateKey,
ed25519.Ed25519PrivateKey,
ed448.Ed448PrivateKey,
rsa.RSAPrivateKey,
dsa.DSAPrivateKey,
ec.EllipticCurvePrivateKey,
x25519.X25519PrivateKey,
x448.X448PrivateKey,
]
# Just the key types we allow to be used for x509 signing. This mirrors
# the certificate public key types
CERTIFICATE_PRIVATE_KEY_TYPES = typing.Union[
ed25519.Ed25519PrivateKey,
ed448.Ed448PrivateKey,
rsa.RSAPrivateKey,
dsa.DSAPrivateKey,
ec.EllipticCurvePrivateKey,
]
# Just the key types we allow to be used for x509 signing. This mirrors
# the certificate private key types
CERTIFICATE_ISSUER_PUBLIC_KEY_TYPES = typing.Union[
dsa.DSAPublicKey,
rsa.RSAPublicKey,
ec.EllipticCurvePublicKey,
ed25519.Ed25519PublicKey,
ed448.Ed448PublicKey,
]
# This type removes DHPublicKey. x448/x25519 can be a public key
# but cannot be used in signing so they are allowed here.
CERTIFICATE_PUBLIC_KEY_TYPES = typing.Union[
dsa.DSAPublicKey,
rsa.RSAPublicKey,
ec.EllipticCurvePublicKey,
ed25519.Ed25519PublicKey,
ed448.Ed448PublicKey,
x25519.X25519PublicKey,
x448.X448PublicKey,
]

View File

@@ -0,0 +1,24 @@
# 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:
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
@property
def digest_size(self) -> int:
return self._digest_size

View File

@@ -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.
"""

View File

@@ -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.
"""

View File

@@ -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",
]

View File

@@ -0,0 +1,361 @@
# 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:
_MAX_SIZE = 2**31 - 1
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**31 - 1 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:
_MAX_SIZE = 2**31 - 1
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
if not backend.aead_cipher_supported(self):
raise exceptions.UnsupportedAlgorithm(
"AESCCM is not supported by this version of OpenSSL",
exceptions._Reasons.UNSUPPORTED_CIPHER,
)
@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**31 - 1 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:
_MAX_SIZE = 2**31 - 1
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**31 - 1 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")
class AESOCB3:
_MAX_SIZE = 2**31 - 1
def __init__(self, key: bytes):
utils._check_byteslike("key", key)
if len(key) not in (16, 24, 32):
raise ValueError("AESOCB3 key must be 128, 192, or 256 bits.")
self._key = key
if not backend.aead_cipher_supported(self):
raise exceptions.UnsupportedAlgorithm(
"OCB3 is not supported by this version of OpenSSL",
exceptions._Reasons.UNSUPPORTED_CIPHER,
)
@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**31 - 1 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 or len(nonce) > 15:
raise ValueError("Nonce must be between 12 and 15 bytes")
class AESSIV(object):
_MAX_SIZE = 2**31 - 1
def __init__(self, key: bytes):
utils._check_byteslike("key", key)
if len(key) not in (32, 48, 64):
raise ValueError("AESSIV key must be 256, 384, or 512 bits.")
self._key = key
if not backend.aead_cipher_supported(self):
raise exceptions.UnsupportedAlgorithm(
"AES-SIV is not supported by this version of OpenSSL",
exceptions._Reasons.UNSUPPORTED_CIPHER,
)
@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 (256, 384, 512):
raise ValueError("bit_length must be 256, 384, or 512")
return os.urandom(bit_length // 8)
def encrypt(
self,
data: bytes,
associated_data: typing.Optional[typing.List[bytes]],
) -> bytes:
if associated_data is None:
associated_data = []
self._check_params(data, associated_data)
if len(data) > self._MAX_SIZE or any(
len(ad) > self._MAX_SIZE for ad in associated_data
):
# This is OverflowError to match what cffi would raise
raise OverflowError(
"Data or associated data too long. Max 2**31 - 1 bytes"
)
return aead._encrypt(backend, self, b"", data, associated_data, 16)
def decrypt(
self,
data: bytes,
associated_data: typing.Optional[typing.List[bytes]],
) -> bytes:
if associated_data is None:
associated_data = []
self._check_params(data, associated_data)
return aead._decrypt(backend, self, b"", data, associated_data, 16)
def _check_params(
self,
data: bytes,
associated_data: typing.List,
) -> None:
utils._check_bytes("data", data)
if not isinstance(associated_data, list) or not all(
isinstance(x, bytes) for x in associated_data
):
raise TypeError("associated_data must be a list of bytes or None")

View File

@@ -0,0 +1,207 @@
# 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,
)
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
_BlowfishInternal = Blowfish
utils.deprecated(
Blowfish,
__name__,
"Blowfish has been deprecated",
utils.DeprecatedIn37,
name="Blowfish",
)
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
_CAST5Internal = CAST5
utils.deprecated(
CAST5,
__name__,
"CAST5 has been deprecated",
utils.DeprecatedIn37,
name="CAST5",
)
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, BlockCipherAlgorithm):
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
_IDEAInternal = IDEA
utils.deprecated(
IDEA,
__name__,
"IDEA has been deprecated",
utils.DeprecatedIn37,
name="IDEA",
)
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
_SEEDInternal = SEED
utils.deprecated(
SEED,
__name__,
"SEED has been deprecated",
utils.DeprecatedIn37,
name="SEED",
)
class ChaCha20(CipherAlgorithm):
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

View File

@@ -0,0 +1,269 @@
# 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.exceptions import (
AlreadyFinalized,
AlreadyUpdated,
NotYetFinalized,
)
from cryptography.hazmat.primitives._cipheralgorithm import CipherAlgorithm
from cryptography.hazmat.primitives.ciphers import modes
if typing.TYPE_CHECKING:
from cryptography.hazmat.backends.openssl.ciphers import (
_CipherContext as _BackendCipherContext,
)
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: bytes) -> 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(CipherContext, metaclass=abc.ABCMeta):
@abc.abstractmethod
def authenticate_additional_data(self, data: bytes) -> None:
"""
Authenticates the provided bytes.
"""
class AEADDecryptionContext(AEADCipherContext, 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(AEADCipherContext, metaclass=abc.ABCMeta):
@abc.abstractproperty
def tag(self) -> bytes:
"""
Returns tag bytes. This is only available after encryption is
finalized.
"""
Mode = typing.TypeVar(
"Mode", bound=typing.Optional[modes.Mode], covariant=True
)
class Cipher(typing.Generic[Mode]):
def __init__(
self,
algorithm: CipherAlgorithm,
mode: Mode,
backend: typing.Any = None,
):
if not isinstance(algorithm, CipherAlgorithm):
raise TypeError("Expected interface of CipherAlgorithm.")
if mode is not None:
# mypy needs this assert to narrow the type from our generic
# type. Maybe it won't some time in the future.
assert isinstance(mode, modes.Mode)
mode.validate_for_algorithm(algorithm)
self.algorithm = algorithm
self.mode = mode
@typing.overload
def encryptor(
self: "Cipher[modes.ModeWithAuthenticationTag]",
) -> AEADEncryptionContext:
...
@typing.overload
def encryptor(
self: "_CIPHER_TYPE",
) -> CipherContext:
...
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."
)
from cryptography.hazmat.backends.openssl.backend import backend
ctx = backend.create_symmetric_encryption_ctx(
self.algorithm, self.mode
)
return self._wrap_ctx(ctx, encrypt=True)
@typing.overload
def decryptor(
self: "Cipher[modes.ModeWithAuthenticationTag]",
) -> AEADDecryptionContext:
...
@typing.overload
def decryptor(
self: "_CIPHER_TYPE",
) -> CipherContext:
...
def decryptor(self):
from cryptography.hazmat.backends.openssl.backend import backend
ctx = backend.create_symmetric_decryption_ctx(
self.algorithm, self.mode
)
return self._wrap_ctx(ctx, encrypt=False)
def _wrap_ctx(
self, ctx: "_BackendCipherContext", encrypt: bool
) -> typing.Union[
AEADEncryptionContext, AEADDecryptionContext, CipherContext
]:
if isinstance(self.mode, modes.ModeWithAuthenticationTag):
if encrypt:
return _AEADEncryptionContext(ctx)
else:
return _AEADDecryptionContext(ctx)
else:
return _CipherContext(ctx)
_CIPHER_TYPE = Cipher[
typing.Union[
modes.ModeWithNonce,
modes.ModeWithTweak,
None,
modes.ECB,
modes.ModeWithInitializationVector,
]
]
class _CipherContext(CipherContext):
_ctx: typing.Optional["_BackendCipherContext"]
def __init__(self, ctx: "_BackendCipherContext") -> None:
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: bytes) -> 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
class _AEADCipherContext(AEADCipherContext):
_ctx: typing.Optional["_BackendCipherContext"]
_tag: typing.Optional[bytes]
def __init__(self, ctx: "_BackendCipherContext") -> None:
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))
# mypy needs this assert even though _check_limit already checked
assert self._ctx is not None
return self._ctx.update(data)
def update_into(self, data: bytes, buf: bytes) -> int:
self._check_limit(len(data))
# mypy needs this assert even though _check_limit already checked
assert self._ctx is not None
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 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)
class _AEADDecryptionContext(_AEADCipherContext, AEADDecryptionContext):
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
class _AEADEncryptionContext(_AEADCipherContext, AEADEncryptionContext):
@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

View File

@@ -0,0 +1,263 @@
# 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(Mode, metaclass=abc.ABCMeta):
@abc.abstractproperty
def initialization_vector(self) -> bytes:
"""
The value of the initialization vector for this mode as bytes.
"""
class ModeWithTweak(Mode, metaclass=abc.ABCMeta):
@abc.abstractproperty
def tweak(self) -> bytes:
"""
The value of the tweak for this mode as bytes.
"""
class ModeWithNonce(Mode, metaclass=abc.ABCMeta):
@abc.abstractproperty
def nonce(self) -> bytes:
"""
The value of the nonce for this mode as bytes.
"""
class ModeWithAuthenticationTag(Mode, 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: Mode, algorithm: CipherAlgorithm) -> None:
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: ModeWithInitializationVector, algorithm: BlockCipherAlgorithm
) -> None:
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: CipherAlgorithm
) -> None:
if not isinstance(algorithm, BlockCipherAlgorithm):
raise UnsupportedAlgorithm(
f"{name} requires a block cipher algorithm",
_Reasons.UNSUPPORTED_CIPHER,
)
if len(nonce) * 8 != algorithm.block_size:
raise ValueError(
"Invalid nonce size ({}) for {}.".format(len(nonce), name)
)
def _check_iv_and_key_length(
self: ModeWithInitializationVector, algorithm: CipherAlgorithm
) -> None:
if not isinstance(algorithm, BlockCipherAlgorithm):
raise UnsupportedAlgorithm(
f"{self} requires a block cipher algorithm",
_Reasons.UNSUPPORTED_CIPHER,
)
_check_aes_key_length(self, algorithm)
_check_iv_length(self, algorithm)
class CBC(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(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(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(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(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(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(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
)
)

View File

@@ -0,0 +1,66 @@
# 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,
)
from cryptography.hazmat.primitives import ciphers
if typing.TYPE_CHECKING:
from cryptography.hazmat.backends.openssl.cmac import _CMACContext
class CMAC:
_ctx: typing.Optional["_CMACContext"]
_algorithm: ciphers.BlockCipherAlgorithm
def __init__(
self,
algorithm: ciphers.BlockCipherAlgorithm,
backend: typing.Any = None,
ctx: typing.Optional["_CMACContext"] = None,
):
if not isinstance(algorithm, ciphers.BlockCipherAlgorithm):
raise TypeError("Expected instance of BlockCipherAlgorithm.")
self._algorithm = algorithm
if ctx is None:
from cryptography.hazmat.backends.openssl.backend import (
backend as ossl,
)
self._ctx = ossl.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, ctx=self._ctx.copy())

View File

@@ -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)

View File

@@ -0,0 +1,259 @@
# 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,
)
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):
_ctx: typing.Optional[HashContext]
def __init__(
self,
algorithm: HashAlgorithm,
backend: typing.Any = None,
ctx: typing.Optional["HashContext"] = None,
):
if not isinstance(algorithm, HashAlgorithm):
raise TypeError("Expected instance of hashes.HashAlgorithm.")
self._algorithm = algorithm
if ctx is None:
from cryptography.hazmat.backends.openssl.backend import (
backend as ossl,
)
self._ctx = ossl.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, 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

View File

@@ -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 utils
from cryptography.exceptions import (
AlreadyFinalized,
)
from cryptography.hazmat.backends.openssl.hmac import _HMACContext
from cryptography.hazmat.primitives import hashes
class HMAC(hashes.HashContext):
_ctx: typing.Optional[_HMACContext]
def __init__(
self,
key: bytes,
algorithm: hashes.HashAlgorithm,
backend: typing.Any = None,
ctx=None,
):
if not isinstance(algorithm, hashes.HashAlgorithm):
raise TypeError("Expected instance of hashes.HashAlgorithm.")
self._algorithm = algorithm
self._key = key
if ctx is None:
from cryptography.hazmat.backends.openssl.backend import (
backend as ossl,
)
self._ctx = ossl.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,
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)

View File

@@ -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.
"""

View File

@@ -0,0 +1,130 @@
# 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,
)
from cryptography.hazmat.primitives import constant_time, hashes, hmac
from cryptography.hazmat.primitives.kdf import KeyDerivationFunction
def _int_to_u32be(n: int) -> bytes:
return n.to_bytes(length=4, byteorder="big")
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.Any = None,
):
_common_args_checks(algorithm, length, otherinfo)
self._algorithm = algorithm
self._length = length
self._otherinfo: bytes = otherinfo if otherinfo is not None else b""
self._used = False
def _hash(self) -> hashes.Hash:
return hashes.Hash(self._algorithm)
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.Any = None,
):
_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
self._used = False
def _hmac(self) -> hmac.HMAC:
return hmac.HMAC(self._salt, self._algorithm)
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

View File

@@ -0,0 +1,103 @@
# 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,
)
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.Any = None,
):
self._algorithm = algorithm
if salt is None:
salt = b"\x00" * self._algorithm.digest_size
else:
utils._check_bytes("salt", salt)
self._salt = salt
self._hkdf_expand = HKDFExpand(self._algorithm, length, info)
def _extract(self, key_material: bytes) -> bytes:
h = hmac.HMAC(self._salt, self._algorithm)
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.Any = None,
):
self._algorithm = algorithm
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)
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

View File

@@ -0,0 +1,258 @@
# 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.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.Any = None,
):
if not isinstance(algorithm, hashes.HashAlgorithm):
raise UnsupportedAlgorithm(
"Algorithm supplied is not a supported hash algorithm.",
_Reasons.UNSUPPORTED_HASH,
)
from cryptography.hazmat.backends.openssl.backend import (
backend as ossl,
)
if not ossl.hmac_supported(algorithm):
raise UnsupportedAlgorithm(
"Algorithm supplied is not a supported hmac algorithm.",
_Reasons.UNSUPPORTED_HASH,
)
self._algorithm = algorithm
self._deriver = _KBKDFDeriver(
self._prf,
mode,
length,
rlen,
llen,
location,
label,
context,
fixed,
)
def _prf(self, key_material: bytes) -> hmac.HMAC:
return hmac.HMAC(key_material, self._algorithm)
def derive(self, key_material: bytes) -> 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.Any = None,
):
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._cipher: typing.Optional[ciphers.BlockCipherAlgorithm] = None
self._deriver = _KBKDFDeriver(
self._prf,
mode,
length,
rlen,
llen,
location,
label,
context,
fixed,
)
def _prf(self, _: bytes) -> cmac.CMAC:
assert self._cipher is not None
return cmac.CMAC(self._cipher)
def derive(self, key_material: bytes) -> bytes:
self._cipher = self._algorithm(key_material)
assert self._cipher is not None
from cryptography.hazmat.backends.openssl.backend import (
backend as ossl,
)
if not ossl.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

View File

@@ -0,0 +1,65 @@
# 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.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.Any = None,
):
from cryptography.hazmat.backends.openssl.backend import (
backend as ossl,
)
if not ossl.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
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)
from cryptography.hazmat.backends.openssl.backend import backend
return 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.")

View File

@@ -0,0 +1,74 @@
# 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,
)
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.Any = None,
):
from cryptography.hazmat.backends.openssl.backend import (
backend as ossl,
)
if not ossl.scrypt_supported():
raise UnsupportedAlgorithm(
"This version of OpenSSL does not support scrypt"
)
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
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)
from cryptography.hazmat.backends.openssl.backend import backend
return 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.")

View File

@@ -0,0 +1,65 @@
# 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,
)
from cryptography.hazmat.primitives import constant_time, hashes
from cryptography.hazmat.primitives.kdf import KeyDerivationFunction
def _int_to_u32be(n: int) -> bytes:
return n.to_bytes(length=4, byteorder="big")
class X963KDF(KeyDerivationFunction):
def __init__(
self,
algorithm: hashes.HashAlgorithm,
length: int,
sharedinfo: typing.Optional[bytes],
backend: typing.Any = None,
):
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
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)
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

View File

@@ -0,0 +1,176 @@
# 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.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],
) -> bytes:
# RFC 3394 Key Wrap - 2.2.1 (index method)
encryptor = Cipher(AES(wrapping_key), ECB()).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])
a = (
int.from_bytes(b[:8], byteorder="big") ^ ((n * j) + i + 1)
).to_bytes(length=8, byteorder="big")
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.Any = None,
) -> bytes:
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)
def _unwrap_core(
wrapping_key: bytes,
a: bytes,
r: typing.List[bytes],
) -> typing.Tuple[bytes, typing.List[bytes]]:
# Implement RFC 3394 Key Unwrap - 2.2.2 (index method)
decryptor = Cipher(AES(wrapping_key), ECB()).decryptor()
n = len(r)
for j in reversed(range(6)):
for i in reversed(range(n)):
atr = (
int.from_bytes(a, byteorder="big") ^ ((n * j) + i + 1)
).to_bytes(length=8, byteorder="big") + 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.Any = None,
) -> 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\x59\x59\xA6" + len(key_to_wrap).to_bytes(
length=4, byteorder="big"
)
# 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()).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)
def aes_key_unwrap_with_padding(
wrapping_key: bytes,
wrapped_key: bytes,
backend: typing.Any = None,
) -> bytes:
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()).decryptor()
out = decryptor.update(wrapped_key)
assert decryptor.finalize() == b""
a = out[:8]
data = out[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)
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 = int.from_bytes(a[4:], byteorder="big")
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.Any = None,
) -> bytes:
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)
if not bytes_eq(a, aiv):
raise InvalidUnwrap()
return b"".join(r)
class InvalidUnwrap(Exception):
pass

View File

@@ -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:
def __init__(self, block_size: int):
_byte_padding_check(block_size)
self.block_size = block_size
def padder(self) -> PaddingContext:
return _PKCS7PaddingContext(self.block_size)
def unpadder(self) -> PaddingContext:
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:
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

View File

@@ -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 import utils
from cryptography.exceptions import (
AlreadyFinalized,
UnsupportedAlgorithm,
_Reasons,
)
from cryptography.hazmat.backends.openssl.poly1305 import _Poly1305Context
class Poly1305:
_ctx: typing.Optional[_Poly1305Context]
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)

View File

@@ -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",
]

View File

@@ -0,0 +1,64 @@
# 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 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.Any = None,
) -> PRIVATE_KEY_TYPES:
from cryptography.hazmat.backends.openssl.backend import backend as ossl
return ossl.load_pem_private_key(data, password)
def load_pem_public_key(
data: bytes, backend: typing.Any = None
) -> PUBLIC_KEY_TYPES:
from cryptography.hazmat.backends.openssl.backend import backend as ossl
return ossl.load_pem_public_key(data)
def load_pem_parameters(
data: bytes, backend: typing.Any = None
) -> "dh.DHParameters":
from cryptography.hazmat.backends.openssl.backend import backend as ossl
return ossl.load_pem_parameters(data)
def load_der_private_key(
data: bytes,
password: typing.Optional[bytes],
backend: typing.Any = None,
) -> PRIVATE_KEY_TYPES:
from cryptography.hazmat.backends.openssl.backend import backend as ossl
return ossl.load_der_private_key(data, password)
def load_der_public_key(
data: bytes, backend: typing.Any = None
) -> PUBLIC_KEY_TYPES:
from cryptography.hazmat.backends.openssl.backend import backend as ossl
return ossl.load_der_public_key(data)
def load_der_parameters(
data: bytes, backend: typing.Any = None
) -> "dh.DHParameters":
from cryptography.hazmat.backends.openssl.backend import backend as ossl
return ossl.load_der_parameters(data)

View File

@@ -0,0 +1,219 @@
# 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.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import (
dsa,
ec,
ed25519,
ed448,
rsa,
)
from cryptography.hazmat.primitives.asymmetric.types import (
PRIVATE_KEY_TYPES,
)
_ALLOWED_PKCS12_TYPES = typing.Union[
rsa.RSAPrivateKey,
dsa.DSAPrivateKey,
ec.EllipticCurvePrivateKey,
ed25519.Ed25519PrivateKey,
ed448.Ed448PrivateKey,
]
class PKCS12Certificate:
def __init__(
self,
cert: x509.Certificate,
friendly_name: typing.Optional[bytes],
):
if not isinstance(cert, x509.Certificate):
raise TypeError("Expecting x509.Certificate object")
if friendly_name is not None and not isinstance(friendly_name, bytes):
raise TypeError("friendly_name must be bytes or None")
self._cert = cert
self._friendly_name = friendly_name
@property
def friendly_name(self) -> typing.Optional[bytes]:
return self._friendly_name
@property
def certificate(self) -> x509.Certificate:
return self._cert
def __eq__(self, other: object) -> bool:
if not isinstance(other, PKCS12Certificate):
return NotImplemented
return (
self.certificate == other.certificate
and self.friendly_name == other.friendly_name
)
def __hash__(self) -> int:
return hash((self.certificate, self.friendly_name))
def __repr__(self) -> str:
return "<PKCS12Certificate({}, friendly_name={!r})>".format(
self.certificate, self.friendly_name
)
class PKCS12KeyAndCertificates:
def __init__(
self,
key: typing.Optional[PRIVATE_KEY_TYPES],
cert: typing.Optional[PKCS12Certificate],
additional_certs: typing.List[PKCS12Certificate],
):
if key is not None and not isinstance(
key,
(
rsa.RSAPrivateKey,
dsa.DSAPrivateKey,
ec.EllipticCurvePrivateKey,
ed25519.Ed25519PrivateKey,
ed448.Ed448PrivateKey,
),
):
raise TypeError(
"Key must be RSA, DSA, EllipticCurve, ED25519, or ED448"
" private key, or None."
)
if cert is not None and not isinstance(cert, PKCS12Certificate):
raise TypeError("cert must be a PKCS12Certificate object or None")
if not all(
isinstance(add_cert, PKCS12Certificate)
for add_cert in additional_certs
):
raise TypeError(
"all values in additional_certs must be PKCS12Certificate"
" objects"
)
self._key = key
self._cert = cert
self._additional_certs = additional_certs
@property
def key(self) -> typing.Optional[PRIVATE_KEY_TYPES]:
return self._key
@property
def cert(self) -> typing.Optional[PKCS12Certificate]:
return self._cert
@property
def additional_certs(self) -> typing.List[PKCS12Certificate]:
return self._additional_certs
def __eq__(self, other: object) -> bool:
if not isinstance(other, PKCS12KeyAndCertificates):
return NotImplemented
return (
self.key == other.key
and self.cert == other.cert
and self.additional_certs == other.additional_certs
)
def __hash__(self) -> int:
return hash((self.key, self.cert, tuple(self.additional_certs)))
def __repr__(self) -> str:
fmt = (
"<PKCS12KeyAndCertificates(key={}, cert={}, additional_certs={})>"
)
return fmt.format(self.key, self.cert, self.additional_certs)
def load_key_and_certificates(
data: bytes,
password: typing.Optional[bytes],
backend: typing.Any = None,
) -> typing.Tuple[
typing.Optional[PRIVATE_KEY_TYPES],
typing.Optional[x509.Certificate],
typing.List[x509.Certificate],
]:
from cryptography.hazmat.backends.openssl.backend import backend as ossl
return ossl.load_key_and_certificates_from_pkcs12(data, password)
def load_pkcs12(
data: bytes,
password: typing.Optional[bytes],
backend: typing.Any = None,
) -> PKCS12KeyAndCertificates:
from cryptography.hazmat.backends.openssl.backend import backend as ossl
return ossl.load_pkcs12(data, password)
_PKCS12_CAS_TYPES = typing.Union[
x509.Certificate,
PKCS12Certificate,
]
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[_PKCS12_CAS_TYPES]],
encryption_algorithm: serialization.KeySerializationEncryption,
) -> bytes:
if key is not None and not isinstance(
key,
(
rsa.RSAPrivateKey,
dsa.DSAPrivateKey,
ec.EllipticCurvePrivateKey,
ed25519.Ed25519PrivateKey,
ed448.Ed448PrivateKey,
),
):
raise TypeError(
"Key must be RSA, DSA, EllipticCurve, ED25519, or ED448"
" private key, or None."
)
if cert is not None and not isinstance(cert, x509.Certificate):
raise TypeError("cert must be a certificate or None")
if cas is not None:
cas = list(cas)
if not all(
isinstance(
val,
(
x509.Certificate,
PKCS12Certificate,
),
)
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")
from cryptography.hazmat.backends.openssl.backend import backend
return backend.serialize_key_and_certificates_to_pkcs12(
name, key, cert, cas, encryption_algorithm
)

View File

@@ -0,0 +1,180 @@
# 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.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]:
from cryptography.hazmat.backends.openssl.backend import backend
return backend.load_pem_pkcs7_certificates(data)
def load_der_pkcs7_certificates(data: bytes) -> typing.List[x509.Certificate]:
from cryptography.hazmat.backends.openssl.backend import backend
return backend.load_der_pkcs7_certificates(data)
def serialize_certificates(
certs: typing.List[x509.Certificate],
encoding: serialization.Encoding,
) -> bytes:
from cryptography.hazmat.backends.openssl.backend import backend
return backend.pkcs7_serialize_certificates(certs, encoding)
_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:
def __init__(
self,
data: typing.Optional[bytes] = None,
signers: typing.List[
typing.Tuple[
x509.Certificate,
_ALLOWED_PRIVATE_KEY_TYPES,
_ALLOWED_PKCS7_HASH_TYPES,
]
] = [],
additional_certs: typing.List[x509.Certificate] = [],
):
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.Any = 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."
)
from cryptography.hazmat.backends.openssl.backend import (
backend as ossl,
)
return ossl.pkcs7_sign(self, encoding, options)

View File

@@ -0,0 +1,757 @@
# 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 typing
from base64 import encodebytes as _base64_encode
from cryptography import utils
from cryptography.exceptions import UnsupportedAlgorithm
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(rb"\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: typing.Dict[
bytes,
typing.Tuple[
typing.Type[algorithms.AES],
int,
typing.Union[typing.Type[modes.CTR], typing.Type[modes.CBC]],
int,
],
] = {
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,
}
def _ecdsa_key_type(public_key: ec.EllipticCurvePublicKey) -> bytes:
"""Return SSH key_type and curve_name for private key."""
curve = public_key.curve
if curve.name not in _ECDSA_KEY_TYPE:
raise ValueError(
f"Unsupported curve for ssh private key: {curve.name!r}"
)
return _ECDSA_KEY_TYPE[curve.name]
def _ssh_pem_encode(
data: bytes,
prefix: bytes = _SK_START + b"\n",
suffix: bytes = _SK_END + b"\n",
) -> bytes:
return b"".join([prefix, _base64_encode(data), suffix])
def _check_block_size(data: bytes, block_len: int) -> None:
"""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: bytes) -> None:
"""All data should have been parsed."""
if data:
raise ValueError("Corrupt data: unparsed data")
def _init_cipher(
ciphername: bytes,
password: typing.Optional[bytes],
salt: bytes,
rounds: int,
) -> Cipher[typing.Union[modes.CBC, modes.CTR]]:
"""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:]))
def _get_u32(data: memoryview) -> typing.Tuple[int, memoryview]:
"""Uint32"""
if len(data) < 4:
raise ValueError("Invalid data")
return int.from_bytes(data[:4], byteorder="big"), data[4:]
def _get_u64(data: memoryview) -> typing.Tuple[int, memoryview]:
"""Uint64"""
if len(data) < 8:
raise ValueError("Invalid data")
return int.from_bytes(data[:8], byteorder="big"), data[8:]
def _get_sshstr(data: memoryview) -> typing.Tuple[memoryview, memoryview]:
"""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: memoryview) -> typing.Tuple[int, memoryview]:
"""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: int) -> bytes:
"""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:
"""Build recursive structure without data copy."""
flist: typing.List[bytes]
def __init__(self, init: typing.List[bytes] = None) -> None:
self.flist = []
if init:
self.flist.extend(init)
def put_raw(self, val: bytes) -> None:
"""Add plain bytes"""
self.flist.append(val)
def put_u32(self, val: int) -> None:
"""Big-endian uint32"""
self.flist.append(val.to_bytes(length=4, byteorder="big"))
def put_sshstr(self, val: typing.Union[bytes, "_FragList"]) -> None:
"""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: int) -> None:
"""Big-endian bigint prefixed with u32 length"""
self.put_sshstr(_to_mpint(val))
def size(self) -> int:
"""Current number of bytes"""
return sum(map(len, self.flist))
def render(self, dstbuf: memoryview, pos: int = 0) -> int:
"""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) -> bytes:
"""Return as bytes"""
buf = memoryview(bytearray(self.size()))
self.render(buf)
return buf.tobytes()
class _SSHFormatRSA:
"""Format for RSA keys.
Public:
mpint e, n
Private:
mpint n, e, d, iqmp, p, q
"""
def get_public(self, data: memoryview):
"""RSA public fields"""
e, data = _get_mpint(data)
n, data = _get_mpint(data)
return (e, n), data
def load_public(
self, data: memoryview
) -> typing.Tuple[rsa.RSAPublicKey, memoryview]:
"""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()
return public_key, data
def load_private(
self, data: memoryview, pubfields
) -> typing.Tuple[rsa.RSAPrivateKey, memoryview]:
"""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()
return private_key, data
def encode_public(
self, public_key: rsa.RSAPublicKey, f_pub: _FragList
) -> None:
"""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: rsa.RSAPrivateKey, f_priv: _FragList
) -> None:
"""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:
"""Format for DSA keys.
Public:
mpint p, q, g, y
Private:
mpint p, q, g, y, x
"""
def get_public(
self, data: memoryview
) -> typing.Tuple[typing.Tuple, memoryview]:
"""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, data: memoryview
) -> typing.Tuple[dsa.DSAPublicKey, memoryview]:
"""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()
return public_key, data
def load_private(
self, data: memoryview, pubfields
) -> typing.Tuple[dsa.DSAPrivateKey, memoryview]:
"""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()
return private_key, data
def encode_public(
self, public_key: dsa.DSAPublicKey, f_pub: _FragList
) -> None:
"""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: dsa.DSAPrivateKey, f_priv: _FragList
) -> None:
"""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: dsa.DSAPublicNumbers) -> None:
parameter_numbers = public_numbers.parameter_numbers
if parameter_numbers.p.bit_length() != 1024:
raise ValueError("SSH supports only 1024 bit DSA keys")
class _SSHFormatECDSA:
"""Format for ECDSA keys.
Public:
str curve
bytes point
Private:
str curve
bytes point
mpint secret
"""
def __init__(self, ssh_curve_name: bytes, curve: ec.EllipticCurve):
self.ssh_curve_name = ssh_curve_name
self.curve = curve
def get_public(
self, data: memoryview
) -> typing.Tuple[typing.Tuple, memoryview]:
"""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, data: memoryview
) -> typing.Tuple[ec.EllipticCurvePublicKey, memoryview]:
"""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: memoryview, pubfields
) -> typing.Tuple[ec.EllipticCurvePrivateKey, memoryview]:
"""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)
return private_key, data
def encode_public(
self, public_key: ec.EllipticCurvePublicKey, f_pub: _FragList
) -> None:
"""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: ec.EllipticCurvePrivateKey, f_priv: _FragList
) -> None:
"""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:
"""Format for Ed25519 keys.
Public:
bytes point
Private:
bytes point
bytes secret_and_point
"""
def get_public(
self, data: memoryview
) -> typing.Tuple[typing.Tuple, memoryview]:
"""Ed25519 public fields"""
point, data = _get_sshstr(data)
return (point,), data
def load_public(
self, data: memoryview
) -> typing.Tuple[ed25519.Ed25519PublicKey, memoryview]:
"""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: memoryview, pubfields
) -> typing.Tuple[ed25519.Ed25519PrivateKey, memoryview]:
"""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: ed25519.Ed25519PublicKey, f_pub: _FragList
) -> None:
"""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: ed25519.Ed25519PrivateKey, f_priv: _FragList
) -> None:
"""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: bytes):
"""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(f"Unsupported key type: {key_type!r}")
_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.Any = None,
) -> _SSH_PRIVATE_KEY_TYPES:
"""Load private key from OpenSSH custom encoding."""
utils._check_byteslike("data", data)
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_bytes = ciphername.tobytes()
if ciphername_bytes not in _SSH_CIPHERS:
raise UnsupportedAlgorithm(
f"Unsupported cipher: {ciphername_bytes!r}"
)
if kdfname != _BCRYPT:
raise UnsupportedAlgorithm(f"Unsupported KDF: {kdfname!r}")
blklen = _SSH_CIPHERS[ciphername_bytes][3]
_check_block_size(edata, blklen)
salt, kbuf = _get_sshstr(kdfoptions)
rounds, kbuf = _get_u32(kbuf)
_check_empty(kbuf)
ciph = _init_cipher(ciphername_bytes, password, salt.tobytes(), rounds)
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)
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)
ciph = _init_cipher(ciphername, password, salt, rounds)
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])
buf[ofs:mlen] = bytearray(slen)
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.Any = None
) -> _SSH_PUBLIC_KEY_TYPES:
"""Load public key from OpenSSH one-line format."""
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:
rest = memoryview(binascii.a2b_base64(key_body))
except (TypeError, binascii.Error):
raise ValueError("Invalid key format")
inner_key_type, rest = _get_sshstr(rest)
if inner_key_type != orig_key_type:
raise ValueError("Invalid key format")
if with_cert:
nonce, rest = _get_sshstr(rest)
public_key, rest = kformat.load_public(rest)
if with_cert:
serial, rest = _get_u64(rest)
cctype, rest = _get_u32(rest)
key_id, rest = _get_sshstr(rest)
principals, rest = _get_sshstr(rest)
valid_after, rest = _get_u64(rest)
valid_before, rest = _get_u64(rest)
crit_options, rest = _get_sshstr(rest)
extensions, rest = _get_sshstr(rest)
reserved, rest = _get_sshstr(rest)
sig_key, rest = _get_sshstr(rest)
signature, rest = _get_sshstr(rest)
_check_empty(rest)
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])

View File

@@ -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

View File

@@ -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 base64
import typing
from urllib.parse import quote, urlencode
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)
label = (
f"{quote(issuer)}:{quote(account_name)}"
if issuer
else quote(account_name)
)
return f"otpauth://{type_name}/{label}?{urlencode(parameters)}"
class HOTP:
def __init__(
self,
key: bytes,
length: int,
algorithm: _ALLOWED_HASH_TYPES,
backend: typing.Any = None,
enforce_key_length: bool = True,
) -> None:
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
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)
ctx.update(counter.to_bytes(length=8, byteorder="big"))
hmac_value = ctx.finalize()
offset = hmac_value[len(hmac_value) - 1] & 0b1111
p = hmac_value[offset : offset + 4]
return int.from_bytes(p, byteorder="big") & 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))]
)

View File

@@ -0,0 +1,48 @@
# 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 constant_time
from cryptography.hazmat.primitives.twofactor import InvalidToken
from cryptography.hazmat.primitives.twofactor.hotp import (
HOTP,
_ALLOWED_HASH_TYPES,
_generate_uri,
)
class TOTP:
def __init__(
self,
key: bytes,
length: int,
algorithm: _ALLOWED_HASH_TYPES,
time_step: int,
backend: typing.Any = None,
enforce_key_length: bool = True,
):
self._time_step = time_step
self._hotp = HOTP(
key, length, algorithm, enforce_key_length=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))],
)

View File

@@ -0,0 +1,180 @@
# 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 enum
import inspect
import sys
import types
import typing
import warnings
# We use a UserWarning subclass, instead of DeprecationWarning, because CPython
# decided deprecation warnings should be invisble by default.
class CryptographyDeprecationWarning(UserWarning):
pass
# Several APIs were deprecated with no specific end-of-life date because of the
# ubiquity of their use. They should not be removed until we agree on when that
# cycle ends.
PersistentlyDeprecated2019 = CryptographyDeprecationWarning
DeprecatedIn35 = CryptographyDeprecationWarning
DeprecatedIn36 = CryptographyDeprecationWarning
DeprecatedIn37 = CryptographyDeprecationWarning
def _check_bytes(name: str, value: bytes) -> None:
if not isinstance(value, bytes):
raise TypeError("{} must be bytes".format(name))
def _check_byteslike(name: str, value: bytes) -> None:
try:
memoryview(value)
except TypeError:
raise TypeError("{} must be bytes-like".format(name))
if typing.TYPE_CHECKING:
from typing_extensions import Protocol
_T_class = typing.TypeVar("_T_class", bound=type)
class _RegisterDecoratorType(Protocol):
def __call__(
self, klass: _T_class, *, check_annotations: bool = False
) -> _T_class:
...
def register_interface(iface: abc.ABCMeta) -> "_RegisterDecoratorType":
def register_decorator(
klass: "_T_class", *, check_annotations: bool = False
) -> "_T_class":
verify_interface(iface, klass, check_annotations=check_annotations)
iface.register(klass)
return klass
return register_decorator
def int_to_bytes(integer: int, length: typing.Optional[int] = None) -> bytes:
return integer.to_bytes(
length or (integer.bit_length() + 7) // 8 or 1, "big"
)
class InterfaceNotImplemented(Exception):
pass
def strip_annotation(signature: inspect.Signature) -> inspect.Signature:
return inspect.Signature(
[
param.replace(annotation=inspect.Parameter.empty)
for param in signature.parameters.values()
]
)
def verify_interface(
iface: abc.ABCMeta, klass: object, *, check_annotations: bool = False
):
for method in iface.__abstractmethods__:
if not hasattr(klass, method):
raise InterfaceNotImplemented(
"{} is missing a {!r} method".format(klass, method)
)
if isinstance(getattr(iface, method), abc.abstractproperty):
# Can't properly verify these yet.
continue
sig = inspect.signature(getattr(iface, method))
actual = inspect.signature(getattr(klass, method))
if check_annotations:
ok = sig == actual
else:
ok = strip_annotation(sig) == strip_annotation(actual)
if not ok:
raise InterfaceNotImplemented(
"{}.{}'s signature differs from the expected. Expected: "
"{!r}. Received: {!r}".format(klass, method, sig, actual)
)
class _DeprecatedValue:
def __init__(self, value: object, message: str, warning_class):
self.value = value
self.message = message
self.warning_class = warning_class
class _ModuleWithDeprecations(types.ModuleType):
def __init__(self, module: types.ModuleType):
super().__init__(module.__name__)
self.__dict__["_module"] = module
def __getattr__(self, attr: str) -> object:
obj = getattr(self._module, attr)
if isinstance(obj, _DeprecatedValue):
warnings.warn(obj.message, obj.warning_class, stacklevel=2)
obj = obj.value
return obj
def __setattr__(self, attr: str, value: object) -> None:
setattr(self._module, attr, value)
def __delattr__(self, attr: str) -> None:
obj = getattr(self._module, attr)
if isinstance(obj, _DeprecatedValue):
warnings.warn(obj.message, obj.warning_class, stacklevel=2)
delattr(self._module, attr)
def __dir__(self) -> typing.Sequence[str]:
return ["_module"] + dir(self._module)
def deprecated(
value: object,
module_name: str,
message: str,
warning_class: typing.Type[Warning],
name: typing.Optional[str] = None,
) -> _DeprecatedValue:
module = sys.modules[module_name]
if not isinstance(module, _ModuleWithDeprecations):
sys.modules[module_name] = module = _ModuleWithDeprecations(module)
dv = _DeprecatedValue(value, message, warning_class)
# Maintain backwards compatibility with `name is None` for pyOpenSSL.
if name is not None:
setattr(module, name, dv)
return dv
def cached_property(func: typing.Callable) -> property:
cached_name = "_cached_{}".format(func)
sentinel = object()
def inner(instance: object):
cache = getattr(instance, cached_name, sentinel)
if cache is not sentinel:
return cache
result = func(instance)
setattr(instance, cached_name, result)
return result
return property(inner)
# Python 3.10 changed representation of enums. We use well-defined object
# representation and string representation from Python 3.9.
class Enum(enum.Enum):
def __repr__(self) -> str:
return f"<{self.__class__.__name__}.{self._name_}: {self._value_!r}>"
def __str__(self) -> str:
return f"{self.__class__.__name__}.{self._name_}"

View File

@@ -0,0 +1,249 @@
# 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.x509 import certificate_transparency
from cryptography.x509.base import (
Attribute,
AttributeNotFound,
Attributes,
Certificate,
CertificateBuilder,
CertificateRevocationList,
CertificateRevocationListBuilder,
CertificateSigningRequest,
CertificateSigningRequestBuilder,
InvalidVersion,
RevokedCertificate,
RevokedCertificateBuilder,
Version,
load_der_x509_certificate,
load_der_x509_crl,
load_der_x509_csr,
load_pem_x509_certificate,
load_pem_x509_crl,
load_pem_x509_csr,
random_serial_number,
)
from cryptography.x509.extensions import (
AccessDescription,
AuthorityInformationAccess,
AuthorityKeyIdentifier,
BasicConstraints,
CRLDistributionPoints,
CRLNumber,
CRLReason,
CertificateIssuer,
CertificatePolicies,
DeltaCRLIndicator,
DistributionPoint,
DuplicateExtension,
ExtendedKeyUsage,
Extension,
ExtensionNotFound,
ExtensionType,
Extensions,
FreshestCRL,
GeneralNames,
InhibitAnyPolicy,
InvalidityDate,
IssuerAlternativeName,
IssuingDistributionPoint,
KeyUsage,
NameConstraints,
NoticeReference,
OCSPNoCheck,
OCSPNonce,
PolicyConstraints,
PolicyInformation,
PrecertPoison,
PrecertificateSignedCertificateTimestamps,
ReasonFlags,
SignedCertificateTimestamps,
SubjectAlternativeName,
SubjectInformationAccess,
SubjectKeyIdentifier,
TLSFeature,
TLSFeatureType,
UnrecognizedExtension,
UserNotice,
)
from cryptography.x509.general_name import (
DNSName,
DirectoryName,
GeneralName,
IPAddress,
OtherName,
RFC822Name,
RegisteredID,
UniformResourceIdentifier,
UnsupportedGeneralNameType,
)
from cryptography.x509.name import (
Name,
NameAttribute,
RelativeDistinguishedName,
)
from cryptography.x509.oid import (
AuthorityInformationAccessOID,
CRLEntryExtensionOID,
CertificatePoliciesOID,
ExtendedKeyUsageOID,
ExtensionOID,
NameOID,
ObjectIdentifier,
SignatureAlgorithmOID,
)
OID_AUTHORITY_INFORMATION_ACCESS = ExtensionOID.AUTHORITY_INFORMATION_ACCESS
OID_AUTHORITY_KEY_IDENTIFIER = ExtensionOID.AUTHORITY_KEY_IDENTIFIER
OID_BASIC_CONSTRAINTS = ExtensionOID.BASIC_CONSTRAINTS
OID_CERTIFICATE_POLICIES = ExtensionOID.CERTIFICATE_POLICIES
OID_CRL_DISTRIBUTION_POINTS = ExtensionOID.CRL_DISTRIBUTION_POINTS
OID_EXTENDED_KEY_USAGE = ExtensionOID.EXTENDED_KEY_USAGE
OID_FRESHEST_CRL = ExtensionOID.FRESHEST_CRL
OID_INHIBIT_ANY_POLICY = ExtensionOID.INHIBIT_ANY_POLICY
OID_ISSUER_ALTERNATIVE_NAME = ExtensionOID.ISSUER_ALTERNATIVE_NAME
OID_KEY_USAGE = ExtensionOID.KEY_USAGE
OID_NAME_CONSTRAINTS = ExtensionOID.NAME_CONSTRAINTS
OID_OCSP_NO_CHECK = ExtensionOID.OCSP_NO_CHECK
OID_POLICY_CONSTRAINTS = ExtensionOID.POLICY_CONSTRAINTS
OID_POLICY_MAPPINGS = ExtensionOID.POLICY_MAPPINGS
OID_SUBJECT_ALTERNATIVE_NAME = ExtensionOID.SUBJECT_ALTERNATIVE_NAME
OID_SUBJECT_DIRECTORY_ATTRIBUTES = ExtensionOID.SUBJECT_DIRECTORY_ATTRIBUTES
OID_SUBJECT_INFORMATION_ACCESS = ExtensionOID.SUBJECT_INFORMATION_ACCESS
OID_SUBJECT_KEY_IDENTIFIER = ExtensionOID.SUBJECT_KEY_IDENTIFIER
OID_DSA_WITH_SHA1 = SignatureAlgorithmOID.DSA_WITH_SHA1
OID_DSA_WITH_SHA224 = SignatureAlgorithmOID.DSA_WITH_SHA224
OID_DSA_WITH_SHA256 = SignatureAlgorithmOID.DSA_WITH_SHA256
OID_ECDSA_WITH_SHA1 = SignatureAlgorithmOID.ECDSA_WITH_SHA1
OID_ECDSA_WITH_SHA224 = SignatureAlgorithmOID.ECDSA_WITH_SHA224
OID_ECDSA_WITH_SHA256 = SignatureAlgorithmOID.ECDSA_WITH_SHA256
OID_ECDSA_WITH_SHA384 = SignatureAlgorithmOID.ECDSA_WITH_SHA384
OID_ECDSA_WITH_SHA512 = SignatureAlgorithmOID.ECDSA_WITH_SHA512
OID_RSA_WITH_MD5 = SignatureAlgorithmOID.RSA_WITH_MD5
OID_RSA_WITH_SHA1 = SignatureAlgorithmOID.RSA_WITH_SHA1
OID_RSA_WITH_SHA224 = SignatureAlgorithmOID.RSA_WITH_SHA224
OID_RSA_WITH_SHA256 = SignatureAlgorithmOID.RSA_WITH_SHA256
OID_RSA_WITH_SHA384 = SignatureAlgorithmOID.RSA_WITH_SHA384
OID_RSA_WITH_SHA512 = SignatureAlgorithmOID.RSA_WITH_SHA512
OID_RSASSA_PSS = SignatureAlgorithmOID.RSASSA_PSS
OID_COMMON_NAME = NameOID.COMMON_NAME
OID_COUNTRY_NAME = NameOID.COUNTRY_NAME
OID_DOMAIN_COMPONENT = NameOID.DOMAIN_COMPONENT
OID_DN_QUALIFIER = NameOID.DN_QUALIFIER
OID_EMAIL_ADDRESS = NameOID.EMAIL_ADDRESS
OID_GENERATION_QUALIFIER = NameOID.GENERATION_QUALIFIER
OID_GIVEN_NAME = NameOID.GIVEN_NAME
OID_LOCALITY_NAME = NameOID.LOCALITY_NAME
OID_ORGANIZATIONAL_UNIT_NAME = NameOID.ORGANIZATIONAL_UNIT_NAME
OID_ORGANIZATION_NAME = NameOID.ORGANIZATION_NAME
OID_PSEUDONYM = NameOID.PSEUDONYM
OID_SERIAL_NUMBER = NameOID.SERIAL_NUMBER
OID_STATE_OR_PROVINCE_NAME = NameOID.STATE_OR_PROVINCE_NAME
OID_SURNAME = NameOID.SURNAME
OID_TITLE = NameOID.TITLE
OID_CLIENT_AUTH = ExtendedKeyUsageOID.CLIENT_AUTH
OID_CODE_SIGNING = ExtendedKeyUsageOID.CODE_SIGNING
OID_EMAIL_PROTECTION = ExtendedKeyUsageOID.EMAIL_PROTECTION
OID_OCSP_SIGNING = ExtendedKeyUsageOID.OCSP_SIGNING
OID_SERVER_AUTH = ExtendedKeyUsageOID.SERVER_AUTH
OID_TIME_STAMPING = ExtendedKeyUsageOID.TIME_STAMPING
OID_ANY_POLICY = CertificatePoliciesOID.ANY_POLICY
OID_CPS_QUALIFIER = CertificatePoliciesOID.CPS_QUALIFIER
OID_CPS_USER_NOTICE = CertificatePoliciesOID.CPS_USER_NOTICE
OID_CERTIFICATE_ISSUER = CRLEntryExtensionOID.CERTIFICATE_ISSUER
OID_CRL_REASON = CRLEntryExtensionOID.CRL_REASON
OID_INVALIDITY_DATE = CRLEntryExtensionOID.INVALIDITY_DATE
OID_CA_ISSUERS = AuthorityInformationAccessOID.CA_ISSUERS
OID_OCSP = AuthorityInformationAccessOID.OCSP
__all__ = [
"certificate_transparency",
"load_pem_x509_certificate",
"load_der_x509_certificate",
"load_pem_x509_csr",
"load_der_x509_csr",
"load_pem_x509_crl",
"load_der_x509_crl",
"random_serial_number",
"Attribute",
"AttributeNotFound",
"Attributes",
"InvalidVersion",
"DeltaCRLIndicator",
"DuplicateExtension",
"ExtensionNotFound",
"UnsupportedGeneralNameType",
"NameAttribute",
"Name",
"RelativeDistinguishedName",
"ObjectIdentifier",
"ExtensionType",
"Extensions",
"Extension",
"ExtendedKeyUsage",
"FreshestCRL",
"IssuingDistributionPoint",
"TLSFeature",
"TLSFeatureType",
"OCSPNoCheck",
"BasicConstraints",
"CRLNumber",
"KeyUsage",
"AuthorityInformationAccess",
"SubjectInformationAccess",
"AccessDescription",
"CertificatePolicies",
"PolicyInformation",
"UserNotice",
"NoticeReference",
"SubjectKeyIdentifier",
"NameConstraints",
"CRLDistributionPoints",
"DistributionPoint",
"ReasonFlags",
"InhibitAnyPolicy",
"SubjectAlternativeName",
"IssuerAlternativeName",
"AuthorityKeyIdentifier",
"GeneralNames",
"GeneralName",
"RFC822Name",
"DNSName",
"UniformResourceIdentifier",
"RegisteredID",
"DirectoryName",
"IPAddress",
"OtherName",
"Certificate",
"CertificateRevocationList",
"CertificateRevocationListBuilder",
"CertificateSigningRequest",
"RevokedCertificate",
"RevokedCertificateBuilder",
"CertificateSigningRequestBuilder",
"CertificateBuilder",
"Version",
"OID_CA_ISSUERS",
"OID_OCSP",
"CertificateIssuer",
"CRLReason",
"InvalidityDate",
"UnrecognizedExtension",
"PolicyConstraints",
"PrecertificateSignedCertificateTimestamps",
"PrecertPoison",
"OCSPNonce",
"SignedCertificateTimestamps",
"SignatureAlgorithmOID",
"NameOID",
]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,48 @@
# 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 datetime
from cryptography import utils
from cryptography.hazmat.bindings._rust import x509 as rust_x509
class LogEntryType(utils.Enum):
X509_CERTIFICATE = 0
PRE_CERTIFICATE = 1
class Version(utils.Enum):
v1 = 0
class SignedCertificateTimestamp(metaclass=abc.ABCMeta):
@abc.abstractproperty
def version(self) -> Version:
"""
Returns the SCT version.
"""
@abc.abstractproperty
def log_id(self) -> bytes:
"""
Returns an identifier indicating which log this SCT is for.
"""
@abc.abstractproperty
def timestamp(self) -> datetime.datetime:
"""
Returns the timestamp for this SCT.
"""
@abc.abstractproperty
def entry_type(self) -> LogEntryType:
"""
Returns whether this is an SCT for a certificate or pre-certificate.
"""
SignedCertificateTimestamp.register(rust_x509.Sct)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,284 @@
# 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 ipaddress
import typing
from email.utils import parseaddr
from cryptography.x509.name import Name
from cryptography.x509.oid import ObjectIdentifier
_IPADDRESS_TYPES = typing.Union[
ipaddress.IPv4Address,
ipaddress.IPv6Address,
ipaddress.IPv4Network,
ipaddress.IPv6Network,
]
class UnsupportedGeneralNameType(Exception):
pass
class GeneralName(metaclass=abc.ABCMeta):
@abc.abstractproperty
def value(self) -> typing.Any:
"""
Return the value of the object
"""
class RFC822Name(GeneralName):
def __init__(self, value: str) -> None:
if isinstance(value, str):
try:
value.encode("ascii")
except UnicodeEncodeError:
raise ValueError(
"RFC822Name values should be passed as an A-label string. "
"This means unicode characters should be encoded via "
"a library like idna."
)
else:
raise TypeError("value must be string")
name, address = parseaddr(value)
if name or not address:
# parseaddr has found a name (e.g. Name <email>) or the entire
# value is an empty string.
raise ValueError("Invalid rfc822name value")
self._value = value
@property
def value(self) -> str:
return self._value
@classmethod
def _init_without_validation(cls, value: str) -> "RFC822Name":
instance = cls.__new__(cls)
instance._value = value
return instance
def __repr__(self) -> str:
return "<RFC822Name(value={0!r})>".format(self.value)
def __eq__(self, other: object) -> bool:
if not isinstance(other, RFC822Name):
return NotImplemented
return self.value == other.value
def __hash__(self) -> int:
return hash(self.value)
class DNSName(GeneralName):
def __init__(self, value: str) -> None:
if isinstance(value, str):
try:
value.encode("ascii")
except UnicodeEncodeError:
raise ValueError(
"DNSName values should be passed as an A-label string. "
"This means unicode characters should be encoded via "
"a library like idna."
)
else:
raise TypeError("value must be string")
self._value = value
@property
def value(self) -> str:
return self._value
@classmethod
def _init_without_validation(cls, value: str) -> "DNSName":
instance = cls.__new__(cls)
instance._value = value
return instance
def __repr__(self) -> str:
return "<DNSName(value={0!r})>".format(self.value)
def __eq__(self, other: object) -> bool:
if not isinstance(other, DNSName):
return NotImplemented
return self.value == other.value
def __hash__(self) -> int:
return hash(self.value)
class UniformResourceIdentifier(GeneralName):
def __init__(self, value: str) -> None:
if isinstance(value, str):
try:
value.encode("ascii")
except UnicodeEncodeError:
raise ValueError(
"URI values should be passed as an A-label string. "
"This means unicode characters should be encoded via "
"a library like idna."
)
else:
raise TypeError("value must be string")
self._value = value
@property
def value(self) -> str:
return self._value
@classmethod
def _init_without_validation(
cls, value: str
) -> "UniformResourceIdentifier":
instance = cls.__new__(cls)
instance._value = value
return instance
def __repr__(self) -> str:
return "<UniformResourceIdentifier(value={0!r})>".format(self.value)
def __eq__(self, other: object) -> bool:
if not isinstance(other, UniformResourceIdentifier):
return NotImplemented
return self.value == other.value
def __hash__(self) -> int:
return hash(self.value)
class DirectoryName(GeneralName):
def __init__(self, value: Name) -> None:
if not isinstance(value, Name):
raise TypeError("value must be a Name")
self._value = value
@property
def value(self) -> Name:
return self._value
def __repr__(self) -> str:
return "<DirectoryName(value={})>".format(self.value)
def __eq__(self, other: object) -> bool:
if not isinstance(other, DirectoryName):
return NotImplemented
return self.value == other.value
def __hash__(self) -> int:
return hash(self.value)
class RegisteredID(GeneralName):
def __init__(self, value: ObjectIdentifier) -> None:
if not isinstance(value, ObjectIdentifier):
raise TypeError("value must be an ObjectIdentifier")
self._value = value
@property
def value(self) -> ObjectIdentifier:
return self._value
def __repr__(self) -> str:
return "<RegisteredID(value={})>".format(self.value)
def __eq__(self, other: object) -> bool:
if not isinstance(other, RegisteredID):
return NotImplemented
return self.value == other.value
def __hash__(self) -> int:
return hash(self.value)
class IPAddress(GeneralName):
def __init__(self, value: _IPADDRESS_TYPES) -> None:
if not isinstance(
value,
(
ipaddress.IPv4Address,
ipaddress.IPv6Address,
ipaddress.IPv4Network,
ipaddress.IPv6Network,
),
):
raise TypeError(
"value must be an instance of ipaddress.IPv4Address, "
"ipaddress.IPv6Address, ipaddress.IPv4Network, or "
"ipaddress.IPv6Network"
)
self._value = value
@property
def value(self) -> _IPADDRESS_TYPES:
return self._value
def _packed(self) -> bytes:
if isinstance(
self.value, (ipaddress.IPv4Address, ipaddress.IPv6Address)
):
return self.value.packed
else:
return (
self.value.network_address.packed + self.value.netmask.packed
)
def __repr__(self) -> str:
return "<IPAddress(value={})>".format(self.value)
def __eq__(self, other: object) -> bool:
if not isinstance(other, IPAddress):
return NotImplemented
return self.value == other.value
def __hash__(self) -> int:
return hash(self.value)
class OtherName(GeneralName):
def __init__(self, type_id: ObjectIdentifier, value: bytes) -> None:
if not isinstance(type_id, ObjectIdentifier):
raise TypeError("type_id must be an ObjectIdentifier")
if not isinstance(value, bytes):
raise TypeError("value must be a binary string")
self._type_id = type_id
self._value = value
@property
def type_id(self) -> ObjectIdentifier:
return self._type_id
@property
def value(self) -> bytes:
return self._value
def __repr__(self) -> str:
return "<OtherName(type_id={}, value={!r})>".format(
self.type_id, self.value
)
def __eq__(self, other: object) -> bool:
if not isinstance(other, OtherName):
return NotImplemented
return self.type_id == other.type_id and self.value == other.value
def __hash__(self) -> int:
return hash((self.type_id, self.value))

View File

@@ -0,0 +1,445 @@
# 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 re
import sys
import typing
import warnings
from cryptography import utils
from cryptography.hazmat.bindings._rust import (
x509 as rust_x509,
)
from cryptography.x509.oid import NameOID, ObjectIdentifier
class _ASN1Type(utils.Enum):
BitString = 3
OctetString = 4
UTF8String = 12
NumericString = 18
PrintableString = 19
T61String = 20
IA5String = 22
UTCTime = 23
GeneralizedTime = 24
VisibleString = 26
UniversalString = 28
BMPString = 30
_ASN1_TYPE_TO_ENUM = {i.value: i for i in _ASN1Type}
_NAMEOID_DEFAULT_TYPE: typing.Dict[ObjectIdentifier, _ASN1Type] = {
NameOID.COUNTRY_NAME: _ASN1Type.PrintableString,
NameOID.JURISDICTION_COUNTRY_NAME: _ASN1Type.PrintableString,
NameOID.SERIAL_NUMBER: _ASN1Type.PrintableString,
NameOID.DN_QUALIFIER: _ASN1Type.PrintableString,
NameOID.EMAIL_ADDRESS: _ASN1Type.IA5String,
NameOID.DOMAIN_COMPONENT: _ASN1Type.IA5String,
}
# Type alias
_OidNameMap = typing.Mapping[ObjectIdentifier, str]
#: Short attribute names from RFC 4514:
#: https://tools.ietf.org/html/rfc4514#page-7
_NAMEOID_TO_NAME: _OidNameMap = {
NameOID.COMMON_NAME: "CN",
NameOID.LOCALITY_NAME: "L",
NameOID.STATE_OR_PROVINCE_NAME: "ST",
NameOID.ORGANIZATION_NAME: "O",
NameOID.ORGANIZATIONAL_UNIT_NAME: "OU",
NameOID.COUNTRY_NAME: "C",
NameOID.STREET_ADDRESS: "STREET",
NameOID.DOMAIN_COMPONENT: "DC",
NameOID.USER_ID: "UID",
}
_NAME_TO_NAMEOID = {v: k for k, v in _NAMEOID_TO_NAME.items()}
def _escape_dn_value(val: typing.Union[str, bytes]) -> str:
"""Escape special characters in RFC4514 Distinguished Name value."""
if not val:
return ""
# RFC 4514 Section 2.4 defines the value as being the # (U+0023) character
# followed by the hexadecimal encoding of the octets.
if isinstance(val, bytes):
return "#" + binascii.hexlify(val).decode("utf8")
# See https://tools.ietf.org/html/rfc4514#section-2.4
val = val.replace("\\", "\\\\")
val = val.replace('"', '\\"')
val = val.replace("+", "\\+")
val = val.replace(",", "\\,")
val = val.replace(";", "\\;")
val = val.replace("<", "\\<")
val = val.replace(">", "\\>")
val = val.replace("\0", "\\00")
if val[0] in ("#", " "):
val = "\\" + val
if val[-1] == " ":
val = val[:-1] + "\\ "
return val
def _unescape_dn_value(val: str) -> str:
if not val:
return ""
# See https://tools.ietf.org/html/rfc4514#section-3
# special = escaped / SPACE / SHARP / EQUALS
# escaped = DQUOTE / PLUS / COMMA / SEMI / LANGLE / RANGLE
def sub(m):
val = m.group(1)
# Regular escape
if len(val) == 1:
return val
# Hex-value scape
return chr(int(val, 16))
return _RFC4514NameParser._PAIR_RE.sub(sub, val)
class NameAttribute:
def __init__(
self,
oid: ObjectIdentifier,
value: typing.Union[str, bytes],
_type: typing.Optional[_ASN1Type] = None,
*,
_validate: bool = True,
) -> None:
if not isinstance(oid, ObjectIdentifier):
raise TypeError(
"oid argument must be an ObjectIdentifier instance."
)
if _type == _ASN1Type.BitString:
if oid != NameOID.X500_UNIQUE_IDENTIFIER:
raise TypeError(
"oid must be X500_UNIQUE_IDENTIFIER for BitString type."
)
if not isinstance(value, bytes):
raise TypeError("value must be bytes for BitString")
else:
if not isinstance(value, str):
raise TypeError("value argument must be a str")
if (
oid == NameOID.COUNTRY_NAME
or oid == NameOID.JURISDICTION_COUNTRY_NAME
):
assert isinstance(value, str)
c_len = len(value.encode("utf8"))
if c_len != 2 and _validate is True:
raise ValueError(
"Country name must be a 2 character country code"
)
elif c_len != 2:
warnings.warn(
"Country names should be two characters, but the "
"attribute is {} characters in length.".format(c_len),
stacklevel=2,
)
# The appropriate ASN1 string type varies by OID and is defined across
# multiple RFCs including 2459, 3280, and 5280. In general UTF8String
# is preferred (2459), but 3280 and 5280 specify several OIDs with
# alternate types. This means when we see the sentinel value we need
# to look up whether the OID has a non-UTF8 type. If it does, set it
# to that. Otherwise, UTF8!
if _type is None:
_type = _NAMEOID_DEFAULT_TYPE.get(oid, _ASN1Type.UTF8String)
if not isinstance(_type, _ASN1Type):
raise TypeError("_type must be from the _ASN1Type enum")
self._oid = oid
self._value = value
self._type = _type
@property
def oid(self) -> ObjectIdentifier:
return self._oid
@property
def value(self) -> typing.Union[str, bytes]:
return self._value
@property
def rfc4514_attribute_name(self) -> str:
"""
The short attribute name (for example "CN") if available,
otherwise the OID dotted string.
"""
return _NAMEOID_TO_NAME.get(self.oid, self.oid.dotted_string)
def rfc4514_string(
self, attr_name_overrides: typing.Optional[_OidNameMap] = None
) -> str:
"""
Format as RFC4514 Distinguished Name string.
Use short attribute name if available, otherwise fall back to OID
dotted string.
"""
attr_name = (
attr_name_overrides.get(self.oid) if attr_name_overrides else None
)
if attr_name is None:
attr_name = self.rfc4514_attribute_name
return f"{attr_name}={_escape_dn_value(self.value)}"
def __eq__(self, other: object) -> bool:
if not isinstance(other, NameAttribute):
return NotImplemented
return self.oid == other.oid and self.value == other.value
def __hash__(self) -> int:
return hash((self.oid, self.value))
def __repr__(self) -> str:
return "<NameAttribute(oid={0.oid}, value={0.value!r})>".format(self)
class RelativeDistinguishedName:
def __init__(self, attributes: typing.Iterable[NameAttribute]):
attributes = list(attributes)
if not attributes:
raise ValueError("a relative distinguished name cannot be empty")
if not all(isinstance(x, NameAttribute) for x in attributes):
raise TypeError("attributes must be an iterable of NameAttribute")
# Keep list and frozenset to preserve attribute order where it matters
self._attributes = attributes
self._attribute_set = frozenset(attributes)
if len(self._attribute_set) != len(attributes):
raise ValueError("duplicate attributes are not allowed")
def get_attributes_for_oid(
self, oid: ObjectIdentifier
) -> typing.List[NameAttribute]:
return [i for i in self if i.oid == oid]
def rfc4514_string(
self, attr_name_overrides: typing.Optional[_OidNameMap] = None
) -> str:
"""
Format as RFC4514 Distinguished Name string.
Within each RDN, attributes are joined by '+', although that is rarely
used in certificates.
"""
return "+".join(
attr.rfc4514_string(attr_name_overrides)
for attr in self._attributes
)
def __eq__(self, other: object) -> bool:
if not isinstance(other, RelativeDistinguishedName):
return NotImplemented
return self._attribute_set == other._attribute_set
def __hash__(self) -> int:
return hash(self._attribute_set)
def __iter__(self) -> typing.Iterator[NameAttribute]:
return iter(self._attributes)
def __len__(self) -> int:
return len(self._attributes)
def __repr__(self) -> str:
return "<RelativeDistinguishedName({})>".format(self.rfc4514_string())
class Name:
@typing.overload
def __init__(self, attributes: typing.Iterable[NameAttribute]) -> None:
...
@typing.overload
def __init__(
self, attributes: typing.Iterable[RelativeDistinguishedName]
) -> None:
...
def __init__(
self,
attributes: typing.Iterable[
typing.Union[NameAttribute, RelativeDistinguishedName]
],
) -> None:
attributes = list(attributes)
if all(isinstance(x, NameAttribute) for x in attributes):
self._attributes = [
RelativeDistinguishedName([typing.cast(NameAttribute, x)])
for x in attributes
]
elif all(isinstance(x, RelativeDistinguishedName) for x in attributes):
self._attributes = typing.cast(
typing.List[RelativeDistinguishedName], attributes
)
else:
raise TypeError(
"attributes must be a list of NameAttribute"
" or a list RelativeDistinguishedName"
)
@classmethod
def from_rfc4514_string(cls, data: str) -> "Name":
return _RFC4514NameParser(data).parse()
def rfc4514_string(
self, attr_name_overrides: typing.Optional[_OidNameMap] = None
) -> str:
"""
Format as RFC4514 Distinguished Name string.
For example 'CN=foobar.com,O=Foo Corp,C=US'
An X.509 name is a two-level structure: a list of sets of attributes.
Each list element is separated by ',' and within each list element, set
elements are separated by '+'. The latter is almost never used in
real world certificates. According to RFC4514 section 2.1 the
RDNSequence must be reversed when converting to string representation.
"""
return ",".join(
attr.rfc4514_string(attr_name_overrides)
for attr in reversed(self._attributes)
)
def get_attributes_for_oid(
self, oid: ObjectIdentifier
) -> typing.List[NameAttribute]:
return [i for i in self if i.oid == oid]
@property
def rdns(self) -> typing.List[RelativeDistinguishedName]:
return self._attributes
def public_bytes(self, backend: typing.Any = None) -> bytes:
return rust_x509.encode_name_bytes(self)
def __eq__(self, other: object) -> bool:
if not isinstance(other, Name):
return NotImplemented
return self._attributes == other._attributes
def __hash__(self) -> int:
# TODO: this is relatively expensive, if this looks like a bottleneck
# for you, consider optimizing!
return hash(tuple(self._attributes))
def __iter__(self) -> typing.Iterator[NameAttribute]:
for rdn in self._attributes:
for ava in rdn:
yield ava
def __len__(self) -> int:
return sum(len(rdn) for rdn in self._attributes)
def __repr__(self) -> str:
rdns = ",".join(attr.rfc4514_string() for attr in self._attributes)
return "<Name({})>".format(rdns)
class _RFC4514NameParser:
_OID_RE = re.compile(r"(0|([1-9]\d*))(\.(0|([1-9]\d*)))+")
_DESCR_RE = re.compile(r"[a-zA-Z][a-zA-Z\d-]*")
_PAIR = r"\\([\\ #=\"\+,;<>]|[\da-zA-Z]{2})"
_PAIR_RE = re.compile(_PAIR)
_LUTF1 = r"[\x01-\x1f\x21\x24-\x2A\x2D-\x3A\x3D\x3F-\x5B\x5D-\x7F]"
_SUTF1 = r"[\x01-\x21\x23-\x2A\x2D-\x3A\x3D\x3F-\x5B\x5D-\x7F]"
_TUTF1 = r"[\x01-\x1F\x21\x23-\x2A\x2D-\x3A\x3D\x3F-\x5B\x5D-\x7F]"
_UTFMB = rf"[\x80-{chr(sys.maxunicode)}]"
_LEADCHAR = rf"{_LUTF1}|{_UTFMB}"
_STRINGCHAR = rf"{_SUTF1}|{_UTFMB}"
_TRAILCHAR = rf"{_TUTF1}|{_UTFMB}"
_STRING_RE = re.compile(
rf"""
(
({_LEADCHAR}|{_PAIR})
(
({_STRINGCHAR}|{_PAIR})*
({_TRAILCHAR}|{_PAIR})
)?
)?
""",
re.VERBOSE,
)
_HEXSTRING_RE = re.compile(r"#([\da-zA-Z]{2})+")
def __init__(self, data: str) -> None:
self._data = data
self._idx = 0
def _has_data(self) -> bool:
return self._idx < len(self._data)
def _peek(self) -> typing.Optional[str]:
if self._has_data():
return self._data[self._idx]
return None
def _read_char(self, ch: str) -> None:
if self._peek() != ch:
raise ValueError
self._idx += 1
def _read_re(self, pat) -> str:
match = pat.match(self._data, pos=self._idx)
if match is None:
raise ValueError
val = match.group()
self._idx += len(val)
return val
def parse(self) -> Name:
rdns = [self._parse_rdn()]
while self._has_data():
self._read_char(",")
rdns.append(self._parse_rdn())
return Name(rdns)
def _parse_rdn(self) -> RelativeDistinguishedName:
nas = [self._parse_na()]
while self._peek() == "+":
self._read_char("+")
nas.append(self._parse_na())
return RelativeDistinguishedName(nas)
def _parse_na(self) -> NameAttribute:
try:
oid_value = self._read_re(self._OID_RE)
except ValueError:
name = self._read_re(self._DESCR_RE)
oid = _NAME_TO_NAMEOID.get(name)
if oid is None:
raise ValueError
else:
oid = ObjectIdentifier(oid_value)
self._read_char("=")
if self._peek() == "#":
value = self._read_re(self._HEXSTRING_RE)
value = binascii.unhexlify(value[1:]).decode()
else:
raw_value = self._read_re(self._STRING_RE)
value = _unescape_dn_value(raw_value)
return NameAttribute(oid, value)

View File

@@ -0,0 +1,551 @@
# 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 datetime
import typing
from cryptography import utils
from cryptography import x509
from cryptography.hazmat.bindings._rust import ocsp
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric.types import (
CERTIFICATE_PRIVATE_KEY_TYPES,
)
from cryptography.x509.base import (
_EARLIEST_UTC_TIME,
_convert_to_naive_utc_time,
_reject_duplicate_extension,
)
class OCSPResponderEncoding(utils.Enum):
HASH = "By Hash"
NAME = "By Name"
class OCSPResponseStatus(utils.Enum):
SUCCESSFUL = 0
MALFORMED_REQUEST = 1
INTERNAL_ERROR = 2
TRY_LATER = 3
SIG_REQUIRED = 5
UNAUTHORIZED = 6
_ALLOWED_HASHES = (
hashes.SHA1,
hashes.SHA224,
hashes.SHA256,
hashes.SHA384,
hashes.SHA512,
)
def _verify_algorithm(algorithm: hashes.HashAlgorithm) -> None:
if not isinstance(algorithm, _ALLOWED_HASHES):
raise ValueError(
"Algorithm must be SHA1, SHA224, SHA256, SHA384, or SHA512"
)
class OCSPCertStatus(utils.Enum):
GOOD = 0
REVOKED = 1
UNKNOWN = 2
class _SingleResponse:
def __init__(
self,
cert: x509.Certificate,
issuer: x509.Certificate,
algorithm: hashes.HashAlgorithm,
cert_status: OCSPCertStatus,
this_update: datetime.datetime,
next_update: typing.Optional[datetime.datetime],
revocation_time: typing.Optional[datetime.datetime],
revocation_reason: typing.Optional[x509.ReasonFlags],
):
if not isinstance(cert, x509.Certificate) or not isinstance(
issuer, x509.Certificate
):
raise TypeError("cert and issuer must be a Certificate")
_verify_algorithm(algorithm)
if not isinstance(this_update, datetime.datetime):
raise TypeError("this_update must be a datetime object")
if next_update is not None and not isinstance(
next_update, datetime.datetime
):
raise TypeError("next_update must be a datetime object or None")
self._cert = cert
self._issuer = issuer
self._algorithm = algorithm
self._this_update = this_update
self._next_update = next_update
if not isinstance(cert_status, OCSPCertStatus):
raise TypeError(
"cert_status must be an item from the OCSPCertStatus enum"
)
if cert_status is not OCSPCertStatus.REVOKED:
if revocation_time is not None:
raise ValueError(
"revocation_time can only be provided if the certificate "
"is revoked"
)
if revocation_reason is not None:
raise ValueError(
"revocation_reason can only be provided if the certificate"
" is revoked"
)
else:
if not isinstance(revocation_time, datetime.datetime):
raise TypeError("revocation_time must be a datetime object")
revocation_time = _convert_to_naive_utc_time(revocation_time)
if revocation_time < _EARLIEST_UTC_TIME:
raise ValueError(
"The revocation_time must be on or after"
" 1950 January 1."
)
if revocation_reason is not None and not isinstance(
revocation_reason, x509.ReasonFlags
):
raise TypeError(
"revocation_reason must be an item from the ReasonFlags "
"enum or None"
)
self._cert_status = cert_status
self._revocation_time = revocation_time
self._revocation_reason = revocation_reason
class OCSPRequest(metaclass=abc.ABCMeta):
@abc.abstractproperty
def issuer_key_hash(self) -> bytes:
"""
The hash of the issuer public key
"""
@abc.abstractproperty
def issuer_name_hash(self) -> bytes:
"""
The hash of the issuer name
"""
@abc.abstractproperty
def hash_algorithm(self) -> hashes.HashAlgorithm:
"""
The hash algorithm used in the issuer name and key hashes
"""
@abc.abstractproperty
def serial_number(self) -> int:
"""
The serial number of the cert whose status is being checked
"""
@abc.abstractmethod
def public_bytes(self, encoding: serialization.Encoding) -> bytes:
"""
Serializes the request to DER
"""
@abc.abstractproperty
def extensions(self) -> x509.Extensions:
"""
The list of request extensions. Not single request extensions.
"""
class OCSPSingleResponse(metaclass=abc.ABCMeta):
@abc.abstractproperty
def certificate_status(self) -> OCSPCertStatus:
"""
The status of the certificate (an element from the OCSPCertStatus enum)
"""
@abc.abstractproperty
def revocation_time(self) -> typing.Optional[datetime.datetime]:
"""
The date of when the certificate was revoked or None if not
revoked.
"""
@abc.abstractproperty
def revocation_reason(self) -> typing.Optional[x509.ReasonFlags]:
"""
The reason the certificate was revoked or None if not specified or
not revoked.
"""
@abc.abstractproperty
def this_update(self) -> datetime.datetime:
"""
The most recent time at which the status being indicated is known by
the responder to have been correct
"""
@abc.abstractproperty
def next_update(self) -> typing.Optional[datetime.datetime]:
"""
The time when newer information will be available
"""
@abc.abstractproperty
def issuer_key_hash(self) -> bytes:
"""
The hash of the issuer public key
"""
@abc.abstractproperty
def issuer_name_hash(self) -> bytes:
"""
The hash of the issuer name
"""
@abc.abstractproperty
def hash_algorithm(self) -> hashes.HashAlgorithm:
"""
The hash algorithm used in the issuer name and key hashes
"""
@abc.abstractproperty
def serial_number(self) -> int:
"""
The serial number of the cert whose status is being checked
"""
class OCSPResponse(metaclass=abc.ABCMeta):
@abc.abstractproperty
def responses(self) -> typing.Iterator[OCSPSingleResponse]:
"""
An iterator over the individual SINGLERESP structures in the
response
"""
@abc.abstractproperty
def response_status(self) -> OCSPResponseStatus:
"""
The status of the response. This is a value from the OCSPResponseStatus
enumeration
"""
@abc.abstractproperty
def signature_algorithm_oid(self) -> x509.ObjectIdentifier:
"""
The ObjectIdentifier of the signature algorithm
"""
@abc.abstractproperty
def signature_hash_algorithm(
self,
) -> typing.Optional[hashes.HashAlgorithm]:
"""
Returns a HashAlgorithm corresponding to the type of the digest signed
"""
@abc.abstractproperty
def signature(self) -> bytes:
"""
The signature bytes
"""
@abc.abstractproperty
def tbs_response_bytes(self) -> bytes:
"""
The tbsResponseData bytes
"""
@abc.abstractproperty
def certificates(self) -> typing.List[x509.Certificate]:
"""
A list of certificates used to help build a chain to verify the OCSP
response. This situation occurs when the OCSP responder uses a delegate
certificate.
"""
@abc.abstractproperty
def responder_key_hash(self) -> typing.Optional[bytes]:
"""
The responder's key hash or None
"""
@abc.abstractproperty
def responder_name(self) -> typing.Optional[x509.Name]:
"""
The responder's Name or None
"""
@abc.abstractproperty
def produced_at(self) -> datetime.datetime:
"""
The time the response was produced
"""
@abc.abstractproperty
def certificate_status(self) -> OCSPCertStatus:
"""
The status of the certificate (an element from the OCSPCertStatus enum)
"""
@abc.abstractproperty
def revocation_time(self) -> typing.Optional[datetime.datetime]:
"""
The date of when the certificate was revoked or None if not
revoked.
"""
@abc.abstractproperty
def revocation_reason(self) -> typing.Optional[x509.ReasonFlags]:
"""
The reason the certificate was revoked or None if not specified or
not revoked.
"""
@abc.abstractproperty
def this_update(self) -> datetime.datetime:
"""
The most recent time at which the status being indicated is known by
the responder to have been correct
"""
@abc.abstractproperty
def next_update(self) -> typing.Optional[datetime.datetime]:
"""
The time when newer information will be available
"""
@abc.abstractproperty
def issuer_key_hash(self) -> bytes:
"""
The hash of the issuer public key
"""
@abc.abstractproperty
def issuer_name_hash(self) -> bytes:
"""
The hash of the issuer name
"""
@abc.abstractproperty
def hash_algorithm(self) -> hashes.HashAlgorithm:
"""
The hash algorithm used in the issuer name and key hashes
"""
@abc.abstractproperty
def serial_number(self) -> int:
"""
The serial number of the cert whose status is being checked
"""
@abc.abstractproperty
def extensions(self) -> x509.Extensions:
"""
The list of response extensions. Not single response extensions.
"""
@abc.abstractproperty
def single_extensions(self) -> x509.Extensions:
"""
The list of single response extensions. Not response extensions.
"""
@abc.abstractmethod
def public_bytes(self, encoding: serialization.Encoding) -> bytes:
"""
Serializes the response to DER
"""
class OCSPRequestBuilder:
def __init__(
self,
request: typing.Optional[
typing.Tuple[
x509.Certificate, x509.Certificate, hashes.HashAlgorithm
]
] = None,
extensions: typing.List[x509.Extension[x509.ExtensionType]] = [],
) -> None:
self._request = request
self._extensions = extensions
def add_certificate(
self,
cert: x509.Certificate,
issuer: x509.Certificate,
algorithm: hashes.HashAlgorithm,
) -> "OCSPRequestBuilder":
if self._request is not None:
raise ValueError("Only one certificate can be added to a request")
_verify_algorithm(algorithm)
if not isinstance(cert, x509.Certificate) or not isinstance(
issuer, x509.Certificate
):
raise TypeError("cert and issuer must be a Certificate")
return OCSPRequestBuilder((cert, issuer, algorithm), self._extensions)
def add_extension(
self, extval: x509.ExtensionType, critical: bool
) -> "OCSPRequestBuilder":
if not isinstance(extval, x509.ExtensionType):
raise TypeError("extension must be an ExtensionType")
extension = x509.Extension(extval.oid, critical, extval)
_reject_duplicate_extension(extension, self._extensions)
return OCSPRequestBuilder(
self._request, self._extensions + [extension]
)
def build(self) -> OCSPRequest:
if self._request is None:
raise ValueError("You must add a certificate before building")
return ocsp.create_ocsp_request(self)
class OCSPResponseBuilder:
def __init__(
self,
response: typing.Optional[_SingleResponse] = None,
responder_id: typing.Optional[
typing.Tuple[x509.Certificate, OCSPResponderEncoding]
] = None,
certs: typing.Optional[typing.List[x509.Certificate]] = None,
extensions: typing.List[x509.Extension[x509.ExtensionType]] = [],
):
self._response = response
self._responder_id = responder_id
self._certs = certs
self._extensions = extensions
def add_response(
self,
cert: x509.Certificate,
issuer: x509.Certificate,
algorithm: hashes.HashAlgorithm,
cert_status: OCSPCertStatus,
this_update: datetime.datetime,
next_update: typing.Optional[datetime.datetime],
revocation_time: typing.Optional[datetime.datetime],
revocation_reason: typing.Optional[x509.ReasonFlags],
) -> "OCSPResponseBuilder":
if self._response is not None:
raise ValueError("Only one response per OCSPResponse.")
singleresp = _SingleResponse(
cert,
issuer,
algorithm,
cert_status,
this_update,
next_update,
revocation_time,
revocation_reason,
)
return OCSPResponseBuilder(
singleresp,
self._responder_id,
self._certs,
self._extensions,
)
def responder_id(
self, encoding: OCSPResponderEncoding, responder_cert: x509.Certificate
) -> "OCSPResponseBuilder":
if self._responder_id is not None:
raise ValueError("responder_id can only be set once")
if not isinstance(responder_cert, x509.Certificate):
raise TypeError("responder_cert must be a Certificate")
if not isinstance(encoding, OCSPResponderEncoding):
raise TypeError(
"encoding must be an element from OCSPResponderEncoding"
)
return OCSPResponseBuilder(
self._response,
(responder_cert, encoding),
self._certs,
self._extensions,
)
def certificates(
self, certs: typing.Iterable[x509.Certificate]
) -> "OCSPResponseBuilder":
if self._certs is not None:
raise ValueError("certificates may only be set once")
certs = list(certs)
if len(certs) == 0:
raise ValueError("certs must not be an empty list")
if not all(isinstance(x, x509.Certificate) for x in certs):
raise TypeError("certs must be a list of Certificates")
return OCSPResponseBuilder(
self._response,
self._responder_id,
certs,
self._extensions,
)
def add_extension(
self, extval: x509.ExtensionType, critical: bool
) -> "OCSPResponseBuilder":
if not isinstance(extval, x509.ExtensionType):
raise TypeError("extension must be an ExtensionType")
extension = x509.Extension(extval.oid, critical, extval)
_reject_duplicate_extension(extension, self._extensions)
return OCSPResponseBuilder(
self._response,
self._responder_id,
self._certs,
self._extensions + [extension],
)
def sign(
self,
private_key: CERTIFICATE_PRIVATE_KEY_TYPES,
algorithm: typing.Optional[hashes.HashAlgorithm],
) -> OCSPResponse:
if self._response is None:
raise ValueError("You must add a response before signing")
if self._responder_id is None:
raise ValueError("You must add a responder_id before signing")
return ocsp.create_ocsp_response(
OCSPResponseStatus.SUCCESSFUL, self, private_key, algorithm
)
@classmethod
def build_unsuccessful(
cls, response_status: OCSPResponseStatus
) -> OCSPResponse:
if not isinstance(response_status, OCSPResponseStatus):
raise TypeError(
"response_status must be an item from OCSPResponseStatus"
)
if response_status is OCSPResponseStatus.SUCCESSFUL:
raise ValueError("response_status cannot be SUCCESSFUL")
return ocsp.create_ocsp_response(response_status, None, None, None)
def load_der_ocsp_request(data: bytes) -> OCSPRequest:
return ocsp.load_der_ocsp_request(data)
def load_der_ocsp_response(data: bytes) -> OCSPResponse:
return ocsp.load_der_ocsp_response(data)

View File

@@ -0,0 +1,32 @@
# 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._oid import (
AttributeOID,
AuthorityInformationAccessOID,
CRLEntryExtensionOID,
CertificatePoliciesOID,
ExtendedKeyUsageOID,
ExtensionOID,
NameOID,
OCSPExtensionOID,
ObjectIdentifier,
SignatureAlgorithmOID,
SubjectInformationAccessOID,
)
__all__ = [
"AttributeOID",
"AuthorityInformationAccessOID",
"CRLEntryExtensionOID",
"CertificatePoliciesOID",
"ExtendedKeyUsageOID",
"ExtensionOID",
"NameOID",
"OCSPExtensionOID",
"ObjectIdentifier",
"SignatureAlgorithmOID",
"SubjectInformationAccessOID",
]