Add Poly1305 and ChaCha20-Poly1305 AEAD with RFC 8439 tests
This commit is contained in:
2
build.sh
2
build.sh
@@ -500,6 +500,8 @@ detect_system_curl
|
||||
SOURCES="nostr_core/crypto/nostr_secp256k1.c"
|
||||
SOURCES="$SOURCES nostr_core/crypto/nostr_aes.c"
|
||||
SOURCES="$SOURCES nostr_core/crypto/nostr_chacha20.c"
|
||||
SOURCES="$SOURCES nostr_core/crypto/nostr_poly1305.c"
|
||||
SOURCES="$SOURCES nostr_core/crypto/nostr_chacha20poly1305.c"
|
||||
SOURCES="$SOURCES cjson/cJSON.c"
|
||||
SOURCES="$SOURCES nostr_core/utils.c"
|
||||
SOURCES="$SOURCES nostr_core/nostr_common.c"
|
||||
|
||||
192
nostr_core/crypto/nostr_chacha20poly1305.c
Normal file
192
nostr_core/crypto/nostr_chacha20poly1305.c
Normal file
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* nostr_chacha20poly1305.c - ChaCha20-Poly1305 AEAD implementation
|
||||
*
|
||||
* RFC 8439 Section 2.8
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// ChaCha20 private API (provided by nostr_chacha20.c)
|
||||
int chacha20_block(const uint8_t key[32], uint32_t counter,
|
||||
const uint8_t nonce[12], uint8_t output[64]);
|
||||
int chacha20_encrypt(const uint8_t key[32], uint32_t counter,
|
||||
const uint8_t nonce[12], const uint8_t* input,
|
||||
uint8_t* output, size_t length);
|
||||
|
||||
// Poly1305 API (provided by nostr_poly1305.c)
|
||||
int nostr_poly1305_mac(const unsigned char key[32], const unsigned char *msg,
|
||||
size_t msg_len, unsigned char tag[16]);
|
||||
|
||||
static void store64_le(unsigned char out[8], uint64_t v) {
|
||||
out[0] = (unsigned char)(v & 0xff);
|
||||
out[1] = (unsigned char)((v >> 8) & 0xff);
|
||||
out[2] = (unsigned char)((v >> 16) & 0xff);
|
||||
out[3] = (unsigned char)((v >> 24) & 0xff);
|
||||
out[4] = (unsigned char)((v >> 32) & 0xff);
|
||||
out[5] = (unsigned char)((v >> 40) & 0xff);
|
||||
out[6] = (unsigned char)((v >> 48) & 0xff);
|
||||
out[7] = (unsigned char)((v >> 56) & 0xff);
|
||||
}
|
||||
|
||||
static int constant_time_tag_eq(const unsigned char a[16], const unsigned char b[16]) {
|
||||
unsigned char diff = 0;
|
||||
size_t i;
|
||||
for (i = 0; i < 16; i++) {
|
||||
diff |= (unsigned char)(a[i] ^ b[i]);
|
||||
}
|
||||
return diff == 0;
|
||||
}
|
||||
|
||||
static int build_poly1305_input(const unsigned char *aad, size_t aad_len,
|
||||
const unsigned char *ciphertext, size_t ct_len,
|
||||
unsigned char **out, size_t *out_len) {
|
||||
size_t aad_pad = (16 - (aad_len % 16)) % 16;
|
||||
size_t ct_pad = (16 - (ct_len % 16)) % 16;
|
||||
size_t total = aad_len + aad_pad + ct_len + ct_pad + 16; // len(aad)||len(ct)
|
||||
unsigned char *buf;
|
||||
size_t pos = 0;
|
||||
unsigned char len_block[16];
|
||||
|
||||
if (!out || !out_len) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
buf = (unsigned char *)malloc(total);
|
||||
if (!buf) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (aad_len > 0 && aad) {
|
||||
memcpy(buf + pos, aad, aad_len);
|
||||
pos += aad_len;
|
||||
}
|
||||
if (aad_pad > 0) {
|
||||
memset(buf + pos, 0, aad_pad);
|
||||
pos += aad_pad;
|
||||
}
|
||||
|
||||
if (ct_len > 0 && ciphertext) {
|
||||
memcpy(buf + pos, ciphertext, ct_len);
|
||||
pos += ct_len;
|
||||
}
|
||||
if (ct_pad > 0) {
|
||||
memset(buf + pos, 0, ct_pad);
|
||||
pos += ct_pad;
|
||||
}
|
||||
|
||||
store64_le(len_block, (uint64_t)aad_len);
|
||||
store64_le(len_block + 8, (uint64_t)ct_len);
|
||||
memcpy(buf + pos, len_block, sizeof(len_block));
|
||||
pos += sizeof(len_block);
|
||||
|
||||
*out = buf;
|
||||
*out_len = pos;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nostr_chacha20poly1305_encrypt(const unsigned char key[32],
|
||||
const unsigned char nonce[12],
|
||||
const unsigned char *aad, size_t aad_len,
|
||||
const unsigned char *plaintext, size_t pt_len,
|
||||
unsigned char *ciphertext,
|
||||
unsigned char tag[16]) {
|
||||
unsigned char block0[64];
|
||||
unsigned char poly_key[32];
|
||||
unsigned char *poly_in = NULL;
|
||||
size_t poly_in_len = 0;
|
||||
int rc;
|
||||
|
||||
if (!key || !nonce || !ciphertext || !tag || (!plaintext && pt_len != 0) || (!aad && aad_len != 0)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (chacha20_block(key, 0, nonce, block0) != 0) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(poly_key, block0, sizeof(poly_key));
|
||||
|
||||
if (pt_len > 0) {
|
||||
rc = chacha20_encrypt(key, 1, nonce, plaintext, ciphertext, pt_len);
|
||||
if (rc != 0) {
|
||||
memset(poly_key, 0, sizeof(poly_key));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
rc = build_poly1305_input(aad, aad_len, ciphertext, pt_len, &poly_in, &poly_in_len);
|
||||
if (rc != 0) {
|
||||
memset(poly_key, 0, sizeof(poly_key));
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = nostr_poly1305_mac(poly_key, poly_in, poly_in_len, tag);
|
||||
|
||||
memset(poly_key, 0, sizeof(poly_key));
|
||||
memset(block0, 0, sizeof(block0));
|
||||
memset(poly_in, 0, poly_in_len);
|
||||
free(poly_in);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int nostr_chacha20poly1305_decrypt(const unsigned char key[32],
|
||||
const unsigned char nonce[12],
|
||||
const unsigned char *aad, size_t aad_len,
|
||||
const unsigned char *ciphertext, size_t ct_len,
|
||||
const unsigned char tag[16],
|
||||
unsigned char *plaintext) {
|
||||
unsigned char block0[64];
|
||||
unsigned char poly_key[32];
|
||||
unsigned char expected_tag[16];
|
||||
unsigned char *poly_in = NULL;
|
||||
size_t poly_in_len = 0;
|
||||
int rc;
|
||||
|
||||
if (!key || !nonce || !tag || !plaintext || (!ciphertext && ct_len != 0) || (!aad && aad_len != 0)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (chacha20_block(key, 0, nonce, block0) != 0) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(poly_key, block0, sizeof(poly_key));
|
||||
|
||||
rc = build_poly1305_input(aad, aad_len, ciphertext, ct_len, &poly_in, &poly_in_len);
|
||||
if (rc != 0) {
|
||||
memset(poly_key, 0, sizeof(poly_key));
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = nostr_poly1305_mac(poly_key, poly_in, poly_in_len, expected_tag);
|
||||
if (rc != 0 || !constant_time_tag_eq(tag, expected_tag)) {
|
||||
memset(poly_key, 0, sizeof(poly_key));
|
||||
memset(block0, 0, sizeof(block0));
|
||||
memset(expected_tag, 0, sizeof(expected_tag));
|
||||
memset(poly_in, 0, poly_in_len);
|
||||
free(poly_in);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ct_len > 0) {
|
||||
rc = chacha20_encrypt(key, 1, nonce, ciphertext, plaintext, ct_len);
|
||||
if (rc != 0) {
|
||||
memset(poly_key, 0, sizeof(poly_key));
|
||||
memset(block0, 0, sizeof(block0));
|
||||
memset(expected_tag, 0, sizeof(expected_tag));
|
||||
memset(poly_in, 0, poly_in_len);
|
||||
free(poly_in);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
memset(poly_key, 0, sizeof(poly_key));
|
||||
memset(block0, 0, sizeof(block0));
|
||||
memset(expected_tag, 0, sizeof(expected_tag));
|
||||
memset(poly_in, 0, poly_in_len);
|
||||
free(poly_in);
|
||||
|
||||
return 0;
|
||||
}
|
||||
262
nostr_core/crypto/nostr_poly1305.c
Normal file
262
nostr_core/crypto/nostr_poly1305.c
Normal file
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* nostr_poly1305.c - Poly1305 message authentication code
|
||||
*
|
||||
* Public-domain style 32-bit implementation based on the poly1305-donna
|
||||
* approach, adapted for nostr_core_lib naming and conventions.
|
||||
*
|
||||
* RFC 8439 Section 2.5
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
uint32_t r0, r1, r2, r3, r4;
|
||||
uint32_t s1, s2, s3, s4;
|
||||
uint32_t h0, h1, h2, h3, h4;
|
||||
uint32_t pad0, pad1, pad2, pad3;
|
||||
size_t leftover;
|
||||
unsigned char buffer[16];
|
||||
unsigned char final;
|
||||
} nostr_poly1305_ctx_t;
|
||||
|
||||
static uint32_t u8to32_le(const unsigned char *p) {
|
||||
return ((uint32_t)p[0]) |
|
||||
((uint32_t)p[1] << 8) |
|
||||
((uint32_t)p[2] << 16) |
|
||||
((uint32_t)p[3] << 24);
|
||||
}
|
||||
|
||||
static void u32to8_le(unsigned char *p, uint32_t v) {
|
||||
p[0] = (unsigned char)(v & 0xff);
|
||||
p[1] = (unsigned char)((v >> 8) & 0xff);
|
||||
p[2] = (unsigned char)((v >> 16) & 0xff);
|
||||
p[3] = (unsigned char)((v >> 24) & 0xff);
|
||||
}
|
||||
|
||||
static void poly1305_blocks(nostr_poly1305_ctx_t *st, const unsigned char *m, size_t bytes) {
|
||||
const uint32_t r0 = st->r0;
|
||||
const uint32_t r1 = st->r1;
|
||||
const uint32_t r2 = st->r2;
|
||||
const uint32_t r3 = st->r3;
|
||||
const uint32_t r4 = st->r4;
|
||||
const uint32_t s1 = st->s1;
|
||||
const uint32_t s2 = st->s2;
|
||||
const uint32_t s3 = st->s3;
|
||||
const uint32_t s4 = st->s4;
|
||||
uint32_t h0 = st->h0;
|
||||
uint32_t h1 = st->h1;
|
||||
uint32_t h2 = st->h2;
|
||||
uint32_t h3 = st->h3;
|
||||
uint32_t h4 = st->h4;
|
||||
const uint32_t hibit = st->final ? 0 : (1U << 24);
|
||||
|
||||
while (bytes >= 16) {
|
||||
uint32_t t0 = u8to32_le(m + 0);
|
||||
uint32_t t1 = u8to32_le(m + 4);
|
||||
uint32_t t2 = u8to32_le(m + 8);
|
||||
uint32_t t3 = u8to32_le(m + 12);
|
||||
|
||||
uint64_t d0, d1, d2, d3, d4;
|
||||
uint32_t c;
|
||||
|
||||
h0 += ( t0 ) & 0x3ffffff;
|
||||
h1 += (((t1 << 6) | (t0 >> 26)) ) & 0x3ffffff;
|
||||
h2 += (((t2 << 12) | (t1 >> 20))) & 0x3ffffff;
|
||||
h3 += (((t3 << 18) | (t2 >> 14))) & 0x3ffffff;
|
||||
h4 += (( t3 >> 8) ) & 0x00ffffff;
|
||||
h4 += hibit;
|
||||
|
||||
d0 = ((uint64_t)h0 * r0) + ((uint64_t)h1 * s4) + ((uint64_t)h2 * s3) + ((uint64_t)h3 * s2) + ((uint64_t)h4 * s1);
|
||||
d1 = ((uint64_t)h0 * r1) + ((uint64_t)h1 * r0) + ((uint64_t)h2 * s4) + ((uint64_t)h3 * s3) + ((uint64_t)h4 * s2);
|
||||
d2 = ((uint64_t)h0 * r2) + ((uint64_t)h1 * r1) + ((uint64_t)h2 * r0) + ((uint64_t)h3 * s4) + ((uint64_t)h4 * s3);
|
||||
d3 = ((uint64_t)h0 * r3) + ((uint64_t)h1 * r2) + ((uint64_t)h2 * r1) + ((uint64_t)h3 * r0) + ((uint64_t)h4 * s4);
|
||||
d4 = ((uint64_t)h0 * r4) + ((uint64_t)h1 * r3) + ((uint64_t)h2 * r2) + ((uint64_t)h3 * r1) + ((uint64_t)h4 * r0);
|
||||
|
||||
c = (uint32_t)(d0 >> 26); h0 = (uint32_t)d0 & 0x3ffffff;
|
||||
d1 += c;
|
||||
c = (uint32_t)(d1 >> 26); h1 = (uint32_t)d1 & 0x3ffffff;
|
||||
d2 += c;
|
||||
c = (uint32_t)(d2 >> 26); h2 = (uint32_t)d2 & 0x3ffffff;
|
||||
d3 += c;
|
||||
c = (uint32_t)(d3 >> 26); h3 = (uint32_t)d3 & 0x3ffffff;
|
||||
d4 += c;
|
||||
c = (uint32_t)(d4 >> 26); h4 = (uint32_t)d4 & 0x3ffffff;
|
||||
h0 += c * 5;
|
||||
c = h0 >> 26; h0 &= 0x3ffffff;
|
||||
h1 += c;
|
||||
|
||||
m += 16;
|
||||
bytes -= 16;
|
||||
}
|
||||
|
||||
st->h0 = h0;
|
||||
st->h1 = h1;
|
||||
st->h2 = h2;
|
||||
st->h3 = h3;
|
||||
st->h4 = h4;
|
||||
}
|
||||
|
||||
void nostr_poly1305_init(nostr_poly1305_ctx_t *st, const unsigned char key[32]) {
|
||||
uint32_t t0, t1, t2, t3;
|
||||
|
||||
t0 = u8to32_le(key + 0);
|
||||
t1 = u8to32_le(key + 4);
|
||||
t2 = u8to32_le(key + 8);
|
||||
t3 = u8to32_le(key + 12);
|
||||
|
||||
st->r0 = ( t0 ) & 0x3ffffff;
|
||||
st->r1 = (((t1 << 6) | (t0 >> 26)) ) & 0x3ffff03;
|
||||
st->r2 = (((t2 << 12) | (t1 >> 20))) & 0x3ffc0ff;
|
||||
st->r3 = (((t3 << 18) | (t2 >> 14))) & 0x3f03fff;
|
||||
st->r4 = (( t3 >> 8) ) & 0x00fffff;
|
||||
|
||||
st->s1 = st->r1 * 5;
|
||||
st->s2 = st->r2 * 5;
|
||||
st->s3 = st->r3 * 5;
|
||||
st->s4 = st->r4 * 5;
|
||||
|
||||
st->h0 = 0;
|
||||
st->h1 = 0;
|
||||
st->h2 = 0;
|
||||
st->h3 = 0;
|
||||
st->h4 = 0;
|
||||
|
||||
st->pad0 = u8to32_le(key + 16);
|
||||
st->pad1 = u8to32_le(key + 20);
|
||||
st->pad2 = u8to32_le(key + 24);
|
||||
st->pad3 = u8to32_le(key + 28);
|
||||
|
||||
st->leftover = 0;
|
||||
st->final = 0;
|
||||
}
|
||||
|
||||
void nostr_poly1305_update(nostr_poly1305_ctx_t *st, const unsigned char *m, size_t bytes) {
|
||||
size_t i;
|
||||
|
||||
if (st->leftover) {
|
||||
size_t want = (size_t)(16 - st->leftover);
|
||||
if (want > bytes) {
|
||||
want = bytes;
|
||||
}
|
||||
for (i = 0; i < want; i++) {
|
||||
st->buffer[st->leftover + i] = m[i];
|
||||
}
|
||||
bytes -= want;
|
||||
m += want;
|
||||
st->leftover += want;
|
||||
if (st->leftover < 16) {
|
||||
return;
|
||||
}
|
||||
poly1305_blocks(st, st->buffer, 16);
|
||||
st->leftover = 0;
|
||||
}
|
||||
|
||||
if (bytes >= 16) {
|
||||
size_t want = bytes & ~(size_t)0xf;
|
||||
poly1305_blocks(st, m, want);
|
||||
m += want;
|
||||
bytes -= want;
|
||||
}
|
||||
|
||||
if (bytes) {
|
||||
for (i = 0; i < bytes; i++) {
|
||||
st->buffer[st->leftover + i] = m[i];
|
||||
}
|
||||
st->leftover += bytes;
|
||||
}
|
||||
}
|
||||
|
||||
void nostr_poly1305_final(nostr_poly1305_ctx_t *st, unsigned char tag[16]) {
|
||||
uint32_t h0, h1, h2, h3, h4, c;
|
||||
uint32_t g0, g1, g2, g3, g4;
|
||||
uint64_t f;
|
||||
uint32_t mask;
|
||||
|
||||
if (st->leftover) {
|
||||
size_t i = st->leftover;
|
||||
st->buffer[i++] = 1;
|
||||
for (; i < 16; i++) {
|
||||
st->buffer[i] = 0;
|
||||
}
|
||||
st->final = 1;
|
||||
poly1305_blocks(st, st->buffer, 16);
|
||||
}
|
||||
|
||||
h0 = st->h0;
|
||||
h1 = st->h1;
|
||||
h2 = st->h2;
|
||||
h3 = st->h3;
|
||||
h4 = st->h4;
|
||||
|
||||
c = h1 >> 26; h1 &= 0x3ffffff;
|
||||
h2 += c;
|
||||
c = h2 >> 26; h2 &= 0x3ffffff;
|
||||
h3 += c;
|
||||
c = h3 >> 26; h3 &= 0x3ffffff;
|
||||
h4 += c;
|
||||
c = h4 >> 26; h4 &= 0x3ffffff;
|
||||
h0 += c * 5;
|
||||
c = h0 >> 26; h0 &= 0x3ffffff;
|
||||
h1 += c;
|
||||
|
||||
g0 = h0 + 5;
|
||||
c = g0 >> 26; g0 &= 0x3ffffff;
|
||||
g1 = h1 + c;
|
||||
c = g1 >> 26; g1 &= 0x3ffffff;
|
||||
g2 = h2 + c;
|
||||
c = g2 >> 26; g2 &= 0x3ffffff;
|
||||
g3 = h3 + c;
|
||||
c = g3 >> 26; g3 &= 0x3ffffff;
|
||||
g4 = h4 + c - (1U << 26);
|
||||
|
||||
mask = (g4 >> 31) - 1U;
|
||||
g0 &= mask;
|
||||
g1 &= mask;
|
||||
g2 &= mask;
|
||||
g3 &= mask;
|
||||
g4 &= mask;
|
||||
mask = ~mask;
|
||||
h0 = (h0 & mask) | g0;
|
||||
h1 = (h1 & mask) | g1;
|
||||
h2 = (h2 & mask) | g2;
|
||||
h3 = (h3 & mask) | g3;
|
||||
h4 = (h4 & mask) | g4;
|
||||
|
||||
h0 = ((h0 ) | (h1 << 26)) & 0xffffffff;
|
||||
h1 = ((h1 >> 6 ) | (h2 << 20)) & 0xffffffff;
|
||||
h2 = ((h2 >> 12) | (h3 << 14)) & 0xffffffff;
|
||||
h3 = ((h3 >> 18) | (h4 << 8 )) & 0xffffffff;
|
||||
|
||||
f = (uint64_t)h0 + st->pad0;
|
||||
h0 = (uint32_t)f;
|
||||
f = (uint64_t)h1 + st->pad1 + (f >> 32);
|
||||
h1 = (uint32_t)f;
|
||||
f = (uint64_t)h2 + st->pad2 + (f >> 32);
|
||||
h2 = (uint32_t)f;
|
||||
f = (uint64_t)h3 + st->pad3 + (f >> 32);
|
||||
h3 = (uint32_t)f;
|
||||
|
||||
u32to8_le(tag + 0, h0);
|
||||
u32to8_le(tag + 4, h1);
|
||||
u32to8_le(tag + 8, h2);
|
||||
u32to8_le(tag + 12, h3);
|
||||
|
||||
memset(st, 0, sizeof(*st));
|
||||
}
|
||||
|
||||
int nostr_poly1305_mac(const unsigned char key[32], const unsigned char *msg,
|
||||
size_t msg_len, unsigned char tag[16]) {
|
||||
nostr_poly1305_ctx_t ctx;
|
||||
|
||||
if (!key || (!msg && msg_len != 0) || !tag) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
nostr_poly1305_init(&ctx, key);
|
||||
nostr_poly1305_update(&ctx, msg, msg_len);
|
||||
nostr_poly1305_final(&ctx, tag);
|
||||
|
||||
return 0;
|
||||
}
|
||||
243
tests/chacha20poly1305_test.c
Normal file
243
tests/chacha20poly1305_test.c
Normal file
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* ChaCha20-Poly1305 / Poly1305 Test Suite - RFC 8439 vectors
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int nostr_poly1305_mac(const unsigned char key[32], const unsigned char *msg,
|
||||
size_t msg_len, unsigned char tag[16]);
|
||||
|
||||
int nostr_chacha20poly1305_encrypt(const unsigned char key[32],
|
||||
const unsigned char nonce[12],
|
||||
const unsigned char *aad, size_t aad_len,
|
||||
const unsigned char *plaintext, size_t pt_len,
|
||||
unsigned char *ciphertext,
|
||||
unsigned char tag[16]);
|
||||
|
||||
int nostr_chacha20poly1305_decrypt(const unsigned char key[32],
|
||||
const unsigned char nonce[12],
|
||||
const unsigned char *aad, size_t aad_len,
|
||||
const unsigned char *ciphertext, size_t ct_len,
|
||||
const unsigned char tag[16],
|
||||
unsigned char *plaintext);
|
||||
|
||||
static int hex_to_bytes(const char *hex, uint8_t *out, size_t out_len) {
|
||||
size_t i;
|
||||
if (!hex || !out) return -1;
|
||||
for (i = 0; i < out_len; i++) {
|
||||
if (sscanf(hex + (i * 2), "%2hhx", &out[i]) != 1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bytes_equal(const uint8_t *a, const uint8_t *b, size_t len) {
|
||||
return memcmp(a, b, len) == 0;
|
||||
}
|
||||
|
||||
static void print_hex(const uint8_t *buf, size_t len) {
|
||||
size_t i;
|
||||
for (i = 0; i < len; i++) {
|
||||
printf("%02x", buf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static int test_poly1305_rfc8439_2_5_2(void) {
|
||||
const char *key_hex =
|
||||
"85d6be7857556d337f4452fe42d506a8"
|
||||
"0103808afb0db2fd4abff6af4149f51b";
|
||||
const char *msg = "Cryptographic Forum Research Group";
|
||||
const char *expected_tag_hex = "a8061dc1305136c6c22b8baf0c0127a9";
|
||||
|
||||
uint8_t key[32];
|
||||
uint8_t tag[16];
|
||||
uint8_t expected[16];
|
||||
|
||||
printf("=== Poly1305 RFC 8439 §2.5.2 ===\n");
|
||||
|
||||
if (hex_to_bytes(key_hex, key, sizeof(key)) != 0 ||
|
||||
hex_to_bytes(expected_tag_hex, expected, sizeof(expected)) != 0) {
|
||||
printf("❌ hex parse failed\n\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (nostr_poly1305_mac(key, (const unsigned char *)msg, strlen(msg), tag) != 0) {
|
||||
printf("❌ nostr_poly1305_mac failed\n\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!bytes_equal(tag, expected, sizeof(tag))) {
|
||||
printf("❌ tag mismatch\nExpected: ");
|
||||
print_hex(expected, sizeof(expected));
|
||||
printf("\nGot: ");
|
||||
print_hex(tag, sizeof(tag));
|
||||
printf("\n\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("✅ passed\n\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int test_aead_rfc8439_appendix_a_5(void) {
|
||||
const char *key_hex =
|
||||
"1c9240a5eb55d38af333888604f6b5f0"
|
||||
"473917c1402b80099dca5cbc207075c0";
|
||||
const char *nonce_hex = "000000000102030405060708";
|
||||
const char *aad_hex = "f33388860000000000004e91";
|
||||
const char *plaintext_hex =
|
||||
"496e7465726e65742d4472616674732061726520647261667420646f63756d656e74732076616c696420"
|
||||
"666f722061206d6178696d756d206f6620736978206d6f6e74687320616e64206d61792062652075706461"
|
||||
"7465642c207265706c616365642c206f72206f62736f6c65746564206279206f7468657220646f63756d65"
|
||||
"6e747320617420616e792074696d652e20497420697320696e617070726f70726961746520746f20757365"
|
||||
"20496e7465726e65742d447261667473206173207265666572656e6365206d6174657269616c206f722074"
|
||||
"6f2063697465207468656d206f74686572207468616e206173202fe2809c776f726b20696e2070726f6772"
|
||||
"6573732e2fe2809d";
|
||||
const char *expected_ct_hex =
|
||||
"64a0861575861af460f062c79be643bd"
|
||||
"5e805cfd345cf389f108670ac76c8cb2"
|
||||
"4c6cfc18755d43eea09ee94e382d26b0"
|
||||
"bdb7b73c321b0100d4f03b7f355894cf"
|
||||
"332f830e710b97ce98c8a84abd0b9481"
|
||||
"14ad176e008d33bd60f982b1ff37c855"
|
||||
"9797a06ef4f0ef61c186324e2b350638"
|
||||
"3606907b6a7c02b0f9f6157b53c867e4"
|
||||
"b9166c767b804d46a59b5216cde7a4e9"
|
||||
"9040c5a40433225ee282a1b0a06c523e"
|
||||
"af4534d7f83fa1155b0047718cbc546a"
|
||||
"0d072b04b3564eea1b422273f548271a"
|
||||
"0bb2316053fa76991955ebd63159434e"
|
||||
"cebb4e466dae5a1073a6727627097a10"
|
||||
"49e617d91d361094fa68f0ff77987130"
|
||||
"305beaba2eda04df997b714d6c6f2c29"
|
||||
"a6ad5cb4022b02709b";
|
||||
const char *expected_tag_hex = "eead9d67890cbb22392336fea1851f38";
|
||||
|
||||
uint8_t key[32], nonce[12], aad[12], expected_tag[16];
|
||||
uint8_t plaintext[1024], ciphertext[1024], decrypted[1024], expected_ct[1024];
|
||||
uint8_t tag[16];
|
||||
size_t pt_len = strlen(plaintext_hex) / 2;
|
||||
size_t aad_len = strlen(aad_hex) / 2;
|
||||
size_t ct_len = strlen(expected_ct_hex) / 2;
|
||||
|
||||
printf("=== ChaCha20-Poly1305 RFC 8439 Appendix A.5 ===\n");
|
||||
|
||||
if (hex_to_bytes(key_hex, key, sizeof(key)) != 0 ||
|
||||
hex_to_bytes(nonce_hex, nonce, sizeof(nonce)) != 0 ||
|
||||
hex_to_bytes(aad_hex, aad, aad_len) != 0 ||
|
||||
hex_to_bytes(plaintext_hex, plaintext, pt_len) != 0 ||
|
||||
hex_to_bytes(expected_ct_hex, expected_ct, ct_len) != 0 ||
|
||||
hex_to_bytes(expected_tag_hex, expected_tag, sizeof(expected_tag)) != 0) {
|
||||
printf("❌ hex parse failed\n\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (nostr_chacha20poly1305_encrypt(key, nonce, aad, aad_len,
|
||||
plaintext, pt_len, ciphertext, tag) != 0) {
|
||||
printf("❌ encrypt failed\n\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!bytes_equal(ciphertext, expected_ct, ct_len)) {
|
||||
size_t i;
|
||||
size_t mismatch_count = 0;
|
||||
printf("❌ ciphertext mismatch\n");
|
||||
for (i = 0; i < ct_len; i++) {
|
||||
if (ciphertext[i] != expected_ct[i]) {
|
||||
if (mismatch_count == 0) {
|
||||
size_t start = (i > 8) ? (i - 8) : 0;
|
||||
size_t end = (i + 8 < ct_len) ? (i + 8) : (ct_len - 1);
|
||||
size_t j;
|
||||
printf("first mismatch at byte %zu: got=%02x expected=%02x\n", i, ciphertext[i], expected_ct[i]);
|
||||
printf("context (got): ");
|
||||
for (j = start; j <= end; j++) printf("%02x", ciphertext[j]);
|
||||
printf("\ncontext (expected): ");
|
||||
for (j = start; j <= end; j++) printf("%02x", expected_ct[j]);
|
||||
printf("\n");
|
||||
}
|
||||
mismatch_count++;
|
||||
}
|
||||
}
|
||||
printf("total mismatched bytes: %zu\n\n", mismatch_count);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!bytes_equal(tag, expected_tag, sizeof(tag))) {
|
||||
printf("❌ tag mismatch\n\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (nostr_chacha20poly1305_decrypt(key, nonce, aad, aad_len,
|
||||
ciphertext, ct_len, tag, decrypted) != 0) {
|
||||
printf("❌ decrypt failed\n\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!bytes_equal(decrypted, plaintext, pt_len)) {
|
||||
printf("❌ decrypted plaintext mismatch\n\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("✅ passed\n\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int test_aead_tamper_rejects(void) {
|
||||
uint8_t key[32] = {0};
|
||||
uint8_t nonce[12] = {0};
|
||||
uint8_t aad[8] = {1,2,3,4,5,6,7,8};
|
||||
uint8_t pt[32] = "hello chacha20-poly1305 world";
|
||||
uint8_t ct[64] = {0};
|
||||
uint8_t tag[16] = {0};
|
||||
uint8_t out[64] = {0};
|
||||
|
||||
printf("=== AEAD tamper rejection ===\n");
|
||||
|
||||
if (nostr_chacha20poly1305_encrypt(key, nonce, aad, sizeof(aad), pt, strlen((char *)pt), ct, tag) != 0) {
|
||||
printf("❌ encrypt failed\n\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ct[0] ^= 0x01;
|
||||
if (nostr_chacha20poly1305_decrypt(key, nonce, aad, sizeof(aad), ct, strlen((char *)pt), tag, out) == 0) {
|
||||
printf("❌ tampered ciphertext accepted\n\n");
|
||||
return 0;
|
||||
}
|
||||
ct[0] ^= 0x01;
|
||||
|
||||
tag[0] ^= 0x80;
|
||||
if (nostr_chacha20poly1305_decrypt(key, nonce, aad, sizeof(aad), ct, strlen((char *)pt), tag, out) == 0) {
|
||||
printf("❌ tampered tag accepted\n\n");
|
||||
return 0;
|
||||
}
|
||||
tag[0] ^= 0x80;
|
||||
|
||||
aad[0] ^= 0x01;
|
||||
if (nostr_chacha20poly1305_decrypt(key, nonce, aad, sizeof(aad), ct, strlen((char *)pt), tag, out) == 0) {
|
||||
printf("❌ tampered AAD accepted\n\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("✅ passed\n\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int pass = 1;
|
||||
|
||||
pass &= test_poly1305_rfc8439_2_5_2();
|
||||
pass &= test_aead_rfc8439_appendix_a_5();
|
||||
pass &= test_aead_tamper_rejects();
|
||||
|
||||
if (pass) {
|
||||
printf("🎉 All ChaCha20-Poly1305/Poly1305 tests PASSED\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("💥 Some tests FAILED\n");
|
||||
return 1;
|
||||
}
|
||||
Reference in New Issue
Block a user