Skip to content

Commit f68ca0f

Browse files
committed
Simplify X25519 key loading
1 parent f8c98d2 commit f68ca0f

File tree

3 files changed

+25
-73
lines changed

3 files changed

+25
-73
lines changed

src/_cffi_src/openssl/cryptography.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@
8686
static const int CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE;
8787
static const int CRYPTOGRAPHY_HAS_WORKING_ED25519;
8888
89+
static const int CRYPTOGRAPHY_LIBRESSL_LESS_THAN_370;
90+
8991
static const int CRYPTOGRAPHY_IS_LIBRESSL;
9092
static const int CRYPTOGRAPHY_IS_BORINGSSL;
9193
"""

src/cryptography/hazmat/backends/openssl/backend.py

Lines changed: 10 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1838,54 +1838,29 @@ def dh_x942_serialization_supported(self) -> bool:
18381838
return self._lib.Cryptography_HAS_EVP_PKEY_DHX == 1
18391839

18401840
def x25519_load_public_bytes(self, data: bytes) -> x25519.X25519PublicKey:
1841-
# If/when LibreSSL adds support for EVP_PKEY_new_raw_public_key we
1842-
# can switch to it (Cryptography_HAS_RAW_KEY)
18431841
if len(data) != 32:
18441842
raise ValueError("An X25519 public key is 32 bytes long")
18451843

1846-
evp_pkey = self._create_evp_pkey_gc()
1847-
res = self._lib.EVP_PKEY_set_type(evp_pkey, self._lib.NID_X25519)
1848-
self.openssl_assert(res == 1)
1849-
res = self._lib.EVP_PKEY_set1_tls_encodedpoint(
1850-
evp_pkey, data, len(data)
1844+
data_ptr = self._ffi.from_buffer(data)
1845+
evp_pkey = self._lib.EVP_PKEY_new_raw_public_key(
1846+
self._lib.NID_X25519, self._ffi.NULL, data_ptr, len(data)
18511847
)
1852-
self.openssl_assert(res == 1)
1848+
self.openssl_assert(evp_pkey != self._ffi.NULL)
1849+
evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free)
18531850
return _X25519PublicKey(self, evp_pkey)
18541851

18551852
def x25519_load_private_bytes(
18561853
self, data: bytes
18571854
) -> x25519.X25519PrivateKey:
1858-
# If/when LibreSSL adds support for EVP_PKEY_new_raw_private_key we
1859-
# can switch to it (Cryptography_HAS_RAW_KEY) drop the
1860-
# zeroed_bytearray garbage.
1861-
# OpenSSL only has facilities for loading PKCS8 formatted private
1862-
# keys using the algorithm identifiers specified in
1863-
# https://tools.ietf.org/html/draft-ietf-curdle-pkix-09.
1864-
# This is the standard PKCS8 prefix for a 32 byte X25519 key.
1865-
# The form is:
1866-
# 0:d=0 hl=2 l= 46 cons: SEQUENCE
1867-
# 2:d=1 hl=2 l= 1 prim: INTEGER :00
1868-
# 5:d=1 hl=2 l= 5 cons: SEQUENCE
1869-
# 7:d=2 hl=2 l= 3 prim: OBJECT :1.3.101.110
1870-
# 12:d=1 hl=2 l= 34 prim: OCTET STRING (the key)
1871-
# Of course there's a bit more complexity. In reality OCTET STRING
1872-
# contains an OCTET STRING of length 32! So the last two bytes here
1873-
# are \x04\x20, which is an OCTET STRING of length 32.
18741855
if len(data) != 32:
18751856
raise ValueError("An X25519 private key is 32 bytes long")
18761857

1877-
pkcs8_prefix = b'0.\x02\x01\x000\x05\x06\x03+en\x04"\x04 '
1878-
with self._zeroed_bytearray(48) as ba:
1879-
ba[0:16] = pkcs8_prefix
1880-
ba[16:] = data
1881-
bio = self._bytes_to_bio(ba)
1882-
evp_pkey = self._lib.d2i_PrivateKey_bio(bio.bio, self._ffi.NULL)
1883-
1858+
data_ptr = self._ffi.from_buffer(data)
1859+
evp_pkey = self._lib.EVP_PKEY_new_raw_private_key(
1860+
self._lib.NID_X25519, self._ffi.NULL, data_ptr, len(data)
1861+
)
18841862
self.openssl_assert(evp_pkey != self._ffi.NULL)
18851863
evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free)
1886-
self.openssl_assert(
1887-
self._lib.EVP_PKEY_id(evp_pkey) == self._lib.EVP_PKEY_X25519
1888-
)
18891864
return _X25519PrivateKey(self, evp_pkey)
18901865

18911866
def _evp_pkey_keygen_gc(self, nid):
@@ -1908,7 +1883,7 @@ def x25519_generate_key(self) -> x25519.X25519PrivateKey:
19081883
def x25519_supported(self) -> bool:
19091884
if self._fips_enabled:
19101885
return False
1911-
return not self._lib.CRYPTOGRAPHY_IS_LIBRESSL
1886+
return not self._lib.CRYPTOGRAPHY_LIBRESSL_LESS_THAN_370
19121887

19131888
def x448_load_public_bytes(self, data: bytes) -> x448.X448PublicKey:
19141889
if len(data) != 56:
@@ -2074,19 +2049,6 @@ def aead_cipher_supported(self, cipher) -> bool:
20742049
self._lib.EVP_get_cipherbyname(cipher_name) != self._ffi.NULL
20752050
)
20762051

2077-
@contextlib.contextmanager
2078-
def _zeroed_bytearray(self, length: int) -> typing.Iterator[bytearray]:
2079-
"""
2080-
This method creates a bytearray, which we copy data into (hopefully
2081-
also from a mutable buffer that can be dynamically erased!), and then
2082-
zero when we're done.
2083-
"""
2084-
ba = bytearray(length)
2085-
try:
2086-
yield ba
2087-
finally:
2088-
self._zero_data(ba, length)
2089-
20902052
def _zero_data(self, data, length: int) -> None:
20912053
# We clear things this way because at the moment we're not
20922054
# sure of a better way that can guarantee it overwrites the

src/cryptography/hazmat/backends/openssl/x25519.py

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,14 @@ def public_bytes(
4747
)
4848

4949
def _raw_public_bytes(self) -> bytes:
50-
ucharpp = self._backend._ffi.new("unsigned char **")
51-
res = self._backend._lib.EVP_PKEY_get1_tls_encodedpoint(
52-
self._evp_pkey, ucharpp
50+
buf = self._backend._ffi.new("unsigned char []", _X25519_KEY_SIZE)
51+
buflen = self._backend._ffi.new("size_t *", _X25519_KEY_SIZE)
52+
res = self._backend._lib.EVP_PKEY_get_raw_public_key(
53+
self._evp_pkey, buf, buflen
5354
)
54-
self._backend.openssl_assert(res == 32)
55-
self._backend.openssl_assert(ucharpp[0] != self._backend._ffi.NULL)
56-
data = self._backend._ffi.gc(
57-
ucharpp[0], self._backend._lib.OPENSSL_free
58-
)
59-
return self._backend._ffi.buffer(data, res)[:]
55+
self._backend.openssl_assert(res == 1)
56+
self._backend.openssl_assert(buflen[0] == _X25519_KEY_SIZE)
57+
return self._backend._ffi.buffer(buf, _X25519_KEY_SIZE)[:]
6058

6159

6260
class _X25519PrivateKey(X25519PrivateKey):
@@ -112,21 +110,11 @@ def private_bytes(
112110
)
113111

114112
def _raw_private_bytes(self) -> bytes:
115-
# If/when LibreSSL adds support for EVP_PKEY_get_raw_private_key we
116-
# can switch to it (Cryptography_HAS_RAW_KEY)
117-
# The trick we use here is serializing to a PKCS8 key and just
118-
# using the last 32 bytes, which is the key itself.
119-
bio = self._backend._create_mem_bio_gc()
120-
res = self._backend._lib.i2d_PKCS8PrivateKey_bio(
121-
bio,
122-
self._evp_pkey,
123-
self._backend._ffi.NULL,
124-
self._backend._ffi.NULL,
125-
0,
126-
self._backend._ffi.NULL,
127-
self._backend._ffi.NULL,
113+
buf = self._backend._ffi.new("unsigned char []", _X25519_KEY_SIZE)
114+
buflen = self._backend._ffi.new("size_t *", _X25519_KEY_SIZE)
115+
res = self._backend._lib.EVP_PKEY_get_raw_private_key(
116+
self._evp_pkey, buf, buflen
128117
)
129118
self._backend.openssl_assert(res == 1)
130-
pkcs8 = self._backend._read_mem_bio(bio)
131-
self._backend.openssl_assert(len(pkcs8) == 48)
132-
return pkcs8[-_X25519_KEY_SIZE:]
119+
self._backend.openssl_assert(buflen[0] == _X25519_KEY_SIZE)
120+
return self._backend._ffi.buffer(buf, _X25519_KEY_SIZE)[:]

0 commit comments

Comments
 (0)