Add MQTT enrollment demo with Gitea deploy pipeline
Go backend plus C (libsodium) and C++ (OpenSSL) simulated boards enroll through a Mosquitto 2.1 broker with Dynamic Security using X25519/HKDF/ Ed25519; a web view shows the run live. The Gitea workflow validates and builds the images, redeploys the stack on the server with the viewer on port 8099 and fails unless both boards pass all checks. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
/* 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 <sodium.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/* LOCAL TEST SHIM ONLY. The machine has the libsodium 1.0.20 shared library (Nix store) but no
|
||||
* development headers, so this declares just the prototypes main.c uses, copied from the
|
||||
* libsodium 1.0.20 API. On the ESP32 (ESP-IDF libsodium component) and any normal install,
|
||||
* delete this directory and let the real <sodium.h> be found instead. */
|
||||
#ifndef SODIUM_SHIM_H
|
||||
#define SODIUM_SHIM_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
int sodium_init(void);
|
||||
|
||||
int crypto_scalarmult_base(unsigned char *q, const unsigned char *n);
|
||||
int crypto_scalarmult(unsigned char *q, const unsigned char *n, const unsigned char *p);
|
||||
|
||||
#define crypto_sign_PUBLICKEYBYTES 32U
|
||||
#define crypto_sign_SECRETKEYBYTES 64U
|
||||
#define crypto_sign_BYTES 64U
|
||||
int crypto_sign_seed_keypair(unsigned char *pk, unsigned char *sk, const unsigned char *seed);
|
||||
int crypto_sign_detached(unsigned char *sig, unsigned long long *siglen_p, const unsigned char *m,
|
||||
unsigned long long mlen, const unsigned char *sk);
|
||||
int crypto_sign_verify_detached(const unsigned char *sig, const unsigned char *m,
|
||||
unsigned long long mlen, const unsigned char *pk);
|
||||
|
||||
#define crypto_kdf_hkdf_sha256_KEYBYTES 32U
|
||||
int crypto_kdf_hkdf_sha256_extract(unsigned char prk[crypto_kdf_hkdf_sha256_KEYBYTES],
|
||||
const unsigned char *salt, size_t salt_len,
|
||||
const unsigned char *ikm, size_t ikm_len);
|
||||
int crypto_kdf_hkdf_sha256_expand(unsigned char *out, size_t out_len, const char *ctx,
|
||||
size_t ctx_len,
|
||||
const unsigned char prk[crypto_kdf_hkdf_sha256_KEYBYTES]);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user