import os
import shutil
import subprocess
import pytest
from binascii import hexlify, unhexlify
from .curves import NIST192p, NIST224p, NIST256p, NIST384p, NIST521p
from .curves import curves
from .ecdh import ECDH, InvalidCurveError, InvalidSharedSecretError, NoKeyError
from .keys import SigningKey, VerifyingKey
@pytest.mark.parametrize(
"vcurve", curves, ids=[curve.name for curve in curves]
)
def test_ecdh_each(vcurve):
ecdh1 = ECDH(curve=vcurve)
ecdh2 = ECDH(curve=vcurve)
ecdh2.generate_private_key()
ecdh1.load_received_public_key(ecdh2.get_public_key())
ecdh2.load_received_public_key(ecdh1.generate_private_key())
secret1 = ecdh1.generate_sharedsecret_bytes()
secret2 = ecdh2.generate_sharedsecret_bytes()
assert secret1 == secret2
def test_ecdh_no_public_key():
ecdh1 = ECDH(curve=NIST192p)
with pytest.raises(NoKeyError):
ecdh1.generate_sharedsecret_bytes()
ecdh1.generate_private_key()
with pytest.raises(NoKeyError):
ecdh1.generate_sharedsecret_bytes()
def test_ecdh_wrong_public_key_curve():
ecdh1 = ECDH(curve=NIST192p)
ecdh1.generate_private_key()
ecdh2 = ECDH(curve=NIST256p)
ecdh2.generate_private_key()
with pytest.raises(InvalidCurveError):
ecdh1.load_received_public_key(ecdh2.get_public_key())
with pytest.raises(InvalidCurveError):
ecdh2.load_received_public_key(ecdh1.get_public_key())
ecdh1.public_key = ecdh2.get_public_key()
ecdh2.public_key = ecdh1.get_public_key()
with pytest.raises(InvalidCurveError):
ecdh1.generate_sharedsecret_bytes()
with pytest.raises(InvalidCurveError):
ecdh2.generate_sharedsecret_bytes()
def test_ecdh_invalid_shared_secret_curve():
ecdh1 = ECDH(curve=NIST256p)
ecdh1.generate_private_key()
ecdh1.load_received_public_key(
SigningKey.generate(NIST256p).get_verifying_key()
)
ecdh1.private_key.privkey.secret_multiplier = ecdh1.private_key.curve.order
with pytest.raises(InvalidSharedSecretError):
ecdh1.generate_sharedsecret_bytes()
# https://github.com/scogliani/ecc-test-vectors/blob/master/ecdh_kat/secp192r1.txt
# https://github.com/scogliani/ecc-test-vectors/blob/master/ecdh_kat/secp256r1.txt
# https://github.com/coruus/nist-testvectors/blob/master/csrc.nist.gov/groups/STM/cavp/documents/components/ecccdhtestvectors/KAS_ECC_CDH_PrimitiveTest.txt
@pytest.mark.parametrize(
"curve,privatekey,pubkey,secret",
[
pytest.param(
NIST192p,
"f17d3fea367b74d340851ca4270dcb24c271f445bed9d527",
"42ea6dd9969dd2a61fea1aac7f8e98edcc896c6e55857cc0"
"dfbe5d7c61fac88b11811bde328e8a0d12bf01a9d204b523",
"803d8ab2e5b6e6fca715737c3a82f7ce3c783124f6d51cd0",
id="NIST192p-1",
),
pytest.param(
NIST192p,
"56e853349d96fe4c442448dacb7cf92bb7a95dcf574a9bd5",
"deb5712fa027ac8d2f22c455ccb73a91e17b6512b5e030e7"
"7e2690a02cc9b28708431a29fb54b87b1f0c14e011ac2125",
"c208847568b98835d7312cef1f97f7aa298283152313c29d",
id="NIST192p-2",
),
pytest.param(
NIST192p,
"c6ef61fe12e80bf56f2d3f7d0bb757394519906d55500949",
"4edaa8efc5a0f40f843663ec5815e7762dddc008e663c20f"
"0a9f8dc67a3e60ef6d64b522185d03df1fc0adfd42478279",
"87229107047a3b611920d6e3b2c0c89bea4f49412260b8dd",
id="NIST192p-3",
),
pytest.param(
NIST192p,
"e6747b9c23ba7044f38ff7e62c35e4038920f5a0163d3cda",
"8887c276edeed3e9e866b46d58d895c73fbd80b63e382e88"
"04c5097ba6645e16206cfb70f7052655947dd44a17f1f9d5",
"eec0bed8fc55e1feddc82158fd6dc0d48a4d796aaf47d46c",
id="NIST192p-4",
),
pytest.param(
NIST192p,
"beabedd0154a1afcfc85d52181c10f5eb47adc51f655047d",
"0d045f30254adc1fcefa8a5b1f31bf4e739dd327cd18d594"
"542c314e41427c08278a08ce8d7305f3b5b849c72d8aff73",
"716e743b1b37a2cd8479f0a3d5a74c10ba2599be18d7e2f4",
id="NIST192p-5",
),
pytest.param(
NIST192p,
"cf70354226667321d6e2baf40999e2fd74c7a0f793fa8699",
"fb35ca20d2e96665c51b98e8f6eb3d79113508d8bccd4516"
"368eec0d5bfb847721df6aaff0e5d48c444f74bf9cd8a5a7",
"f67053b934459985a315cb017bf0302891798d45d0e19508",
id="NIST192p-6",
),
pytest.param(
NIST224p,
"8346a60fc6f293ca5a0d2af68ba71d1dd389e5e40837942df3e43cbd",
"af33cd0629bc7e996320a3f40368f74de8704fa37b8fab69abaae280"
"882092ccbba7930f419a8a4f9bb16978bbc3838729992559a6f2e2d7",
"7d96f9a3bd3c05cf5cc37feb8b9d5209d5c2597464dec3e9983743e8",
id="NIST224p",
),
pytest.param(
NIST256p,
"7d7dc5f71eb29ddaf80d6214632eeae03d9058af1fb6d22ed80badb62bc1a534",
"700c48f77f56584c5cc632ca65640db91b6bacce3a4df6b42ce7cc838833d287"
"db71e509e3fd9b060ddb20ba5c51dcc5948d46fbf640dfe0441782cab85fa4ac",
"46fc62106420ff012e54a434fbdd2d25ccc5852060561e68040dd7778997bd7b",
id="NIST256p-1",
),
pytest.param(
NIST256p,
"38f65d6dce47676044d58ce5139582d568f64bb16098d179dbab07741dd5caf5",
"809f04289c64348c01515eb03d5ce7ac1a8cb9498f5caa50197e58d43a86a7ae"
"b29d84e811197f25eba8f5194092cb6ff440e26d4421011372461f579271cda3",
"057d636096cb80b67a8c038c890e887d1adfa4195e9b3ce241c8a778c59cda67",
id="NIST256p-2",
),
pytest.param(
NIST256p,
"1accfaf1b97712b85a6f54b148985a1bdc4c9bec0bd258cad4b3d603f49f32c8",
"a2339c12d4a03c33546de533268b4ad667debf458b464d77443636440ee7fec3"
"ef48a3ab26e20220bcda2c1851076839dae88eae962869a497bf73cb66faf536",
"2d457b78b4614132477618a5b077965ec90730a8c81a1c75d6d4ec68005d67ec",
id="NIST256p-3",
),
pytest.param(
NIST256p,
"207c43a79bfee03db6f4b944f53d2fb76cc49ef1c9c4d34d51b6c65c4db6932d",
"df3989b9fa55495719b3cf46dccd28b5153f7808191dd518eff0c3cff2b705ed"
"422294ff46003429d739a33206c8752552c8ba54a270defc06e221e0feaf6ac4",
"96441259534b80f6aee3d287a6bb17b5094dd4277d9e294f8fe73e48bf2a0024",
id="NIST256p-4",
),
pytest.param(
NIST256p,
"59137e38152350b195c9718d39673d519838055ad908dd4757152fd8255c09bf",
"41192d2813e79561e6a1d6f53c8bc1a433a199c835e141b05a74a97b0faeb922"
"1af98cc45e98a7e041b01cf35f462b7562281351c8ebf3ffa02e33a0722a1328",
"19d44c8d63e8e8dd12c22a87b8cd4ece27acdde04dbf47f7f27537a6999a8e62",
id="NIST256p-5",
),
pytest.param(
NIST256p,
"f5f8e0174610a661277979b58ce5c90fee6c9b3bb346a90a7196255e40b132ef",
"33e82092a0f1fb38f5649d5867fba28b503172b7035574bf8e5b7100a3052792"
"f2cf6b601e0a05945e335550bf648d782f46186c772c0f20d3cd0d6b8ca14b2f",
"664e45d5bba4ac931cd65d52017e4be9b19a515f669bea4703542a2c525cd3d3",
id="NIST256p-6",
),
pytest.param(
NIST384p,
"3cc3122a68f0d95027ad38c067916ba0eb8c38894d22e1b1"
"5618b6818a661774ad463b205da88cf699ab4d43c9cf98a1",
"a7c76b970c3b5fe8b05d2838ae04ab47697b9eaf52e76459"
"2efda27fe7513272734466b400091adbf2d68c58e0c50066"
"ac68f19f2e1cb879aed43a9969b91a0839c4c38a49749b66"
"1efedf243451915ed0905a32b060992b468c64766fc8437a",
"5f9d29dc5e31a163060356213669c8ce132e22f57c9a04f4"
"0ba7fcead493b457e5621e766c40a2e3d4d6a04b25e533f1",
id="NIST384p",
),
pytest.param(
NIST521p,
"017eecc07ab4b329068fba65e56a1f8890aa935e57134ae0ffcce802735151f4ea"
"c6564f6ee9974c5e6887a1fefee5743ae2241bfeb95d5ce31ddcb6f9edb4d6fc47",
"00685a48e86c79f0f0875f7bc18d25eb5fc8c0b07e5da4f4370f3a949034085433"
"4b1e1b87fa395464c60626124a4e70d0f785601d37c09870ebf176666877a2046d"
"01ba52c56fc8776d9e8f5db4f0cc27636d0b741bbe05400697942e80b739884a83"
"bde99e0f6716939e632bc8986fa18dccd443a348b6c3e522497955a4f3c302f676",
"005fc70477c3e63bc3954bd0df3ea0d1f41ee21746ed95fc5e1fdf90930d5e1366"
"72d72cc770742d1711c3c3a4c334a0ad9759436a4d3c5bf6e74b9578fac148c831",
id="NIST521p",
),
],
)
def test_ecdh_NIST(curve, privatekey, pubkey, secret):
ecdh = ECDH(curve=curve)
ecdh.load_private_key_bytes(unhexlify(privatekey))
ecdh.load_received_public_key_bytes(unhexlify(pubkey))
sharedsecret = ecdh.generate_sharedsecret_bytes()
assert sharedsecret == unhexlify(secret)
pem_local_private_key = (
"-----BEGIN EC PRIVATE KEY-----\n"
"MF8CAQEEGF7IQgvW75JSqULpiQQ8op9WH6Uldw6xxaAKBggqhkjOPQMBAaE0AzIA\n"
"BLiBd9CE7xf15FY5QIAoNg+fWbSk1yZOYtoGUdzkejWkxbRc9RWTQjqLVXucIJnz\n"
"bA==\n"
"-----END EC PRIVATE KEY-----\n"
)
der_local_private_key = (
"305f02010104185ec8420bd6ef9252a942e989043ca29f561fa525770eb1c5a00a06082a864"
"8ce3d030101a13403320004b88177d084ef17f5e45639408028360f9f59b4a4d7264e62da06"
"51dce47a35a4c5b45cf51593423a8b557b9c2099f36c"
)
pem_remote_public_key = (
"-----BEGIN PUBLIC KEY-----\n"
"MEkwEwYHKoZIzj0CAQYIKoZIzj0DAQEDMgAEuIF30ITvF/XkVjlAgCg2D59ZtKTX\n"
"Jk5i2gZR3OR6NaTFtFz1FZNCOotVe5wgmfNs\n"
"-----END PUBLIC KEY-----\n"
)
der_remote_public_key = (
"3049301306072a8648ce3d020106082a8648ce3d03010103320004b88177d084ef17f5e4563"
"9408028360f9f59b4a4d7264e62da0651dce47a35a4c5b45cf51593423a8b557b9c2099f36c"
)
gshared_secret = "8f457e34982478d1c34b9cd2d0c15911b72dd60d869e2cea"
def test_ecdh_pem():
ecdh = ECDH()
ecdh.load_private_key_pem(pem_local_private_key)
ecdh.load_received_public_key_pem(pem_remote_public_key)
sharedsecret = ecdh.generate_sharedsecret_bytes()
assert sharedsecret == unhexlify(gshared_secret)
def test_ecdh_der():
ecdh = ECDH()
ecdh.load_private_key_der(unhexlify(der_local_private_key))
ecdh.load_received_public_key_der(unhexlify(der_remote_public_key))
sharedsecret = ecdh.generate_sharedsecret_bytes()
assert sharedsecret == unhexlify(gshared_secret)
# Exception classes used by run_openssl.
class RunOpenSslError(Exception):
pass
def run_openssl(cmd):
OPENSSL = "openssl"
p = subprocess.Popen(
[OPENSSL] + cmd.split(),
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
stdout, ignored = p.communicate()
if p.returncode != 0:
raise RunOpenSslError(
"cmd '%s %s' failed: rc=%s, stdout/err was %s"
% (OPENSSL, cmd, p.returncode, stdout)
)
return stdout.decode()
OPENSSL_SUPPORTED_CURVES = set(
c.split(":")[0].strip()
for c in run_openssl("ecparam -list_curves").split("\n")
)
@pytest.mark.parametrize(
"vcurve", curves, ids=[curve.name for curve in curves]
)
def test_ecdh_with_openssl(vcurve):
assert vcurve.openssl_name
if vcurve.openssl_name not in OPENSSL_SUPPORTED_CURVES:
pytest.skip("system openssl does not support " + vcurve.openssl_name)
return
try:
hlp = run_openssl("pkeyutl -help")
if hlp.find("-derive") == 0:
pytest.skip("system openssl does not support `pkeyutl -derive`")
return
except RunOpenSslError:
pytest.skip("system openssl does not support `pkeyutl -derive`")
return
if os.path.isdir("t"):
shutil.rmtree("t")
os.mkdir("t")
run_openssl(
"ecparam -name %s -genkey -out t/privkey1.pem" % vcurve.openssl_name
)
run_openssl(
"ecparam -name %s -genkey -out t/privkey2.pem" % vcurve.openssl_name
)
run_openssl("ec -in t/privkey1.pem -pubout -out t/pubkey1.pem")
ecdh1 = ECDH(curve=vcurve)
ecdh2 = ECDH(curve=vcurve)
with open("t/privkey1.pem") as e:
key = e.read()
ecdh1.load_private_key_pem(key)
with open("t/privkey2.pem") as e:
key = e.read()
ecdh2.load_private_key_pem(key)
with open("t/pubkey1.pem") as e:
key = e.read()
vk1 = VerifyingKey.from_pem(key)
assert vk1.to_string() == ecdh1.get_public_key().to_string()
vk2 = ecdh2.get_public_key()
with open("t/pubkey2.pem", "wb") as e:
e.write(vk2.to_pem())
ecdh1.load_received_public_key(vk2)
ecdh2.load_received_public_key(vk1)
secret1 = ecdh1.generate_sharedsecret_bytes()
secret2 = ecdh2.generate_sharedsecret_bytes()
assert secret1 == secret2
try:
run_openssl(
"pkeyutl -derive -inkey t/privkey1.pem -peerkey t/pubkey2.pem -out t/secret1"
)
run_openssl(
"pkeyutl -derive -inkey t/privkey2.pem -peerkey t/pubkey1.pem -out t/secret2"
)
except RunOpenSslError:
pytest.skip("system openssl does not support `pkeyutl -derive`")
return
with open("t/secret1", "rb") as e:
ssl_secret1 = e.read()
with open("t/secret1", "rb") as e:
ssl_secret2 = e.read()
if len(ssl_secret1) != vk1.curve.baselen:
pytest.skip("system openssl does not support `pkeyutl -derive`")
return
assert ssl_secret1 == ssl_secret2
assert secret1 == ssl_secret1