This commit is contained in:
2024-11-29 18:15:30 +00:00
parent 40aade2d8e
commit bc9415586e
5298 changed files with 1938676 additions and 80 deletions

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.
from __future__ import annotations
__all__ = [
"__author__",
"__copyright__",
"__version__",
]
__version__ = "44.0.0"
__author__ = "The Python Cryptographic Authority and individual contributors"
__copyright__ = f"Copyright 2013-2024 {__author__}"

View File

@ -0,0 +1,26 @@
# 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 __future__ import annotations
import sys
import warnings
from cryptography import utils
from cryptography.__about__ import __author__, __copyright__, __version__
__all__ = [
"__author__",
"__copyright__",
"__version__",
]
if sys.version_info[:2] == (3, 7):
warnings.warn(
"Python 3.7 is no longer supported by the Python core team "
"and support for it is deprecated in cryptography. A future "
"release of cryptography will remove support for Python 3.7.",
utils.CryptographyDeprecationWarning,
stacklevel=2,
)

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.
from __future__ import annotations
import typing
from cryptography.hazmat.bindings._rust import exceptions as rust_exceptions
if typing.TYPE_CHECKING:
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
_Reasons = rust_exceptions._Reasons
class UnsupportedAlgorithm(Exception):
def __init__(self, message: str, reason: _Reasons | None = None) -> None:
super().__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: list[rust_openssl.OpenSSLError]
) -> None:
super().__init__(msg)
self.err_code = err_code
class InvalidKey(Exception):
pass

View File

@ -0,0 +1,223 @@
# 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 __future__ import annotations
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: bytes | str,
backend: typing.Any = None,
) -> 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 | str, ttl: int | None = 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 | str, 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 | str) -> 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 | str) -> tuple[int, bytes]:
if not isinstance(token, (str, bytes)):
raise TypeError("token must be bytes or str")
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: tuple[int, int] | None,
) -> 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 | str) -> 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 | str, ttl: int | None = 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 | str, 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
def extract_timestamp(self, msg: bytes | str) -> int:
for f in self._fernets:
try:
return f.extract_timestamp(msg)
except InvalidToken:
pass
raise InvalidToken

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.
from __future__ import annotations
"""
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,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.
from __future__ import annotations
from cryptography.hazmat.bindings._rust import (
ObjectIdentifier as ObjectIdentifier,
)
from cryptography.hazmat.primitives import hashes
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")
MS_CERTIFICATE_TEMPLATE = ObjectIdentifier("1.3.6.1.4.1.311.21.7")
ADMISSIONS = ObjectIdentifier("1.3.36.8.3.3")
class OCSPExtensionOID:
NONCE = ObjectIdentifier("1.3.6.1.5.5.7.48.1.2")
ACCEPTABLE_RESPONSES = ObjectIdentifier("1.3.6.1.5.5.7.48.1.4")
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_IDENTIFIER = ObjectIdentifier("2.5.4.97")
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")
INITIALS = ObjectIdentifier("2.5.4.43")
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: dict[ObjectIdentifier, hashes.HashAlgorithm | None] = {
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.RSA_WITH_SHA3_224: hashes.SHA3_224(),
SignatureAlgorithmOID.RSA_WITH_SHA3_256: hashes.SHA3_256(),
SignatureAlgorithmOID.RSA_WITH_SHA3_384: hashes.SHA3_384(),
SignatureAlgorithmOID.RSA_WITH_SHA3_512: hashes.SHA3_512(),
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.ECDSA_WITH_SHA3_224: hashes.SHA3_224(),
SignatureAlgorithmOID.ECDSA_WITH_SHA3_256: hashes.SHA3_256(),
SignatureAlgorithmOID.ECDSA_WITH_SHA3_384: hashes.SHA3_384(),
SignatureAlgorithmOID.ECDSA_WITH_SHA3_512: hashes.SHA3_512(),
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 PublicKeyAlgorithmOID:
DSA = ObjectIdentifier("1.2.840.10040.4.1")
EC_PUBLIC_KEY = ObjectIdentifier("1.2.840.10045.2.1")
RSAES_PKCS1_v1_5 = ObjectIdentifier("1.2.840.113549.1.1.1")
RSASSA_PSS = ObjectIdentifier("1.2.840.113549.1.1.10")
X25519 = ObjectIdentifier("1.3.101.110")
X448 = ObjectIdentifier("1.3.101.111")
ED25519 = ObjectIdentifier("1.3.101.112")
ED448 = ObjectIdentifier("1.3.101.113")
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")
CERTIFICATE_TRANSPARENCY = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.4")
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)"
),
PublicKeyAlgorithmOID.DSA: "dsaEncryption",
PublicKeyAlgorithmOID.EC_PUBLIC_KEY: "id-ecPublicKey",
PublicKeyAlgorithmOID.RSAES_PKCS1_v1_5: "rsaEncryption",
PublicKeyAlgorithmOID.RSASSA_PSS: "rsassaPss",
PublicKeyAlgorithmOID.X25519: "X25519",
PublicKeyAlgorithmOID.X448: "X448",
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",
ExtensionOID.MS_CERTIFICATE_TEMPLATE: "msCertificateTemplate",
ExtensionOID.ADMISSIONS: "Admissions",
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,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.
from __future__ import annotations
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 __future__ import annotations
from cryptography.hazmat.backends.openssl.backend import backend
__all__ = ["backend"]

View File

@ -0,0 +1,285 @@
# 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 __future__ import annotations
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
from cryptography.hazmat.bindings.openssl import binding
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives._asymmetric import AsymmetricPadding
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric import utils as asym_utils
from cryptography.hazmat.primitives.asymmetric.padding import (
MGF1,
OAEP,
PSS,
PKCS1v15,
)
from cryptography.hazmat.primitives.ciphers import (
CipherAlgorithm,
)
from cryptography.hazmat.primitives.ciphers.algorithms import (
AES,
)
from cryptography.hazmat.primitives.ciphers.modes import (
CBC,
Mode,
)
class Backend:
"""
OpenSSL API binding interfaces.
"""
name = "openssl"
# TripleDES encryption is disallowed/deprecated throughout 2023 in
# FIPS 140-3. To keep it simple we denylist any use of TripleDES (TDEA).
_fips_ciphers = (AES,)
# Sometimes SHA1 is still permissible. That logic is contained
# within the various *_supported methods.
_fips_hashes = (
hashes.SHA224,
hashes.SHA256,
hashes.SHA384,
hashes.SHA512,
hashes.SHA512_224,
hashes.SHA512_256,
hashes.SHA3_224,
hashes.SHA3_256,
hashes.SHA3_384,
hashes.SHA3_512,
hashes.SHAKE128,
hashes.SHAKE256,
)
_fips_ecdh_curves = (
ec.SECP224R1,
ec.SECP256R1,
ec.SECP384R1,
ec.SECP521R1,
)
_fips_rsa_min_key_size = 2048
_fips_rsa_min_public_exponent = 65537
_fips_dsa_min_modulus = 1 << 2048
_fips_dh_min_key_size = 2048
_fips_dh_min_modulus = 1 << _fips_dh_min_key_size
def __init__(self) -> None:
self._binding = binding.Binding()
self._ffi = self._binding.ffi
self._lib = self._binding.lib
self._fips_enabled = rust_openssl.is_fips_enabled()
def __repr__(self) -> str:
return (
f"<OpenSSLBackend(version: {self.openssl_version_text()}, "
f"FIPS: {self._fips_enabled}, "
f"Legacy: {rust_openssl._legacy_provider_loaded})>"
)
def openssl_assert(self, ok: bool) -> None:
return binding._openssl_assert(ok)
def _enable_fips(self) -> None:
# This function enables FIPS mode for OpenSSL 3.0.0 on installs that
# have the FIPS provider installed properly.
rust_openssl.enable_fips(rust_openssl._providers)
assert rust_openssl.is_fips_enabled()
self._fips_enabled = rust_openssl.is_fips_enabled()
def openssl_version_text(self) -> str:
"""
Friendly string name of the loaded OpenSSL library. This is not
necessarily the same version as it was compiled against.
Example: OpenSSL 3.2.1 30 Jan 2024
"""
return rust_openssl.openssl_version_text()
def openssl_version_number(self) -> int:
return rust_openssl.openssl_version()
def hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool:
if self._fips_enabled and not isinstance(algorithm, self._fips_hashes):
return False
return rust_openssl.hashes.hash_supported(algorithm)
def signature_hash_supported(
self, algorithm: hashes.HashAlgorithm
) -> bool:
# Dedicated check for hashing algorithm use in message digest for
# signatures, e.g. RSA PKCS#1 v1.5 SHA1 (sha1WithRSAEncryption).
if self._fips_enabled and isinstance(algorithm, hashes.SHA1):
return False
return self.hash_supported(algorithm)
def scrypt_supported(self) -> bool:
if self._fips_enabled:
return False
else:
return hasattr(rust_openssl.kdf.Scrypt, "derive")
def argon2_supported(self) -> bool:
if self._fips_enabled:
return False
else:
return hasattr(rust_openssl.kdf.Argon2id, "derive")
def hmac_supported(self, algorithm: hashes.HashAlgorithm) -> bool:
# FIPS mode still allows SHA1 for HMAC
if self._fips_enabled and isinstance(algorithm, hashes.SHA1):
return True
return self.hash_supported(algorithm)
def cipher_supported(self, cipher: CipherAlgorithm, mode: Mode) -> bool:
if self._fips_enabled:
# FIPS mode requires AES. TripleDES is disallowed/deprecated in
# FIPS 140-3.
if not isinstance(cipher, self._fips_ciphers):
return False
return rust_openssl.ciphers.cipher_supported(cipher, mode)
def pbkdf2_hmac_supported(self, algorithm: hashes.HashAlgorithm) -> bool:
return self.hmac_supported(algorithm)
def _consume_errors(self) -> list[rust_openssl.OpenSSLError]:
return rust_openssl.capture_error_stack()
def _oaep_hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool:
if self._fips_enabled and isinstance(algorithm, hashes.SHA1):
return False
return isinstance(
algorithm,
(
hashes.SHA1,
hashes.SHA224,
hashes.SHA256,
hashes.SHA384,
hashes.SHA512,
),
)
def rsa_padding_supported(self, padding: AsymmetricPadding) -> bool:
if isinstance(padding, PKCS1v15):
return True
elif isinstance(padding, PSS) and isinstance(padding._mgf, MGF1):
# SHA1 is permissible in MGF1 in FIPS even when SHA1 is blocked
# as signature algorithm.
if self._fips_enabled and isinstance(
padding._mgf._algorithm, hashes.SHA1
):
return True
else:
return self.hash_supported(padding._mgf._algorithm)
elif isinstance(padding, OAEP) and isinstance(padding._mgf, MGF1):
return self._oaep_hash_supported(
padding._mgf._algorithm
) and self._oaep_hash_supported(padding._algorithm)
else:
return False
def rsa_encryption_supported(self, padding: AsymmetricPadding) -> bool:
if self._fips_enabled and isinstance(padding, PKCS1v15):
return False
else:
return self.rsa_padding_supported(padding)
def dsa_supported(self) -> bool:
return (
not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL
and not self._fips_enabled
)
def dsa_hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool:
if not self.dsa_supported():
return False
return self.signature_hash_supported(algorithm)
def cmac_algorithm_supported(self, algorithm) -> bool:
return self.cipher_supported(
algorithm, CBC(b"\x00" * algorithm.block_size)
)
def elliptic_curve_supported(self, curve: ec.EllipticCurve) -> bool:
if self._fips_enabled and not isinstance(
curve, self._fips_ecdh_curves
):
return False
return rust_openssl.ec.curve_supported(curve)
def elliptic_curve_signature_algorithm_supported(
self,
signature_algorithm: ec.EllipticCurveSignatureAlgorithm,
curve: ec.EllipticCurve,
) -> bool:
# We only support ECDSA right now.
if not isinstance(signature_algorithm, ec.ECDSA):
return False
return self.elliptic_curve_supported(curve) and (
isinstance(signature_algorithm.algorithm, asym_utils.Prehashed)
or self.hash_supported(signature_algorithm.algorithm)
)
def elliptic_curve_exchange_algorithm_supported(
self, algorithm: ec.ECDH, curve: ec.EllipticCurve
) -> bool:
return self.elliptic_curve_supported(curve) and isinstance(
algorithm, ec.ECDH
)
def dh_supported(self) -> bool:
return not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL
def dh_x942_serialization_supported(self) -> bool:
return self._lib.Cryptography_HAS_EVP_PKEY_DHX == 1
def x25519_supported(self) -> bool:
if self._fips_enabled:
return False
return True
def x448_supported(self) -> bool:
if self._fips_enabled:
return False
return (
not rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL
and not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL
)
def ed25519_supported(self) -> bool:
if self._fips_enabled:
return False
return True
def ed448_supported(self) -> bool:
if self._fips_enabled:
return False
return (
not rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL
and not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL
)
def ecdsa_deterministic_supported(self) -> bool:
return (
rust_openssl.CRYPTOGRAPHY_OPENSSL_320_OR_GREATER
and not self._fips_enabled
)
def poly1305_supported(self) -> bool:
if self._fips_enabled:
return False
return True
def pkcs7_supported(self) -> bool:
return not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL
backend = Backend()

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,28 @@
# 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 padding
def check_ansix923_padding(data: bytes) -> bool: ...
class PKCS7PaddingContext(padding.PaddingContext):
def __init__(self, block_size: int) -> None: ...
def update(self, data: bytes) -> bytes: ...
def finalize(self) -> bytes: ...
class PKCS7UnpaddingContext(padding.PaddingContext):
def __init__(self, block_size: int) -> None: ...
def update(self, data: bytes) -> bytes: ...
def finalize(self) -> bytes: ...
class ObjectIdentifier:
def __init__(self, val: str) -> None: ...
@property
def dotted_string(self) -> str: ...
@property
def _name(self) -> str: ...
T = typing.TypeVar("T")

View File

@ -0,0 +1,8 @@
# 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
lib = typing.Any
ffi = typing.Any

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.
def decode_dss_signature(signature: bytes) -> tuple[int, int]: ...
def encode_dss_signature(r: int, s: int) -> bytes: ...
def parse_spki_for_data(data: bytes) -> bytes: ...

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.
class _Reasons:
BACKEND_MISSING_INTERFACE: _Reasons
UNSUPPORTED_HASH: _Reasons
UNSUPPORTED_CIPHER: _Reasons
UNSUPPORTED_PADDING: _Reasons
UNSUPPORTED_MGF: _Reasons
UNSUPPORTED_PUBLIC_KEY_ALGORITHM: _Reasons
UNSUPPORTED_ELLIPTIC_CURVE: _Reasons
UNSUPPORTED_SERIALIZATION: _Reasons
UNSUPPORTED_X509: _Reasons
UNSUPPORTED_EXCHANGE_ALGORITHM: _Reasons
UNSUPPORTED_DIFFIE_HELLMAN: _Reasons
UNSUPPORTED_MAC: _Reasons

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 datetime
import typing
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes
from cryptography.x509 import ocsp
class OCSPRequest:
@property
def issuer_key_hash(self) -> bytes: ...
@property
def issuer_name_hash(self) -> bytes: ...
@property
def hash_algorithm(self) -> hashes.HashAlgorithm: ...
@property
def serial_number(self) -> int: ...
def public_bytes(self, encoding: serialization.Encoding) -> bytes: ...
@property
def extensions(self) -> x509.Extensions: ...
class OCSPResponse:
@property
def responses(self) -> typing.Iterator[OCSPSingleResponse]: ...
@property
def response_status(self) -> ocsp.OCSPResponseStatus: ...
@property
def signature_algorithm_oid(self) -> x509.ObjectIdentifier: ...
@property
def signature_hash_algorithm(
self,
) -> hashes.HashAlgorithm | None: ...
@property
def signature(self) -> bytes: ...
@property
def tbs_response_bytes(self) -> bytes: ...
@property
def certificates(self) -> list[x509.Certificate]: ...
@property
def responder_key_hash(self) -> bytes | None: ...
@property
def responder_name(self) -> x509.Name | None: ...
@property
def produced_at(self) -> datetime.datetime: ...
@property
def produced_at_utc(self) -> datetime.datetime: ...
@property
def certificate_status(self) -> ocsp.OCSPCertStatus: ...
@property
def revocation_time(self) -> datetime.datetime | None: ...
@property
def revocation_time_utc(self) -> datetime.datetime | None: ...
@property
def revocation_reason(self) -> x509.ReasonFlags | None: ...
@property
def this_update(self) -> datetime.datetime: ...
@property
def this_update_utc(self) -> datetime.datetime: ...
@property
def next_update(self) -> datetime.datetime | None: ...
@property
def next_update_utc(self) -> datetime.datetime | None: ...
@property
def issuer_key_hash(self) -> bytes: ...
@property
def issuer_name_hash(self) -> bytes: ...
@property
def hash_algorithm(self) -> hashes.HashAlgorithm: ...
@property
def serial_number(self) -> int: ...
@property
def extensions(self) -> x509.Extensions: ...
@property
def single_extensions(self) -> x509.Extensions: ...
def public_bytes(self, encoding: serialization.Encoding) -> bytes: ...
class OCSPSingleResponse:
@property
def certificate_status(self) -> ocsp.OCSPCertStatus: ...
@property
def revocation_time(self) -> datetime.datetime | None: ...
@property
def revocation_time_utc(self) -> datetime.datetime | None: ...
@property
def revocation_reason(self) -> x509.ReasonFlags | None: ...
@property
def this_update(self) -> datetime.datetime: ...
@property
def this_update_utc(self) -> datetime.datetime: ...
@property
def next_update(self) -> datetime.datetime | None: ...
@property
def next_update_utc(self) -> datetime.datetime | None: ...
@property
def issuer_key_hash(self) -> bytes: ...
@property
def issuer_name_hash(self) -> bytes: ...
@property
def hash_algorithm(self) -> hashes.HashAlgorithm: ...
@property
def serial_number(self) -> int: ...
def load_der_ocsp_request(data: bytes) -> ocsp.OCSPRequest: ...
def load_der_ocsp_response(data: bytes) -> ocsp.OCSPResponse: ...
def create_ocsp_request(
builder: ocsp.OCSPRequestBuilder,
) -> ocsp.OCSPRequest: ...
def create_ocsp_response(
status: ocsp.OCSPResponseStatus,
builder: ocsp.OCSPResponseBuilder | None,
private_key: PrivateKeyTypes | None,
hash_algorithm: hashes.HashAlgorithm | None,
) -> ocsp.OCSPResponse: ...

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.hazmat.bindings._rust.openssl import (
aead,
ciphers,
cmac,
dh,
dsa,
ec,
ed448,
ed25519,
hashes,
hmac,
kdf,
keys,
poly1305,
rsa,
x448,
x25519,
)
__all__ = [
"aead",
"ciphers",
"cmac",
"dh",
"dsa",
"ec",
"ed448",
"ed25519",
"hashes",
"hmac",
"kdf",
"keys",
"openssl_version",
"openssl_version_text",
"poly1305",
"raise_openssl_error",
"rsa",
"x448",
"x25519",
]
CRYPTOGRAPHY_IS_LIBRESSL: bool
CRYPTOGRAPHY_IS_BORINGSSL: bool
CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: bool
CRYPTOGRAPHY_OPENSSL_309_OR_GREATER: bool
CRYPTOGRAPHY_OPENSSL_320_OR_GREATER: bool
class Providers: ...
_legacy_provider_loaded: bool
_providers: Providers
def openssl_version() -> int: ...
def openssl_version_text() -> str: ...
def raise_openssl_error() -> typing.NoReturn: ...
def capture_error_stack() -> list[OpenSSLError]: ...
def is_fips_enabled() -> bool: ...
def enable_fips(providers: Providers) -> None: ...
class OpenSSLError:
@property
def lib(self) -> int: ...
@property
def reason(self) -> int: ...
@property
def reason_text(self) -> bytes: ...

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.
class AESGCM:
def __init__(self, key: bytes) -> None: ...
@staticmethod
def generate_key(key_size: int) -> bytes: ...
def encrypt(
self,
nonce: bytes,
data: bytes,
associated_data: bytes | None,
) -> bytes: ...
def decrypt(
self,
nonce: bytes,
data: bytes,
associated_data: bytes | None,
) -> bytes: ...
class ChaCha20Poly1305:
def __init__(self, key: bytes) -> None: ...
@staticmethod
def generate_key() -> bytes: ...
def encrypt(
self,
nonce: bytes,
data: bytes,
associated_data: bytes | None,
) -> bytes: ...
def decrypt(
self,
nonce: bytes,
data: bytes,
associated_data: bytes | None,
) -> bytes: ...
class AESCCM:
def __init__(self, key: bytes, tag_length: int = 16) -> None: ...
@staticmethod
def generate_key(key_size: int) -> bytes: ...
def encrypt(
self,
nonce: bytes,
data: bytes,
associated_data: bytes | None,
) -> bytes: ...
def decrypt(
self,
nonce: bytes,
data: bytes,
associated_data: bytes | None,
) -> bytes: ...
class AESSIV:
def __init__(self, key: bytes) -> None: ...
@staticmethod
def generate_key(key_size: int) -> bytes: ...
def encrypt(
self,
data: bytes,
associated_data: list[bytes] | None,
) -> bytes: ...
def decrypt(
self,
data: bytes,
associated_data: list[bytes] | None,
) -> bytes: ...
class AESOCB3:
def __init__(self, key: bytes) -> None: ...
@staticmethod
def generate_key(key_size: int) -> bytes: ...
def encrypt(
self,
nonce: bytes,
data: bytes,
associated_data: bytes | None,
) -> bytes: ...
def decrypt(
self,
nonce: bytes,
data: bytes,
associated_data: bytes | None,
) -> bytes: ...
class AESGCMSIV:
def __init__(self, key: bytes) -> None: ...
@staticmethod
def generate_key(key_size: int) -> bytes: ...
def encrypt(
self,
nonce: bytes,
data: bytes,
associated_data: bytes | None,
) -> bytes: ...
def decrypt(
self,
nonce: bytes,
data: bytes,
associated_data: bytes | None,
) -> bytes: ...

View File

@ -0,0 +1,38 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
import typing
from cryptography.hazmat.primitives import ciphers
from cryptography.hazmat.primitives.ciphers import modes
@typing.overload
def create_encryption_ctx(
algorithm: ciphers.CipherAlgorithm, mode: modes.ModeWithAuthenticationTag
) -> ciphers.AEADEncryptionContext: ...
@typing.overload
def create_encryption_ctx(
algorithm: ciphers.CipherAlgorithm, mode: modes.Mode
) -> ciphers.CipherContext: ...
@typing.overload
def create_decryption_ctx(
algorithm: ciphers.CipherAlgorithm, mode: modes.ModeWithAuthenticationTag
) -> ciphers.AEADDecryptionContext: ...
@typing.overload
def create_decryption_ctx(
algorithm: ciphers.CipherAlgorithm, mode: modes.Mode
) -> ciphers.CipherContext: ...
def cipher_supported(
algorithm: ciphers.CipherAlgorithm, mode: modes.Mode
) -> bool: ...
def _advance(
ctx: ciphers.AEADEncryptionContext | ciphers.AEADDecryptionContext, n: int
) -> None: ...
def _advance_aad(
ctx: ciphers.AEADEncryptionContext | ciphers.AEADDecryptionContext, n: int
) -> None: ...
class CipherContext: ...
class AEADEncryptionContext: ...
class AEADDecryptionContext: ...

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.
import typing
from cryptography.hazmat.primitives import ciphers
class CMAC:
def __init__(
self,
algorithm: ciphers.BlockCipherAlgorithm,
backend: typing.Any = None,
) -> None: ...
def update(self, data: bytes) -> None: ...
def finalize(self) -> bytes: ...
def verify(self, signature: bytes) -> None: ...
def copy(self) -> CMAC: ...

View File

@ -0,0 +1,51 @@
# 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
MIN_MODULUS_SIZE: int
class DHPrivateKey: ...
class DHPublicKey: ...
class DHParameters: ...
class DHPrivateNumbers:
def __init__(self, x: int, public_numbers: DHPublicNumbers) -> None: ...
def private_key(self, backend: typing.Any = None) -> dh.DHPrivateKey: ...
@property
def x(self) -> int: ...
@property
def public_numbers(self) -> DHPublicNumbers: ...
class DHPublicNumbers:
def __init__(
self, y: int, parameter_numbers: DHParameterNumbers
) -> None: ...
def public_key(self, backend: typing.Any = None) -> dh.DHPublicKey: ...
@property
def y(self) -> int: ...
@property
def parameter_numbers(self) -> DHParameterNumbers: ...
class DHParameterNumbers:
def __init__(self, p: int, g: int, q: int | None = None) -> None: ...
def parameters(self, backend: typing.Any = None) -> dh.DHParameters: ...
@property
def p(self) -> int: ...
@property
def g(self) -> int: ...
@property
def q(self) -> int | None: ...
def generate_parameters(
generator: int, key_size: int, backend: typing.Any = None
) -> dh.DHParameters: ...
def from_pem_parameters(
data: bytes, backend: typing.Any = None
) -> dh.DHParameters: ...
def from_der_parameters(
data: bytes, backend: typing.Any = None
) -> dh.DHParameters: ...

View File

@ -0,0 +1,41 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
import typing
from cryptography.hazmat.primitives.asymmetric import dsa
class DSAPrivateKey: ...
class DSAPublicKey: ...
class DSAParameters: ...
class DSAPrivateNumbers:
def __init__(self, x: int, public_numbers: DSAPublicNumbers) -> None: ...
@property
def x(self) -> int: ...
@property
def public_numbers(self) -> DSAPublicNumbers: ...
def private_key(self, backend: typing.Any = None) -> dsa.DSAPrivateKey: ...
class DSAPublicNumbers:
def __init__(
self, y: int, parameter_numbers: DSAParameterNumbers
) -> None: ...
@property
def y(self) -> int: ...
@property
def parameter_numbers(self) -> DSAParameterNumbers: ...
def public_key(self, backend: typing.Any = None) -> dsa.DSAPublicKey: ...
class DSAParameterNumbers:
def __init__(self, p: int, q: int, g: int) -> None: ...
@property
def p(self) -> int: ...
@property
def q(self) -> int: ...
@property
def g(self) -> int: ...
def parameters(self, backend: typing.Any = None) -> dsa.DSAParameters: ...
def generate_parameters(key_size: int) -> dsa.DSAParameters: ...

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.asymmetric import ec
class ECPrivateKey: ...
class ECPublicKey: ...
class EllipticCurvePrivateNumbers:
def __init__(
self, private_value: int, public_numbers: EllipticCurvePublicNumbers
) -> None: ...
def private_key(
self, backend: typing.Any = None
) -> ec.EllipticCurvePrivateKey: ...
@property
def private_value(self) -> int: ...
@property
def public_numbers(self) -> EllipticCurvePublicNumbers: ...
class EllipticCurvePublicNumbers:
def __init__(self, x: int, y: int, curve: ec.EllipticCurve) -> None: ...
def public_key(
self, backend: typing.Any = None
) -> ec.EllipticCurvePublicKey: ...
@property
def x(self) -> int: ...
@property
def y(self) -> int: ...
@property
def curve(self) -> ec.EllipticCurve: ...
def __eq__(self, other: object) -> bool: ...
def curve_supported(curve: ec.EllipticCurve) -> bool: ...
def generate_private_key(
curve: ec.EllipticCurve, backend: typing.Any = None
) -> ec.EllipticCurvePrivateKey: ...
def from_private_numbers(
numbers: ec.EllipticCurvePrivateNumbers,
) -> ec.EllipticCurvePrivateKey: ...
def from_public_numbers(
numbers: ec.EllipticCurvePublicNumbers,
) -> ec.EllipticCurvePublicKey: ...
def from_public_bytes(
curve: ec.EllipticCurve, data: bytes
) -> ec.EllipticCurvePublicKey: ...
def derive_private_key(
private_value: int, curve: ec.EllipticCurve
) -> ec.EllipticCurvePrivateKey: ...

View File

@ -0,0 +1,12 @@
# 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.asymmetric import ed25519
class Ed25519PrivateKey: ...
class Ed25519PublicKey: ...
def generate_key() -> ed25519.Ed25519PrivateKey: ...
def from_private_bytes(data: bytes) -> ed25519.Ed25519PrivateKey: ...
def from_public_bytes(data: bytes) -> ed25519.Ed25519PublicKey: ...

View File

@ -0,0 +1,12 @@
# 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.asymmetric import ed448
class Ed448PrivateKey: ...
class Ed448PublicKey: ...
def generate_key() -> ed448.Ed448PrivateKey: ...
def from_private_bytes(data: bytes) -> ed448.Ed448PrivateKey: ...
def from_public_bytes(data: bytes) -> ed448.Ed448PublicKey: ...

View File

@ -0,0 +1,19 @@
# 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 Hash(hashes.HashContext):
def __init__(
self, algorithm: hashes.HashAlgorithm, backend: typing.Any = None
) -> None: ...
@property
def algorithm(self) -> hashes.HashAlgorithm: ...
def update(self, data: bytes) -> None: ...
def finalize(self) -> bytes: ...
def copy(self) -> Hash: ...
def hash_supported(algorithm: hashes.HashAlgorithm) -> bool: ...

View File

@ -0,0 +1,21 @@
# 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 HMAC(hashes.HashContext):
def __init__(
self,
key: bytes,
algorithm: hashes.HashAlgorithm,
backend: typing.Any = None,
) -> None: ...
@property
def algorithm(self) -> hashes.HashAlgorithm: ...
def update(self, data: bytes) -> None: ...
def finalize(self) -> bytes: ...
def verify(self, signature: bytes) -> None: ...
def copy(self) -> HMAC: ...

View File

@ -0,0 +1,43 @@
# 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.hashes import HashAlgorithm
def derive_pbkdf2_hmac(
key_material: bytes,
algorithm: HashAlgorithm,
salt: bytes,
iterations: int,
length: int,
) -> bytes: ...
class Scrypt:
def __init__(
self,
salt: bytes,
length: int,
n: int,
r: int,
p: int,
backend: typing.Any = None,
) -> None: ...
def derive(self, key_material: bytes) -> bytes: ...
def verify(self, key_material: bytes, expected_key: bytes) -> None: ...
class Argon2id:
def __init__(
self,
*,
salt: bytes,
length: int,
iterations: int,
lanes: int,
memory_cost: int,
ad: bytes | None = None,
secret: bytes | None = None,
) -> None: ...
def derive(self, key_material: bytes) -> bytes: ...
def verify(self, key_material: bytes, expected_key: bytes) -> None: ...

View File

@ -0,0 +1,33 @@
# 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.types import (
PrivateKeyTypes,
PublicKeyTypes,
)
def load_der_private_key(
data: bytes,
password: bytes | None,
backend: typing.Any = None,
*,
unsafe_skip_rsa_key_validation: bool = False,
) -> PrivateKeyTypes: ...
def load_pem_private_key(
data: bytes,
password: bytes | None,
backend: typing.Any = None,
*,
unsafe_skip_rsa_key_validation: bool = False,
) -> PrivateKeyTypes: ...
def load_der_public_key(
data: bytes,
backend: typing.Any = None,
) -> PublicKeyTypes: ...
def load_pem_public_key(
data: bytes,
backend: typing.Any = None,
) -> PublicKeyTypes: ...

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.
class Poly1305:
def __init__(self, key: bytes) -> None: ...
@staticmethod
def generate_tag(key: bytes, data: bytes) -> bytes: ...
@staticmethod
def verify_tag(key: bytes, data: bytes, tag: bytes) -> None: ...
def update(self, data: bytes) -> None: ...
def finalize(self) -> bytes: ...
def verify(self, tag: bytes) -> None: ...

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 typing
from cryptography.hazmat.primitives.asymmetric import rsa
class RSAPrivateKey: ...
class RSAPublicKey: ...
class RSAPrivateNumbers:
def __init__(
self,
p: int,
q: int,
d: int,
dmp1: int,
dmq1: int,
iqmp: int,
public_numbers: RSAPublicNumbers,
) -> None: ...
@property
def p(self) -> int: ...
@property
def q(self) -> int: ...
@property
def d(self) -> int: ...
@property
def dmp1(self) -> int: ...
@property
def dmq1(self) -> int: ...
@property
def iqmp(self) -> int: ...
@property
def public_numbers(self) -> RSAPublicNumbers: ...
def private_key(
self,
backend: typing.Any = None,
*,
unsafe_skip_rsa_key_validation: bool = False,
) -> rsa.RSAPrivateKey: ...
class RSAPublicNumbers:
def __init__(self, e: int, n: int) -> None: ...
@property
def n(self) -> int: ...
@property
def e(self) -> int: ...
def public_key(self, backend: typing.Any = None) -> rsa.RSAPublicKey: ...
def generate_private_key(
public_exponent: int,
key_size: int,
) -> rsa.RSAPrivateKey: ...

View File

@ -0,0 +1,12 @@
# 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.asymmetric import x25519
class X25519PrivateKey: ...
class X25519PublicKey: ...
def generate_key() -> x25519.X25519PrivateKey: ...
def from_private_bytes(data: bytes) -> x25519.X25519PrivateKey: ...
def from_public_bytes(data: bytes) -> x25519.X25519PublicKey: ...

View File

@ -0,0 +1,12 @@
# 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.asymmetric import x448
class X448PrivateKey: ...
class X448PublicKey: ...
def generate_key() -> x448.X448PrivateKey: ...
def from_private_bytes(data: bytes) -> x448.X448PrivateKey: ...
def from_public_bytes(data: bytes) -> x448.X448PublicKey: ...

View File

@ -0,0 +1,46 @@
# 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.asymmetric.types import PrivateKeyTypes
from cryptography.hazmat.primitives.serialization import (
KeySerializationEncryption,
)
from cryptography.hazmat.primitives.serialization.pkcs12 import (
PKCS12KeyAndCertificates,
PKCS12PrivateKeyTypes,
)
class PKCS12Certificate:
def __init__(
self, cert: x509.Certificate, friendly_name: bytes | None
) -> None: ...
@property
def friendly_name(self) -> bytes | None: ...
@property
def certificate(self) -> x509.Certificate: ...
def load_key_and_certificates(
data: bytes,
password: bytes | None,
backend: typing.Any = None,
) -> tuple[
PrivateKeyTypes | None,
x509.Certificate | None,
list[x509.Certificate],
]: ...
def load_pkcs12(
data: bytes,
password: bytes | None,
backend: typing.Any = None,
) -> PKCS12KeyAndCertificates: ...
def serialize_key_and_certificates(
name: bytes | None,
key: PKCS12PrivateKeyTypes | None,
cert: x509.Certificate | None,
cas: typing.Iterable[x509.Certificate | PKCS12Certificate] | None,
encryption_algorithm: KeySerializationEncryption,
) -> bytes: ...

View File

@ -0,0 +1,49 @@
# 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 rsa
from cryptography.hazmat.primitives.serialization import pkcs7
def serialize_certificates(
certs: list[x509.Certificate],
encoding: serialization.Encoding,
) -> bytes: ...
def encrypt_and_serialize(
builder: pkcs7.PKCS7EnvelopeBuilder,
encoding: serialization.Encoding,
options: typing.Iterable[pkcs7.PKCS7Options],
) -> bytes: ...
def sign_and_serialize(
builder: pkcs7.PKCS7SignatureBuilder,
encoding: serialization.Encoding,
options: typing.Iterable[pkcs7.PKCS7Options],
) -> bytes: ...
def decrypt_der(
data: bytes,
certificate: x509.Certificate,
private_key: rsa.RSAPrivateKey,
options: typing.Iterable[pkcs7.PKCS7Options],
) -> bytes: ...
def decrypt_pem(
data: bytes,
certificate: x509.Certificate,
private_key: rsa.RSAPrivateKey,
options: typing.Iterable[pkcs7.PKCS7Options],
) -> bytes: ...
def decrypt_smime(
data: bytes,
certificate: x509.Certificate,
private_key: rsa.RSAPrivateKey,
options: typing.Iterable[pkcs7.PKCS7Options],
) -> bytes: ...
def load_pem_pkcs7_certificates(
data: bytes,
) -> list[x509.Certificate]: ...
def load_der_pkcs7_certificates(
data: bytes,
) -> list[x509.Certificate]: ...

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.
from cryptography import x509
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.serialization import pkcs7
class TestCertificate:
not_after_tag: int
not_before_tag: int
issuer_value_tags: list[int]
subject_value_tags: list[int]
def test_parse_certificate(data: bytes) -> TestCertificate: ...
def pkcs7_verify(
encoding: serialization.Encoding,
sig: bytes,
msg: bytes | None,
certs: list[x509.Certificate],
options: list[pkcs7.PKCS7Options],
) -> None: ...

View File

@ -0,0 +1,246 @@
# 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 datetime
import typing
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric.ec import ECDSA
from cryptography.hazmat.primitives.asymmetric.padding import PSS, PKCS1v15
from cryptography.hazmat.primitives.asymmetric.types import (
CertificateIssuerPublicKeyTypes,
CertificatePublicKeyTypes,
PrivateKeyTypes,
)
from cryptography.x509 import certificate_transparency
def load_pem_x509_certificate(
data: bytes, backend: typing.Any = None
) -> x509.Certificate: ...
def load_der_x509_certificate(
data: bytes, backend: typing.Any = None
) -> x509.Certificate: ...
def load_pem_x509_certificates(
data: bytes,
) -> list[x509.Certificate]: ...
def load_pem_x509_crl(
data: bytes, backend: typing.Any = None
) -> x509.CertificateRevocationList: ...
def load_der_x509_crl(
data: bytes, backend: typing.Any = None
) -> x509.CertificateRevocationList: ...
def load_pem_x509_csr(
data: bytes, backend: typing.Any = None
) -> x509.CertificateSigningRequest: ...
def load_der_x509_csr(
data: bytes, backend: typing.Any = None
) -> x509.CertificateSigningRequest: ...
def encode_name_bytes(name: x509.Name) -> bytes: ...
def encode_extension_value(extension: x509.ExtensionType) -> bytes: ...
def create_x509_certificate(
builder: x509.CertificateBuilder,
private_key: PrivateKeyTypes,
hash_algorithm: hashes.HashAlgorithm | None,
rsa_padding: PKCS1v15 | PSS | None,
) -> x509.Certificate: ...
def create_x509_csr(
builder: x509.CertificateSigningRequestBuilder,
private_key: PrivateKeyTypes,
hash_algorithm: hashes.HashAlgorithm | None,
rsa_padding: PKCS1v15 | PSS | None,
) -> x509.CertificateSigningRequest: ...
def create_x509_crl(
builder: x509.CertificateRevocationListBuilder,
private_key: PrivateKeyTypes,
hash_algorithm: hashes.HashAlgorithm | None,
rsa_padding: PKCS1v15 | PSS | None,
) -> x509.CertificateRevocationList: ...
class Sct:
@property
def version(self) -> certificate_transparency.Version: ...
@property
def log_id(self) -> bytes: ...
@property
def timestamp(self) -> datetime.datetime: ...
@property
def entry_type(self) -> certificate_transparency.LogEntryType: ...
@property
def signature_hash_algorithm(self) -> hashes.HashAlgorithm: ...
@property
def signature_algorithm(
self,
) -> certificate_transparency.SignatureAlgorithm: ...
@property
def signature(self) -> bytes: ...
@property
def extension_bytes(self) -> bytes: ...
class Certificate:
def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes: ...
@property
def serial_number(self) -> int: ...
@property
def version(self) -> x509.Version: ...
def public_key(self) -> CertificatePublicKeyTypes: ...
@property
def public_key_algorithm_oid(self) -> x509.ObjectIdentifier: ...
@property
def not_valid_before(self) -> datetime.datetime: ...
@property
def not_valid_before_utc(self) -> datetime.datetime: ...
@property
def not_valid_after(self) -> datetime.datetime: ...
@property
def not_valid_after_utc(self) -> datetime.datetime: ...
@property
def issuer(self) -> x509.Name: ...
@property
def subject(self) -> x509.Name: ...
@property
def signature_hash_algorithm(
self,
) -> hashes.HashAlgorithm | None: ...
@property
def signature_algorithm_oid(self) -> x509.ObjectIdentifier: ...
@property
def signature_algorithm_parameters(
self,
) -> None | PSS | PKCS1v15 | ECDSA: ...
@property
def extensions(self) -> x509.Extensions: ...
@property
def signature(self) -> bytes: ...
@property
def tbs_certificate_bytes(self) -> bytes: ...
@property
def tbs_precertificate_bytes(self) -> bytes: ...
def __eq__(self, other: object) -> bool: ...
def __hash__(self) -> int: ...
def public_bytes(self, encoding: serialization.Encoding) -> bytes: ...
def verify_directly_issued_by(self, issuer: Certificate) -> None: ...
class RevokedCertificate: ...
class CertificateRevocationList:
def public_bytes(self, encoding: serialization.Encoding) -> bytes: ...
def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes: ...
def get_revoked_certificate_by_serial_number(
self, serial_number: int
) -> RevokedCertificate | None: ...
@property
def signature_hash_algorithm(
self,
) -> hashes.HashAlgorithm | None: ...
@property
def signature_algorithm_oid(self) -> x509.ObjectIdentifier: ...
@property
def signature_algorithm_parameters(
self,
) -> None | PSS | PKCS1v15 | ECDSA: ...
@property
def issuer(self) -> x509.Name: ...
@property
def next_update(self) -> datetime.datetime | None: ...
@property
def next_update_utc(self) -> datetime.datetime | None: ...
@property
def last_update(self) -> datetime.datetime: ...
@property
def last_update_utc(self) -> datetime.datetime: ...
@property
def extensions(self) -> x509.Extensions: ...
@property
def signature(self) -> bytes: ...
@property
def tbs_certlist_bytes(self) -> bytes: ...
def __eq__(self, other: object) -> bool: ...
def __len__(self) -> int: ...
@typing.overload
def __getitem__(self, idx: int) -> x509.RevokedCertificate: ...
@typing.overload
def __getitem__(self, idx: slice) -> list[x509.RevokedCertificate]: ...
def __iter__(self) -> typing.Iterator[x509.RevokedCertificate]: ...
def is_signature_valid(
self, public_key: CertificateIssuerPublicKeyTypes
) -> bool: ...
class CertificateSigningRequest:
def __eq__(self, other: object) -> bool: ...
def __hash__(self) -> int: ...
def public_key(self) -> CertificatePublicKeyTypes: ...
@property
def subject(self) -> x509.Name: ...
@property
def signature_hash_algorithm(
self,
) -> hashes.HashAlgorithm | None: ...
@property
def signature_algorithm_oid(self) -> x509.ObjectIdentifier: ...
@property
def signature_algorithm_parameters(
self,
) -> None | PSS | PKCS1v15 | ECDSA: ...
@property
def extensions(self) -> x509.Extensions: ...
@property
def attributes(self) -> x509.Attributes: ...
def public_bytes(self, encoding: serialization.Encoding) -> bytes: ...
@property
def signature(self) -> bytes: ...
@property
def tbs_certrequest_bytes(self) -> bytes: ...
@property
def is_signature_valid(self) -> bool: ...
def get_attribute_for_oid(self, oid: x509.ObjectIdentifier) -> bytes: ...
class PolicyBuilder:
def time(self, new_time: datetime.datetime) -> PolicyBuilder: ...
def store(self, new_store: Store) -> PolicyBuilder: ...
def max_chain_depth(self, new_max_chain_depth: int) -> PolicyBuilder: ...
def build_client_verifier(self) -> ClientVerifier: ...
def build_server_verifier(
self, subject: x509.verification.Subject
) -> ServerVerifier: ...
class VerifiedClient:
@property
def subjects(self) -> list[x509.GeneralName] | None: ...
@property
def chain(self) -> list[x509.Certificate]: ...
class ClientVerifier:
@property
def validation_time(self) -> datetime.datetime: ...
@property
def store(self) -> Store: ...
@property
def max_chain_depth(self) -> int: ...
def verify(
self,
leaf: x509.Certificate,
intermediates: list[x509.Certificate],
) -> VerifiedClient: ...
class ServerVerifier:
@property
def subject(self) -> x509.verification.Subject: ...
@property
def validation_time(self) -> datetime.datetime: ...
@property
def store(self) -> Store: ...
@property
def max_chain_depth(self) -> int: ...
def verify(
self,
leaf: x509.Certificate,
intermediates: list[x509.Certificate],
) -> list[x509.Certificate]: ...
class Store:
def __init__(self, certs: list[x509.Certificate]) -> None: ...
class VerificationError(Exception):
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,183 @@
# 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 __future__ import annotations
def cryptography_has_set_cert_cb() -> list[str]:
return [
"SSL_CTX_set_cert_cb",
"SSL_set_cert_cb",
]
def cryptography_has_ssl_st() -> list[str]:
return [
"SSL_ST_BEFORE",
"SSL_ST_OK",
"SSL_ST_INIT",
"SSL_ST_RENEGOTIATE",
]
def cryptography_has_tls_st() -> list[str]:
return [
"TLS_ST_BEFORE",
"TLS_ST_OK",
]
def cryptography_has_ssl_sigalgs() -> list[str]:
return [
"SSL_CTX_set1_sigalgs_list",
]
def cryptography_has_psk() -> 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() -> 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() -> list[str]:
return [
"SSL_CTX_add_client_custom_ext",
"SSL_CTX_add_server_custom_ext",
"SSL_extension_supported",
]
def cryptography_has_tlsv13_functions() -> 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_engine() -> 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",
"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() -> list[str]:
return [
"SSL_get0_verified_chain",
]
def cryptography_has_srtp() -> list[str]:
return [
"SSL_CTX_set_tlsext_use_srtp",
"SSL_set_tlsext_use_srtp",
"SSL_get_selected_srtp_profile",
]
def cryptography_has_op_no_renegotiation() -> list[str]:
return [
"SSL_OP_NO_RENEGOTIATION",
]
def cryptography_has_dtls_get_data_mtu() -> list[str]:
return [
"DTLS_get_data_mtu",
]
def cryptography_has_ssl_cookie() -> list[str]:
return [
"SSL_OP_COOKIE_EXCHANGE",
"DTLSv1_listen",
"SSL_CTX_set_cookie_generate_cb",
"SSL_CTX_set_cookie_verify_cb",
]
def cryptography_has_prime_checks() -> list[str]:
return [
"BN_prime_checks_for_size",
]
def cryptography_has_unexpected_eof_while_reading() -> list[str]:
return ["SSL_R_UNEXPECTED_EOF_WHILE_READING"]
def cryptography_has_ssl_op_ignore_unexpected_eof() -> list[str]:
return [
"SSL_OP_IGNORE_UNEXPECTED_EOF",
]
def cryptography_has_get_extms_support() -> list[str]:
return ["SSL_get_extms_support"]
# 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_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_SIGALGS": cryptography_has_ssl_sigalgs,
"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_TLSv1_3_FUNCTIONS": cryptography_has_tlsv13_functions,
"Cryptography_HAS_ENGINE": cryptography_has_engine,
"Cryptography_HAS_VERIFIED_CHAIN": cryptography_has_verified_chain,
"Cryptography_HAS_SRTP": cryptography_has_srtp,
"Cryptography_HAS_OP_NO_RENEGOTIATION": (
cryptography_has_op_no_renegotiation
),
"Cryptography_HAS_DTLS_GET_DATA_MTU": cryptography_has_dtls_get_data_mtu,
"Cryptography_HAS_SSL_COOKIE": cryptography_has_ssl_cookie,
"Cryptography_HAS_PRIME_CHECKS": cryptography_has_prime_checks,
"Cryptography_HAS_UNEXPECTED_EOF_WHILE_READING": (
cryptography_has_unexpected_eof_while_reading
),
"Cryptography_HAS_SSL_OP_IGNORE_UNEXPECTED_EOF": (
cryptography_has_ssl_op_ignore_unexpected_eof
),
"Cryptography_HAS_GET_EXTMS_SUPPORT": cryptography_has_get_extms_support,
}

View File

@ -0,0 +1,121 @@
# 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 __future__ import annotations
import os
import sys
import threading
import types
import typing
import warnings
import cryptography
from cryptography.exceptions import InternalError
from cryptography.hazmat.bindings._rust import _openssl, openssl
from cryptography.hazmat.bindings.openssl._conditional import CONDITIONAL_NAMES
def _openssl_assert(ok: bool) -> None:
if not ok:
errors = openssl.capture_error_stack()
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 "
f"this. ({errors!r})",
errors,
)
def build_conditional_library(
lib: typing.Any,
conditional_names: dict[str, typing.Callable[[], list[str]]],
) -> typing.Any:
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 = _openssl.ffi
_lib_loaded = False
_init_lock = threading.Lock()
def __init__(self) -> None:
self._ensure_ffi_initialized()
@classmethod
def _ensure_ffi_initialized(cls) -> None:
with cls._init_lock:
if not cls._lib_loaded:
cls.lib = build_conditional_library(
_openssl.lib, CONDITIONAL_NAMES
)
cls._lib_loaded = True
@classmethod
def init_static_locks(cls) -> None:
cls._ensure_ffi_initialized()
def _verify_package_version(version: str) -> None:
# 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 = _openssl.ffi.string(
_openssl.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. "
f"Loaded python version: {version}, "
f"shared object version: {so_package_version}"
)
_openssl_assert(
_openssl.lib.OpenSSL_version_num() == openssl.openssl_version(),
)
_verify_package_version(cryptography.__version__)
Binding.init_static_locks()
if (
sys.platform == "win32"
and os.environ.get("PROCESSOR_ARCHITEW6432") is not None
):
warnings.warn(
"You are using cryptography on a 32-bit Python on a 64-bit Windows "
"Operating System. Cryptography will be significantly faster if you "
"switch to using a 64-bit Python.",
UserWarning,
stacklevel=2,
)

View File

@ -0,0 +1,5 @@
# 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 __future__ import annotations

View File

@ -0,0 +1,5 @@
# 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 __future__ import annotations

View File

@ -0,0 +1,107 @@
# 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 __future__ import annotations
from cryptography.hazmat.primitives._cipheralgorithm import (
BlockCipherAlgorithm,
CipherAlgorithm,
_verify_key_size,
)
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 TripleDES(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(BlockCipherAlgorithm):
name = "Blowfish"
block_size = 64
key_sizes = frozenset(range(32, 449, 8))
def __init__(self, key: bytes):
self.key = _verify_key_size(self, key)
@property
def key_size(self) -> int:
return len(self.key) * 8
class CAST5(BlockCipherAlgorithm):
name = "CAST5"
block_size = 64
key_sizes = frozenset(range(40, 129, 8))
def __init__(self, key: bytes):
self.key = _verify_key_size(self, key)
@property
def key_size(self) -> int:
return len(self.key) * 8
class SEED(BlockCipherAlgorithm):
name = "SEED"
block_size = 128
key_sizes = frozenset([128])
def __init__(self, key: bytes):
self.key = _verify_key_size(self, key)
@property
def key_size(self) -> int:
return len(self.key) * 8
class IDEA(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
# This class only allows RC2 with a 128-bit key. No support for
# effective key bits or other key sizes is provided.
class RC2(BlockCipherAlgorithm):
name = "RC2"
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

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,19 @@
# 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 __future__ import annotations
import abc
# This exists to break an import cycle. It is normally accessible from the
# asymmetric padding module.
class AsymmetricPadding(metaclass=abc.ABCMeta):
@property
@abc.abstractmethod
def name(self) -> str:
"""
A string naming this padding (e.g. "PSS", "PKCS1").
"""

View File

@ -0,0 +1,58 @@
# 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 __future__ import annotations
import abc
from cryptography import utils
# This exists to break an import cycle. It is normally accessible from the
# ciphers module.
class CipherAlgorithm(metaclass=abc.ABCMeta):
@property
@abc.abstractmethod
def name(self) -> str:
"""
A string naming this mode (e.g. "AES", "Camellia").
"""
@property
@abc.abstractmethod
def key_sizes(self) -> frozenset[int]:
"""
Valid key sizes for this algorithm in bits
"""
@property
@abc.abstractmethod
def key_size(self) -> int:
"""
The size of the key being used as an integer in bits (e.g. 128, 256).
"""
class BlockCipherAlgorithm(CipherAlgorithm):
key: bytes
@property
@abc.abstractmethod
def block_size(self) -> int:
"""
The size of a block as an integer in bits (e.g. 64, 128).
"""
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(
f"Invalid key size ({len(key) * 8}) for {algorithm.name}."
)
return key

View File

@ -0,0 +1,169 @@
# 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 __future__ import annotations
import abc
from cryptography import utils
from cryptography.hazmat.primitives.hashes import HashAlgorithm
# This exists to break an import cycle. These classes are normally accessible
# from the serialization module.
class PBES(utils.Enum):
PBESv1SHA1And3KeyTripleDESCBC = "PBESv1 using SHA1 and 3-Key TripleDES"
PBESv2SHA256AndAES256CBC = "PBESv2 using SHA256 PBKDF2 and AES256 CBC"
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"
PKCS12 = "PKCS12"
def encryption_builder(self) -> KeySerializationEncryptionBuilder:
if self not in (PrivateFormat.OpenSSH, PrivateFormat.PKCS12):
raise ValueError(
"encryption_builder only supported with PrivateFormat.OpenSSH"
" and PrivateFormat.PKCS12"
)
return KeySerializationEncryptionBuilder(self)
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
class KeySerializationEncryptionBuilder:
def __init__(
self,
format: PrivateFormat,
*,
_kdf_rounds: int | None = None,
_hmac_hash: HashAlgorithm | None = None,
_key_cert_algorithm: PBES | None = None,
) -> None:
self._format = format
self._kdf_rounds = _kdf_rounds
self._hmac_hash = _hmac_hash
self._key_cert_algorithm = _key_cert_algorithm
def kdf_rounds(self, rounds: int) -> KeySerializationEncryptionBuilder:
if self._kdf_rounds is not None:
raise ValueError("kdf_rounds already set")
if not isinstance(rounds, int):
raise TypeError("kdf_rounds must be an integer")
if rounds < 1:
raise ValueError("kdf_rounds must be a positive integer")
return KeySerializationEncryptionBuilder(
self._format,
_kdf_rounds=rounds,
_hmac_hash=self._hmac_hash,
_key_cert_algorithm=self._key_cert_algorithm,
)
def hmac_hash(
self, algorithm: HashAlgorithm
) -> KeySerializationEncryptionBuilder:
if self._format is not PrivateFormat.PKCS12:
raise TypeError(
"hmac_hash only supported with PrivateFormat.PKCS12"
)
if self._hmac_hash is not None:
raise ValueError("hmac_hash already set")
return KeySerializationEncryptionBuilder(
self._format,
_kdf_rounds=self._kdf_rounds,
_hmac_hash=algorithm,
_key_cert_algorithm=self._key_cert_algorithm,
)
def key_cert_algorithm(
self, algorithm: PBES
) -> KeySerializationEncryptionBuilder:
if self._format is not PrivateFormat.PKCS12:
raise TypeError(
"key_cert_algorithm only supported with "
"PrivateFormat.PKCS12"
)
if self._key_cert_algorithm is not None:
raise ValueError("key_cert_algorithm already set")
return KeySerializationEncryptionBuilder(
self._format,
_kdf_rounds=self._kdf_rounds,
_hmac_hash=self._hmac_hash,
_key_cert_algorithm=algorithm,
)
def build(self, password: bytes) -> KeySerializationEncryption:
if not isinstance(password, bytes) or len(password) == 0:
raise ValueError("Password must be 1 or more bytes.")
return _KeySerializationEncryption(
self._format,
password,
kdf_rounds=self._kdf_rounds,
hmac_hash=self._hmac_hash,
key_cert_algorithm=self._key_cert_algorithm,
)
class _KeySerializationEncryption(KeySerializationEncryption):
def __init__(
self,
format: PrivateFormat,
password: bytes,
*,
kdf_rounds: int | None,
hmac_hash: HashAlgorithm | None,
key_cert_algorithm: PBES | None,
):
self._format = format
self.password = password
self._kdf_rounds = kdf_rounds
self._hmac_hash = hmac_hash
self._key_cert_algorithm = key_cert_algorithm

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,135 @@
# 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 __future__ import annotations
import abc
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
from cryptography.hazmat.primitives import _serialization
generate_parameters = rust_openssl.dh.generate_parameters
DHPrivateNumbers = rust_openssl.dh.DHPrivateNumbers
DHPublicNumbers = rust_openssl.dh.DHPublicNumbers
DHParameterNumbers = rust_openssl.dh.DHParameterNumbers
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
DHParameters.register(rust_openssl.dh.DHParameters)
class DHPublicKey(metaclass=abc.ABCMeta):
@property
@abc.abstractmethod
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.
"""
@abc.abstractmethod
def __eq__(self, other: object) -> bool:
"""
Checks equality.
"""
DHPublicKeyWithSerialization = DHPublicKey
DHPublicKey.register(rust_openssl.dh.DHPublicKey)
class DHPrivateKey(metaclass=abc.ABCMeta):
@property
@abc.abstractmethod
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
DHPrivateKey.register(rust_openssl.dh.DHPrivateKey)

View File

@ -0,0 +1,154 @@
# 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 __future__ import annotations
import abc
import typing
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
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
DSAParameters.register(rust_openssl.dsa.DSAParameters)
class DSAPrivateKey(metaclass=abc.ABCMeta):
@property
@abc.abstractmethod
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: 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
DSAPrivateKey.register(rust_openssl.dsa.DSAPrivateKey)
class DSAPublicKey(metaclass=abc.ABCMeta):
@property
@abc.abstractmethod
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: asym_utils.Prehashed | hashes.HashAlgorithm,
) -> None:
"""
Verifies the signature of the data.
"""
@abc.abstractmethod
def __eq__(self, other: object) -> bool:
"""
Checks equality.
"""
DSAPublicKeyWithSerialization = DSAPublicKey
DSAPublicKey.register(rust_openssl.dsa.DSAPublicKey)
DSAPrivateNumbers = rust_openssl.dsa.DSAPrivateNumbers
DSAPublicNumbers = rust_openssl.dsa.DSAPublicNumbers
DSAParameterNumbers = rust_openssl.dsa.DSAParameterNumbers
def generate_parameters(
key_size: int, backend: typing.Any = None
) -> DSAParameters:
if key_size not in (1024, 2048, 3072, 4096):
raise ValueError("Key size must be 1024, 2048, 3072, or 4096 bits.")
return rust_openssl.dsa.generate_parameters(key_size)
def generate_private_key(
key_size: int, backend: typing.Any = None
) -> DSAPrivateKey:
parameters = generate_parameters(key_size)
return parameters.generate_private_key()

View File

@ -0,0 +1,403 @@
# 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 __future__ import annotations
import abc
import typing
from cryptography import utils
from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
from cryptography.hazmat._oid import ObjectIdentifier
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
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):
@property
@abc.abstractmethod
def name(self) -> str:
"""
The name of the curve. e.g. secp256r1.
"""
@property
@abc.abstractmethod
def key_size(self) -> int:
"""
Bit size of a secret scalar for the curve.
"""
class EllipticCurveSignatureAlgorithm(metaclass=abc.ABCMeta):
@property
@abc.abstractmethod
def algorithm(
self,
) -> 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.
"""
@property
@abc.abstractmethod
def curve(self) -> EllipticCurve:
"""
The EllipticCurve that this key is on.
"""
@property
@abc.abstractmethod
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
EllipticCurvePrivateKey.register(rust_openssl.ec.ECPrivateKey)
class EllipticCurvePublicKey(metaclass=abc.ABCMeta):
@property
@abc.abstractmethod
def curve(self) -> EllipticCurve:
"""
The EllipticCurve that this key is on.
"""
@property
@abc.abstractmethod
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 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")
return rust_openssl.ec.from_public_bytes(curve, data)
@abc.abstractmethod
def __eq__(self, other: object) -> bool:
"""
Checks equality.
"""
EllipticCurvePublicKeyWithSerialization = EllipticCurvePublicKey
EllipticCurvePublicKey.register(rust_openssl.ec.ECPublicKey)
EllipticCurvePrivateNumbers = rust_openssl.ec.EllipticCurvePrivateNumbers
EllipticCurvePublicNumbers = rust_openssl.ec.EllipticCurvePublicNumbers
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: dict[str, 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: asym_utils.Prehashed | hashes.HashAlgorithm,
deterministic_signing: bool = False,
):
from cryptography.hazmat.backends.openssl.backend import backend
if (
deterministic_signing
and not backend.ecdsa_deterministic_supported()
):
raise UnsupportedAlgorithm(
"ECDSA with deterministic signature (RFC 6979) is not "
"supported by this version of OpenSSL.",
_Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM,
)
self._algorithm = algorithm
self._deterministic_signing = deterministic_signing
@property
def algorithm(
self,
) -> asym_utils.Prehashed | hashes.HashAlgorithm:
return self._algorithm
@property
def deterministic_signing(
self,
) -> bool:
return self._deterministic_signing
generate_private_key = rust_openssl.ec.generate_private_key
def derive_private_key(
private_value: int,
curve: EllipticCurve,
backend: typing.Any = None,
) -> EllipticCurvePrivateKey:
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.")
return rust_openssl.ec.derive_private_key(private_value, curve)
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) -> 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,116 @@
# 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 __future__ import annotations
import abc
from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
from cryptography.hazmat.primitives import _serialization
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 rust_openssl.ed25519.from_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 public_bytes_raw(self) -> bytes:
"""
The raw bytes of the public key.
Equivalent to public_bytes(Raw, Raw).
"""
@abc.abstractmethod
def verify(self, signature: bytes, data: bytes) -> None:
"""
Verify the signature.
"""
@abc.abstractmethod
def __eq__(self, other: object) -> bool:
"""
Checks equality.
"""
Ed25519PublicKey.register(rust_openssl.ed25519.Ed25519PublicKey)
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 rust_openssl.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 rust_openssl.ed25519.from_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 private_bytes_raw(self) -> bytes:
"""
The raw bytes of the private key.
Equivalent to private_bytes(Raw, Raw, NoEncryption()).
"""
@abc.abstractmethod
def sign(self, data: bytes) -> bytes:
"""
Signs the data.
"""
Ed25519PrivateKey.register(rust_openssl.ed25519.Ed25519PrivateKey)

View File

@ -0,0 +1,118 @@
# 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 __future__ import annotations
import abc
from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
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 rust_openssl.ed448.from_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 public_bytes_raw(self) -> bytes:
"""
The raw bytes of the public key.
Equivalent to public_bytes(Raw, Raw).
"""
@abc.abstractmethod
def verify(self, signature: bytes, data: bytes) -> None:
"""
Verify the signature.
"""
@abc.abstractmethod
def __eq__(self, other: object) -> bool:
"""
Checks equality.
"""
if hasattr(rust_openssl, "ed448"):
Ed448PublicKey.register(rust_openssl.ed448.Ed448PublicKey)
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 rust_openssl.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 rust_openssl.ed448.from_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.
"""
@abc.abstractmethod
def private_bytes_raw(self) -> bytes:
"""
The raw bytes of the private key.
Equivalent to private_bytes(Raw, Raw, NoEncryption()).
"""
if hasattr(rust_openssl, "x448"):
Ed448PrivateKey.register(rust_openssl.ed448.Ed448PrivateKey)

View File

@ -0,0 +1,113 @@
# 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 __future__ import annotations
import abc
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: int | _MaxLength | _Auto | _DigestLength
def __init__(
self,
mgf: MGF,
salt_length: 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
@property
def mgf(self) -> MGF:
return self._mgf
class OAEP(AsymmetricPadding):
name = "EME-OAEP"
def __init__(
self,
mgf: MGF,
algorithm: hashes.HashAlgorithm,
label: bytes | None,
):
if not isinstance(algorithm, hashes.HashAlgorithm):
raise TypeError("Expected instance of hashes.HashAlgorithm.")
self._mgf = mgf
self._algorithm = algorithm
self._label = label
@property
def algorithm(self) -> hashes.HashAlgorithm:
return self._algorithm
@property
def mgf(self) -> MGF:
return self._mgf
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: 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,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.
from __future__ import annotations
import abc
import random
import typing
from math import gcd
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
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.
"""
@property
@abc.abstractmethod
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: 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
RSAPrivateKey.register(rust_openssl.rsa.RSAPrivateKey)
class RSAPublicKey(metaclass=abc.ABCMeta):
@abc.abstractmethod
def encrypt(self, plaintext: bytes, padding: AsymmetricPadding) -> bytes:
"""
Encrypts the given plaintext.
"""
@property
@abc.abstractmethod
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: 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: hashes.HashAlgorithm | None,
) -> bytes:
"""
Recovers the original data from the signature.
"""
@abc.abstractmethod
def __eq__(self, other: object) -> bool:
"""
Checks equality.
"""
RSAPublicKeyWithSerialization = RSAPublicKey
RSAPublicKey.register(rust_openssl.rsa.RSAPublicKey)
RSAPrivateNumbers = rust_openssl.rsa.RSAPrivateNumbers
RSAPublicNumbers = rust_openssl.rsa.RSAPublicNumbers
def generate_private_key(
public_exponent: int,
key_size: int,
backend: typing.Any = None,
) -> RSAPrivateKey:
_verify_rsa_parameters(public_exponent, key_size)
return rust_openssl.rsa.generate_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 < 1024:
raise ValueError("key_size must be at least 1024-bits.")
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)
def rsa_recover_private_exponent(e: int, p: int, q: int) -> int:
"""
Compute the RSA private_exponent (d) given the public exponent (e)
and the RSA primes p and q.
This uses the Carmichael totient function to generate the
smallest possible working value of the private exponent.
"""
# This lambda_n is the Carmichael totient function.
# The original RSA paper uses the Euler totient function
# here: phi_n = (p - 1) * (q - 1)
# Either version of the private exponent will work, but the
# one generated by the older formulation may be larger
# than necessary. (lambda_n always divides phi_n)
#
# TODO: Replace with lcm(p - 1, q - 1) once the minimum
# supported Python version is >= 3.9.
lambda_n = (p - 1) * (q - 1) // gcd(p - 1, q - 1)
return _modinv(e, lambda_n)
# Controls the number of iterations rsa_recover_prime_factors will perform
# to obtain the prime factors.
_MAX_RECOVERY_ATTEMPTS = 500
def rsa_recover_prime_factors(n: int, e: int, d: int) -> 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.
"""
# reject invalid values early
if 17 != pow(17, e * d, n):
raise ValueError("n, d, e don't match")
# 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
tries = 0
while not spotted and tries < _MAX_RECOVERY_ATTEMPTS:
a = random.randint(2, n - 1)
tries += 1
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
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)

View File

@ -0,0 +1,111 @@
# 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 __future__ import annotations
import typing
from cryptography import utils
from cryptography.hazmat.primitives.asymmetric import (
dh,
dsa,
ec,
ed448,
ed25519,
rsa,
x448,
x25519,
)
# Every asymmetric key type
PublicKeyTypes = typing.Union[
dh.DHPublicKey,
dsa.DSAPublicKey,
rsa.RSAPublicKey,
ec.EllipticCurvePublicKey,
ed25519.Ed25519PublicKey,
ed448.Ed448PublicKey,
x25519.X25519PublicKey,
x448.X448PublicKey,
]
PUBLIC_KEY_TYPES = PublicKeyTypes
utils.deprecated(
PUBLIC_KEY_TYPES,
__name__,
"Use PublicKeyTypes instead",
utils.DeprecatedIn40,
name="PUBLIC_KEY_TYPES",
)
# Every asymmetric key type
PrivateKeyTypes = typing.Union[
dh.DHPrivateKey,
ed25519.Ed25519PrivateKey,
ed448.Ed448PrivateKey,
rsa.RSAPrivateKey,
dsa.DSAPrivateKey,
ec.EllipticCurvePrivateKey,
x25519.X25519PrivateKey,
x448.X448PrivateKey,
]
PRIVATE_KEY_TYPES = PrivateKeyTypes
utils.deprecated(
PRIVATE_KEY_TYPES,
__name__,
"Use PrivateKeyTypes instead",
utils.DeprecatedIn40,
name="PRIVATE_KEY_TYPES",
)
# Just the key types we allow to be used for x509 signing. This mirrors
# the certificate public key types
CertificateIssuerPrivateKeyTypes = typing.Union[
ed25519.Ed25519PrivateKey,
ed448.Ed448PrivateKey,
rsa.RSAPrivateKey,
dsa.DSAPrivateKey,
ec.EllipticCurvePrivateKey,
]
CERTIFICATE_PRIVATE_KEY_TYPES = CertificateIssuerPrivateKeyTypes
utils.deprecated(
CERTIFICATE_PRIVATE_KEY_TYPES,
__name__,
"Use CertificateIssuerPrivateKeyTypes instead",
utils.DeprecatedIn40,
name="CERTIFICATE_PRIVATE_KEY_TYPES",
)
# Just the key types we allow to be used for x509 signing. This mirrors
# the certificate private key types
CertificateIssuerPublicKeyTypes = typing.Union[
dsa.DSAPublicKey,
rsa.RSAPublicKey,
ec.EllipticCurvePublicKey,
ed25519.Ed25519PublicKey,
ed448.Ed448PublicKey,
]
CERTIFICATE_ISSUER_PUBLIC_KEY_TYPES = CertificateIssuerPublicKeyTypes
utils.deprecated(
CERTIFICATE_ISSUER_PUBLIC_KEY_TYPES,
__name__,
"Use CertificateIssuerPublicKeyTypes instead",
utils.DeprecatedIn40,
name="CERTIFICATE_ISSUER_PUBLIC_KEY_TYPES",
)
# This type removes DHPublicKey. x448/x25519 can be a public key
# but cannot be used in signing so they are allowed here.
CertificatePublicKeyTypes = typing.Union[
dsa.DSAPublicKey,
rsa.RSAPublicKey,
ec.EllipticCurvePublicKey,
ed25519.Ed25519PublicKey,
ed448.Ed448PublicKey,
x25519.X25519PublicKey,
x448.X448PublicKey,
]
CERTIFICATE_PUBLIC_KEY_TYPES = CertificatePublicKeyTypes
utils.deprecated(
CERTIFICATE_PUBLIC_KEY_TYPES,
__name__,
"Use CertificatePublicKeyTypes instead",
utils.DeprecatedIn40,
name="CERTIFICATE_PUBLIC_KEY_TYPES",
)

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 __future__ import annotations
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,109 @@
# 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 __future__ import annotations
import abc
from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
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 rust_openssl.x25519.from_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 public_bytes_raw(self) -> bytes:
"""
The raw bytes of the public key.
Equivalent to public_bytes(Raw, Raw).
"""
@abc.abstractmethod
def __eq__(self, other: object) -> bool:
"""
Checks equality.
"""
X25519PublicKey.register(rust_openssl.x25519.X25519PublicKey)
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 rust_openssl.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 rust_openssl.x25519.from_private_bytes(data)
@abc.abstractmethod
def public_key(self) -> X25519PublicKey:
"""
Returns the public key associated with this 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 private_bytes_raw(self) -> bytes:
"""
The raw bytes of the private key.
Equivalent to private_bytes(Raw, Raw, NoEncryption()).
"""
@abc.abstractmethod
def exchange(self, peer_public_key: X25519PublicKey) -> bytes:
"""
Performs a key exchange operation using the provided peer's public key.
"""
X25519PrivateKey.register(rust_openssl.x25519.X25519PrivateKey)

View File

@ -0,0 +1,112 @@
# 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 __future__ import annotations
import abc
from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
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 rust_openssl.x448.from_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 public_bytes_raw(self) -> bytes:
"""
The raw bytes of the public key.
Equivalent to public_bytes(Raw, Raw).
"""
@abc.abstractmethod
def __eq__(self, other: object) -> bool:
"""
Checks equality.
"""
if hasattr(rust_openssl, "x448"):
X448PublicKey.register(rust_openssl.x448.X448PublicKey)
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 rust_openssl.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 rust_openssl.x448.from_private_bytes(data)
@abc.abstractmethod
def public_key(self) -> X448PublicKey:
"""
Returns the public key associated with this 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 private_bytes_raw(self) -> bytes:
"""
The raw bytes of the private key.
Equivalent to private_bytes(Raw, Raw, NoEncryption()).
"""
@abc.abstractmethod
def exchange(self, peer_public_key: X448PublicKey) -> bytes:
"""
Performs a key exchange operation using the provided peer's public key.
"""
if hasattr(rust_openssl, "x448"):
X448PrivateKey.register(rust_openssl.x448.X448PrivateKey)

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 __future__ import annotations
from cryptography.hazmat.primitives._cipheralgorithm import (
BlockCipherAlgorithm,
CipherAlgorithm,
)
from cryptography.hazmat.primitives.ciphers.base import (
AEADCipherContext,
AEADDecryptionContext,
AEADEncryptionContext,
Cipher,
CipherContext,
)
__all__ = [
"AEADCipherContext",
"AEADDecryptionContext",
"AEADEncryptionContext",
"BlockCipherAlgorithm",
"Cipher",
"CipherAlgorithm",
"CipherContext",
]

View File

@ -0,0 +1,23 @@
# 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 __future__ import annotations
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
__all__ = [
"AESCCM",
"AESGCM",
"AESGCMSIV",
"AESOCB3",
"AESSIV",
"ChaCha20Poly1305",
]
AESGCM = rust_openssl.aead.AESGCM
ChaCha20Poly1305 = rust_openssl.aead.ChaCha20Poly1305
AESCCM = rust_openssl.aead.AESCCM
AESSIV = rust_openssl.aead.AESSIV
AESOCB3 = rust_openssl.aead.AESOCB3
AESGCMSIV = rust_openssl.aead.AESGCMSIV

View File

@ -0,0 +1,183 @@
# 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 __future__ import annotations
from cryptography import utils
from cryptography.hazmat.decrepit.ciphers.algorithms import (
ARC4 as ARC4,
)
from cryptography.hazmat.decrepit.ciphers.algorithms import (
CAST5 as CAST5,
)
from cryptography.hazmat.decrepit.ciphers.algorithms import (
IDEA as IDEA,
)
from cryptography.hazmat.decrepit.ciphers.algorithms import (
SEED as SEED,
)
from cryptography.hazmat.decrepit.ciphers.algorithms import (
Blowfish as Blowfish,
)
from cryptography.hazmat.decrepit.ciphers.algorithms import (
TripleDES as TripleDES,
)
from cryptography.hazmat.primitives._cipheralgorithm import _verify_key_size
from cryptography.hazmat.primitives.ciphers import (
BlockCipherAlgorithm,
CipherAlgorithm,
)
class AES(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 AES128(BlockCipherAlgorithm):
name = "AES"
block_size = 128
key_sizes = frozenset([128])
key_size = 128
def __init__(self, key: bytes):
self.key = _verify_key_size(self, key)
class AES256(BlockCipherAlgorithm):
name = "AES"
block_size = 128
key_sizes = frozenset([256])
key_size = 256
def __init__(self, key: bytes):
self.key = _verify_key_size(self, key)
class Camellia(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
utils.deprecated(
ARC4,
__name__,
"ARC4 has been moved to "
"cryptography.hazmat.decrepit.ciphers.algorithms.ARC4 and "
"will be removed from "
"cryptography.hazmat.primitives.ciphers.algorithms in 48.0.0.",
utils.DeprecatedIn43,
name="ARC4",
)
utils.deprecated(
TripleDES,
__name__,
"TripleDES has been moved to "
"cryptography.hazmat.decrepit.ciphers.algorithms.TripleDES and "
"will be removed from "
"cryptography.hazmat.primitives.ciphers.algorithms in 48.0.0.",
utils.DeprecatedIn43,
name="TripleDES",
)
utils.deprecated(
Blowfish,
__name__,
"Blowfish has been moved to "
"cryptography.hazmat.decrepit.ciphers.algorithms.Blowfish and "
"will be removed from "
"cryptography.hazmat.primitives.ciphers.algorithms in 45.0.0.",
utils.DeprecatedIn37,
name="Blowfish",
)
utils.deprecated(
CAST5,
__name__,
"CAST5 has been moved to "
"cryptography.hazmat.decrepit.ciphers.algorithms.CAST5 and "
"will be removed from "
"cryptography.hazmat.primitives.ciphers.algorithms in 45.0.0.",
utils.DeprecatedIn37,
name="CAST5",
)
utils.deprecated(
IDEA,
__name__,
"IDEA has been moved to "
"cryptography.hazmat.decrepit.ciphers.algorithms.IDEA and "
"will be removed from "
"cryptography.hazmat.primitives.ciphers.algorithms in 45.0.0.",
utils.DeprecatedIn37,
name="IDEA",
)
utils.deprecated(
SEED,
__name__,
"SEED has been moved to "
"cryptography.hazmat.decrepit.ciphers.algorithms.SEED and "
"will be removed from "
"cryptography.hazmat.primitives.ciphers.algorithms in 45.0.0.",
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(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,145 @@
# 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 __future__ import annotations
import abc
import typing
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
from cryptography.hazmat.primitives._cipheralgorithm import CipherAlgorithm
from cryptography.hazmat.primitives.ciphers import modes
class CipherContext(metaclass=abc.ABCMeta):
@abc.abstractmethod
def update(self, data: bytes) -> bytes:
"""
Processes the provided bytes through the cipher and returns the results
as bytes.
"""
@abc.abstractmethod
def update_into(self, data: bytes, buf: 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.
"""
@abc.abstractmethod
def reset_nonce(self, nonce: bytes) -> None:
"""
Resets the nonce for the cipher context to the provided value.
Raises an exception if it does not support reset or if the
provided nonce does not have a valid length.
"""
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):
@property
@abc.abstractmethod
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,
) -> 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."
)
return rust_openssl.ciphers.create_encryption_ctx(
self.algorithm, self.mode
)
@typing.overload
def decryptor(
self: Cipher[modes.ModeWithAuthenticationTag],
) -> AEADDecryptionContext: ...
@typing.overload
def decryptor(
self: _CIPHER_TYPE,
) -> CipherContext: ...
def decryptor(self):
return rust_openssl.ciphers.create_decryption_ctx(
self.algorithm, self.mode
)
_CIPHER_TYPE = Cipher[
typing.Union[
modes.ModeWithNonce,
modes.ModeWithTweak,
None,
modes.ECB,
modes.ModeWithInitializationVector,
]
]
CipherContext.register(rust_openssl.ciphers.CipherContext)
AEADEncryptionContext.register(rust_openssl.ciphers.AEADEncryptionContext)
AEADDecryptionContext.register(rust_openssl.ciphers.AEADDecryptionContext)

View File

@ -0,0 +1,268 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
from __future__ import annotations
import abc
from cryptography import utils
from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
from cryptography.hazmat.primitives._cipheralgorithm import (
BlockCipherAlgorithm,
CipherAlgorithm,
)
from cryptography.hazmat.primitives.ciphers import algorithms
class Mode(metaclass=abc.ABCMeta):
@property
@abc.abstractmethod
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):
@property
@abc.abstractmethod
def initialization_vector(self) -> bytes:
"""
The value of the initialization vector for this mode as bytes.
"""
class ModeWithTweak(Mode, metaclass=abc.ABCMeta):
@property
@abc.abstractmethod
def tweak(self) -> bytes:
"""
The value of the tweak for this mode as bytes.
"""
class ModeWithNonce(Mode, metaclass=abc.ABCMeta):
@property
@abc.abstractmethod
def nonce(self) -> bytes:
"""
The value of the nonce for this mode as bytes.
"""
class ModeWithAuthenticationTag(Mode, metaclass=abc.ABCMeta):
@property
@abc.abstractmethod
def tag(self) -> bytes | None:
"""
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:
iv_len = len(self.initialization_vector)
if iv_len * 8 != algorithm.block_size:
raise ValueError(f"Invalid IV size ({iv_len}) for {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(f"Invalid nonce size ({len(nonce)}) for {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 isinstance(algorithm, (algorithms.AES128, algorithms.AES256)):
raise TypeError(
"The AES128 and AES256 classes do not support XTS, please use "
"the standard AES class instead."
)
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: bytes | None = 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(
f"Authentication tag must be {min_tag_length} bytes or "
"longer."
)
self._tag = tag
self._min_tag_length = min_tag_length
@property
def tag(self) -> bytes | None:
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(
f"Authentication tag cannot be more than {block_size_bytes} "
"bytes."
)

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 __future__ import annotations
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
__all__ = ["CMAC"]
CMAC = rust_openssl.cmac.CMAC

View File

@ -0,0 +1,14 @@
# 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 __future__ import annotations
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,242 @@
# 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 __future__ import annotations
import abc
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
__all__ = [
"MD5",
"SHA1",
"SHA3_224",
"SHA3_256",
"SHA3_384",
"SHA3_512",
"SHA224",
"SHA256",
"SHA384",
"SHA512",
"SHA512_224",
"SHA512_256",
"SHAKE128",
"SHAKE256",
"SM3",
"BLAKE2b",
"BLAKE2s",
"ExtendableOutputFunction",
"Hash",
"HashAlgorithm",
"HashContext",
]
class HashAlgorithm(metaclass=abc.ABCMeta):
@property
@abc.abstractmethod
def name(self) -> str:
"""
A string naming this algorithm (e.g. "sha256", "md5").
"""
@property
@abc.abstractmethod
def digest_size(self) -> int:
"""
The size of the resulting digest in bytes.
"""
@property
@abc.abstractmethod
def block_size(self) -> int | None:
"""
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):
@property
@abc.abstractmethod
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.
"""
Hash = rust_openssl.hashes.Hash
HashContext.register(Hash)
class ExtendableOutputFunction(metaclass=abc.ABCMeta):
"""
An interface for extendable output functions.
"""
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,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.
from __future__ import annotations
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
from cryptography.hazmat.primitives import hashes
__all__ = ["HMAC"]
HMAC = rust_openssl.hmac.HMAC
hashes.HashContext.register(HMAC)

View File

@ -0,0 +1,23 @@
# 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 __future__ import annotations
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,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.
from __future__ import annotations
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
from cryptography.hazmat.primitives.kdf import KeyDerivationFunction
Argon2id = rust_openssl.kdf.Argon2id
KeyDerivationFunction.register(Argon2id)
__all__ = ["Argon2id"]

View File

@ -0,0 +1,124 @@
# 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 __future__ import annotations
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: bytes | None,
) -> None:
max_length = algorithm.digest_size * (2**32 - 1)
if length > max_length:
raise ValueError(f"Cannot derive keys larger than {max_length} bits.")
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: bytes | None,
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: bytes | None,
otherinfo: bytes | None,
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(f"{algorithm.name} is unsupported for ConcatKDF")
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,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.
from __future__ import annotations
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: bytes | None,
info: bytes | None,
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: bytes | None,
backend: typing.Any = None,
):
self._algorithm = algorithm
max_length = 255 * algorithm.digest_size
if length > max_length:
raise ValueError(
f"Cannot derive keys larger than {max_length} octets."
)
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,302 @@
# 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 __future__ import annotations
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"
MiddleFixed = "middle_fixed"
class _KBKDFDeriver:
def __init__(
self,
prf: typing.Callable,
mode: Mode,
length: int,
rlen: int,
llen: int | None,
location: CounterLocation,
break_location: int | None,
label: bytes | None,
context: bytes | None,
fixed: bytes | None,
):
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 break_location is None and location is CounterLocation.MiddleFixed:
raise ValueError("Please specify a break_location")
if (
break_location is not None
and location != CounterLocation.MiddleFixed
):
raise ValueError(
"break_location is ignored when location is not"
" CounterLocation.MiddleFixed"
)
if break_location is not None and not isinstance(break_location, int):
raise TypeError("break_location must be an integer")
if break_location is not None and break_location < 0:
raise ValueError("break_location must be a positive integer")
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 llen == 0:
raise ValueError("llen must be non-zero")
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._break_location = break_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.")
fixed = self._generate_fixed_input()
if self._location == CounterLocation.BeforeFixed:
data_before_ctr = b""
data_after_ctr = fixed
elif self._location == CounterLocation.AfterFixed:
data_before_ctr = fixed
data_after_ctr = b""
else:
if isinstance(
self._break_location, int
) and self._break_location > len(fixed):
raise ValueError("break_location offset > len(fixed)")
data_before_ctr = fixed[: self._break_location]
data_after_ctr = fixed[self._break_location :]
for i in range(1, rounds + 1):
h = self._prf(key_material)
counter = utils.int_to_bytes(i, self._rlen)
input_data = data_before_ctr + counter + data_after_ctr
h.update(input_data)
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: int | None,
location: CounterLocation,
label: bytes | None,
context: bytes | None,
fixed: bytes | None,
backend: typing.Any = None,
*,
break_location: int | None = 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,
break_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: int | None,
location: CounterLocation,
label: bytes | None,
context: bytes | None,
fixed: bytes | None,
backend: typing.Any = None,
*,
break_location: int | None = 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: ciphers.BlockCipherAlgorithm | None = None
self._deriver = _KBKDFDeriver(
self._prf,
mode,
length,
rlen,
llen,
location,
break_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,62 @@
# 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 __future__ import annotations
import typing
from cryptography import utils
from cryptography.exceptions import (
AlreadyFinalized,
InvalidKey,
UnsupportedAlgorithm,
_Reasons,
)
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
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(
f"{algorithm.name} is not supported for PBKDF2.",
_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
return rust_openssl.kdf.derive_pbkdf2_hmac(
key_material,
self._algorithm,
self._salt,
self._iterations,
self._length,
)
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,19 @@
# 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 __future__ import annotations
import sys
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
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
Scrypt = rust_openssl.kdf.Scrypt
KeyDerivationFunction.register(Scrypt)
__all__ = ["Scrypt"]

View File

@ -0,0 +1,61 @@
# 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 __future__ import annotations
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: bytes | None,
backend: typing.Any = None,
):
max_len = algorithm.digest_size * (2**32 - 1)
if length > max_len:
raise ValueError(f"Cannot derive keys larger than {max_len} bits.")
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,177 @@
# 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 __future__ import annotations
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: 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: list[bytes],
) -> tuple[bytes, 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,183 @@
# 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 __future__ import annotations
import abc
import typing
from cryptography import utils
from cryptography.exceptions import AlreadyFinalized
from cryptography.hazmat.bindings._rust import (
PKCS7PaddingContext,
PKCS7UnpaddingContext,
check_ansix923_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_: bytes | None, data: bytes, block_size: int
) -> 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_: bytes | None,
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_: bytes | None, data: bytes, block_size: int
) -> 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_: bytes | None,
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)
PaddingContext.register(PKCS7PaddingContext)
PaddingContext.register(PKCS7UnpaddingContext)
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: bytes | None
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: bytes | None
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,11 @@
# 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 __future__ import annotations
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
__all__ = ["Poly1305"]
Poly1305 = rust_openssl.poly1305.Poly1305

View File

@ -0,0 +1,63 @@
# 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 __future__ import annotations
from cryptography.hazmat.primitives._serialization import (
BestAvailableEncryption,
Encoding,
KeySerializationEncryption,
NoEncryption,
ParameterFormat,
PrivateFormat,
PublicFormat,
_KeySerializationEncryption,
)
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 (
SSHCertificate,
SSHCertificateBuilder,
SSHCertificateType,
SSHCertPrivateKeyTypes,
SSHCertPublicKeyTypes,
SSHPrivateKeyTypes,
SSHPublicKeyTypes,
load_ssh_private_key,
load_ssh_public_identity,
load_ssh_public_key,
)
__all__ = [
"BestAvailableEncryption",
"Encoding",
"KeySerializationEncryption",
"NoEncryption",
"ParameterFormat",
"PrivateFormat",
"PublicFormat",
"SSHCertPrivateKeyTypes",
"SSHCertPublicKeyTypes",
"SSHCertificate",
"SSHCertificateBuilder",
"SSHCertificateType",
"SSHPrivateKeyTypes",
"SSHPublicKeyTypes",
"_KeySerializationEncryption",
"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_identity",
"load_ssh_public_key",
]

View File

@ -0,0 +1,14 @@
# 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 openssl as rust_openssl
load_pem_private_key = rust_openssl.keys.load_pem_private_key
load_der_private_key = rust_openssl.keys.load_der_private_key
load_pem_public_key = rust_openssl.keys.load_pem_public_key
load_der_public_key = rust_openssl.keys.load_der_public_key
load_pem_parameters = rust_openssl.dh.from_pem_parameters
load_der_parameters = rust_openssl.dh.from_der_parameters

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.
from __future__ import annotations
import typing
from cryptography import x509
from cryptography.hazmat.bindings._rust import pkcs12 as rust_pkcs12
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives._serialization import PBES as PBES
from cryptography.hazmat.primitives.asymmetric import (
dsa,
ec,
ed448,
ed25519,
rsa,
)
from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes
__all__ = [
"PBES",
"PKCS12Certificate",
"PKCS12KeyAndCertificates",
"PKCS12PrivateKeyTypes",
"load_key_and_certificates",
"load_pkcs12",
"serialize_key_and_certificates",
]
PKCS12PrivateKeyTypes = typing.Union[
rsa.RSAPrivateKey,
dsa.DSAPrivateKey,
ec.EllipticCurvePrivateKey,
ed25519.Ed25519PrivateKey,
ed448.Ed448PrivateKey,
]
PKCS12Certificate = rust_pkcs12.PKCS12Certificate
class PKCS12KeyAndCertificates:
def __init__(
self,
key: PrivateKeyTypes | None,
cert: PKCS12Certificate | None,
additional_certs: 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) -> PrivateKeyTypes | None:
return self._key
@property
def cert(self) -> PKCS12Certificate | None:
return self._cert
@property
def additional_certs(self) -> 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)
load_key_and_certificates = rust_pkcs12.load_key_and_certificates
load_pkcs12 = rust_pkcs12.load_pkcs12
_PKCS12CATypes = typing.Union[
x509.Certificate,
PKCS12Certificate,
]
def serialize_key_and_certificates(
name: bytes | None,
key: PKCS12PrivateKeyTypes | None,
cert: x509.Certificate | None,
cas: typing.Iterable[_PKCS12CATypes] | None,
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 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")
return rust_pkcs12.serialize_key_and_certificates(
name, key, cert, cas, encryption_algorithm
)

View File

@ -0,0 +1,369 @@
# 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 __future__ import annotations
import email.base64mime
import email.generator
import email.message
import email.policy
import io
import typing
from cryptography import utils, x509
from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
from cryptography.hazmat.bindings._rust import pkcs7 as rust_pkcs7
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa
from cryptography.utils import _check_byteslike
load_pem_pkcs7_certificates = rust_pkcs7.load_pem_pkcs7_certificates
load_der_pkcs7_certificates = rust_pkcs7.load_der_pkcs7_certificates
serialize_certificates = rust_pkcs7.serialize_certificates
PKCS7HashTypes = typing.Union[
hashes.SHA224,
hashes.SHA256,
hashes.SHA384,
hashes.SHA512,
]
PKCS7PrivateKeyTypes = 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: bytes | None = None,
signers: list[
tuple[
x509.Certificate,
PKCS7PrivateKeyTypes,
PKCS7HashTypes,
padding.PSS | padding.PKCS1v15 | None,
]
] = [],
additional_certs: 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: PKCS7PrivateKeyTypes,
hash_algorithm: PKCS7HashTypes,
*,
rsa_padding: padding.PSS | padding.PKCS1v15 | None = None,
) -> PKCS7SignatureBuilder:
if not isinstance(
hash_algorithm,
(
hashes.SHA224,
hashes.SHA256,
hashes.SHA384,
hashes.SHA512,
),
):
raise TypeError(
"hash_algorithm must be one of hashes.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.")
if rsa_padding is not None:
if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)):
raise TypeError("Padding must be PSS or PKCS1v15")
if not isinstance(private_key, rsa.RSAPrivateKey):
raise TypeError("Padding is only supported for RSA keys")
return PKCS7SignatureBuilder(
self._data,
[
*self._signers,
(certificate, private_key, hash_algorithm, rsa_padding),
],
)
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."
)
return rust_pkcs7.sign_and_serialize(self, encoding, options)
class PKCS7EnvelopeBuilder:
def __init__(
self,
*,
_data: bytes | None = None,
_recipients: list[x509.Certificate] | None = None,
):
from cryptography.hazmat.backends.openssl.backend import (
backend as ossl,
)
if not ossl.rsa_encryption_supported(padding=padding.PKCS1v15()):
raise UnsupportedAlgorithm(
"RSA with PKCS1 v1.5 padding is not supported by this version"
" of OpenSSL.",
_Reasons.UNSUPPORTED_PADDING,
)
self._data = _data
self._recipients = _recipients if _recipients is not None else []
def set_data(self, data: bytes) -> PKCS7EnvelopeBuilder:
_check_byteslike("data", data)
if self._data is not None:
raise ValueError("data may only be set once")
return PKCS7EnvelopeBuilder(_data=data, _recipients=self._recipients)
def add_recipient(
self,
certificate: x509.Certificate,
) -> PKCS7EnvelopeBuilder:
if not isinstance(certificate, x509.Certificate):
raise TypeError("certificate must be a x509.Certificate")
if not isinstance(certificate.public_key(), rsa.RSAPublicKey):
raise TypeError("Only RSA keys are supported at this time.")
return PKCS7EnvelopeBuilder(
_data=self._data,
_recipients=[
*self._recipients,
certificate,
],
)
def encrypt(
self,
encoding: serialization.Encoding,
options: typing.Iterable[PKCS7Options],
) -> bytes:
if len(self._recipients) == 0:
raise ValueError("Must have at least one recipient")
if self._data is None:
raise ValueError("You must add data to encrypt")
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"
)
# Only allow options that make sense for encryption
if any(
opt not in [PKCS7Options.Text, PKCS7Options.Binary]
for opt in options
):
raise ValueError(
"Only the following options are supported for encryption: "
"Text, Binary"
)
elif PKCS7Options.Text in options and PKCS7Options.Binary in options:
# OpenSSL accepts both options at the same time, but ignores Text.
# We fail defensively to avoid unexpected outputs.
raise ValueError(
"Cannot use Binary and Text options at the same time"
)
return rust_pkcs7.encrypt_and_serialize(self, encoding, options)
pkcs7_decrypt_der = rust_pkcs7.decrypt_der
pkcs7_decrypt_pem = rust_pkcs7.decrypt_pem
pkcs7_decrypt_smime = rust_pkcs7.decrypt_smime
def _smime_signed_encode(
data: bytes, signature: bytes, micalg: str, text_mode: bool
) -> bytes:
# This function works pretty hard to replicate what OpenSSL does
# precisely. For good and for ill.
m = email.message.Message()
m.add_header("MIME-Version", "1.0")
m.add_header(
"Content-Type",
"multipart/signed",
protocol="application/x-pkcs7-signature",
micalg=micalg,
)
m.preamble = "This is an S/MIME signed message\n"
msg_part = OpenSSLMimePart()
msg_part.set_payload(data)
if text_mode:
msg_part.add_header("Content-Type", "text/plain")
m.attach(msg_part)
sig_part = email.message.MIMEPart()
sig_part.add_header(
"Content-Type", "application/x-pkcs7-signature", name="smime.p7s"
)
sig_part.add_header("Content-Transfer-Encoding", "base64")
sig_part.add_header(
"Content-Disposition", "attachment", filename="smime.p7s"
)
sig_part.set_payload(
email.base64mime.body_encode(signature, maxlinelen=65)
)
del sig_part["MIME-Version"]
m.attach(sig_part)
fp = io.BytesIO()
g = email.generator.BytesGenerator(
fp,
maxheaderlen=0,
mangle_from_=False,
policy=m.policy.clone(linesep="\r\n"),
)
g.flatten(m)
return fp.getvalue()
def _smime_enveloped_encode(data: bytes) -> bytes:
m = email.message.Message()
m.add_header("MIME-Version", "1.0")
m.add_header("Content-Disposition", "attachment", filename="smime.p7m")
m.add_header(
"Content-Type",
"application/pkcs7-mime",
smime_type="enveloped-data",
name="smime.p7m",
)
m.add_header("Content-Transfer-Encoding", "base64")
m.set_payload(email.base64mime.body_encode(data, maxlinelen=65))
return m.as_bytes(policy=m.policy.clone(linesep="\n", max_line_length=0))
def _smime_enveloped_decode(data: bytes) -> bytes:
m = email.message_from_bytes(data)
if m.get_content_type() not in {
"application/x-pkcs7-mime",
"application/pkcs7-mime",
}:
raise ValueError("Not an S/MIME enveloped message")
return bytes(m.get_payload(decode=True))
def _smime_remove_text_headers(data: bytes) -> bytes:
m = email.message_from_bytes(data)
# Using get() instead of get_content_type() since it has None as default,
# where the latter has "text/plain". Both methods are case-insensitive.
content_type = m.get("content-type")
if content_type is None:
raise ValueError(
"Decrypted MIME data has no 'Content-Type' header. "
"Please remove the 'Text' option to parse it manually."
)
if "text/plain" not in content_type:
raise ValueError(
f"Decrypted MIME data content type is '{content_type}', not "
"'text/plain'. Remove the 'Text' option to parse it manually."
)
return bytes(m.get_payload(decode=True))
class OpenSSLMimePart(email.message.MIMEPart):
# A MIMEPart subclass that replicates OpenSSL's behavior of not including
# a newline if there are no headers.
def _write_headers(self, generator) -> None:
if list(self.raw_items()):
generator._write_headers(self)

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 __future__ import annotations
class InvalidToken(Exception):
pass

View File

@ -0,0 +1,100 @@
# 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 __future__ import annotations
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
HOTPHashTypes = typing.Union[SHA1, SHA256, SHA512]
def _generate_uri(
hotp: HOTP,
type_name: str,
account_name: str,
issuer: str | None,
extra_parameters: list[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: HOTPHashTypes,
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 and 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:
if not isinstance(counter, int):
raise TypeError("Counter parameter must be an integer type.")
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)
try:
ctx.update(counter.to_bytes(length=8, byteorder="big"))
except OverflowError:
raise ValueError(f"Counter must be between 0 and {2 ** 64 - 1}.")
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: str | None
) -> str:
return _generate_uri(
self, "hotp", account_name, issuer, [("counter", int(counter))]
)

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.
from __future__ import annotations
import typing
from cryptography.hazmat.primitives import constant_time
from cryptography.hazmat.primitives.twofactor import InvalidToken
from cryptography.hazmat.primitives.twofactor.hotp import (
HOTP,
HOTPHashTypes,
_generate_uri,
)
class TOTP:
def __init__(
self,
key: bytes,
length: int,
algorithm: HOTPHashTypes,
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: int | float) -> bytes:
if not isinstance(time, (int, float)):
raise TypeError(
"Time parameter must be an integer type or float type."
)
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: str | None
) -> str:
return _generate_uri(
self._hotp,
"totp",
account_name,
issuer,
[("period", int(self._time_step))],
)

View File

@ -0,0 +1,127 @@
# 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 __future__ import annotations
import enum
import sys
import types
import typing
import warnings
# We use a UserWarning subclass, instead of DeprecationWarning, because CPython
# decided deprecation warnings should be invisible 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.
DeprecatedIn36 = CryptographyDeprecationWarning
DeprecatedIn37 = CryptographyDeprecationWarning
DeprecatedIn40 = CryptographyDeprecationWarning
DeprecatedIn41 = CryptographyDeprecationWarning
DeprecatedIn42 = CryptographyDeprecationWarning
DeprecatedIn43 = CryptographyDeprecationWarning
def _check_bytes(name: str, value: bytes) -> None:
if not isinstance(value, bytes):
raise TypeError(f"{name} must be bytes")
def _check_byteslike(name: str, value: bytes) -> None:
try:
memoryview(value)
except TypeError:
raise TypeError(f"{name} must be bytes-like")
def int_to_bytes(integer: int, length: int | None = None) -> bytes:
if length == 0:
raise ValueError("length argument can't be 0")
return integer.to_bytes(
length or (integer.bit_length() + 7) // 8 or 1, "big"
)
class InterfaceNotImplemented(Exception):
pass
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: type[Warning],
name: str | None = 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 = f"_cached_{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,267 @@
# 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 __future__ import annotations
from cryptography.x509 import certificate_transparency, verification
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_certificates,
load_pem_x509_crl,
load_pem_x509_csr,
random_serial_number,
)
from cryptography.x509.extensions import (
AccessDescription,
Admission,
Admissions,
AuthorityInformationAccess,
AuthorityKeyIdentifier,
BasicConstraints,
CertificateIssuer,
CertificatePolicies,
CRLDistributionPoints,
CRLNumber,
CRLReason,
DeltaCRLIndicator,
DistributionPoint,
DuplicateExtension,
ExtendedKeyUsage,
Extension,
ExtensionNotFound,
Extensions,
ExtensionType,
FreshestCRL,
GeneralNames,
InhibitAnyPolicy,
InvalidityDate,
IssuerAlternativeName,
IssuingDistributionPoint,
KeyUsage,
MSCertificateTemplate,
NameConstraints,
NamingAuthority,
NoticeReference,
OCSPAcceptableResponses,
OCSPNoCheck,
OCSPNonce,
PolicyConstraints,
PolicyInformation,
PrecertificateSignedCertificateTimestamps,
PrecertPoison,
ProfessionInfo,
ReasonFlags,
SignedCertificateTimestamps,
SubjectAlternativeName,
SubjectInformationAccess,
SubjectKeyIdentifier,
TLSFeature,
TLSFeatureType,
UnrecognizedExtension,
UserNotice,
)
from cryptography.x509.general_name import (
DirectoryName,
DNSName,
GeneralName,
IPAddress,
OtherName,
RegisteredID,
RFC822Name,
UniformResourceIdentifier,
UnsupportedGeneralNameType,
)
from cryptography.x509.name import (
Name,
NameAttribute,
RelativeDistinguishedName,
)
from cryptography.x509.oid import (
AuthorityInformationAccessOID,
CertificatePoliciesOID,
CRLEntryExtensionOID,
ExtendedKeyUsageOID,
ExtensionOID,
NameOID,
ObjectIdentifier,
PublicKeyAlgorithmOID,
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__ = [
"OID_CA_ISSUERS",
"OID_OCSP",
"AccessDescription",
"Admission",
"Admissions",
"Attribute",
"AttributeNotFound",
"Attributes",
"AuthorityInformationAccess",
"AuthorityKeyIdentifier",
"BasicConstraints",
"CRLDistributionPoints",
"CRLNumber",
"CRLReason",
"Certificate",
"CertificateBuilder",
"CertificateIssuer",
"CertificatePolicies",
"CertificateRevocationList",
"CertificateRevocationListBuilder",
"CertificateSigningRequest",
"CertificateSigningRequestBuilder",
"DNSName",
"DeltaCRLIndicator",
"DirectoryName",
"DistributionPoint",
"DuplicateExtension",
"ExtendedKeyUsage",
"Extension",
"ExtensionNotFound",
"ExtensionType",
"Extensions",
"FreshestCRL",
"GeneralName",
"GeneralNames",
"IPAddress",
"InhibitAnyPolicy",
"InvalidVersion",
"InvalidityDate",
"IssuerAlternativeName",
"IssuingDistributionPoint",
"KeyUsage",
"MSCertificateTemplate",
"Name",
"NameAttribute",
"NameConstraints",
"NameOID",
"NamingAuthority",
"NoticeReference",
"OCSPAcceptableResponses",
"OCSPNoCheck",
"OCSPNonce",
"ObjectIdentifier",
"OtherName",
"PolicyConstraints",
"PolicyInformation",
"PrecertPoison",
"PrecertificateSignedCertificateTimestamps",
"ProfessionInfo",
"PublicKeyAlgorithmOID",
"RFC822Name",
"ReasonFlags",
"RegisteredID",
"RelativeDistinguishedName",
"RevokedCertificate",
"RevokedCertificateBuilder",
"SignatureAlgorithmOID",
"SignedCertificateTimestamps",
"SubjectAlternativeName",
"SubjectInformationAccess",
"SubjectKeyIdentifier",
"TLSFeature",
"TLSFeatureType",
"UniformResourceIdentifier",
"UnrecognizedExtension",
"UnsupportedGeneralNameType",
"UserNotice",
"Version",
"certificate_transparency",
"load_der_x509_certificate",
"load_der_x509_crl",
"load_der_x509_csr",
"load_pem_x509_certificate",
"load_pem_x509_certificates",
"load_pem_x509_crl",
"load_pem_x509_csr",
"random_serial_number",
"verification",
"verification",
]

View File

@ -0,0 +1,815 @@
# 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 __future__ import annotations
import abc
import datetime
import os
import typing
import warnings
from cryptography import utils
from cryptography.hazmat.bindings._rust import x509 as rust_x509
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import (
dsa,
ec,
ed448,
ed25519,
padding,
rsa,
x448,
x25519,
)
from cryptography.hazmat.primitives.asymmetric.types import (
CertificateIssuerPrivateKeyTypes,
CertificatePublicKeyTypes,
)
from cryptography.x509.extensions import (
Extension,
Extensions,
ExtensionType,
_make_sequence_methods,
)
from cryptography.x509.name import Name, _ASN1Type
from cryptography.x509.oid import ObjectIdentifier
_EARLIEST_UTC_TIME = datetime.datetime(1950, 1, 1)
# This must be kept in sync with sign.rs's list of allowable types in
# identify_hash_type
_AllowedHashTypes = typing.Union[
hashes.SHA224,
hashes.SHA256,
hashes.SHA384,
hashes.SHA512,
hashes.SHA3_224,
hashes.SHA3_256,
hashes.SHA3_384,
hashes.SHA3_512,
]
class AttributeNotFound(Exception):
def __init__(self, msg: str, oid: ObjectIdentifier) -> None:
super().__init__(msg)
self.oid = oid
def _reject_duplicate_extension(
extension: Extension[ExtensionType],
extensions: list[Extension[ExtensionType]],
) -> None:
# This is quadratic in the number of extensions
for e in extensions:
if e.oid == extension.oid:
raise ValueError("This extension has already been set.")
def _reject_duplicate_attribute(
oid: ObjectIdentifier,
attributes: list[tuple[ObjectIdentifier, bytes, int | None]],
) -> None:
# This is quadratic in the number of attributes
for attr_oid, _, _ in attributes:
if attr_oid == oid:
raise ValueError("This attribute has already been set.")
def _convert_to_naive_utc_time(time: datetime.datetime) -> datetime.datetime:
"""Normalizes a datetime to a naive datetime in UTC.
time -- datetime to normalize. Assumed to be in UTC if not timezone
aware.
"""
if time.tzinfo is not None:
offset = time.utcoffset()
offset = offset if offset else datetime.timedelta()
return time.replace(tzinfo=None) - offset
else:
return time
class Attribute:
def __init__(
self,
oid: ObjectIdentifier,
value: bytes,
_type: int = _ASN1Type.UTF8String.value,
) -> None:
self._oid = oid
self._value = value
self._type = _type
@property
def oid(self) -> ObjectIdentifier:
return self._oid
@property
def value(self) -> bytes:
return self._value
def __repr__(self) -> str:
return f"<Attribute(oid={self.oid}, value={self.value!r})>"
def __eq__(self, other: object) -> bool:
if not isinstance(other, Attribute):
return NotImplemented
return (
self.oid == other.oid
and self.value == other.value
and self._type == other._type
)
def __hash__(self) -> int:
return hash((self.oid, self.value, self._type))
class Attributes:
def __init__(
self,
attributes: typing.Iterable[Attribute],
) -> None:
self._attributes = list(attributes)
__len__, __iter__, __getitem__ = _make_sequence_methods("_attributes")
def __repr__(self) -> str:
return f"<Attributes({self._attributes})>"
def get_attribute_for_oid(self, oid: ObjectIdentifier) -> Attribute:
for attr in self:
if attr.oid == oid:
return attr
raise AttributeNotFound(f"No {oid} attribute was found", oid)
class Version(utils.Enum):
v1 = 0
v3 = 2
class InvalidVersion(Exception):
def __init__(self, msg: str, parsed_version: int) -> None:
super().__init__(msg)
self.parsed_version = parsed_version
Certificate = rust_x509.Certificate
class RevokedCertificate(metaclass=abc.ABCMeta):
@property
@abc.abstractmethod
def serial_number(self) -> int:
"""
Returns the serial number of the revoked certificate.
"""
@property
@abc.abstractmethod
def revocation_date(self) -> datetime.datetime:
"""
Returns the date of when this certificate was revoked.
"""
@property
@abc.abstractmethod
def revocation_date_utc(self) -> datetime.datetime:
"""
Returns the date of when this certificate was revoked as a non-naive
UTC datetime.
"""
@property
@abc.abstractmethod
def extensions(self) -> Extensions:
"""
Returns an Extensions object containing a list of Revoked extensions.
"""
# Runtime isinstance checks need this since the rust class is not a subclass.
RevokedCertificate.register(rust_x509.RevokedCertificate)
class _RawRevokedCertificate(RevokedCertificate):
def __init__(
self,
serial_number: int,
revocation_date: datetime.datetime,
extensions: Extensions,
):
self._serial_number = serial_number
self._revocation_date = revocation_date
self._extensions = extensions
@property
def serial_number(self) -> int:
return self._serial_number
@property
def revocation_date(self) -> datetime.datetime:
warnings.warn(
"Properties that return a naïve datetime object have been "
"deprecated. Please switch to revocation_date_utc.",
utils.DeprecatedIn42,
stacklevel=2,
)
return self._revocation_date
@property
def revocation_date_utc(self) -> datetime.datetime:
return self._revocation_date.replace(tzinfo=datetime.timezone.utc)
@property
def extensions(self) -> Extensions:
return self._extensions
CertificateRevocationList = rust_x509.CertificateRevocationList
CertificateSigningRequest = rust_x509.CertificateSigningRequest
load_pem_x509_certificate = rust_x509.load_pem_x509_certificate
load_der_x509_certificate = rust_x509.load_der_x509_certificate
load_pem_x509_certificates = rust_x509.load_pem_x509_certificates
load_pem_x509_csr = rust_x509.load_pem_x509_csr
load_der_x509_csr = rust_x509.load_der_x509_csr
load_pem_x509_crl = rust_x509.load_pem_x509_crl
load_der_x509_crl = rust_x509.load_der_x509_crl
class CertificateSigningRequestBuilder:
def __init__(
self,
subject_name: Name | None = None,
extensions: list[Extension[ExtensionType]] = [],
attributes: list[tuple[ObjectIdentifier, bytes, int | None]] = [],
):
"""
Creates an empty X.509 certificate request (v1).
"""
self._subject_name = subject_name
self._extensions = extensions
self._attributes = attributes
def subject_name(self, name: Name) -> CertificateSigningRequestBuilder:
"""
Sets the certificate requestor's distinguished name.
"""
if not isinstance(name, Name):
raise TypeError("Expecting x509.Name object.")
if self._subject_name is not None:
raise ValueError("The subject name may only be set once.")
return CertificateSigningRequestBuilder(
name, self._extensions, self._attributes
)
def add_extension(
self, extval: ExtensionType, critical: bool
) -> CertificateSigningRequestBuilder:
"""
Adds an X.509 extension to the certificate request.
"""
if not isinstance(extval, ExtensionType):
raise TypeError("extension must be an ExtensionType")
extension = Extension(extval.oid, critical, extval)
_reject_duplicate_extension(extension, self._extensions)
return CertificateSigningRequestBuilder(
self._subject_name,
[*self._extensions, extension],
self._attributes,
)
def add_attribute(
self,
oid: ObjectIdentifier,
value: bytes,
*,
_tag: _ASN1Type | None = None,
) -> CertificateSigningRequestBuilder:
"""
Adds an X.509 attribute with an OID and associated value.
"""
if not isinstance(oid, ObjectIdentifier):
raise TypeError("oid must be an ObjectIdentifier")
if not isinstance(value, bytes):
raise TypeError("value must be bytes")
if _tag is not None and not isinstance(_tag, _ASN1Type):
raise TypeError("tag must be _ASN1Type")
_reject_duplicate_attribute(oid, self._attributes)
if _tag is not None:
tag = _tag.value
else:
tag = None
return CertificateSigningRequestBuilder(
self._subject_name,
self._extensions,
[*self._attributes, (oid, value, tag)],
)
def sign(
self,
private_key: CertificateIssuerPrivateKeyTypes,
algorithm: _AllowedHashTypes | None,
backend: typing.Any = None,
*,
rsa_padding: padding.PSS | padding.PKCS1v15 | None = None,
) -> CertificateSigningRequest:
"""
Signs the request using the requestor's private key.
"""
if self._subject_name is None:
raise ValueError("A CertificateSigningRequest must have a subject")
if rsa_padding is not None:
if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)):
raise TypeError("Padding must be PSS or PKCS1v15")
if not isinstance(private_key, rsa.RSAPrivateKey):
raise TypeError("Padding is only supported for RSA keys")
return rust_x509.create_x509_csr(
self, private_key, algorithm, rsa_padding
)
class CertificateBuilder:
_extensions: list[Extension[ExtensionType]]
def __init__(
self,
issuer_name: Name | None = None,
subject_name: Name | None = None,
public_key: CertificatePublicKeyTypes | None = None,
serial_number: int | None = None,
not_valid_before: datetime.datetime | None = None,
not_valid_after: datetime.datetime | None = None,
extensions: list[Extension[ExtensionType]] = [],
) -> None:
self._version = Version.v3
self._issuer_name = issuer_name
self._subject_name = subject_name
self._public_key = public_key
self._serial_number = serial_number
self._not_valid_before = not_valid_before
self._not_valid_after = not_valid_after
self._extensions = extensions
def issuer_name(self, name: Name) -> CertificateBuilder:
"""
Sets the CA's distinguished name.
"""
if not isinstance(name, Name):
raise TypeError("Expecting x509.Name object.")
if self._issuer_name is not None:
raise ValueError("The issuer name may only be set once.")
return CertificateBuilder(
name,
self._subject_name,
self._public_key,
self._serial_number,
self._not_valid_before,
self._not_valid_after,
self._extensions,
)
def subject_name(self, name: Name) -> CertificateBuilder:
"""
Sets the requestor's distinguished name.
"""
if not isinstance(name, Name):
raise TypeError("Expecting x509.Name object.")
if self._subject_name is not None:
raise ValueError("The subject name may only be set once.")
return CertificateBuilder(
self._issuer_name,
name,
self._public_key,
self._serial_number,
self._not_valid_before,
self._not_valid_after,
self._extensions,
)
def public_key(
self,
key: CertificatePublicKeyTypes,
) -> CertificateBuilder:
"""
Sets the requestor's public key (as found in the signing request).
"""
if not isinstance(
key,
(
dsa.DSAPublicKey,
rsa.RSAPublicKey,
ec.EllipticCurvePublicKey,
ed25519.Ed25519PublicKey,
ed448.Ed448PublicKey,
x25519.X25519PublicKey,
x448.X448PublicKey,
),
):
raise TypeError(
"Expecting one of DSAPublicKey, RSAPublicKey,"
" EllipticCurvePublicKey, Ed25519PublicKey,"
" Ed448PublicKey, X25519PublicKey, or "
"X448PublicKey."
)
if self._public_key is not None:
raise ValueError("The public key may only be set once.")
return CertificateBuilder(
self._issuer_name,
self._subject_name,
key,
self._serial_number,
self._not_valid_before,
self._not_valid_after,
self._extensions,
)
def serial_number(self, number: int) -> CertificateBuilder:
"""
Sets the certificate serial number.
"""
if not isinstance(number, int):
raise TypeError("Serial number must be of integral type.")
if self._serial_number is not None:
raise ValueError("The serial number may only be set once.")
if number <= 0:
raise ValueError("The serial number should be positive.")
# ASN.1 integers are always signed, so most significant bit must be
# zero.
if number.bit_length() >= 160: # As defined in RFC 5280
raise ValueError(
"The serial number should not be more than 159 bits."
)
return CertificateBuilder(
self._issuer_name,
self._subject_name,
self._public_key,
number,
self._not_valid_before,
self._not_valid_after,
self._extensions,
)
def not_valid_before(self, time: datetime.datetime) -> CertificateBuilder:
"""
Sets the certificate activation time.
"""
if not isinstance(time, datetime.datetime):
raise TypeError("Expecting datetime object.")
if self._not_valid_before is not None:
raise ValueError("The not valid before may only be set once.")
time = _convert_to_naive_utc_time(time)
if time < _EARLIEST_UTC_TIME:
raise ValueError(
"The not valid before date must be on or after"
" 1950 January 1)."
)
if self._not_valid_after is not None and time > self._not_valid_after:
raise ValueError(
"The not valid before date must be before the not valid after "
"date."
)
return CertificateBuilder(
self._issuer_name,
self._subject_name,
self._public_key,
self._serial_number,
time,
self._not_valid_after,
self._extensions,
)
def not_valid_after(self, time: datetime.datetime) -> CertificateBuilder:
"""
Sets the certificate expiration time.
"""
if not isinstance(time, datetime.datetime):
raise TypeError("Expecting datetime object.")
if self._not_valid_after is not None:
raise ValueError("The not valid after may only be set once.")
time = _convert_to_naive_utc_time(time)
if time < _EARLIEST_UTC_TIME:
raise ValueError(
"The not valid after date must be on or after"
" 1950 January 1."
)
if (
self._not_valid_before is not None
and time < self._not_valid_before
):
raise ValueError(
"The not valid after date must be after the not valid before "
"date."
)
return CertificateBuilder(
self._issuer_name,
self._subject_name,
self._public_key,
self._serial_number,
self._not_valid_before,
time,
self._extensions,
)
def add_extension(
self, extval: ExtensionType, critical: bool
) -> CertificateBuilder:
"""
Adds an X.509 extension to the certificate.
"""
if not isinstance(extval, ExtensionType):
raise TypeError("extension must be an ExtensionType")
extension = Extension(extval.oid, critical, extval)
_reject_duplicate_extension(extension, self._extensions)
return CertificateBuilder(
self._issuer_name,
self._subject_name,
self._public_key,
self._serial_number,
self._not_valid_before,
self._not_valid_after,
[*self._extensions, extension],
)
def sign(
self,
private_key: CertificateIssuerPrivateKeyTypes,
algorithm: _AllowedHashTypes | None,
backend: typing.Any = None,
*,
rsa_padding: padding.PSS | padding.PKCS1v15 | None = None,
) -> Certificate:
"""
Signs the certificate using the CA's private key.
"""
if self._subject_name is None:
raise ValueError("A certificate must have a subject name")
if self._issuer_name is None:
raise ValueError("A certificate must have an issuer name")
if self._serial_number is None:
raise ValueError("A certificate must have a serial number")
if self._not_valid_before is None:
raise ValueError("A certificate must have a not valid before time")
if self._not_valid_after is None:
raise ValueError("A certificate must have a not valid after time")
if self._public_key is None:
raise ValueError("A certificate must have a public key")
if rsa_padding is not None:
if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)):
raise TypeError("Padding must be PSS or PKCS1v15")
if not isinstance(private_key, rsa.RSAPrivateKey):
raise TypeError("Padding is only supported for RSA keys")
return rust_x509.create_x509_certificate(
self, private_key, algorithm, rsa_padding
)
class CertificateRevocationListBuilder:
_extensions: list[Extension[ExtensionType]]
_revoked_certificates: list[RevokedCertificate]
def __init__(
self,
issuer_name: Name | None = None,
last_update: datetime.datetime | None = None,
next_update: datetime.datetime | None = None,
extensions: list[Extension[ExtensionType]] = [],
revoked_certificates: list[RevokedCertificate] = [],
):
self._issuer_name = issuer_name
self._last_update = last_update
self._next_update = next_update
self._extensions = extensions
self._revoked_certificates = revoked_certificates
def issuer_name(
self, issuer_name: Name
) -> CertificateRevocationListBuilder:
if not isinstance(issuer_name, Name):
raise TypeError("Expecting x509.Name object.")
if self._issuer_name is not None:
raise ValueError("The issuer name may only be set once.")
return CertificateRevocationListBuilder(
issuer_name,
self._last_update,
self._next_update,
self._extensions,
self._revoked_certificates,
)
def last_update(
self, last_update: datetime.datetime
) -> CertificateRevocationListBuilder:
if not isinstance(last_update, datetime.datetime):
raise TypeError("Expecting datetime object.")
if self._last_update is not None:
raise ValueError("Last update may only be set once.")
last_update = _convert_to_naive_utc_time(last_update)
if last_update < _EARLIEST_UTC_TIME:
raise ValueError(
"The last update date must be on or after 1950 January 1."
)
if self._next_update is not None and last_update > self._next_update:
raise ValueError(
"The last update date must be before the next update date."
)
return CertificateRevocationListBuilder(
self._issuer_name,
last_update,
self._next_update,
self._extensions,
self._revoked_certificates,
)
def next_update(
self, next_update: datetime.datetime
) -> CertificateRevocationListBuilder:
if not isinstance(next_update, datetime.datetime):
raise TypeError("Expecting datetime object.")
if self._next_update is not None:
raise ValueError("Last update may only be set once.")
next_update = _convert_to_naive_utc_time(next_update)
if next_update < _EARLIEST_UTC_TIME:
raise ValueError(
"The last update date must be on or after 1950 January 1."
)
if self._last_update is not None and next_update < self._last_update:
raise ValueError(
"The next update date must be after the last update date."
)
return CertificateRevocationListBuilder(
self._issuer_name,
self._last_update,
next_update,
self._extensions,
self._revoked_certificates,
)
def add_extension(
self, extval: ExtensionType, critical: bool
) -> CertificateRevocationListBuilder:
"""
Adds an X.509 extension to the certificate revocation list.
"""
if not isinstance(extval, ExtensionType):
raise TypeError("extension must be an ExtensionType")
extension = Extension(extval.oid, critical, extval)
_reject_duplicate_extension(extension, self._extensions)
return CertificateRevocationListBuilder(
self._issuer_name,
self._last_update,
self._next_update,
[*self._extensions, extension],
self._revoked_certificates,
)
def add_revoked_certificate(
self, revoked_certificate: RevokedCertificate
) -> CertificateRevocationListBuilder:
"""
Adds a revoked certificate to the CRL.
"""
if not isinstance(revoked_certificate, RevokedCertificate):
raise TypeError("Must be an instance of RevokedCertificate")
return CertificateRevocationListBuilder(
self._issuer_name,
self._last_update,
self._next_update,
self._extensions,
[*self._revoked_certificates, revoked_certificate],
)
def sign(
self,
private_key: CertificateIssuerPrivateKeyTypes,
algorithm: _AllowedHashTypes | None,
backend: typing.Any = None,
*,
rsa_padding: padding.PSS | padding.PKCS1v15 | None = None,
) -> CertificateRevocationList:
if self._issuer_name is None:
raise ValueError("A CRL must have an issuer name")
if self._last_update is None:
raise ValueError("A CRL must have a last update time")
if self._next_update is None:
raise ValueError("A CRL must have a next update time")
if rsa_padding is not None:
if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)):
raise TypeError("Padding must be PSS or PKCS1v15")
if not isinstance(private_key, rsa.RSAPrivateKey):
raise TypeError("Padding is only supported for RSA keys")
return rust_x509.create_x509_crl(
self, private_key, algorithm, rsa_padding
)
class RevokedCertificateBuilder:
def __init__(
self,
serial_number: int | None = None,
revocation_date: datetime.datetime | None = None,
extensions: list[Extension[ExtensionType]] = [],
):
self._serial_number = serial_number
self._revocation_date = revocation_date
self._extensions = extensions
def serial_number(self, number: int) -> RevokedCertificateBuilder:
if not isinstance(number, int):
raise TypeError("Serial number must be of integral type.")
if self._serial_number is not None:
raise ValueError("The serial number may only be set once.")
if number <= 0:
raise ValueError("The serial number should be positive")
# ASN.1 integers are always signed, so most significant bit must be
# zero.
if number.bit_length() >= 160: # As defined in RFC 5280
raise ValueError(
"The serial number should not be more than 159 bits."
)
return RevokedCertificateBuilder(
number, self._revocation_date, self._extensions
)
def revocation_date(
self, time: datetime.datetime
) -> RevokedCertificateBuilder:
if not isinstance(time, datetime.datetime):
raise TypeError("Expecting datetime object.")
if self._revocation_date is not None:
raise ValueError("The revocation date may only be set once.")
time = _convert_to_naive_utc_time(time)
if time < _EARLIEST_UTC_TIME:
raise ValueError(
"The revocation date must be on or after 1950 January 1."
)
return RevokedCertificateBuilder(
self._serial_number, time, self._extensions
)
def add_extension(
self, extval: ExtensionType, critical: bool
) -> RevokedCertificateBuilder:
if not isinstance(extval, ExtensionType):
raise TypeError("extension must be an ExtensionType")
extension = Extension(extval.oid, critical, extval)
_reject_duplicate_extension(extension, self._extensions)
return RevokedCertificateBuilder(
self._serial_number,
self._revocation_date,
[*self._extensions, extension],
)
def build(self, backend: typing.Any = None) -> RevokedCertificate:
if self._serial_number is None:
raise ValueError("A revoked certificate must have a serial number")
if self._revocation_date is None:
raise ValueError(
"A revoked certificate must have a revocation date"
)
return _RawRevokedCertificate(
self._serial_number,
self._revocation_date,
Extensions(self._extensions),
)
def random_serial_number() -> int:
return int.from_bytes(os.urandom(20), "big") >> 1

View File

@ -0,0 +1,35 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
from __future__ import annotations
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 SignatureAlgorithm(utils.Enum):
"""
Signature algorithms that are valid for SCTs.
These are exactly the same as SignatureAlgorithm in RFC 5246 (TLS 1.2).
See: <https://datatracker.ietf.org/doc/html/rfc5246#section-7.4.1.4.1>
"""
ANONYMOUS = 0
RSA = 1
DSA = 2
ECDSA = 3
SignedCertificateTimestamp = rust_x509.Sct

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,281 @@
# 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 __future__ import annotations
import abc
import ipaddress
import typing
from email.utils import parseaddr
from cryptography.x509.name import Name
from cryptography.x509.oid import ObjectIdentifier
_IPAddressTypes = typing.Union[
ipaddress.IPv4Address,
ipaddress.IPv6Address,
ipaddress.IPv4Network,
ipaddress.IPv6Network,
]
class UnsupportedGeneralNameType(Exception):
pass
class GeneralName(metaclass=abc.ABCMeta):
@property
@abc.abstractmethod
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 f"<RFC822Name(value={self.value!r})>"
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 f"<DNSName(value={self.value!r})>"
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 f"<UniformResourceIdentifier(value={self.value!r})>"
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 f"<DirectoryName(value={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 f"<RegisteredID(value={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: _IPAddressTypes) -> 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) -> _IPAddressTypes:
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 f"<IPAddress(value={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 f"<OtherName(type_id={self.type_id}, value={self.value!r})>"
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,465 @@
# 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 __future__ import annotations
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: 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]
_NameOidMap = typing.Mapping[str, ObjectIdentifier]
#: 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()}
_NAMEOID_LENGTH_LIMIT = {
NameOID.COUNTRY_NAME: (2, 2),
NameOID.JURISDICTION_COUNTRY_NAME: (2, 2),
NameOID.COMMON_NAME: (1, 64),
}
def _escape_dn_value(val: 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: str | bytes,
_type: _ASN1Type | None = 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")
length_limits = _NAMEOID_LENGTH_LIMIT.get(oid)
if length_limits is not None:
min_length, max_length = length_limits
assert isinstance(value, str)
c_len = len(value.encode("utf8"))
if c_len < min_length or c_len > max_length:
msg = (
f"Attribute's length must be >= {min_length} and "
f"<= {max_length}, but it was {c_len}"
)
if _validate is True:
raise ValueError(msg)
else:
warnings.warn(msg, 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) -> 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: _OidNameMap | None = 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 f"<NameAttribute(oid={self.oid}, value={self.value!r})>"
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
) -> list[NameAttribute]:
return [i for i in self if i.oid == oid]
def rfc4514_string(
self, attr_name_overrides: _OidNameMap | None = 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 f"<RelativeDistinguishedName({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[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,
attr_name_overrides: _NameOidMap | None = None,
) -> Name:
return _RFC4514NameParser(data, attr_name_overrides or {}).parse()
def rfc4514_string(
self, attr_name_overrides: _OidNameMap | None = 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
) -> list[NameAttribute]:
return [i for i in self if i.oid == oid]
@property
def rdns(self) -> 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:
yield from rdn
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 f"<Name({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, attr_name_overrides: _NameOidMap) -> None:
self._data = data
self._idx = 0
self._attr_name_overrides = attr_name_overrides
def _has_data(self) -> bool:
return self._idx < len(self._data)
def _peek(self) -> str | None:
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:
"""
Parses the `data` string and converts it to a Name.
According to RFC4514 section 2.1 the RDNSequence must be
reversed when converting to string representation. So, when
we parse it, we need to reverse again to get the RDNs on the
correct order.
"""
if not self._has_data():
return Name([])
rdns = [self._parse_rdn()]
while self._has_data():
self._read_char(",")
rdns.append(self._parse_rdn())
return Name(reversed(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 = self._attr_name_overrides.get(
name, _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,344 @@
# 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 __future__ import annotations
import datetime
import typing
from cryptography import utils, x509
from cryptography.hazmat.bindings._rust import ocsp
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric.types import (
CertificateIssuerPrivateKeyTypes,
)
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: datetime.datetime | None,
revocation_time: datetime.datetime | None,
revocation_reason: x509.ReasonFlags | None,
):
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
OCSPRequest = ocsp.OCSPRequest
OCSPResponse = ocsp.OCSPResponse
OCSPSingleResponse = ocsp.OCSPSingleResponse
class OCSPRequestBuilder:
def __init__(
self,
request: tuple[
x509.Certificate, x509.Certificate, hashes.HashAlgorithm
]
| None = None,
request_hash: tuple[bytes, bytes, int, hashes.HashAlgorithm]
| None = None,
extensions: list[x509.Extension[x509.ExtensionType]] = [],
) -> None:
self._request = request
self._request_hash = request_hash
self._extensions = extensions
def add_certificate(
self,
cert: x509.Certificate,
issuer: x509.Certificate,
algorithm: hashes.HashAlgorithm,
) -> OCSPRequestBuilder:
if self._request is not None or self._request_hash 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._request_hash, self._extensions
)
def add_certificate_by_hash(
self,
issuer_name_hash: bytes,
issuer_key_hash: bytes,
serial_number: int,
algorithm: hashes.HashAlgorithm,
) -> OCSPRequestBuilder:
if self._request is not None or self._request_hash is not None:
raise ValueError("Only one certificate can be added to a request")
if not isinstance(serial_number, int):
raise TypeError("serial_number must be an integer")
_verify_algorithm(algorithm)
utils._check_bytes("issuer_name_hash", issuer_name_hash)
utils._check_bytes("issuer_key_hash", issuer_key_hash)
if algorithm.digest_size != len(
issuer_name_hash
) or algorithm.digest_size != len(issuer_key_hash):
raise ValueError(
"issuer_name_hash and issuer_key_hash must be the same length "
"as the digest size of the algorithm"
)
return OCSPRequestBuilder(
self._request,
(issuer_name_hash, issuer_key_hash, serial_number, 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._request_hash, [*self._extensions, extension]
)
def build(self) -> OCSPRequest:
if self._request is None and self._request_hash is None:
raise ValueError("You must add a certificate before building")
return ocsp.create_ocsp_request(self)
class OCSPResponseBuilder:
def __init__(
self,
response: _SingleResponse | None = None,
responder_id: tuple[x509.Certificate, OCSPResponderEncoding]
| None = None,
certs: list[x509.Certificate] | None = None,
extensions: 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: datetime.datetime | None,
revocation_time: datetime.datetime | None,
revocation_reason: x509.ReasonFlags | None,
) -> 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: CertificateIssuerPrivateKeyTypes,
algorithm: hashes.HashAlgorithm | None,
) -> 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)
load_der_ocsp_request = ocsp.load_der_ocsp_request
load_der_ocsp_response = ocsp.load_der_ocsp_response

View File

@ -0,0 +1,35 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.
from __future__ import annotations
from cryptography.hazmat._oid import (
AttributeOID,
AuthorityInformationAccessOID,
CertificatePoliciesOID,
CRLEntryExtensionOID,
ExtendedKeyUsageOID,
ExtensionOID,
NameOID,
ObjectIdentifier,
OCSPExtensionOID,
PublicKeyAlgorithmOID,
SignatureAlgorithmOID,
SubjectInformationAccessOID,
)
__all__ = [
"AttributeOID",
"AuthorityInformationAccessOID",
"CRLEntryExtensionOID",
"CertificatePoliciesOID",
"ExtendedKeyUsageOID",
"ExtensionOID",
"NameOID",
"OCSPExtensionOID",
"ObjectIdentifier",
"PublicKeyAlgorithmOID",
"SignatureAlgorithmOID",
"SubjectInformationAccessOID",
]

View File

@ -0,0 +1,28 @@
# 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 __future__ import annotations
import typing
from cryptography.hazmat.bindings._rust import x509 as rust_x509
from cryptography.x509.general_name import DNSName, IPAddress
__all__ = [
"ClientVerifier",
"PolicyBuilder",
"ServerVerifier",
"Store",
"Subject",
"VerificationError",
"VerifiedClient",
]
Store = rust_x509.Store
Subject = typing.Union[DNSName, IPAddress]
VerifiedClient = rust_x509.VerifiedClient
ClientVerifier = rust_x509.ClientVerifier
ServerVerifier = rust_x509.ServerVerifier
PolicyBuilder = rust_x509.PolicyBuilder
VerificationError = rust_x509.VerificationError