x509.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. # This file is dual licensed under the terms of the Apache License, Version
  2. # 2.0, and the BSD License. See the LICENSE file in the root of this repository
  3. # for complete details.
  4. import datetime
  5. import operator
  6. import typing
  7. from cryptography import utils, x509
  8. from cryptography.exceptions import UnsupportedAlgorithm
  9. from cryptography.hazmat.backends.openssl import dsa, ec, rsa
  10. from cryptography.hazmat.backends.openssl.decode_asn1 import (
  11. _asn1_integer_to_int,
  12. _asn1_string_to_bytes,
  13. _decode_x509_name,
  14. _obj2txt,
  15. _parse_asn1_time,
  16. )
  17. from cryptography.hazmat.backends.openssl.encode_asn1 import (
  18. _encode_asn1_int_gc,
  19. _txt2obj_gc,
  20. )
  21. from cryptography.hazmat.primitives import hashes, serialization
  22. from cryptography.x509.base import _PUBLIC_KEY_TYPES
  23. from cryptography.x509.name import _ASN1Type
  24. class _Certificate(x509.Certificate):
  25. # Keep-alive reference used by OCSP
  26. _ocsp_resp_ref: typing.Any
  27. def __init__(self, backend, x509_cert):
  28. self._backend = backend
  29. self._x509 = x509_cert
  30. version = self._backend._lib.X509_get_version(self._x509)
  31. if version == 0:
  32. self._version = x509.Version.v1
  33. elif version == 2:
  34. self._version = x509.Version.v3
  35. else:
  36. raise x509.InvalidVersion(
  37. "{} is not a valid X509 version".format(version), version
  38. )
  39. def __repr__(self):
  40. return "<Certificate(subject={}, ...)>".format(self.subject)
  41. def __eq__(self, other: object) -> bool:
  42. if not isinstance(other, _Certificate):
  43. return NotImplemented
  44. res = self._backend._lib.X509_cmp(self._x509, other._x509)
  45. return res == 0
  46. def __ne__(self, other: object) -> bool:
  47. return not self == other
  48. def __hash__(self) -> int:
  49. return hash(self.public_bytes(serialization.Encoding.DER))
  50. def __deepcopy__(self, memo):
  51. return self
  52. def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes:
  53. h = hashes.Hash(algorithm, self._backend)
  54. h.update(self.public_bytes(serialization.Encoding.DER))
  55. return h.finalize()
  56. version = utils.read_only_property("_version")
  57. @property
  58. def serial_number(self) -> int:
  59. asn1_int = self._backend._lib.X509_get_serialNumber(self._x509)
  60. self._backend.openssl_assert(asn1_int != self._backend._ffi.NULL)
  61. return _asn1_integer_to_int(self._backend, asn1_int)
  62. def public_key(self) -> _PUBLIC_KEY_TYPES:
  63. pkey = self._backend._lib.X509_get_pubkey(self._x509)
  64. if pkey == self._backend._ffi.NULL:
  65. # Remove errors from the stack.
  66. self._backend._consume_errors()
  67. raise ValueError("Certificate public key is of an unknown type")
  68. pkey = self._backend._ffi.gc(pkey, self._backend._lib.EVP_PKEY_free)
  69. return self._backend._evp_pkey_to_public_key(pkey)
  70. @property
  71. def not_valid_before(self) -> datetime.datetime:
  72. asn1_time = self._backend._lib.X509_get0_notBefore(self._x509)
  73. return _parse_asn1_time(self._backend, asn1_time)
  74. @property
  75. def not_valid_after(self) -> datetime.datetime:
  76. asn1_time = self._backend._lib.X509_get0_notAfter(self._x509)
  77. return _parse_asn1_time(self._backend, asn1_time)
  78. @property
  79. def issuer(self) -> x509.Name:
  80. issuer = self._backend._lib.X509_get_issuer_name(self._x509)
  81. self._backend.openssl_assert(issuer != self._backend._ffi.NULL)
  82. return _decode_x509_name(self._backend, issuer)
  83. @property
  84. def subject(self) -> x509.Name:
  85. subject = self._backend._lib.X509_get_subject_name(self._x509)
  86. self._backend.openssl_assert(subject != self._backend._ffi.NULL)
  87. return _decode_x509_name(self._backend, subject)
  88. @property
  89. def signature_hash_algorithm(
  90. self,
  91. ) -> typing.Optional[hashes.HashAlgorithm]:
  92. oid = self.signature_algorithm_oid
  93. try:
  94. return x509._SIG_OIDS_TO_HASH[oid]
  95. except KeyError:
  96. raise UnsupportedAlgorithm(
  97. "Signature algorithm OID:{} not recognized".format(oid)
  98. )
  99. @property
  100. def signature_algorithm_oid(self) -> x509.ObjectIdentifier:
  101. alg = self._backend._ffi.new("X509_ALGOR **")
  102. self._backend._lib.X509_get0_signature(
  103. self._backend._ffi.NULL, alg, self._x509
  104. )
  105. self._backend.openssl_assert(alg[0] != self._backend._ffi.NULL)
  106. oid = _obj2txt(self._backend, alg[0].algorithm)
  107. return x509.ObjectIdentifier(oid)
  108. @utils.cached_property
  109. def extensions(self) -> x509.Extensions:
  110. return self._backend._certificate_extension_parser.parse(self._x509)
  111. @property
  112. def signature(self) -> bytes:
  113. sig = self._backend._ffi.new("ASN1_BIT_STRING **")
  114. self._backend._lib.X509_get0_signature(
  115. sig, self._backend._ffi.NULL, self._x509
  116. )
  117. self._backend.openssl_assert(sig[0] != self._backend._ffi.NULL)
  118. return _asn1_string_to_bytes(self._backend, sig[0])
  119. @property
  120. def tbs_certificate_bytes(self) -> bytes:
  121. pp = self._backend._ffi.new("unsigned char **")
  122. res = self._backend._lib.i2d_re_X509_tbs(self._x509, pp)
  123. self._backend.openssl_assert(res > 0)
  124. pp = self._backend._ffi.gc(
  125. pp, lambda pointer: self._backend._lib.OPENSSL_free(pointer[0])
  126. )
  127. return self._backend._ffi.buffer(pp[0], res)[:]
  128. def public_bytes(self, encoding: serialization.Encoding) -> bytes:
  129. bio = self._backend._create_mem_bio_gc()
  130. if encoding is serialization.Encoding.PEM:
  131. res = self._backend._lib.PEM_write_bio_X509(bio, self._x509)
  132. elif encoding is serialization.Encoding.DER:
  133. res = self._backend._lib.i2d_X509_bio(bio, self._x509)
  134. else:
  135. raise TypeError("encoding must be an item from the Encoding enum")
  136. self._backend.openssl_assert(res == 1)
  137. return self._backend._read_mem_bio(bio)
  138. class _RevokedCertificate(x509.RevokedCertificate):
  139. def __init__(self, backend, crl, x509_revoked):
  140. self._backend = backend
  141. # The X509_REVOKED_value is a X509_REVOKED * that has
  142. # no reference counting. This means when X509_CRL_free is
  143. # called then the CRL and all X509_REVOKED * are freed. Since
  144. # you can retain a reference to a single revoked certificate
  145. # and let the CRL fall out of scope we need to retain a
  146. # private reference to the CRL inside the RevokedCertificate
  147. # object to prevent the gc from being called inappropriately.
  148. self._crl = crl
  149. self._x509_revoked = x509_revoked
  150. @property
  151. def serial_number(self) -> int:
  152. asn1_int = self._backend._lib.X509_REVOKED_get0_serialNumber(
  153. self._x509_revoked
  154. )
  155. self._backend.openssl_assert(asn1_int != self._backend._ffi.NULL)
  156. return _asn1_integer_to_int(self._backend, asn1_int)
  157. @property
  158. def revocation_date(self) -> datetime.datetime:
  159. return _parse_asn1_time(
  160. self._backend,
  161. self._backend._lib.X509_REVOKED_get0_revocationDate(
  162. self._x509_revoked
  163. ),
  164. )
  165. @utils.cached_property
  166. def extensions(self) -> x509.Extensions:
  167. return self._backend._revoked_cert_extension_parser.parse(
  168. self._x509_revoked
  169. )
  170. @utils.register_interface(x509.CertificateRevocationList)
  171. class _CertificateRevocationList(object):
  172. def __init__(self, backend, x509_crl):
  173. self._backend = backend
  174. self._x509_crl = x509_crl
  175. def __eq__(self, other: object) -> bool:
  176. if not isinstance(other, _CertificateRevocationList):
  177. return NotImplemented
  178. res = self._backend._lib.X509_CRL_cmp(self._x509_crl, other._x509_crl)
  179. return res == 0
  180. def __ne__(self, other: object) -> bool:
  181. return not self == other
  182. def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes:
  183. h = hashes.Hash(algorithm, self._backend)
  184. bio = self._backend._create_mem_bio_gc()
  185. res = self._backend._lib.i2d_X509_CRL_bio(bio, self._x509_crl)
  186. self._backend.openssl_assert(res == 1)
  187. der = self._backend._read_mem_bio(bio)
  188. h.update(der)
  189. return h.finalize()
  190. @utils.cached_property
  191. def _sorted_crl(self):
  192. # X509_CRL_get0_by_serial sorts in place, which breaks a variety of
  193. # things we don't want to break (like iteration and the signature).
  194. # Let's dupe it and sort that instead.
  195. dup = self._backend._lib.X509_CRL_dup(self._x509_crl)
  196. self._backend.openssl_assert(dup != self._backend._ffi.NULL)
  197. dup = self._backend._ffi.gc(dup, self._backend._lib.X509_CRL_free)
  198. return dup
  199. def get_revoked_certificate_by_serial_number(
  200. self, serial_number: int
  201. ) -> typing.Optional[x509.RevokedCertificate]:
  202. revoked = self._backend._ffi.new("X509_REVOKED **")
  203. asn1_int = _encode_asn1_int_gc(self._backend, serial_number)
  204. res = self._backend._lib.X509_CRL_get0_by_serial(
  205. self._sorted_crl, revoked, asn1_int
  206. )
  207. if res == 0:
  208. return None
  209. else:
  210. self._backend.openssl_assert(revoked[0] != self._backend._ffi.NULL)
  211. return _RevokedCertificate(
  212. self._backend, self._sorted_crl, revoked[0]
  213. )
  214. @property
  215. def signature_hash_algorithm(
  216. self,
  217. ) -> typing.Optional[hashes.HashAlgorithm]:
  218. oid = self.signature_algorithm_oid
  219. try:
  220. return x509._SIG_OIDS_TO_HASH[oid]
  221. except KeyError:
  222. raise UnsupportedAlgorithm(
  223. "Signature algorithm OID:{} not recognized".format(oid)
  224. )
  225. @property
  226. def signature_algorithm_oid(self) -> x509.ObjectIdentifier:
  227. alg = self._backend._ffi.new("X509_ALGOR **")
  228. self._backend._lib.X509_CRL_get0_signature(
  229. self._x509_crl, self._backend._ffi.NULL, alg
  230. )
  231. self._backend.openssl_assert(alg[0] != self._backend._ffi.NULL)
  232. oid = _obj2txt(self._backend, alg[0].algorithm)
  233. return x509.ObjectIdentifier(oid)
  234. @property
  235. def issuer(self) -> x509.Name:
  236. issuer = self._backend._lib.X509_CRL_get_issuer(self._x509_crl)
  237. self._backend.openssl_assert(issuer != self._backend._ffi.NULL)
  238. return _decode_x509_name(self._backend, issuer)
  239. @property
  240. def next_update(self) -> datetime.datetime:
  241. nu = self._backend._lib.X509_CRL_get0_nextUpdate(self._x509_crl)
  242. self._backend.openssl_assert(nu != self._backend._ffi.NULL)
  243. return _parse_asn1_time(self._backend, nu)
  244. @property
  245. def last_update(self) -> datetime.datetime:
  246. lu = self._backend._lib.X509_CRL_get0_lastUpdate(self._x509_crl)
  247. self._backend.openssl_assert(lu != self._backend._ffi.NULL)
  248. return _parse_asn1_time(self._backend, lu)
  249. @property
  250. def signature(self) -> bytes:
  251. sig = self._backend._ffi.new("ASN1_BIT_STRING **")
  252. self._backend._lib.X509_CRL_get0_signature(
  253. self._x509_crl, sig, self._backend._ffi.NULL
  254. )
  255. self._backend.openssl_assert(sig[0] != self._backend._ffi.NULL)
  256. return _asn1_string_to_bytes(self._backend, sig[0])
  257. @property
  258. def tbs_certlist_bytes(self) -> bytes:
  259. pp = self._backend._ffi.new("unsigned char **")
  260. res = self._backend._lib.i2d_re_X509_CRL_tbs(self._x509_crl, pp)
  261. self._backend.openssl_assert(res > 0)
  262. pp = self._backend._ffi.gc(
  263. pp, lambda pointer: self._backend._lib.OPENSSL_free(pointer[0])
  264. )
  265. return self._backend._ffi.buffer(pp[0], res)[:]
  266. def public_bytes(self, encoding: serialization.Encoding) -> bytes:
  267. bio = self._backend._create_mem_bio_gc()
  268. if encoding is serialization.Encoding.PEM:
  269. res = self._backend._lib.PEM_write_bio_X509_CRL(
  270. bio, self._x509_crl
  271. )
  272. elif encoding is serialization.Encoding.DER:
  273. res = self._backend._lib.i2d_X509_CRL_bio(bio, self._x509_crl)
  274. else:
  275. raise TypeError("encoding must be an item from the Encoding enum")
  276. self._backend.openssl_assert(res == 1)
  277. return self._backend._read_mem_bio(bio)
  278. def _revoked_cert(self, idx):
  279. revoked = self._backend._lib.X509_CRL_get_REVOKED(self._x509_crl)
  280. r = self._backend._lib.sk_X509_REVOKED_value(revoked, idx)
  281. self._backend.openssl_assert(r != self._backend._ffi.NULL)
  282. return _RevokedCertificate(self._backend, self, r)
  283. def __iter__(self):
  284. for i in range(len(self)):
  285. yield self._revoked_cert(i)
  286. def __getitem__(self, idx):
  287. if isinstance(idx, slice):
  288. start, stop, step = idx.indices(len(self))
  289. return [self._revoked_cert(i) for i in range(start, stop, step)]
  290. else:
  291. idx = operator.index(idx)
  292. if idx < 0:
  293. idx += len(self)
  294. if not 0 <= idx < len(self):
  295. raise IndexError
  296. return self._revoked_cert(idx)
  297. def __len__(self) -> int:
  298. revoked = self._backend._lib.X509_CRL_get_REVOKED(self._x509_crl)
  299. if revoked == self._backend._ffi.NULL:
  300. return 0
  301. else:
  302. return self._backend._lib.sk_X509_REVOKED_num(revoked)
  303. @utils.cached_property
  304. def extensions(self) -> x509.Extensions:
  305. return self._backend._crl_extension_parser.parse(self._x509_crl)
  306. def is_signature_valid(self, public_key: _PUBLIC_KEY_TYPES) -> bool:
  307. if not isinstance(
  308. public_key,
  309. (
  310. dsa._DSAPublicKey,
  311. rsa._RSAPublicKey,
  312. ec._EllipticCurvePublicKey,
  313. ),
  314. ):
  315. raise TypeError(
  316. "Expecting one of DSAPublicKey, RSAPublicKey,"
  317. " or EllipticCurvePublicKey."
  318. )
  319. res = self._backend._lib.X509_CRL_verify(
  320. self._x509_crl, public_key._evp_pkey
  321. )
  322. if res != 1:
  323. self._backend._consume_errors()
  324. return False
  325. return True
  326. @utils.register_interface(x509.CertificateSigningRequest)
  327. class _CertificateSigningRequest(object):
  328. def __init__(self, backend, x509_req):
  329. self._backend = backend
  330. self._x509_req = x509_req
  331. def __eq__(self, other: object) -> bool:
  332. if not isinstance(other, _CertificateSigningRequest):
  333. return NotImplemented
  334. self_bytes = self.public_bytes(serialization.Encoding.DER)
  335. other_bytes = other.public_bytes(serialization.Encoding.DER)
  336. return self_bytes == other_bytes
  337. def __ne__(self, other: object) -> bool:
  338. return not self == other
  339. def __hash__(self) -> int:
  340. return hash(self.public_bytes(serialization.Encoding.DER))
  341. def public_key(self) -> _PUBLIC_KEY_TYPES:
  342. pkey = self._backend._lib.X509_REQ_get_pubkey(self._x509_req)
  343. self._backend.openssl_assert(pkey != self._backend._ffi.NULL)
  344. pkey = self._backend._ffi.gc(pkey, self._backend._lib.EVP_PKEY_free)
  345. return self._backend._evp_pkey_to_public_key(pkey)
  346. @property
  347. def subject(self) -> x509.Name:
  348. subject = self._backend._lib.X509_REQ_get_subject_name(self._x509_req)
  349. self._backend.openssl_assert(subject != self._backend._ffi.NULL)
  350. return _decode_x509_name(self._backend, subject)
  351. @property
  352. def signature_hash_algorithm(
  353. self,
  354. ) -> typing.Optional[hashes.HashAlgorithm]:
  355. oid = self.signature_algorithm_oid
  356. try:
  357. return x509._SIG_OIDS_TO_HASH[oid]
  358. except KeyError:
  359. raise UnsupportedAlgorithm(
  360. "Signature algorithm OID:{} not recognized".format(oid)
  361. )
  362. @property
  363. def signature_algorithm_oid(self) -> x509.ObjectIdentifier:
  364. alg = self._backend._ffi.new("X509_ALGOR **")
  365. self._backend._lib.X509_REQ_get0_signature(
  366. self._x509_req, self._backend._ffi.NULL, alg
  367. )
  368. self._backend.openssl_assert(alg[0] != self._backend._ffi.NULL)
  369. oid = _obj2txt(self._backend, alg[0].algorithm)
  370. return x509.ObjectIdentifier(oid)
  371. @utils.cached_property
  372. def extensions(self) -> x509.Extensions:
  373. x509_exts = self._backend._lib.X509_REQ_get_extensions(self._x509_req)
  374. x509_exts = self._backend._ffi.gc(
  375. x509_exts,
  376. lambda x: self._backend._lib.sk_X509_EXTENSION_pop_free(
  377. x,
  378. self._backend._ffi.addressof(
  379. self._backend._lib._original_lib, "X509_EXTENSION_free"
  380. ),
  381. ),
  382. )
  383. return self._backend._csr_extension_parser.parse(x509_exts)
  384. def public_bytes(self, encoding: serialization.Encoding) -> bytes:
  385. bio = self._backend._create_mem_bio_gc()
  386. if encoding is serialization.Encoding.PEM:
  387. res = self._backend._lib.PEM_write_bio_X509_REQ(
  388. bio, self._x509_req
  389. )
  390. elif encoding is serialization.Encoding.DER:
  391. res = self._backend._lib.i2d_X509_REQ_bio(bio, self._x509_req)
  392. else:
  393. raise TypeError("encoding must be an item from the Encoding enum")
  394. self._backend.openssl_assert(res == 1)
  395. return self._backend._read_mem_bio(bio)
  396. @property
  397. def tbs_certrequest_bytes(self) -> bytes:
  398. pp = self._backend._ffi.new("unsigned char **")
  399. res = self._backend._lib.i2d_re_X509_REQ_tbs(self._x509_req, pp)
  400. self._backend.openssl_assert(res > 0)
  401. pp = self._backend._ffi.gc(
  402. pp, lambda pointer: self._backend._lib.OPENSSL_free(pointer[0])
  403. )
  404. return self._backend._ffi.buffer(pp[0], res)[:]
  405. @property
  406. def signature(self) -> bytes:
  407. sig = self._backend._ffi.new("ASN1_BIT_STRING **")
  408. self._backend._lib.X509_REQ_get0_signature(
  409. self._x509_req, sig, self._backend._ffi.NULL
  410. )
  411. self._backend.openssl_assert(sig[0] != self._backend._ffi.NULL)
  412. return _asn1_string_to_bytes(self._backend, sig[0])
  413. @property
  414. def is_signature_valid(self) -> bool:
  415. pkey = self._backend._lib.X509_REQ_get_pubkey(self._x509_req)
  416. self._backend.openssl_assert(pkey != self._backend._ffi.NULL)
  417. pkey = self._backend._ffi.gc(pkey, self._backend._lib.EVP_PKEY_free)
  418. res = self._backend._lib.X509_REQ_verify(self._x509_req, pkey)
  419. if res != 1:
  420. self._backend._consume_errors()
  421. return False
  422. return True
  423. def get_attribute_for_oid(self, oid: x509.ObjectIdentifier) -> bytes:
  424. obj = _txt2obj_gc(self._backend, oid.dotted_string)
  425. pos = self._backend._lib.X509_REQ_get_attr_by_OBJ(
  426. self._x509_req, obj, -1
  427. )
  428. if pos == -1:
  429. raise x509.AttributeNotFound(
  430. "No {} attribute was found".format(oid), oid
  431. )
  432. attr = self._backend._lib.X509_REQ_get_attr(self._x509_req, pos)
  433. self._backend.openssl_assert(attr != self._backend._ffi.NULL)
  434. # We don't support multiple valued attributes for now.
  435. self._backend.openssl_assert(
  436. self._backend._lib.X509_ATTRIBUTE_count(attr) == 1
  437. )
  438. asn1_type = self._backend._lib.X509_ATTRIBUTE_get0_type(attr, 0)
  439. self._backend.openssl_assert(asn1_type != self._backend._ffi.NULL)
  440. # We need this to ensure that our C type cast is safe.
  441. # Also this should always be a sane string type, but we'll see if
  442. # that is true in the real world...
  443. if asn1_type.type not in (
  444. _ASN1Type.UTF8String.value,
  445. _ASN1Type.PrintableString.value,
  446. _ASN1Type.IA5String.value,
  447. ):
  448. raise ValueError(
  449. "OID {} has a disallowed ASN.1 type: {}".format(
  450. oid, asn1_type.type
  451. )
  452. )
  453. data = self._backend._lib.X509_ATTRIBUTE_get0_data(
  454. attr, 0, asn1_type.type, self._backend._ffi.NULL
  455. )
  456. self._backend.openssl_assert(data != self._backend._ffi.NULL)
  457. # This cast is safe iff we assert on the type above to ensure
  458. # that it is always a type of ASN1_STRING
  459. data = self._backend._ffi.cast("ASN1_STRING *", data)
  460. return _asn1_string_to_bytes(self._backend, data)
  461. @utils.register_interface(
  462. x509.certificate_transparency.SignedCertificateTimestamp
  463. )
  464. class _SignedCertificateTimestamp(object):
  465. def __init__(self, backend, sct_list, sct):
  466. self._backend = backend
  467. # Keep the SCT_LIST that this SCT came from alive.
  468. self._sct_list = sct_list
  469. self._sct = sct
  470. @property
  471. def version(self) -> x509.certificate_transparency.Version:
  472. version = self._backend._lib.SCT_get_version(self._sct)
  473. assert version == self._backend._lib.SCT_VERSION_V1
  474. return x509.certificate_transparency.Version.v1
  475. @property
  476. def log_id(self) -> bytes:
  477. out = self._backend._ffi.new("unsigned char **")
  478. log_id_length = self._backend._lib.SCT_get0_log_id(self._sct, out)
  479. assert log_id_length >= 0
  480. return self._backend._ffi.buffer(out[0], log_id_length)[:]
  481. @property
  482. def timestamp(self) -> datetime.datetime:
  483. timestamp = self._backend._lib.SCT_get_timestamp(self._sct)
  484. milliseconds = timestamp % 1000
  485. return datetime.datetime.utcfromtimestamp(timestamp // 1000).replace(
  486. microsecond=milliseconds * 1000
  487. )
  488. @property
  489. def entry_type(self) -> x509.certificate_transparency.LogEntryType:
  490. entry_type = self._backend._lib.SCT_get_log_entry_type(self._sct)
  491. # We currently only support loading SCTs from the X.509 extension, so
  492. # we only have precerts.
  493. assert entry_type == self._backend._lib.CT_LOG_ENTRY_TYPE_PRECERT
  494. return x509.certificate_transparency.LogEntryType.PRE_CERTIFICATE
  495. @property
  496. def _signature(self):
  497. ptrptr = self._backend._ffi.new("unsigned char **")
  498. res = self._backend._lib.SCT_get0_signature(self._sct, ptrptr)
  499. self._backend.openssl_assert(res > 0)
  500. self._backend.openssl_assert(ptrptr[0] != self._backend._ffi.NULL)
  501. return self._backend._ffi.buffer(ptrptr[0], res)[:]
  502. def __hash__(self) -> int:
  503. return hash(self._signature)
  504. def __eq__(self, other: object) -> bool:
  505. if not isinstance(other, _SignedCertificateTimestamp):
  506. return NotImplemented
  507. return self._signature == other._signature
  508. def __ne__(self, other: object) -> bool:
  509. return not self == other