Skip to content

Commit

Permalink
support for ECDH encryption using Curve25519 (cv25519)
Browse files Browse the repository at this point in the history
This increases the minimum versioned dependency on the cryptography
module to 2.5, where the necessary features are exposed.

We also add a mixed RSA + ECDH OepnPGP certificate for testing
purposes.

Signed-off-by: Daniel Kahn Gillmor <[email protected]>
  • Loading branch information
rot42 authored and dkg committed Aug 1, 2019
1 parent 8cc340d commit 53c6c3b
Show file tree
Hide file tree
Showing 12 changed files with 170 additions and 27 deletions.
4 changes: 4 additions & 0 deletions pgpy/_curves.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ def _openssl_get_supported_curves():

# store the result so we don't have to do all of this every time
curves = { b.ffi.string(b.lib.OBJ_nid2sn(c.nid)).decode('utf-8') for c in cs }
# X25519 is always present in cryptography>=2.5
# The python cryptography lib provides a different interface for this curve,
# so it is handled differently in the ECDHPriv/ECDHPub classes
curves |= {'X25519'}
_openssl_get_supported_curves._curves = curves
return curves

Expand Down
3 changes: 0 additions & 3 deletions pgpy/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,6 @@ class EllipticCurveOID(Enum):
# id = (oid, curve)
Invalid = ('', )
#: DJB's fast elliptic curve
#:
#: .. warning::
#: This curve is not currently usable by PGPy
Curve25519 = ('1.3.6.1.4.1.3029.1.5.1', X25519)
#: Twisted Edwards variant of Curve25519
#:
Expand Down
72 changes: 58 additions & 14 deletions pgpy/packet/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@
from cryptography.hazmat.backends import default_backend

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization

from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.asymmetric import dsa
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.asymmetric import x25519

from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash

Expand Down Expand Up @@ -581,7 +583,10 @@ def __len__(self):
return len(self.p) + len(self.kdf) + len(encoder.encode(self.oid.value)) - 1

def __pubkey__(self):
return ec.EllipticCurvePublicNumbers(self.p.x, self.p.y, self.oid.curve()).public_key(default_backend())
if self.oid == EllipticCurveOID.Curve25519:
return x25519.X25519PublicKey.from_public_bytes(self.p.x)
else:
return ec.EllipticCurvePublicNumbers(self.p.x, self.p.y, self.oid.curve()).public_key(default_backend())

def __bytearray__(self):
_b = bytearray()
Expand Down Expand Up @@ -636,8 +641,11 @@ def parse(self, packet):
del packet[:oidlen]

self.p = ECPoint(packet)
if self.p.format != ECPointFormat.Standard:
raise PGPIncompatibleECPointFormat("Only curves using Standard format are currently supported for ECDH")
if self.oid == EllipticCurveOID.Curve25519:
if self.p.format != ECPointFormat.Native:
raise PGPIncompatibleECPointFormat("Only Native format is valid for Curve25519")
elif self.p.format != ECPointFormat.Standard:
raise PGPIncompatibleECPointFormat("Only Standard format is valid for this curve")
self.kdf.parse(packet)


Expand Down Expand Up @@ -1446,8 +1454,32 @@ def __len__(self):
l += sum(len(getattr(self, i)) for i in self.__privfields__)
return l

def __privkey__(self):
if self.oid == EllipticCurveOID.Curve25519:
# NOTE: openssl and GPG don't use the same endianness for Curve25519 secret value
s = self.int_to_bytes(self.s, (self.oid.key_size + 7) // 8, 'little')
return x25519.X25519PrivateKey.from_private_bytes(s)
else:
return ECDSAPriv.__privkey__(self)

def _generate(self, oid):
ECDSAPriv._generate(self, oid)
_oid = EllipticCurveOID(oid)
if _oid == EllipticCurveOID.Curve25519:
if any(c != 0 for c in self): # pragma: no cover
raise PGPError("Key is already populated!")
self.oid = _oid
pk = x25519.X25519PrivateKey.generate()
x = pk.public_key().public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw)
self.p = ECPoint.from_values(self.oid.key_size, ECPointFormat.Native, x)
# NOTE: openssl and GPG don't use the same endianness for Curve25519 secret value
self.s = MPI(self.bytes_to_int(pk.private_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PrivateFormat.Raw,
encryption_algorithm=serialization.NoEncryption()
), 'little'))
self._compute_chksum()
else:
ECDSAPriv._generate(self, oid)
self.kdf.halg = self.oid.kdf_halg
self.kdf.encalg = self.oid.kek_alg

Expand All @@ -1467,6 +1499,9 @@ def parse(self, packet):
##TODO: this needs to be bounded to the length of the encrypted key material
self.encbytes = packet

def sign(self, sigdata, hash_alg):
raise PGPError("Cannot sign with an ECDH key")


class CipherText(MPIs):
def __init__(self):
Expand Down Expand Up @@ -1562,11 +1597,17 @@ def encrypt(cls, pk, *args):

# generate ephemeral key pair and keep public key in ct
# use private key to compute the shared point "s"
v = ec.generate_private_key(km.oid.curve(), default_backend())
x = MPI(v.public_key().public_numbers().x)
y = MPI(v.public_key().public_numbers().y)
ct.p = ECPoint.from_values(km.oid.key_size, ECPointFormat.Standard, x, y)
s = v.exchange(ec.ECDH(), km.__pubkey__())
if km.oid == EllipticCurveOID.Curve25519:
v = x25519.X25519PrivateKey.generate()
x = v.public_key().public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw)
ct.p = ECPoint.from_values(km.oid.key_size, ECPointFormat.Native, x)
s = v.exchange(km.__pubkey__())
else:
v = ec.generate_private_key(km.oid.curve(), default_backend())
x = MPI(v.public_key().public_numbers().x)
y = MPI(v.public_key().public_numbers().y)
ct.p = ECPoint.from_values(km.oid.key_size, ECPointFormat.Standard, x, y)
s = v.exchange(ec.ECDH(), km.__pubkey__())

# derive the wrapping key
z = km.kdf.derive_key(s, km.oid, PubKeyAlgorithm.ECDH, pk.fingerprint)
Expand All @@ -1578,11 +1619,14 @@ def encrypt(cls, pk, *args):

def decrypt(self, pk, *args):
km = pk.keymaterial
# assemble the public component of ephemeral key v
v = ec.EllipticCurvePublicNumbers(self.p.x, self.p.y, km.oid.curve()).public_key(default_backend())

# compute s using the inverse of how it was derived during encryption
s = km.__privkey__().exchange(ec.ECDH(), v)
if km.oid == EllipticCurveOID.Curve25519:
v = x25519.X25519PublicKey.from_public_bytes(self.p.x)
s = km.__privkey__().exchange(v)
else:
# assemble the public component of ephemeral key v
v = ec.EllipticCurvePublicNumbers(self.p.x, self.p.y, km.oid.curve()).public_key(default_backend())
# compute s using the inverse of how it was derived during encryption
s = km.__privkey__().exchange(ec.ECDH(), v)

# derive the wrapping key
z = km.kdf.derive_key(s, km.oid, PubKeyAlgorithm.ECDH, pk.fingerprint)
Expand Down
6 changes: 5 additions & 1 deletion pgpy/pgp.py
Original file line number Diff line number Diff line change
Expand Up @@ -1326,7 +1326,11 @@ def key_size(self):
"""
if self.key_algorithm in {PubKeyAlgorithm.ECDSA, PubKeyAlgorithm.ECDH}:
return self._key.keymaterial.oid
return next(iter(self._key.keymaterial)).bit_length()
# check if keymaterial is not an Opaque class containing a bytearray
param = next(iter(self._key.keymaterial))
if isinstance(param, bytearray):
return 0
return param.bit_length()

@property
def magic(self):
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
cryptography>=1.5
cryptography>=2.5
enum34
pyasn1
six>=1.9.0
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@


_requires = [
'cryptography>=1.5',
'cryptography>=2.5',
'pyasn1',
'six>=1.9.0',
]
Expand Down
11 changes: 6 additions & 5 deletions tests/test_05_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,8 @@ def userphoto():
(PubKeyAlgorithm.DSA, 1024),
(PubKeyAlgorithm.ElGamal, 1024),
(PubKeyAlgorithm.ECDSA, EllipticCurveOID.SECP256K1),
(PubKeyAlgorithm.ECDH, EllipticCurveOID.Brainpool_P256),)
(PubKeyAlgorithm.ECDH, EllipticCurveOID.Brainpool_P256),
(PubKeyAlgorithm.ECDH, EllipticCurveOID.Curve25519),)


class TestPGPKey_Management(object):
Expand Down Expand Up @@ -712,14 +713,14 @@ def test_sign_timestamp(self, sec):
sig = sec.sign(None)
assert sig.type == SignatureType.Timestamp

self.sigs[(sec.key_algorithm, 'timestamp')] = sig
self.sigs[(sec._key.fingerprint.keyid, 'timestamp')] = sig

@pytest.mark.run(after='test_sign_timestamp')
@pytest.mark.parametrize('pub', pubkeys,
ids=[os.path.basename(f) for f in sorted(glob.glob('tests/testdata/keys/*.pub.asc'))])
def test_verify_timestamp(self, pub):
# test verifying a timestamp signature
sig = self.sigs[(pub.key_algorithm, 'timestamp')]
sig = self.sigs[(pub._key.fingerprint.keyid, 'timestamp')]
sv = pub.verify(None, sig)

assert sv
Expand All @@ -733,14 +734,14 @@ def test_sign_standalone(self, sec):

assert sig.type == SignatureType.Standalone
assert sig.notation == {"cheese status": "standing alone"}
self.sigs[(sec.key_algorithm, 'standalone')] = sig
self.sigs[(sec._key.fingerprint.keyid, 'standalone')] = sig

@pytest.mark.run(after='test_sign_standalone')
@pytest.mark.parametrize('pub', pubkeys,
ids=[os.path.basename(f) for f in sorted(glob.glob('tests/testdata/keys/*.pub.asc'))])
def test_verify_standalone(self, pub):
# test verifying a standalone signature
sig = self.sigs[(pub.key_algorithm, 'standalone')]
sig = self.sigs[(pub._key.fingerprint.keyid, 'standalone')]
sv = pub.verify(None, sig)

assert sv
Expand Down
6 changes: 5 additions & 1 deletion tests/test_99_regressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,13 +195,17 @@ def test_reg_bug_157(monkeypatch):
warnings.warn("tuned_count: {}; elapsed time: {:.5f}".format(pgpy.constants.HashAlgorithm.SHA256.tuned_count, elapsed))


_seckeys = {sk.key_algorithm.name: sk for sk in (PGPKey.from_file(f)[0] for f in sorted(glob.glob('tests/testdata/keys/*.sec.asc')))}
# load mixed keys separately so they do not overwrite "single algo" keys in the _seckeys mapping
_seckeys = {sk.key_algorithm.name: sk for sk in (PGPKey.from_file(f)[0] for f in sorted(glob.glob('tests/testdata/keys/*.sec.asc')) if 'keys/mixed' not in f)}
_mixed1 = PGPKey.from_file('tests/testdata/keys/mixed.1.sec.asc')[0]
seckm = [
_seckeys['DSA']._key, # DSA private key packet
_seckeys['DSA'].subkeys['1FD6D5D4DA0170C4']._key, # ElGamal private key packet
_seckeys['RSAEncryptOrSign']._key, # RSA private key packet
_seckeys['ECDSA']._key, # ECDSA private key packet
_seckeys['ECDSA'].subkeys['A81B93FD16BD9806']._key, # ECDH private key packet
_mixed1._key, # RSA private key packet
_mixed1.subkeys['B345506C90A428C5']._key, # ECDH Curve25519 private key packet
]


Expand Down
34 changes: 34 additions & 0 deletions tests/testdata/keys/mixed.1.pub.asc
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQGNBF0yIqUBDADraq8m02SBtv+4ZdsWSIQw2X1xpl3QlX9ww06lfiFWMwtSLCQe
89QIzu88QzcV/YAAEjRo9NMapyqCVwlRdaGQ5kGb1m8/tAbekIqFHsmqtTdlicsy
U+XDxQHMe/lGK1wFGoHkB1dZHUBlk6hqKwuMwyp+zKsnAJPbuiwD4fjNJYjn+dwL
mrvSPgGOhYjvhCSTZMWBMWdNHgwSaQQI/mhHyuz20urux03JPxJbagFjsdyDxXKX
8YGPEBvn36N/GmanV+LvmspDjrYRJNooVeZrhEyrPxm9VnXoGxaZXmjiqrguAtJL
f2w6JALeDDnNPGizMoazCwkU+BHBIEAwikPHj02BbAjW9BJF8+HRSUm0lorJmRVA
V5cWaPNh8LlLH662ZFBZAeZr4XEucfdqvm486nXcEFlzj+SJlvRkesAM3h8VeFn3
8OhZW1bdsD3Bsbgm8q56CDstmCWBjMv/4hEw6Mp3v2UIPneDqnk50cH+8J49Ag+h
g6uWi9x0/6+BCk0AEQEAAbQxTWl4ZWQgdm9uIFRlc3RLZXkgKFJTQStjdjI1NTE5
KSA8bWl4ZWQxQHRlc3Qua2V5PokBzgQTAQoAOBYhBDaIlc8KQi6uc28xiP+7NPsV
uTriBQJdMiKlAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEP+7NPsVuTri
sQIMAMHgq65TZnEibo+G6ECh70PMjgFpGRfpd95STO/UOeiP5rVmi4QpnocfUtZw
q9YEO8f6KUe3ijoHkTRHM3HA4ev3HWCgIC+RpKu4y39EDf/NpZ0JwFI1Th+f1gRl
UIPj3cYGrwgNqc1Xbk5pfRtjFXnGUpFJgwtt6I3ENliABQ2l4QT7J8l800AOSGoq
EMsqRYCBCk2DgZGezh1mS66wSa7NR+Jcf1jaRUCc5s2IEVkFdTxxmF6y6e9erAue
T3lSe6jQjk5DtbZv9rYCplJvXPjJT3V1SP7BKdneuOyydvuprA3r4Q4UVpDArTLa
/MnRwV6oVkKQ+mJopZ/wgcF1TNDoKfBcUlqQr91/PqR7jxF+GwDZqwizm9/T0Qzt
QmRtXzYZVsTeYirvUQkw8+mOAfGLvhUVVgV6EhUvImiMTrQLP6N9uTki7COQ25kf
HEHSypiEG2Sect7D9HZ0XS2aM6MYutUyorBawH4APT5Exll68Mm57H+OBPDp8Veu
Kzt6vrg4BF0yIs0SCisGAQQBl1UBBQEBB0Ah/2wCa25j9+Ptd9ofzC8d7Less3MU
Dn2AFne1OD6WMwMBCAeJAbYEGAEKACAWIQQ2iJXPCkIurnNvMYj/uzT7Fbk64gUC
XTIizQIbDAAKCRD/uzT7Fbk64sAzDADSYLERof681Su/8ayA4PF8pWT8vN6A3WXA
G1HK8m3ue4imEVYCWng9+tCVpa5rzB3cfZnVK/xGD4Rnmut0IAwBbfNZF8JNUzZj
Lp9s+ij3PloaxpgapiCdRE8C3C34FuHnAoSjE5ubt89aGcNXIjLjh+WjcTjGyTcH
FiQMieUGpjGunOCcCukIWnltzhPAI4nRZH2gms4TmUi/G27eDU8KoCIaKjXMnGhI
sQFMAYdMyFM4SL8gSc0jt1iUG0EYXksqw/O+uXNzbHB25mGce8GmH6VdV/4Q4MO5
FBBq0ECRHXp6zP/nh5E4/Ge9bVzOnmMBhC2i7sekrRWfeFXXxYKN4zQLynenGmAL
8O9ja0nIAZDF1eB9kDIdltg3XlBtv0NCrgJNx/vlKp/LLcnCi0Ig79NCcLjd18uo
sioTrCB61kH99Gczj5wFluyodvvohZD0jymOTWzTbYnv99Ry7jnHLD8s3xBZ7GQ0
LNugOy1CqXQUniQjTIIhGUHrfqVP75w=
=JuLK
-----END PGP PUBLIC KEY BLOCK-----
55 changes: 55 additions & 0 deletions tests/testdata/keys/mixed.1.sec.asc
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----

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
LNugOy1CqXQUniQjTIIhGUHrfqVP75w=
=6x9u
-----END PGP PRIVATE KEY BLOCK-----
Binary file added tests/testdata/packets/07.v4.cv25519.privsubkey
Binary file not shown.
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ max-line-length = 160
[testenv]
passenv = HOME ARCHFLAGS LDFLAGS CFLAGS INCLUDE LIB LD_LIBRARY_PATH PATH
deps =
cryptography>=1.5
cryptography>=2.5
enum34
gpg==1.8.0
pyasn1
Expand Down

0 comments on commit 53c6c3b

Please sign in to comment.