| @@ -25,6 +25,7 @@ | |||
| import os | |||
| import unittest | |||
| from binascii import a2b_hex | |||
| from ctypes import Structure, POINTER, CFUNCTYPE, pointer, sizeof | |||
| from ctypes import c_uint8, c_uint16, c_ssize_t, c_size_t, c_uint64, c_int | |||
| from ctypes import CDLL | |||
| @@ -149,6 +150,55 @@ def x25519_base(scalar, clamp): | |||
| return bytes(out) | |||
| class X25519: | |||
| '''Class to wrap the x25519 functions into something a bit more | |||
| usable. This provides better key ingestion and better support | |||
| for other key formats. | |||
| Use either the gen method to generate a random key, or the frombytes | |||
| method. | |||
| a = X25519.gen() | |||
| b = X25519.gen() | |||
| a.dh(b.getpub()) == b.dh(a.getpub()) | |||
| That is, each party generates a key, sends their public part to the | |||
| other party, and then uses their received public part as an argument | |||
| to the dh method. The resulting value will be shared between the | |||
| two parties. | |||
| ''' | |||
| def __init__(self, key): | |||
| self.privkey = key | |||
| self.pubkey = x25519_base(key, 1) | |||
| def dh(self, pub): | |||
| '''Perform a DH operation using the public part pub.''' | |||
| return x25519_wrap(self.pubkey, self.privkey, pub, 1) | |||
| def getpub(self): | |||
| '''Get the public part of the key. This is to be sent | |||
| to the other party for key exchange.''' | |||
| return self.pubkey | |||
| def getpriv(self): | |||
| return self.privkey | |||
| @classmethod | |||
| def gen(cls): | |||
| '''Generate a random X25519 key.''' | |||
| return cls(x25519_genkey()) | |||
| @classmethod | |||
| def frombytes(cls, key): | |||
| '''Generate an X25519 key from 32 bytes.''' | |||
| return cls(key) | |||
| def comms_process_wrap(state, input): | |||
| '''A wrapper around comms_process that converts the argument | |||
| into the buffer, and the returns the message as a bytes string. | |||
| @@ -164,7 +214,39 @@ def comms_process_wrap(state, input): | |||
| return outbuf._from() | |||
| class TestX25519(unittest.TestCase): | |||
| def test_basic(self): | |||
| PUBLIC_BYTES = EC_PUBLIC_BYTES | |||
| PRIVATE_BYTES = EC_PRIVATE_BYTES | |||
| def test_class(self): | |||
| key = X25519.gen() | |||
| pubkey = key.getpub() | |||
| privkey = key.getpriv() | |||
| apubkey = x25519_base(privkey, 1) | |||
| self.assertEqual(apubkey, pubkey) | |||
| self.assertEqual(X25519.frombytes(privkey).getpub(), pubkey) | |||
| with self.assertRaises(ValueError): | |||
| X25519(b'0'*31) | |||
| def test_rfc7748_6_1(self): | |||
| # KAT from https://datatracker.ietf.org/doc/html/rfc7748#section-6.1 | |||
| apriv = a2b_hex('77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a') | |||
| akey = X25519(apriv) | |||
| self.assertEqual(akey.getpub(), a2b_hex('8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a')) | |||
| bpriv = a2b_hex('5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb') | |||
| bkey = X25519(bpriv) | |||
| self.assertEqual(bkey.getpub(), a2b_hex('de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f')) | |||
| ss = a2b_hex('4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742') | |||
| self.assertEqual(akey.dh(bkey.getpub()), ss) | |||
| self.assertEqual(bkey.dh(akey.getpub()), ss) | |||
| def test_basic_ops(self): | |||
| aprivkey = x25519_genkey() | |||
| apubkey = x25519_base(aprivkey, 1) | |||