|
@@ -25,6 +25,7 @@ |
|
|
import os |
|
|
import os |
|
|
import unittest |
|
|
import unittest |
|
|
|
|
|
|
|
|
|
|
|
from binascii import a2b_hex |
|
|
from ctypes import Structure, POINTER, CFUNCTYPE, pointer, sizeof |
|
|
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 c_uint8, c_uint16, c_ssize_t, c_size_t, c_uint64, c_int |
|
|
from ctypes import CDLL |
|
|
from ctypes import CDLL |
|
@@ -149,6 +150,55 @@ def x25519_base(scalar, clamp): |
|
|
|
|
|
|
|
|
return bytes(out) |
|
|
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): |
|
|
def comms_process_wrap(state, input): |
|
|
'''A wrapper around comms_process that converts the argument |
|
|
'''A wrapper around comms_process that converts the argument |
|
|
into the buffer, and the returns the message as a bytes string. |
|
|
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() |
|
|
return outbuf._from() |
|
|
|
|
|
|
|
|
class TestX25519(unittest.TestCase): |
|
|
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() |
|
|
aprivkey = x25519_genkey() |
|
|
apubkey = x25519_base(aprivkey, 1) |
|
|
apubkey = x25519_base(aprivkey, 1) |
|
|
|
|
|
|
|
|