codekingpro/portable-devtools
114k
1from __future__ import annotations2 3import calendar4import datetime5import functools6import sys7import typing8from base64 import b16encode9from collections.abc import Sequence10from functools import partial11from typing import (12 Any,13 Callable,14 Union,15)16 17if sys.version_info >= (3, 13):18 from warnings import deprecated19elif sys.version_info < (3, 8):20 _T = typing.TypeVar("T")21 22 def deprecated(msg: str, **kwargs: object) -> Callable[[_T], _T]:23 return lambda f: f24else:25 from typing_extensions import deprecated26 27from cryptography import utils, x50928from cryptography.hazmat.primitives.asymmetric import (29 dsa,30 ec,31 ed448,32 ed25519,33 rsa,34)35 36from OpenSSL._util import StrOrBytesPath37from OpenSSL._util import (38 byte_string as _byte_string,39)40from OpenSSL._util import (41 exception_from_error_queue as _exception_from_error_queue,42)43from OpenSSL._util import (44 ffi as _ffi,45)46from OpenSSL._util import (47 lib as _lib,48)49from OpenSSL._util import (50 make_assert as _make_assert,51)52from OpenSSL._util import (53 path_bytes as _path_bytes,54)55 56__all__ = [57 "FILETYPE_ASN1",58 "FILETYPE_PEM",59 "FILETYPE_TEXT",60 "TYPE_DSA",61 "TYPE_RSA",62 "X509",63 "Error",64 "PKey",65 "X509Name",66 "X509Req",67 "X509Store",68 "X509StoreContext",69 "X509StoreContextError",70 "X509StoreFlags",71 "dump_certificate",72 "dump_certificate_request",73 "dump_privatekey",74 "dump_publickey",75 "get_elliptic_curve",76 "get_elliptic_curves",77 "load_certificate",78 "load_certificate_request",79 "load_privatekey",80 "load_publickey",81]82 83 84_PrivateKey = Union[85 dsa.DSAPrivateKey,86 ec.EllipticCurvePrivateKey,87 ed25519.Ed25519PrivateKey,88 ed448.Ed448PrivateKey,89 rsa.RSAPrivateKey,90]91_PublicKey = Union[92 dsa.DSAPublicKey,93 ec.EllipticCurvePublicKey,94 ed25519.Ed25519PublicKey,95 ed448.Ed448PublicKey,96 rsa.RSAPublicKey,97]98_Key = Union[_PrivateKey, _PublicKey]99PassphraseCallableT = Union[bytes, Callable[..., bytes]]100 101 102FILETYPE_PEM: int = _lib.SSL_FILETYPE_PEM103FILETYPE_ASN1: int = _lib.SSL_FILETYPE_ASN1104 105# TODO This was an API mistake. OpenSSL has no such constant.106FILETYPE_TEXT = 2**16 - 1107 108TYPE_RSA: int = _lib.EVP_PKEY_RSA109TYPE_DSA: int = _lib.EVP_PKEY_DSA110TYPE_DH: int = _lib.EVP_PKEY_DH111TYPE_EC: int = _lib.EVP_PKEY_EC112 113 114class Error(Exception):115 """116 An error occurred in an `OpenSSL.crypto` API.117 """118 119 120_raise_current_error = partial(_exception_from_error_queue, Error)121_openssl_assert = _make_assert(Error)122 123 124def _new_mem_buf(buffer: bytes | None = None) -> Any:125 """126 Allocate a new OpenSSL memory BIO.127 128 Arrange for the garbage collector to clean it up automatically.129 130 :param buffer: None or some bytes to use to put into the BIO so that they131 can be read out.132 """133 if buffer is None:134 bio = _lib.BIO_new(_lib.BIO_s_mem())135 free = _lib.BIO_free136 else:137 data = _ffi.new("char[]", buffer)138 bio = _lib.BIO_new_mem_buf(data, len(buffer))139 140 # Keep the memory alive as long as the bio is alive!141 def free(bio: Any, ref: Any = data) -> Any:142 return _lib.BIO_free(bio)143 144 _openssl_assert(bio != _ffi.NULL)145 146 bio = _ffi.gc(bio, free)147 return bio148 149 150def _bio_to_string(bio: Any) -> bytes:151 """152 Copy the contents of an OpenSSL BIO object into a Python byte string.153 """154 result_buffer = _ffi.new("char**")155 buffer_length = _lib.BIO_get_mem_data(bio, result_buffer)156 return _ffi.buffer(result_buffer[0], buffer_length)[:]157 158 159def _set_asn1_time(boundary: Any, when: bytes) -> None:160 """161 The the time value of an ASN1 time object.162 163 @param boundary: An ASN1_TIME pointer (or an object safely164 castable to that type) which will have its value set.165 @param when: A string representation of the desired time value.166 167 @raise TypeError: If C{when} is not a L{bytes} string.168 @raise ValueError: If C{when} does not represent a time in the required169 format.170 @raise RuntimeError: If the time value cannot be set for some other171 (unspecified) reason.172 """173 if not isinstance(when, bytes):174 raise TypeError("when must be a byte string")175 # ASN1_TIME_set_string validates the string without writing anything176 # when the destination is NULL.177 _openssl_assert(boundary != _ffi.NULL)178 179 set_result = _lib.ASN1_TIME_set_string(boundary, when)180 if set_result == 0:181 raise ValueError("Invalid string")182 183 184def _new_asn1_time(when: bytes) -> Any:185 """186 Behaves like _set_asn1_time but returns a new ASN1_TIME object.187 188 @param when: A string representation of the desired time value.189 190 @raise TypeError: If C{when} is not a L{bytes} string.191 @raise ValueError: If C{when} does not represent a time in the required192 format.193 @raise RuntimeError: If the time value cannot be set for some other194 (unspecified) reason.195 """196 ret = _lib.ASN1_TIME_new()197 _openssl_assert(ret != _ffi.NULL)198 ret = _ffi.gc(ret, _lib.ASN1_TIME_free)199 _set_asn1_time(ret, when)200 return ret201 202 203def _get_asn1_time(timestamp: Any) -> bytes | None:204 """205 Retrieve the time value of an ASN1 time object.206 207 @param timestamp: An ASN1_GENERALIZEDTIME* (or an object safely castable to208 that type) from which the time value will be retrieved.209 210 @return: The time value from C{timestamp} as a L{bytes} string in a certain211 format. Or C{None} if the object contains no time value.212 """213 string_timestamp = _ffi.cast("ASN1_STRING*", timestamp)214 if _lib.ASN1_STRING_length(string_timestamp) == 0:215 return None216 elif (217 _lib.ASN1_STRING_type(string_timestamp) == _lib.V_ASN1_GENERALIZEDTIME218 ):219 return _ffi.string(_lib.ASN1_STRING_get0_data(string_timestamp))220 else:221 generalized_timestamp = _ffi.new("ASN1_GENERALIZEDTIME**")222 _lib.ASN1_TIME_to_generalizedtime(timestamp, generalized_timestamp)223 _openssl_assert(generalized_timestamp[0] != _ffi.NULL)224 225 string_timestamp = _ffi.cast("ASN1_STRING*", generalized_timestamp[0])226 string_data = _lib.ASN1_STRING_get0_data(string_timestamp)227 string_result = _ffi.string(string_data)228 _lib.ASN1_GENERALIZEDTIME_free(generalized_timestamp[0])229 return string_result230 231 232class _X509NameInvalidator:233 def __init__(self) -> None:234 self._names: list[X509Name] = []235 236 def add(self, name: X509Name) -> None:237 self._names.append(name)238 239 def clear(self) -> None:240 for name in self._names:241 # Breaks the object, but also prevents UAF!242 del name._name243 244 245class PKey:246 """247 A class representing an DSA or RSA public key or key pair.248 """249 250 _only_public = False251 _initialized = True252 253 def __init__(self) -> None:254 pkey = _lib.EVP_PKEY_new()255 self._pkey = _ffi.gc(pkey, _lib.EVP_PKEY_free)256 self._initialized = False257 258 def to_cryptography_key(self) -> _Key:259 """260 Export as a ``cryptography`` key.261 262 :rtype: One of ``cryptography``'s `key interfaces`_.263 264 .. _key interfaces: https://cryptography.io/en/latest/hazmat/\265 primitives/asymmetric/rsa/#key-interfaces266 267 .. versionadded:: 16.1.0268 """269 from cryptography.hazmat.primitives.serialization import (270 load_der_private_key,271 load_der_public_key,272 )273 274 if self._only_public:275 der = dump_publickey(FILETYPE_ASN1, self)276 return typing.cast(_Key, load_der_public_key(der))277 else:278 der = dump_privatekey(FILETYPE_ASN1, self)279 return typing.cast(_Key, load_der_private_key(der, password=None))280 281 @classmethod282 def from_cryptography_key(cls, crypto_key: _Key) -> PKey:283 """284 Construct based on a ``cryptography`` *crypto_key*.285 286 :param crypto_key: A ``cryptography`` key.287 :type crypto_key: One of ``cryptography``'s `key interfaces`_.288 289 :rtype: PKey290 291 .. versionadded:: 16.1.0292 """293 if not isinstance(294 crypto_key,295 (296 dsa.DSAPrivateKey,297 dsa.DSAPublicKey,298 ec.EllipticCurvePrivateKey,299 ec.EllipticCurvePublicKey,300 ed25519.Ed25519PrivateKey,301 ed25519.Ed25519PublicKey,302 ed448.Ed448PrivateKey,303 ed448.Ed448PublicKey,304 rsa.RSAPrivateKey,305 rsa.RSAPublicKey,306 ),307 ):308 raise TypeError("Unsupported key type")309 310 from cryptography.hazmat.primitives.serialization import (311 Encoding,312 NoEncryption,313 PrivateFormat,314 PublicFormat,315 )316 317 if isinstance(318 crypto_key,319 (320 dsa.DSAPublicKey,321 ec.EllipticCurvePublicKey,322 ed25519.Ed25519PublicKey,323 ed448.Ed448PublicKey,324 rsa.RSAPublicKey,325 ),326 ):327 return load_publickey(328 FILETYPE_ASN1,329 crypto_key.public_bytes(330 Encoding.DER, PublicFormat.SubjectPublicKeyInfo331 ),332 )333 else:334 der = crypto_key.private_bytes(335 Encoding.DER, PrivateFormat.PKCS8, NoEncryption()336 )337 return load_privatekey(FILETYPE_ASN1, der)338 339 def generate_key(self, type: int, bits: int) -> None:340 """341 Generate a key pair of the given type, with the given number of bits.342 343 This generates a key "into" the this object.344 345 :param type: The key type.346 :type type: :py:data:`TYPE_RSA` or :py:data:`TYPE_DSA`347 :param bits: The number of bits.348 :type bits: :py:data:`int` ``>= 0``349 :raises TypeError: If :py:data:`type` or :py:data:`bits` isn't350 of the appropriate type.351 :raises ValueError: If the number of bits isn't an integer of352 the appropriate size.353 :return: ``None``354 """355 if not isinstance(type, int):356 raise TypeError("type must be an integer")357 358 if not isinstance(bits, int):359 raise TypeError("bits must be an integer")360 361 if type == TYPE_RSA:362 if bits <= 0:363 raise ValueError("Invalid number of bits")364 365 # TODO Check error return366 exponent = _lib.BN_new()367 exponent = _ffi.gc(exponent, _lib.BN_free)368 _lib.BN_set_word(exponent, _lib.RSA_F4)369 370 rsa = _lib.RSA_new()371 372 result = _lib.RSA_generate_key_ex(rsa, bits, exponent, _ffi.NULL)373 _openssl_assert(result == 1)374 375 result = _lib.EVP_PKEY_assign_RSA(self._pkey, rsa)376 _openssl_assert(result == 1)377 378 elif type == TYPE_DSA:379 dsa = _lib.DSA_new()380 _openssl_assert(dsa != _ffi.NULL)381 382 dsa = _ffi.gc(dsa, _lib.DSA_free)383 res = _lib.DSA_generate_parameters_ex(384 dsa, bits, _ffi.NULL, 0, _ffi.NULL, _ffi.NULL, _ffi.NULL385 )386 _openssl_assert(res == 1)387 388 _openssl_assert(_lib.DSA_generate_key(dsa) == 1)389 _openssl_assert(_lib.EVP_PKEY_set1_DSA(self._pkey, dsa) == 1)390 else:391 raise Error("No such key type")392 393 self._initialized = True394 395 def check(self) -> bool:396 """397 Check the consistency of an RSA private key.398 399 This is the Python equivalent of OpenSSL's ``RSA_check_key``.400 401 :return: ``True`` if key is consistent.402 403 :raise OpenSSL.crypto.Error: if the key is inconsistent.404 405 :raise TypeError: if the key is of a type which cannot be checked.406 Only RSA keys can currently be checked.407 """408 if self._only_public:409 raise TypeError("public key only")410 411 if _lib.EVP_PKEY_type(self.type()) != _lib.EVP_PKEY_RSA:412 raise TypeError("Only RSA keys can currently be checked.")413 414 rsa = _lib.EVP_PKEY_get1_RSA(self._pkey)415 rsa = _ffi.gc(rsa, _lib.RSA_free)416 result = _lib.RSA_check_key(rsa)417 if result == 1:418 return True419 _raise_current_error()420 421 def type(self) -> int:422 """423 Returns the type of the key424 425 :return: The type of the key.426 """427 return _lib.EVP_PKEY_id(self._pkey)428 429 def bits(self) -> int:430 """431 Returns the number of bits of the key432 433 :return: The number of bits of the key.434 """435 return _lib.EVP_PKEY_bits(self._pkey)436 437 438class _EllipticCurve:439 """440 A representation of a supported elliptic curve.441 442 @cvar _curves: :py:obj:`None` until an attempt is made to load the curves.443 Thereafter, a :py:type:`set` containing :py:type:`_EllipticCurve`444 instances each of which represents one curve supported by the system.445 @type _curves: :py:type:`NoneType` or :py:type:`set`446 """447 448 _curves = None449 450 def __ne__(self, other: Any) -> bool:451 """452 Implement cooperation with the right-hand side argument of ``!=``.453 454 Python 3 seems to have dropped this cooperation in this very narrow455 circumstance.456 """457 if isinstance(other, _EllipticCurve):458 return super().__ne__(other)459 return NotImplemented460 461 @classmethod462 def _load_elliptic_curves(cls, lib: Any) -> set[_EllipticCurve]:463 """464 Get the curves supported by OpenSSL.465 466 :param lib: The OpenSSL library binding object.467 468 :return: A :py:type:`set` of ``cls`` instances giving the names of the469 elliptic curves the underlying library supports.470 """471 num_curves = lib.EC_get_builtin_curves(_ffi.NULL, 0)472 builtin_curves = _ffi.new("EC_builtin_curve[]", num_curves)473 # The return value on this call should be num_curves again. We474 # could check it to make sure but if it *isn't* then.. what could475 # we do? Abort the whole process, I suppose...? -exarkun476 lib.EC_get_builtin_curves(builtin_curves, num_curves)477 return set(cls.from_nid(lib, c.nid) for c in builtin_curves)478 479 @classmethod480 def _get_elliptic_curves(cls, lib: Any) -> set[_EllipticCurve]:481 """482 Get, cache, and return the curves supported by OpenSSL.483 484 :param lib: The OpenSSL library binding object.485 486 :return: A :py:type:`set` of ``cls`` instances giving the names of the487 elliptic curves the underlying library supports.488 """489 if cls._curves is None:490 cls._curves = cls._load_elliptic_curves(lib)491 return cls._curves492 493 @classmethod494 def from_nid(cls, lib: Any, nid: int) -> _EllipticCurve:495 """496 Instantiate a new :py:class:`_EllipticCurve` associated with the given497 OpenSSL NID.498 499 :param lib: The OpenSSL library binding object.500 501 :param nid: The OpenSSL NID the resulting curve object will represent.502 This must be a curve NID (and not, for example, a hash NID) or503 subsequent operations will fail in unpredictable ways.504 :type nid: :py:class:`int`505 506 :return: The curve object.507 """508 return cls(lib, nid, _ffi.string(lib.OBJ_nid2sn(nid)).decode("ascii"))509 510 def __init__(self, lib: Any, nid: int, name: str) -> None:511 """512 :param _lib: The :py:mod:`cryptography` binding instance used to513 interface with OpenSSL.514 515 :param _nid: The OpenSSL NID identifying the curve this object516 represents.517 :type _nid: :py:class:`int`518 519 :param name: The OpenSSL short name identifying the curve this object520 represents.521 :type name: :py:class:`unicode`522 """523 self._lib = lib524 self._nid = nid525 self.name = name526 527 def __repr__(self) -> str:528 return f"<Curve {self.name!r}>"529 530 def _to_EC_KEY(self) -> Any:531 """532 Create a new OpenSSL EC_KEY structure initialized to use this curve.533 534 The structure is automatically garbage collected when the Python object535 is garbage collected.536 """537 key = self._lib.EC_KEY_new_by_curve_name(self._nid)538 return _ffi.gc(key, _lib.EC_KEY_free)539 540 541@deprecated(542 "get_elliptic_curves is deprecated. You should use the APIs in "543 "cryptography instead."544)545def get_elliptic_curves() -> set[_EllipticCurve]:546 """547 Return a set of objects representing the elliptic curves supported in the548 OpenSSL build in use.549 550 The curve objects have a :py:class:`unicode` ``name`` attribute by which551 they identify themselves.552 553 The curve objects are useful as values for the argument accepted by554 :py:meth:`Context.set_tmp_ecdh` to specify which elliptical curve should be555 used for ECDHE key exchange.556 """557 return _EllipticCurve._get_elliptic_curves(_lib)558 559 560@deprecated(561 "get_elliptic_curve is deprecated. You should use the APIs in "562 "cryptography instead."563)564def get_elliptic_curve(name: str) -> _EllipticCurve:565 """566 Return a single curve object selected by name.567 568 See :py:func:`get_elliptic_curves` for information about curve objects.569 570 :param name: The OpenSSL short name identifying the curve object to571 retrieve.572 :type name: :py:class:`unicode`573 574 If the named curve is not supported then :py:class:`ValueError` is raised.575 """576 for curve in get_elliptic_curves():577 if curve.name == name:578 return curve579 raise ValueError("unknown curve name", name)580 581 582@functools.total_ordering583class X509Name:584 """585 An X.509 Distinguished Name.586 587 :ivar countryName: The country of the entity.588 :ivar C: Alias for :py:attr:`countryName`.589 590 :ivar stateOrProvinceName: The state or province of the entity.591 :ivar ST: Alias for :py:attr:`stateOrProvinceName`.592 593 :ivar localityName: The locality of the entity.594 :ivar L: Alias for :py:attr:`localityName`.595 596 :ivar organizationName: The organization name of the entity.597 :ivar O: Alias for :py:attr:`organizationName`.598 599 :ivar organizationalUnitName: The organizational unit of the entity.600 :ivar OU: Alias for :py:attr:`organizationalUnitName`601 602 :ivar commonName: The common name of the entity.603 :ivar CN: Alias for :py:attr:`commonName`.604 605 :ivar emailAddress: The e-mail address of the entity.606 """607 608 def __init__(self, name: X509Name) -> None:609 """610 Create a new X509Name, copying the given X509Name instance.611 612 :param name: The name to copy.613 :type name: :py:class:`X509Name`614 """615 name = _lib.X509_NAME_dup(name._name)616 self._name: Any = _ffi.gc(name, _lib.X509_NAME_free)617 618 def __setattr__(self, name: str, value: Any) -> None:619 if name.startswith("_"):620 return super().__setattr__(name, value)621 622 # Note: we really do not want str subclasses here, so we do not use623 # isinstance.624 if type(name) is not str:625 raise TypeError(626 f"attribute name must be string, not "627 f"'{type(value).__name__:.200}'"628 )629 630 nid = _lib.OBJ_txt2nid(_byte_string(name))631 if nid == _lib.NID_undef:632 try:633 _raise_current_error()634 except Error:635 pass636 raise AttributeError("No such attribute")637 638 # If there's an old entry for this NID, remove it639 for i in range(_lib.X509_NAME_entry_count(self._name)):640 ent = _lib.X509_NAME_get_entry(self._name, i)641 ent_obj = _lib.X509_NAME_ENTRY_get_object(ent)642 ent_nid = _lib.OBJ_obj2nid(ent_obj)643 if nid == ent_nid:644 ent = _lib.X509_NAME_delete_entry(self._name, i)645 _lib.X509_NAME_ENTRY_free(ent)646 break647 648 if isinstance(value, str):649 value = value.encode("utf-8")650 651 add_result = _lib.X509_NAME_add_entry_by_NID(652 self._name, nid, _lib.MBSTRING_UTF8, value, len(value), -1, 0653 )654 if not add_result:655 _raise_current_error()656 657 def __getattr__(self, name: str) -> str | None:658 """659 Find attribute. An X509Name object has the following attributes:660 countryName (alias C), stateOrProvince (alias ST), locality (alias L),661 organization (alias O), organizationalUnit (alias OU), commonName662 (alias CN) and more...663 """664 nid = _lib.OBJ_txt2nid(_byte_string(name))665 if nid == _lib.NID_undef:666 # This is a bit weird. OBJ_txt2nid indicated failure, but it seems667 # a lower level function, a2d_ASN1_OBJECT, also feels the need to668 # push something onto the error queue. If we don't clean that up669 # now, someone else will bump into it later and be quite confused.670 # See lp#314814.671 try:672 _raise_current_error()673 except Error:674 pass675 raise AttributeError("No such attribute")676 677 entry_index = _lib.X509_NAME_get_index_by_NID(self._name, nid, -1)678 if entry_index == -1:679 return None680 681 entry = _lib.X509_NAME_get_entry(self._name, entry_index)682 data = _lib.X509_NAME_ENTRY_get_data(entry)683 684 result_buffer = _ffi.new("unsigned char**")685 data_length = _lib.ASN1_STRING_to_UTF8(result_buffer, data)686 _openssl_assert(data_length >= 0)687 688 try:689 result = _ffi.buffer(result_buffer[0], data_length)[:].decode(690 "utf-8"691 )692 finally:693 # XXX untested694 _lib.OPENSSL_free(result_buffer[0])695 return result696 697 def __eq__(self, other: Any) -> bool:698 if not isinstance(other, X509Name):699 return NotImplemented700 701 return _lib.X509_NAME_cmp(self._name, other._name) == 0702 703 def __lt__(self, other: Any) -> bool:704 if not isinstance(other, X509Name):705 return NotImplemented706 707 return _lib.X509_NAME_cmp(self._name, other._name) < 0708 709 def __repr__(self) -> str:710 """711 String representation of an X509Name712 """713 result_buffer = _ffi.new("char[]", 512)714 format_result = _lib.X509_NAME_oneline(715 self._name, result_buffer, len(result_buffer)716 )717 _openssl_assert(format_result != _ffi.NULL)718 719 return "<X509Name object '{}'>".format(720 _ffi.string(result_buffer).decode("utf-8"),721 )722 723 def hash(self) -> int:724 """725 Return an integer representation of the first four bytes of the726 MD5 digest of the DER representation of the name.727 728 This is the Python equivalent of OpenSSL's ``X509_NAME_hash``.729 730 :return: The (integer) hash of this name.731 :rtype: :py:class:`int`732 """733 return _lib.X509_NAME_hash(self._name)734 735 def der(self) -> bytes:736 """737 Return the DER encoding of this name.738 739 :return: The DER encoded form of this name.740 :rtype: :py:class:`bytes`741 """742 result_buffer = _ffi.new("unsigned char**")743 encode_result = _lib.i2d_X509_NAME(self._name, result_buffer)744 _openssl_assert(encode_result >= 0)745 746 string_result = _ffi.buffer(result_buffer[0], encode_result)[:]747 _lib.OPENSSL_free(result_buffer[0])748 return string_result749 750 def get_components(self) -> list[tuple[bytes, bytes]]:751 """752 Returns the components of this name, as a sequence of 2-tuples.753 754 :return: The components of this name.755 :rtype: :py:class:`list` of ``name, value`` tuples.756 """757 result = []758 for i in range(_lib.X509_NAME_entry_count(self._name)):759 ent = _lib.X509_NAME_get_entry(self._name, i)760 761 fname = _lib.X509_NAME_ENTRY_get_object(ent)762 fval = _lib.X509_NAME_ENTRY_get_data(ent)763 764 nid = _lib.OBJ_obj2nid(fname)765 name = _lib.OBJ_nid2sn(nid)766 767 # ffi.string does not handle strings containing NULL bytes768 # (which may have been generated by old, broken software)769 value = _ffi.buffer(770 _lib.ASN1_STRING_get0_data(fval), _lib.ASN1_STRING_length(fval)771 )[:]772 result.append((_ffi.string(name), value))773 774 return result775 776 777@deprecated(778 "CSR support in pyOpenSSL is deprecated. You should use the APIs "779 "in cryptography."780)781class X509Req:782 """783 An X.509 certificate signing requests.784 785 .. deprecated:: 24.2.0786 Use `cryptography.x509.CertificateSigningRequest` instead.787 """788 789 def __init__(self) -> None:790 req = _lib.X509_REQ_new()791 self._req = _ffi.gc(req, _lib.X509_REQ_free)792 # Default to version 0.793 self.set_version(0)794 795 def to_cryptography(self) -> x509.CertificateSigningRequest:796 """797 Export as a ``cryptography`` certificate signing request.798 799 :rtype: ``cryptography.x509.CertificateSigningRequest``800 801 .. versionadded:: 17.1.0802 """803 from cryptography.x509 import load_der_x509_csr804 805 der = _dump_certificate_request_internal(FILETYPE_ASN1, self)806 807 return load_der_x509_csr(der)808 809 @classmethod810 def from_cryptography(811 cls, crypto_req: x509.CertificateSigningRequest812 ) -> X509Req:813 """814 Construct based on a ``cryptography`` *crypto_req*.815 816 :param crypto_req: A ``cryptography`` X.509 certificate signing request817 :type crypto_req: ``cryptography.x509.CertificateSigningRequest``818 819 :rtype: X509Req820 821 .. versionadded:: 17.1.0822 """823 if not isinstance(crypto_req, x509.CertificateSigningRequest):824 raise TypeError("Must be a certificate signing request")825 826 from cryptography.hazmat.primitives.serialization import Encoding827 828 der = crypto_req.public_bytes(Encoding.DER)829 return _load_certificate_request_internal(FILETYPE_ASN1, der)830 831 def set_pubkey(self, pkey: PKey) -> None:832 """833 Set the public key of the certificate signing request.834 835 :param pkey: The public key to use.836 :type pkey: :py:class:`PKey`837 838 :return: ``None``839 """840 set_result = _lib.X509_REQ_set_pubkey(self._req, pkey._pkey)841 _openssl_assert(set_result == 1)842 843 def get_pubkey(self) -> PKey:844 """845 Get the public key of the certificate signing request.846 847 :return: The public key.848 :rtype: :py:class:`PKey`849 """850 pkey = PKey.__new__(PKey)851 pkey._pkey = _lib.X509_REQ_get_pubkey(self._req)852 _openssl_assert(pkey._pkey != _ffi.NULL)853 pkey._pkey = _ffi.gc(pkey._pkey, _lib.EVP_PKEY_free)854 pkey._only_public = True855 return pkey856 857 def set_version(self, version: int) -> None:858 """859 Set the version subfield (RFC 2986, section 4.1) of the certificate860 request.861 862 :param int version: The version number.863 :return: ``None``864 """865 if not isinstance(version, int):866 raise TypeError("version must be an int")867 if version != 0:868 raise ValueError(869 "Invalid version. The only valid version for X509Req is 0."870 )871 set_result = _lib.X509_REQ_set_version(self._req, version)872 _openssl_assert(set_result == 1)873 874 def get_version(self) -> int:875 """876 Get the version subfield (RFC 2459, section 4.1.2.1) of the certificate877 request.878 879 :return: The value of the version subfield.880 :rtype: :py:class:`int`881 """882 return _lib.X509_REQ_get_version(self._req)883 884 def get_subject(self) -> X509Name:885 """886 Return the subject of this certificate signing request.887 888 This creates a new :class:`X509Name` that wraps the underlying subject889 name field on the certificate signing request. Modifying it will modify890 the underlying signing request, and will have the effect of modifying891 any other :class:`X509Name` that refers to this subject.892 893 :return: The subject of this certificate signing request.894 :rtype: :class:`X509Name`895 """896 name = X509Name.__new__(X509Name)897 name._name = _lib.X509_REQ_get_subject_name(self._req)898 _openssl_assert(name._name != _ffi.NULL)899 900 # The name is owned by the X509Req structure. As long as the X509Name901 # Python object is alive, keep the X509Req Python object alive.902 name._owner = self903 904 return name905 906 def sign(self, pkey: PKey, digest: str) -> None:907 """908 Sign the certificate signing request with this key and digest type.909 910 :param pkey: The key pair to sign with.911 :type pkey: :py:class:`PKey`912 :param digest: The name of the message digest to use for the signature,913 e.g. :py:data:`"sha256"`.914 :type digest: :py:class:`str`915 :return: ``None``916 """917 if pkey._only_public:918 raise ValueError("Key has only public part")919 920 if not pkey._initialized:921 raise ValueError("Key is uninitialized")922 923 digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest))924 if digest_obj == _ffi.NULL:925 raise ValueError("No such digest method")926 927 sign_result = _lib.X509_REQ_sign(self._req, pkey._pkey, digest_obj)928 _openssl_assert(sign_result > 0)929 930 def verify(self, pkey: PKey) -> bool:931 """932 Verifies the signature on this certificate signing request.933 934 :param PKey key: A public key.935 936 :return: ``True`` if the signature is correct.937 :rtype: bool938 939 :raises OpenSSL.crypto.Error: If the signature is invalid or there is a940 problem verifying the signature.941 """942 if not isinstance(pkey, PKey):943 raise TypeError("pkey must be a PKey instance")944 945 result = _lib.X509_REQ_verify(self._req, pkey._pkey)946 if result <= 0:947 _raise_current_error()948 949 return result950 951 952class X509:953 """954 An X.509 certificate.955 """956 957 def __init__(self) -> None:958 x509 = _lib.X509_new()959 _openssl_assert(x509 != _ffi.NULL)960 self._x509 = _ffi.gc(x509, _lib.X509_free)961 962 self._issuer_invalidator = _X509NameInvalidator()963 self._subject_invalidator = _X509NameInvalidator()964 965 @classmethod966 def _from_raw_x509_ptr(cls, x509: Any) -> X509:967 cert = cls.__new__(cls)968 cert._x509 = _ffi.gc(x509, _lib.X509_free)969 cert._issuer_invalidator = _X509NameInvalidator()970 cert._subject_invalidator = _X509NameInvalidator()971 return cert972 973 def to_cryptography(self) -> x509.Certificate:974 """975 Export as a ``cryptography`` certificate.976 977 :rtype: ``cryptography.x509.Certificate``978 979 .. versionadded:: 17.1.0980 """981 from cryptography.x509 import load_der_x509_certificate982 983 der = dump_certificate(FILETYPE_ASN1, self)984 return load_der_x509_certificate(der)985 986 @classmethod987 def from_cryptography(cls, crypto_cert: x509.Certificate) -> X509:988 """989 Construct based on a ``cryptography`` *crypto_cert*.990 991 :param crypto_key: A ``cryptography`` X.509 certificate.992 :type crypto_key: ``cryptography.x509.Certificate``993 994 :rtype: X509995 996 .. versionadded:: 17.1.0997 """998 if not isinstance(crypto_cert, x509.Certificate):999 raise TypeError("Must be a certificate")1000 1001 from cryptography.hazmat.primitives.serialization import Encoding1002 1003 der = crypto_cert.public_bytes(Encoding.DER)1004 return load_certificate(FILETYPE_ASN1, der)1005 1006 def set_version(self, version: int) -> None:1007 """1008 Set the version number of the certificate. Note that the1009 version value is zero-based, eg. a value of 0 is V1.1010 1011 :param version: The version number of the certificate.1012 :type version: :py:class:`int`1013 1014 :return: ``None``1015 """1016 if not isinstance(version, int):1017 raise TypeError("version must be an integer")1018 1019 _openssl_assert(_lib.X509_set_version(self._x509, version) == 1)1020 1021 def get_version(self) -> int:1022 """1023 Return the version number of the certificate.1024 1025 :return: The version number of the certificate.1026 :rtype: :py:class:`int`1027 """1028 return _lib.X509_get_version(self._x509)1029 1030 def get_pubkey(self) -> PKey:1031 """1032 Get the public key of the certificate.1033 1034 :return: The public key.1035 :rtype: :py:class:`PKey`1036 """1037 pkey = PKey.__new__(PKey)1038 pkey._pkey = _lib.X509_get_pubkey(self._x509)1039 if pkey._pkey == _ffi.NULL:1040 _raise_current_error()1041 pkey._pkey = _ffi.gc(pkey._pkey, _lib.EVP_PKEY_free)1042 pkey._only_public = True1043 return pkey1044 1045 def set_pubkey(self, pkey: PKey) -> None:1046 """1047 Set the public key of the certificate.1048 1049 :param pkey: The public key.1050 :type pkey: :py:class:`PKey`1051 1052 :return: :py:data:`None`1053 """1054 if not isinstance(pkey, PKey):1055 raise TypeError("pkey must be a PKey instance")1056 1057 set_result = _lib.X509_set_pubkey(self._x509, pkey._pkey)1058 _openssl_assert(set_result == 1)1059 1060 def sign(self, pkey: PKey, digest: str) -> None:1061 """1062 Sign the certificate with this key and digest type.1063 1064 :param pkey: The key to sign with.1065 :type pkey: :py:class:`PKey`1066 1067 :param digest: The name of the message digest to use.1068 :type digest: :py:class:`str`1069 1070 :return: :py:data:`None`1071 """1072 if not isinstance(pkey, PKey):1073 raise TypeError("pkey must be a PKey instance")1074 1075 if pkey._only_public:1076 raise ValueError("Key only has public part")1077 1078 if not pkey._initialized:1079 raise ValueError("Key is uninitialized")1080 1081 evp_md = _lib.EVP_get_digestbyname(_byte_string(digest))1082 if evp_md == _ffi.NULL:1083 raise ValueError("No such digest method")1084 1085 sign_result = _lib.X509_sign(self._x509, pkey._pkey, evp_md)1086 _openssl_assert(sign_result > 0)1087 1088 def get_signature_algorithm(self) -> bytes:1089 """1090 Return the signature algorithm used in the certificate.1091 1092 :return: The name of the algorithm.1093 :rtype: :py:class:`bytes`1094 1095 :raises ValueError: If the signature algorithm is undefined.1096 1097 .. versionadded:: 0.131098 """1099 sig_alg = _lib.X509_get0_tbs_sigalg(self._x509)1100 alg = _ffi.new("ASN1_OBJECT **")1101 _lib.X509_ALGOR_get0(alg, _ffi.NULL, _ffi.NULL, sig_alg)1102 nid = _lib.OBJ_obj2nid(alg[0])1103 if nid == _lib.NID_undef:1104 raise ValueError("Undefined signature algorithm")1105 return _ffi.string(_lib.OBJ_nid2ln(nid))1106 1107 def digest(self, digest_name: str) -> bytes:1108 """1109 Return the digest of the X509 object.1110 1111 :param digest_name: The name of the digest algorithm to use.1112 :type digest_name: :py:class:`str`1113 1114 :return: The digest of the object, formatted as1115 :py:const:`b":"`-delimited hex pairs.1116 :rtype: :py:class:`bytes`1117 """1118 digest = _lib.EVP_get_digestbyname(_byte_string(digest_name))1119 if digest == _ffi.NULL:1120 raise ValueError("No such digest method")1121 1122 result_buffer = _ffi.new("unsigned char[]", _lib.EVP_MAX_MD_SIZE)1123 result_length = _ffi.new("unsigned int[]", 1)1124 result_length[0] = len(result_buffer)1125 1126 digest_result = _lib.X509_digest(1127 self._x509, digest, result_buffer, result_length1128 )1129 _openssl_assert(digest_result == 1)1130 1131 return b":".join(1132 [1133 b16encode(ch).upper()1134 for ch in _ffi.buffer(result_buffer, result_length[0])1135 ]1136 )1137 1138 def subject_name_hash(self) -> int:1139 """1140 Return the hash of the X509 subject.1141 1142 :return: The hash of the subject.1143 :rtype: :py:class:`int`1144 """1145 return _lib.X509_subject_name_hash(self._x509)1146 1147 def set_serial_number(self, serial: int) -> None:1148 """1149 Set the serial number of the certificate.1150 1151 :param serial: The new serial number.1152 :type serial: :py:class:`int`1153 1154 :return: :py:data`None`1155 """1156 if not isinstance(serial, int):1157 raise TypeError("serial must be an integer")1158 1159 hex_serial = hex(serial)[2:]1160 hex_serial_bytes = hex_serial.encode("ascii")1161 1162 bignum_serial = _ffi.new("BIGNUM**")1163 1164 # BN_hex2bn stores the result in &bignum.1165 result = _lib.BN_hex2bn(bignum_serial, hex_serial_bytes)1166 _openssl_assert(result != _ffi.NULL)1167 1168 asn1_serial = _lib.BN_to_ASN1_INTEGER(bignum_serial[0], _ffi.NULL)1169 _lib.BN_free(bignum_serial[0])1170 _openssl_assert(asn1_serial != _ffi.NULL)1171 asn1_serial = _ffi.gc(asn1_serial, _lib.ASN1_INTEGER_free)1172 set_result = _lib.X509_set_serialNumber(self._x509, asn1_serial)1173 _openssl_assert(set_result == 1)1174 1175 def get_serial_number(self) -> int:1176 """1177 Return the serial number of this certificate.1178 1179 :return: The serial number.1180 :rtype: int1181 """1182 asn1_serial = _lib.X509_get_serialNumber(self._x509)1183 bignum_serial = _lib.ASN1_INTEGER_to_BN(asn1_serial, _ffi.NULL)1184 try:1185 hex_serial = _lib.BN_bn2hex(bignum_serial)1186 try:1187 hexstring_serial = _ffi.string(hex_serial)1188 serial = int(hexstring_serial, 16)1189 return serial1190 finally:1191 _lib.OPENSSL_free(hex_serial)1192 finally:1193 _lib.BN_free(bignum_serial)1194 1195 def gmtime_adj_notAfter(self, amount: int) -> None:1196 """1197 Adjust the time stamp on which the certificate stops being valid.1198 1199 :param int amount: The number of seconds by which to adjust the1200 timestamp.