/* Interop spike: X25519 + HKDF-SHA256 + Ed25519 with fixed inputs (C, libsodium >= 1.0.19). * This is the API the ESP32 build would use. Prints the same key=value lines as the Go, C * (OpenSSL) and C++ programs so the outputs can be diffed. */ #include #include #include #include #include #define BOARD_PRIV "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" #define BACKEND_EPH "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f" #define SIGNING_SEED "404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f" #define NONCE "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" #define CLIENT_ID "e1c9f0a2b3d4c5b6a79881726354453f" #define FN "FN-000042" #define SN "SN000123" static void die(const char *msg) { fprintf(stderr, "FAIL: %s\n", msg); exit(1); } static size_t unhex(const char *h, uint8_t *out) { size_t n = strlen(h) / 2; for (size_t i = 0; i < n; i++) { unsigned v; if (sscanf(h + 2 * i, "%2x", &v) != 1) die("hex"); out[i] = (uint8_t)v; } return n; } static void tohex(const uint8_t *b, size_t n, char *out) { for (size_t i = 0; i < n; i++) sprintf(out + 2 * i, "%02x", b[i]); out[2 * n] = 0; } static void phex(const char *label, const uint8_t *b, size_t n) { char *s = malloc(2 * n + 1); tohex(b, n, s); printf("%s=%s\n", label, s); free(s); } static void expect(const char *name, const uint8_t *got, size_t n, const char *want) { char *s = malloc(2 * n + 1); tohex(got, n, s); if (strcmp(s, want) != 0) { fprintf(stderr, "FAIL: %s\n got %s\n want %s\n", name, s, want); exit(1); } free(s); } static void x25519_pub(const uint8_t *priv, uint8_t *pub) { if (crypto_scalarmult_base(pub, priv) != 0) die("x25519 pub"); } static void x25519_shared(const uint8_t *priv, const uint8_t *peer, uint8_t *out) { if (crypto_scalarmult(out, priv, peer) != 0) /* also rejects an all-zero result */ die("x25519 derive"); } static void hkdf(const uint8_t *ikm, size_t ikml, const uint8_t *salt, size_t sl, const uint8_t *info, size_t il, uint8_t *out, size_t ol) { uint8_t prk[crypto_kdf_hkdf_sha256_KEYBYTES]; if (crypto_kdf_hkdf_sha256_extract(prk, salt, sl, ikm, ikml) != 0 || crypto_kdf_hkdf_sha256_expand(out, ol, (const char *)info, il, prk) != 0) die("hkdf"); } static void ed_keypair(const uint8_t *seed, uint8_t *pk, uint8_t *sk) { if (crypto_sign_seed_keypair(pk, sk, seed) != 0) die("ed25519 keypair"); } static void ed_sign(const uint8_t *sk, const uint8_t *msg, size_t ml, uint8_t *sig) { unsigned long long sl; if (crypto_sign_detached(sig, &sl, msg, ml, sk) != 0 || sl != 64) die("ed25519 sign"); } static void kat(void) { uint8_t ap[32], bp[32], apub[32], bpub[32], sh[32]; unhex("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a", ap); unhex("5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb", bp); x25519_pub(ap, apub); x25519_pub(bp, bpub); expect("rfc7748 alice pub", apub, 32, "8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a"); expect("rfc7748 bob pub", bpub, 32, "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f"); x25519_shared(ap, bpub, sh); expect("rfc7748 shared a", sh, 32, "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742"); x25519_shared(bp, apub, sh); expect("rfc7748 shared b", sh, 32, "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742"); puts("kat.x25519=ok"); uint8_t ikm[22], salt[13], info[10], okm[42]; memset(ikm, 0x0b, sizeof ikm); unhex("000102030405060708090a0b0c", salt); unhex("f0f1f2f3f4f5f6f7f8f9", info); hkdf(ikm, sizeof ikm, salt, sizeof salt, info, sizeof info, okm, sizeof okm); expect("rfc5869 tc1", okm, 42, "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865"); puts("kat.hkdf=ok"); uint8_t seed[32], pk[crypto_sign_PUBLICKEYBYTES], sk[crypto_sign_SECRETKEYBYTES], sig[64]; unhex("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", seed); ed_keypair(seed, pk, sk); expect("rfc8032 pub", pk, 32, "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"); ed_sign(sk, (const uint8_t *)"", 0, sig); expect("rfc8032 sig", sig, 64, "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"); puts("kat.ed25519=ok"); } static void password(const uint8_t *shared, const uint8_t *nonce, const char *id, char *out) { char info[128]; uint8_t pw[32]; int il = snprintf(info, sizeof info, "robot-market/mqtt-pw/v1|%s", id); hkdf(shared, 32, nonce, 16, (const uint8_t *)info, (size_t)il, pw, sizeof pw); tohex(pw, sizeof pw, out); } int main(void) { if (sodium_init() < 0) die("sodium_init"); kat(); uint8_t b[32], s[32], bpub[32], spub[32], shared[32], shared2[32], nonce[16], seed[32]; unhex(BOARD_PRIV, b); unhex(BACKEND_EPH, s); unhex(NONCE, nonce); unhex(SIGNING_SEED, seed); x25519_pub(b, bpub); x25519_pub(s, spub); phex("board_pub", bpub, 32); phex("backend_eph_pub", spub, 32); x25519_shared(s, bpub, shared); /* backend side */ x25519_shared(b, spub, shared2); /* board side */ phex("shared_secret", shared, 32); if (memcmp(shared, shared2, 32) != 0) die("shared secret mismatch"); puts("shared_secret_peer_match=ok"); char pw[65]; password(shared, nonce, FN, pw); printf("password_fn=%s\n", pw); password(shared, nonce, SN, pw); printf("password_sn=%s\n", pw); /* Backend signs S || B || nonce || clientID. */ uint8_t msg[32 + 32 + 16 + sizeof(CLIENT_ID) - 1], pk[crypto_sign_PUBLICKEYBYTES], sk[crypto_sign_SECRETKEYBYTES], sig[64]; size_t ml = 0; memcpy(msg + ml, spub, 32), ml += 32; memcpy(msg + ml, bpub, 32), ml += 32; memcpy(msg + ml, nonce, 16), ml += 16; memcpy(msg + ml, CLIENT_ID, sizeof(CLIENT_ID) - 1), ml += sizeof(CLIENT_ID) - 1; ed_keypair(seed, pk, sk); ed_sign(sk, msg, ml, sig); phex("signing_pub", pk, 32); phex("signed_message", msg, ml); phex("signature", sig, 64); if (crypto_sign_verify_detached(sig, msg, ml, pk) != 0) die("signature verify"); puts("signature_verify=ok"); return 0; }