/* Interop spike: X25519 + HKDF-SHA256 + Ed25519 with fixed inputs (C, OpenSSL 3 EVP). * Prints the same key=value lines as the Go and C++ programs so the outputs can be diffed. */ #include #include #include #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) { EVP_PKEY *k = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, priv, 32); size_t l = 32; if (!k || EVP_PKEY_get_raw_public_key(k, pub, &l) != 1 || l != 32) die("x25519 pub"); EVP_PKEY_free(k); } static void x25519_shared(const uint8_t *priv, const uint8_t *peer, uint8_t *out) { EVP_PKEY *k = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, priv, 32); EVP_PKEY *p = EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, NULL, peer, 32); EVP_PKEY_CTX *c = k ? EVP_PKEY_CTX_new(k, NULL) : NULL; size_t l = 32; if (!k || !p || !c || EVP_PKEY_derive_init(c) != 1 || EVP_PKEY_derive_set_peer(c, p) != 1 || EVP_PKEY_derive(c, out, &l) != 1 || l != 32) die("x25519 derive"); EVP_PKEY_CTX_free(c); EVP_PKEY_free(p); EVP_PKEY_free(k); } 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) { EVP_KDF *kdf = EVP_KDF_fetch(NULL, "HKDF", NULL); EVP_KDF_CTX *ctx = kdf ? EVP_KDF_CTX_new(kdf) : NULL; char digest[] = "SHA256"; OSSL_PARAM p[5]; p[0] = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, digest, 0); p[1] = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, (void *)ikm, ikml); p[2] = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT, (void *)salt, sl); p[3] = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_INFO, (void *)info, il); p[4] = OSSL_PARAM_construct_end(); if (!ctx || EVP_KDF_derive(ctx, out, ol, p) != 1) die("hkdf"); EVP_KDF_CTX_free(ctx); EVP_KDF_free(kdf); } static void ed_pub(const uint8_t *seed, uint8_t *pub) { EVP_PKEY *k = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, seed, 32); size_t l = 32; if (!k || EVP_PKEY_get_raw_public_key(k, pub, &l) != 1 || l != 32) die("ed25519 pub"); EVP_PKEY_free(k); } static void ed_sign(const uint8_t *seed, const uint8_t *msg, size_t ml, uint8_t *sig) { EVP_PKEY *k = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, seed, 32); EVP_MD_CTX *c = EVP_MD_CTX_new(); size_t sl = 64; if (!k || !c || EVP_DigestSignInit(c, NULL, NULL, NULL, k) != 1 || EVP_DigestSign(c, sig, &sl, msg, ml) != 1 || sl != 64) die("ed25519 sign"); EVP_MD_CTX_free(c); EVP_PKEY_free(k); } static int ed_verify(const uint8_t *pub, const uint8_t *msg, size_t ml, const uint8_t *sig) { EVP_PKEY *k = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, pub, 32); EVP_MD_CTX *c = EVP_MD_CTX_new(); int ok = k && c && EVP_DigestVerifyInit(c, NULL, NULL, NULL, k) == 1 && EVP_DigestVerify(c, sig, 64, msg, ml) == 1; EVP_MD_CTX_free(c); EVP_PKEY_free(k); return ok; } 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], pub[32], sig[64]; unhex("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", seed); ed_pub(seed, pub); expect("rfc8032 pub", pub, 32, "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"); ed_sign(seed, (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) { 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], pub[32], 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_pub(seed, pub); ed_sign(seed, msg, ml, sig); phex("signing_pub", pub, 32); phex("signed_message", msg, ml); phex("signature", sig, 64); if (!ed_verify(pub, msg, ml, sig)) die("signature verify"); puts("signature_verify=ok"); return 0; }