Skip to content

Commit 6518369

Browse files
committed
ed25519 support
1 parent 1717f8c commit 6518369

10 files changed

Lines changed: 388 additions & 22 deletions

File tree

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ matrix:
6060
dist: xenial
6161
sudo: true
6262
- python: 3.6
63-
env: TOXENV=docs OPENSSL=1.1.0i
63+
env: TOXENV=docs OPENSSL=1.1.1
6464
addons:
6565
apt:
6666
packages:
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
.. hazmat::
2+
3+
Ed25519 signing
4+
===============
5+
6+
.. currentmodule:: cryptography.hazmat.primitives.asymmetric.ed25519
7+
8+
9+
Ed25519 is a signing algorithm using `EdDSA`_ and `Curve25519`_. If you do not
10+
have legacy interoperability concerns then you should strongly consider using
11+
this signature algorithm.
12+
13+
14+
Signing & Verification
15+
~~~~~~~~~~~~~~~~~~~~~~
16+
17+
.. doctest::
18+
19+
>>> from cryptography.hazmat.backends import default_backend
20+
>>> from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
21+
>>> private_key = Ed25519PrivateKey.generate()
22+
>>> signature = private_key.sign(b"my authenticated message")
23+
>>> public_key = private_key.public_key()
24+
>>> # Raises InvalidSignature if verification fails
25+
>>> public_key.verify(signature, b"my authenticated message")
26+
27+
Key interfaces
28+
~~~~~~~~~~~~~~
29+
30+
.. class:: Ed25519PrivateKey
31+
32+
.. versionadded:: 2.4
33+
34+
.. classmethod:: generate()
35+
36+
Generate an Ed25519 private key.
37+
38+
:returns: :class:`Ed25519PrivateKey`
39+
40+
.. method:: public_key()
41+
42+
:returns: :class:`Ed25519PublicKey`
43+
44+
.. method:: sign(data)
45+
46+
:param bytes data: The data to sign.
47+
48+
:returns bytes: The 64 byte signature.
49+
50+
.. class:: Ed25519PublicKey
51+
52+
.. versionadded:: 2.4
53+
54+
.. classmethod:: from_public_bytes(data)
55+
56+
:param bytes data: 32 byte public key.
57+
58+
:returns: :class:`Ed25519PublicKey`
59+
60+
.. method:: public_bytes()
61+
62+
:returns bytes: The raw bytes of the public key.
63+
64+
.. method:: verify(signature, data)
65+
66+
:param bytes signature: The signature to verify.
67+
68+
:param bytes data: The data to verify.
69+
70+
:raises cryptography.exceptions.InvalidSignature: Raised when the
71+
signature cannot be verified.
72+
73+
74+
75+
.. _`EdDSA`: https://en.wikipedia.org/wiki/EdDSA
76+
.. _`Curve25519`: https://en.wikipedia.org/wiki/Curve25519

docs/hazmat/primitives/asymmetric/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ private key is able to decrypt it.
2525

2626
x25519
2727
ec
28+
ed25519
2829
rsa
2930
dh
3031
dsa

docs/spelling_wordlist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ hostname
5252
idna
5353
indistinguishability
5454
initialisms
55+
interoperability
5556
interoperable
5657
introspectability
5758
invariants

src/_cffi_src/openssl/cryptography.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
(OPENSSL_VERSION_NUMBER >= 0x10100000 && !CRYPTOGRAPHY_IS_LIBRESSL)
4545
#define CRYPTOGRAPHY_OPENSSL_110F_OR_GREATER \
4646
(OPENSSL_VERSION_NUMBER >= 0x1010006f && !CRYPTOGRAPHY_IS_LIBRESSL)
47+
#define CRYPTOGRAPHY_OPENSSL_111_OR_GREATER \
48+
(OPENSSL_VERSION_NUMBER >= 0x10101000 && !CRYPTOGRAPHY_IS_LIBRESSL)
4749
4850
#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_102 \
4951
(OPENSSL_VERSION_NUMBER < 0x10002000 || CRYPTOGRAPHY_IS_LIBRESSL)
@@ -53,18 +55,16 @@
5355
(OPENSSL_VERSION_NUMBER < 0x10100000 || CRYPTOGRAPHY_IS_LIBRESSL)
5456
#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_110J \
5557
(OPENSSL_VERSION_NUMBER < 0x101000af || CRYPTOGRAPHY_IS_LIBRESSL)
56-
#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_111 \
57-
(OPENSSL_VERSION_NUMBER < 0x10101000 || CRYPTOGRAPHY_IS_LIBRESSL)
5858
"""
5959

6060
TYPES = """
6161
static const int CRYPTOGRAPHY_OPENSSL_102L_OR_GREATER;
6262
static const int CRYPTOGRAPHY_OPENSSL_110_OR_GREATER;
6363
static const int CRYPTOGRAPHY_OPENSSL_110F_OR_GREATER;
64+
static const int CRYPTOGRAPHY_OPENSSL_111_OR_GREATER;
6465
6566
static const int CRYPTOGRAPHY_OPENSSL_LESS_THAN_102I;
6667
static const int CRYPTOGRAPHY_OPENSSL_LESS_THAN_102;
67-
static const int CRYPTOGRAPHY_OPENSSL_LESS_THAN_111;
6868
6969
static const int CRYPTOGRAPHY_IS_LIBRESSL;
7070
"""

src/_cffi_src/openssl/evp.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -244,14 +244,14 @@
244244
size_t) = NULL;
245245
#endif
246246
247-
#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_111
247+
#if CRYPTOGRAPHY_OPENSSL_111_OR_GREATER
248+
static const long Cryptography_HAS_ONESHOT_EVP_DIGEST_SIGN_VERIFY = 1;
249+
#else
248250
static const long Cryptography_HAS_ONESHOT_EVP_DIGEST_SIGN_VERIFY = 0;
249251
int (*EVP_DigestSign)(EVP_MD_CTX *, unsigned char *, size_t *,
250252
const unsigned char *tbs, size_t) = NULL;
251253
int (*EVP_DigestVerify)(EVP_MD_CTX *, const unsigned char *, size_t,
252254
const unsigned char *, size_t) = NULL;
253-
#else
254-
static const long Cryptography_HAS_ONESHOT_EVP_DIGEST_SIGN_VERIFY = 1;
255255
#endif
256256
257257
/* OpenSSL 1.1.0+ does this define for us, but if not present we'll do it */

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

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
from cryptography.hazmat.backends.openssl.ec import (
3636
_EllipticCurvePrivateKey, _EllipticCurvePublicKey
3737
)
38+
from cryptography.hazmat.backends.openssl.ed25519 import (
39+
_Ed25519PrivateKey, _Ed25519PublicKey
40+
)
3841
from cryptography.hazmat.backends.openssl.encode_asn1 import (
3942
_CRL_ENTRY_EXTENSION_ENCODE_HANDLERS,
4043
_CRL_EXTENSION_ENCODE_HANDLERS, _EXTENSION_ENCODE_HANDLERS,
@@ -1935,6 +1938,13 @@ def x25519_load_public_bytes(self, data):
19351938
backend.openssl_assert(res == 1)
19361939
return _X25519PublicKey(self, evp_pkey)
19371940

1941+
def _generic_25519_load_private_bytes(self, prefix, data, key_cls):
1942+
bio = self._bytes_to_bio(prefix + data)
1943+
evp_pkey = backend._lib.d2i_PrivateKey_bio(bio.bio, self._ffi.NULL)
1944+
self.openssl_assert(evp_pkey != self._ffi.NULL)
1945+
evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free)
1946+
return key_cls(self, evp_pkey)
1947+
19381948
def x25519_load_private_bytes(self, data):
19391949
# OpenSSL only has facilities for loading PKCS8 formatted private
19401950
# keys using the algorithm identifiers specified in
@@ -1950,35 +1960,66 @@ def x25519_load_private_bytes(self, data):
19501960
# contains an OCTET STRING of length 32! So the last two bytes here
19511961
# are \x04\x20, which is an OCTET STRING of length 32.
19521962
pkcs8_prefix = b'0.\x02\x01\x000\x05\x06\x03+en\x04"\x04 '
1953-
bio = self._bytes_to_bio(pkcs8_prefix + data)
1954-
evp_pkey = backend._lib.d2i_PrivateKey_bio(bio.bio, self._ffi.NULL)
1955-
self.openssl_assert(evp_pkey != self._ffi.NULL)
1956-
evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free)
1957-
self.openssl_assert(
1958-
self._lib.EVP_PKEY_id(evp_pkey) == self._lib.EVP_PKEY_X25519
1963+
return self._generic_25519_load_private_bytes(
1964+
pkcs8_prefix, data, _X25519PrivateKey
19591965
)
1960-
return _X25519PrivateKey(self, evp_pkey)
19611966

1962-
def x25519_generate_key(self):
1963-
evp_pkey_ctx = self._lib.EVP_PKEY_CTX_new_id(
1964-
self._lib.NID_X25519, self._ffi.NULL
1965-
)
1967+
def _generic_25519_generate_key(self, nid, keycls):
1968+
evp_pkey_ctx = self._lib.EVP_PKEY_CTX_new_id(nid, self._ffi.NULL)
19661969
self.openssl_assert(evp_pkey_ctx != self._ffi.NULL)
1967-
evp_pkey_ctx = self._ffi.gc(
1968-
evp_pkey_ctx, self._lib.EVP_PKEY_CTX_free
1969-
)
1970+
evp_pkey_ctx = self._ffi.gc(evp_pkey_ctx, self._lib.EVP_PKEY_CTX_free)
19701971
res = self._lib.EVP_PKEY_keygen_init(evp_pkey_ctx)
19711972
self.openssl_assert(res == 1)
19721973
evp_ppkey = self._ffi.new("EVP_PKEY **")
19731974
res = self._lib.EVP_PKEY_keygen(evp_pkey_ctx, evp_ppkey)
19741975
self.openssl_assert(res == 1)
19751976
self.openssl_assert(evp_ppkey[0] != self._ffi.NULL)
19761977
evp_pkey = self._ffi.gc(evp_ppkey[0], self._lib.EVP_PKEY_free)
1977-
return _X25519PrivateKey(self, evp_pkey)
1978+
return keycls(self, evp_pkey)
1979+
1980+
def x25519_generate_key(self):
1981+
return self._generic_25519_generate_key(
1982+
self._lib.NID_X25519, _X25519PrivateKey
1983+
)
19781984

19791985
def x25519_supported(self):
19801986
return self._lib.CRYPTOGRAPHY_OPENSSL_110_OR_GREATER
19811987

1988+
def ed25519_supported(self):
1989+
return self._lib.CRYPTOGRAPHY_OPENSSL_111_OR_GREATER
1990+
1991+
def ed25519_load_public_bytes(self, data):
1992+
pkcs8_prefix = b'0.\x02\x01\x000\x05\x06\x03+ep\x04"\x04 '
1993+
bio = self._bytes_to_bio(pkcs8_prefix + data)
1994+
evp_pkey = backend._lib.d2i_PUBKEY_bio(bio.bio, self._ffi.NULL)
1995+
self.openssl_assert(evp_pkey != self._ffi.NULL)
1996+
evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free)
1997+
return _Ed25519PublicKey(self, evp_pkey)
1998+
1999+
def ed25519_load_private_bytes(self, data):
2000+
# OpenSSL only has facilities for loading PKCS8 formatted private
2001+
# keys using the algorithm identifiers specified in
2002+
# https://tools.ietf.org/html/draft-ietf-curdle-pkix-03.
2003+
# This is the standard PKCS8 prefix for a 32 byte Ed25519 key.
2004+
# The form is:
2005+
# 0:d=0 hl=2 l= 46 cons: SEQUENCE
2006+
# 2:d=1 hl=2 l= 1 prim: INTEGER :00
2007+
# 5:d=1 hl=2 l= 5 cons: SEQUENCE
2008+
# 7:d=2 hl=2 l= 3 prim: OBJECT :1.3.101.112
2009+
# 12:d=1 hl=2 l= 34 prim: OCTET STRING (the key)
2010+
# Of course there's a bit more complexity. In reality OCTET STRING
2011+
# contains an OCTET STRING of length 32! So the last two bytes here
2012+
# are \x04\x20, which is an OCTET STRING of length 32.
2013+
pkcs8_prefix = b'0.\x02\x01\x000\x05\x06\x03+ep\x04"\x04 '
2014+
return self._generic_25519_load_private_bytes(
2015+
pkcs8_prefix, data, _Ed25519PrivateKey
2016+
)
2017+
2018+
def ed25519_generate_key(self):
2019+
return self._generic_25519_generate_key(
2020+
self._lib.NID_ED25519, _Ed25519PrivateKey
2021+
)
2022+
19822023
def derive_scrypt(self, key_material, salt, length, n, r, p):
19832024
buf = self._ffi.new("unsigned char[]", length)
19842025
res = self._lib.EVP_PBE_scrypt(
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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+
5+
from __future__ import absolute_import, division, print_function
6+
7+
from cryptography import exceptions, utils
8+
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
9+
Ed25519PrivateKey, Ed25519PublicKey
10+
)
11+
12+
13+
@utils.register_interface(Ed25519PublicKey)
14+
class _Ed25519PublicKey(object):
15+
def __init__(self, backend, evp_pkey):
16+
self._backend = backend
17+
self._evp_pkey = evp_pkey
18+
19+
def public_bytes(self):
20+
bio = self._backend._create_mem_bio_gc()
21+
res = self._backend._lib.i2d_PUBKEY_bio(bio, self._evp_pkey)
22+
self._backend.openssl_assert(res == 1)
23+
asn1 = self._backend._read_mem_bio(bio)
24+
# We serialize to the ASN.1 structure defined in
25+
# https://tools.ietf.org/html/draft-ietf-curdle-pkix-03. and
26+
# then take the last 32 bytes, which are the actual key.
27+
self._backend.openssl_assert(len(asn1) == 44)
28+
return asn1[-32:]
29+
30+
def verify(self, signature, data):
31+
evp_md_ctx = self._backend._lib.Cryptography_EVP_MD_CTX_new()
32+
self._backend.openssl_assert(evp_md_ctx != self._backend._ffi.NULL)
33+
evp_md_ctx = self._backend._ffi.gc(
34+
evp_md_ctx, self._backend._lib.Cryptography_EVP_MD_CTX_free
35+
)
36+
res = self._backend._lib.EVP_DigestVerifyInit(
37+
evp_md_ctx, self._backend._ffi.NULL, self._backend._ffi.NULL,
38+
self._backend._ffi.NULL, self._evp_pkey
39+
)
40+
self._backend.openssl_assert(res == 1)
41+
res = self._backend._lib.EVP_DigestVerify(
42+
evp_md_ctx, signature, len(signature), data, len(data)
43+
)
44+
if res != 1:
45+
self._backend._consume_errors()
46+
raise exceptions.InvalidSignature
47+
48+
49+
@utils.register_interface(Ed25519PrivateKey)
50+
class _Ed25519PrivateKey(object):
51+
def __init__(self, backend, evp_pkey):
52+
self._backend = backend
53+
self._evp_pkey = evp_pkey
54+
55+
def public_key(self):
56+
bio = self._backend._create_mem_bio_gc()
57+
res = self._backend._lib.i2d_PUBKEY_bio(bio, self._evp_pkey)
58+
self._backend.openssl_assert(res == 1)
59+
evp_pkey = self._backend._lib.d2i_PUBKEY_bio(
60+
bio, self._backend._ffi.NULL
61+
)
62+
return _Ed25519PublicKey(self._backend, evp_pkey)
63+
64+
def sign(self, data):
65+
evp_md_ctx = self._backend._lib.Cryptography_EVP_MD_CTX_new()
66+
self._backend.openssl_assert(evp_md_ctx != self._backend._ffi.NULL)
67+
evp_md_ctx = self._backend._ffi.gc(
68+
evp_md_ctx, self._backend._lib.Cryptography_EVP_MD_CTX_free
69+
)
70+
res = self._backend._lib.EVP_DigestSignInit(
71+
evp_md_ctx, self._backend._ffi.NULL, self._backend._ffi.NULL,
72+
self._backend._ffi.NULL, self._evp_pkey
73+
)
74+
self._backend.openssl_assert(res == 1)
75+
buf = self._backend._ffi.new("unsigned char[]", 64)
76+
buflen = self._backend._ffi.new("size_t *", len(buf))
77+
res = self._backend._lib.EVP_DigestSign(
78+
evp_md_ctx, buf, buflen, data, len(data)
79+
)
80+
self._backend.openssl_assert(res == 1)
81+
self._backend.openssl_assert(buflen[0] == 64)
82+
return self._backend._ffi.buffer(buf, buflen[0])[:]

0 commit comments

Comments
 (0)