diff options
| author | eunhhu <[email protected]> | 2026-09-29 13:51:07 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-29 13:51:07 +0100 |
| commit | 19ebac6e3cd5e48e16a73c78342fa676dc905db9 (patch) | |
| tree | ff1ca1db6aa594094e676a0bfd7a4b22ec6a0748 /link | |
| parent | f5ce5e2b6ff0f72cc506e464d0ebfad00bed3532 (diff) | |
| download | pure-19ebac6e3cd5e48e16a73c78342fa676dc905db9.tar.gz pure-19ebac6e3cd5e48e16a73c78342fa676dc905db9.zip | |
Let links make an X25519 pair for once, and agree with it
An epoch's keys (docs/design/011-pure-transport.md): a pair made for the
next epoch, held raw (a link may hold one for long and a node many, so
not in OpenSSL's 16 KiB secure heap), and the secret its private half
shares with the other side's public half, never all zeros. The crypto
table's version is 5.
Diffstat (limited to 'link')
| -rw-r--r-- | link/crypto-openssl.c | 29 | ||||
| -rw-r--r-- | link/crypto.c | 10 | ||||
| -rw-r--r-- | link/crypto.h | 4 | ||||
| -rw-r--r-- | link/seal.h | 7 |
4 files changed, 48 insertions, 2 deletions
diff --git a/link/crypto-openssl.c b/link/crypto-openssl.c index 6bdf6fb..96daa82 100644 --- a/link/crypto-openssl.c +++ b/link/crypto-openssl.c @@ -207,6 +207,32 @@ static bool link_secret(const PureSealKey *own, const unsigned char peer[PURE_SE return ok && any; } +/* A pair made for once (X25519): an epoch's half, raw. */ +static bool link_ephemeral(unsigned char private_key[32], unsigned char public_key[PURE_SEAL_KEY]) { + EVP_PKEY *key = EVP_PKEY_Q_keygen(NULL, NULL, "X25519"); + size_t a = 32, b = PURE_SEAL_KEY; + bool ok = key && EVP_PKEY_get_raw_private_key(key, private_key, &a) == 1 && + EVP_PKEY_get_raw_public_key(key, public_key, &b) == 1 && a == 32 && b == PURE_SEAL_KEY; + EVP_PKEY_free(key); + return ok; +} + +/* The secret a raw private half shares with a peer's public key. */ +static bool link_agree(const unsigned char private_key[32], const unsigned char peer[PURE_SEAL_KEY], unsigned char out[32]) { + EVP_PKEY *mine = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, private_key, 32); + EVP_PKEY *theirs = EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, NULL, peer, PURE_SEAL_KEY); + EVP_PKEY_CTX *context = mine && theirs ? EVP_PKEY_CTX_new(mine, NULL) : NULL; + size_t length = 32; + bool ok = context && EVP_PKEY_derive_init(context) == 1 && EVP_PKEY_derive_set_peer(context, theirs) == 1 && + EVP_PKEY_derive(context, out, &length) == 1 && length == 32; + EVP_PKEY_CTX_free(context); + EVP_PKEY_free(mine); + EVP_PKEY_free(theirs); + unsigned char any = 0; /* a small-order key makes zeros: no secret */ + for (size_t i = 0; ok && i < 32; ++i) any |= out[i]; + return ok && any; +} + /* HKDF, fetched once. */ static EVP_KDF *hkdf(void) { static EVP_KDF *kept; @@ -281,4 +307,5 @@ static bool aead_open(PureAead *a, const unsigned char *key, const unsigned char const PureCrypto pure_crypto = {PURE_CRYPTO_VERSION, tls_start, tls_open, tls_read, tls_write, tls_close, seal_key, seal_key_free, sealer, seal, sealer_free, opener, unseal, opener_free, - link_secret, link_derive, aead, aead_seal, aead_open, aead_free}; + link_secret, link_ephemeral, link_agree, link_derive, aead, aead_seal, aead_open, + aead_free}; diff --git a/link/crypto.c b/link/crypto.c index 2fa7d44..c8fb9bd 100644 --- a/link/crypto.c +++ b/link/crypto.c @@ -141,6 +141,16 @@ bool pure_link_secret(const PureSealKey *own, const unsigned char peer[PURE_SEAL return c && own && c->link_secret(own, peer, out); } +bool pure_link_ephemeral(unsigned char private_key[32], unsigned char public_key[PURE_SEAL_KEY]) { + const PureCrypto *c = loaded(NULL, 0); + return c && c->link_ephemeral(private_key, public_key); +} + +bool pure_link_agree(const unsigned char private_key[32], const unsigned char peer[PURE_SEAL_KEY], unsigned char out[32]) { + const PureCrypto *c = loaded(NULL, 0); + return c && c->link_agree(private_key, peer, out); +} + bool pure_link_derive(const unsigned char secret[32], const unsigned char *info, size_t info_size, unsigned char *out, size_t out_size) { const PureCrypto *c = loaded(NULL, 0); diff --git a/link/crypto.h b/link/crypto.h index 53ac785..ede5fb9 100644 --- a/link/crypto.h +++ b/link/crypto.h @@ -8,7 +8,7 @@ * first time a server seals, opens a seal or connects to an https receiver * (link/crypto.c): one that does none of these maps no OpenSSL, 2.3 MiB * less resident and 0.6 MiB less of its own. */ -enum { PURE_CRYPTO_VERSION = 4 }; +enum { PURE_CRYPTO_VERSION = 5 }; typedef struct { unsigned version; @@ -35,6 +35,8 @@ typedef struct { void (*opener_free)(PureOpener *); /* Links, as link/seal.h says. */ bool (*link_secret)(const PureSealKey *, const unsigned char peer[PURE_SEAL_KEY], unsigned char out[32]); + bool (*link_ephemeral)(unsigned char private_key[32], unsigned char public_key[PURE_SEAL_KEY]); + bool (*link_agree)(const unsigned char private_key[32], const unsigned char peer[PURE_SEAL_KEY], unsigned char out[32]); bool (*link_derive)(const unsigned char secret[32], const unsigned char *info, size_t info_size, unsigned char *out, size_t out_size); PureAead *(*aead)(const unsigned char key[PURE_AEAD_KEY]); diff --git a/link/seal.h b/link/seal.h index 9245f25..de1e7a5 100644 --- a/link/seal.h +++ b/link/seal.h @@ -65,6 +65,13 @@ void pure_opener_free(PureOpener *); typedef struct PureAead PureAead; enum { PURE_AEAD_KEY = 16, PURE_AEAD_NONCE = 12, PURE_AEAD_TAG = 16 }; bool pure_link_secret(const PureSealKey *own, const unsigned char peer[PURE_SEAL_KEY], unsigned char out[32]); +/* An epoch's keys (link/link-protocol.h): a key pair made for once, raw (its + * holder clears the private half as soon as it has agreed: a link may hold + * one for long, and a node many, so they are not kept in the secure heap), + * and the secret it shares with another's public half (never all zeros). + * On a Raspberry Pi 5 the pair costs 57 µs and the secret 220. */ +bool pure_link_ephemeral(unsigned char private_key[32], unsigned char public_key[PURE_SEAL_KEY]); +bool pure_link_agree(const unsigned char private_key[32], const unsigned char peer[PURE_SEAL_KEY], unsigned char out[32]); bool pure_link_derive(const unsigned char secret[32], const unsigned char *info, size_t info_size, unsigned char *out, size_t out_size); PureAead *pure_aead(const unsigned char key[PURE_AEAD_KEY]); |
