cryptography icon indicating copy to clipboard operation
cryptography copied to clipboard

DH exchange fails in 42.0.0

Open daveboutcher opened this issue 10 months ago • 6 comments

The following code works with cryptography 41.0.7 and earlier, and fails with 42.0.0 and later:

from cryptography.hazmat.primitives.serialization import load_pem_public_key

key = b"""-----BEGIN PUBLIC KEY-----
MIICJTCCARcGCSqGSIb3DQEDATCCAQgCggEBAP//////////yQ/aoiFowjTExmKL
gNwc0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVt
bVHCReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR
7ORbPcIAfLihY78FmNpINhxV05ppFj+o/STPX4NlXSPco62WHGLzViCFUrue1SkH
cJaWbWcMNU5KvJgE8XRsCMoYIXwykF5GLjbOO+OedywYDoYDmyeDouwHoo+1xV3w
b0xSyd4ry/aVWBcYOZVJfOqVauUV0iYYmPoFEBVyjlqKrKpo//////////8CAQID
ggEGAAKCAQEAoely6vSHw+/Q3zGYLaJj7eeQkfd25K8SvtC+FMY9D7jwS4g71pyr
U3FJ98Fi45Wdksh+d4u7U089trF5Xbgui29bZ0HcQZtfHEEz0Mh69tkipCm2/QIj
6eDlo6sPk9hhhvgg4MMGiWKhCtHrub3x1FHdmf7KjOhrGeb5apiudo7blGFzGhZ3
NFnbff+ArVNd+rdVmSoZn0aMhXRConlDu/44IYe5/24VLl7G+BzZlIZO4P2M83fd
mBOvR13cmYssQjEFTbaZVQvQHa3t0+aywfdCgsXGmTTK6QDCBP8D+vf1bmhEswzs
oYn1GLtJ3VyYyMBPDBomd2ctchZgTzsX1w==
-----END PUBLIC KEY-----"""

peer_key = load_pem_public_key(key)

params = peer_key.parameters()
private_key = params.generate_private_key()

# Create a shared secret
shared_secret = private_key.exchange(peer_key)

The exchange call fails with a cryptic in 42.0.0 and later

    shared_secret = private_key.exchange(peer_key)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: Error computing shared key.

If I swap out OpenSSL versions (e.g. 3.1.4 with cryptography 42.0.0) the results are the same, so the issue seems to be in the cryptography code.

I have tracked this down to evp_pkey_export_to_provider where, in 42.0.0 the key types are DHX and DH, whereas earlier they seem to be DH and DH. I suspect the issue is in the transition of load_pem_public_key to rust in 42.0.0.

Any insights appreciated.

daveboutcher avatar Apr 11 '24 10:04 daveboutcher

There is a partial workaround for this. Reconstruct the peer key as follows:

# Reconstruct the public key
peer_key = DHPublicNumbers(peer_key.public_numbers().y, peer_key.parameters().parameter_numbers()).public_key()

However in my case the server side is unhappy with the result

daveboutcher avatar Apr 12 '24 09:04 daveboutcher

This issue exists in keys generated by cryptography 41.0.7 and loaded into 42.0.0. If the following code is run on 41.0.7 to generate a key (to stdout)

from cryptography.hazmat.primitives.serialization import load_pem_parameters
from cryptography.hazmat.primitives import serialization

pem_params = b"""-----BEGIN DH PARAMETERS-----
MIIBCAKCAQEA///////////JD9qiIWjCNMTGYouA3BzRKQJOCIpnzHQCC76mOxOb
IlFKCHmONATd75UZs806QxswKwpt8l8UN0/hNW1tUcJF5IW1dmJefsb0TELppjft
awv/XLb0Brft7jhr+1qJn6WunyQRfEsf5kkoZlHs5Fs9wgB8uKFjvwWY2kg2HFXT
mmkWP6j9JM9fg2VdI9yjrZYcYvNWIIVSu57VKQdwlpZtZww1Tkq8mATxdGwIyhgh
fDKQXkYuNs474553LBgOhgObJ4Oi7Aeij7XFXfBvTFLJ3ivL9pVYFxg5lUl86pVq
5RXSJhiY+gUQFXKOWoqsqmj//////////wIBAg==
-----END DH PARAMETERS-----"""

params = load_pem_parameters(pem_params)
pk = params.generate_private_key()
k = pk.public_key()
x = k.public_bytes(encoding=serialization.Encoding.PEM,
                   format=serialization.PublicFormat.SubjectPublicKeyInfo)
print(x.decode())

And then the following is run on 42.0.0 to load the same key (from stdin), it will fail

import sys
from cryptography.hazmat.primitives.serialization import load_pem_public_key

public_key = load_pem_public_key(sys.stdin.read().encode())

params = public_key.parameters()

private_key = params.generate_private_key()

shared_key = private_key.exchange(public_key)

daveboutcher avatar Apr 21 '24 14:04 daveboutcher

This is definitely a bug, though how we should fix it is not immediately obvious to me.

On Sun, Apr 21, 2024, 10:24 AM Dave Boutcher @.***> wrote:

This issue exists in keys generated by cryptography 41.0.7 and loaded into 42.0.0. If the following code is run on 41.0.7 to generate a key (to stdout)

from cryptography.hazmat.primitives.serialization import load_pem_parameters from cryptography.hazmat.primitives import serialization

pem_params = b"""-----BEGIN DH PARAMETERS----- MIIBCAKCAQEA///////////JD9qiIWjCNMTGYouA3BzRKQJOCIpnzHQCC76mOxOb IlFKCHmONATd75UZs806QxswKwpt8l8UN0/hNW1tUcJF5IW1dmJefsb0TELppjft awv/XLb0Brft7jhr+1qJn6WunyQRfEsf5kkoZlHs5Fs9wgB8uKFjvwWY2kg2HFXT mmkWP6j9JM9fg2VdI9yjrZYcYvNWIIVSu57VKQdwlpZtZww1Tkq8mATxdGwIyhgh fDKQXkYuNs474553LBgOhgObJ4Oi7Aeij7XFXfBvTFLJ3ivL9pVYFxg5lUl86pVq 5RXSJhiY+gUQFXKOWoqsqmj//////////wIBAg== -----END DH PARAMETERS-----"""

params = load_pem_parameters(pem_params) pk = params.generate_private_key() k = pk.public_key() x = k.public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo) print(x.decode())

And then the following is run on 42.0.0 to load the same key (from stdin), it will fail

import sys from cryptography.hazmat.primitives.serialization import load_pem_public_key

public_key = load_pem_public_key(sys.stdin.read().encode())

params = public_key.parameters()

private_key = params.generate_private_key()

shared_key = private_key.exchange(public_key)

— Reply to this email directly, view it on GitHub https://github.com/pyca/cryptography/issues/10790#issuecomment-2068063349, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAAGBHU2GHNUNAQKHSM3MLY6PD2HAVCNFSM6AAAAABGCA2FF2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANRYGA3DGMZUHE . You are receiving this because you are subscribed to this thread.Message ID: @.***>

alex avatar Apr 21 '24 14:04 alex

PR proposed.....passes all the tests and Works For Me™

daveboutcher avatar Apr 21 '24 19:04 daveboutcher

@alex is there a process for requesting a review of a PR? Or an irc/discord/mailing list thats worth joining?

daveboutcher avatar Apr 26 '24 15:04 daveboutcher

Reviewing is on my TODO list, hopefully will have time this weekend.

On Fri, Apr 26, 2024 at 11:30 AM Dave Boutcher @.***> wrote:

@alex is there a process for requesting a review of a PR? Or an irc/discord/mailing list thats worth joining?

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

-- All that is necessary for evil to succeed is for good people to do nothing.

alex avatar Apr 26 '24 15:04 alex

The more I poke at this, the more I'm convinced this is an OpenSSL bug: https://github.com/openssl/openssl/issues/24804

alex avatar Jul 06 '24 14:07 alex