EV-Nats
view release on metacpan or search on metacpan
src/EV__Nats.xs view on Meta::CPAN
/* NATS base64url encode (no padding) */
static int nats_base64url_encode(const unsigned char *src, size_t src_len, char *dst, size_t dst_size)
{
static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
size_t di = 0;
size_t i;
for (i = 0; i + 2 < src_len; i += 3) {
if (di + 4 > dst_size) return -1;
uint32_t v = ((uint32_t)src[i] << 16) | ((uint32_t)src[i+1] << 8) | src[i+2];
dst[di++] = b64[(v >> 18) & 0x3F];
dst[di++] = b64[(v >> 12) & 0x3F];
dst[di++] = b64[(v >> 6) & 0x3F];
dst[di++] = b64[v & 0x3F];
}
if (i < src_len) {
if (di + 4 > dst_size) return -1;
uint32_t v = (uint32_t)src[i] << 16;
if (i + 1 < src_len) v |= (uint32_t)src[i+1] << 8;
dst[di++] = b64[(v >> 18) & 0x3F];
dst[di++] = b64[(v >> 12) & 0x3F];
if (i + 1 < src_len) dst[di++] = b64[(v >> 6) & 0x3F];
}
if (di < dst_size) dst[di] = '\0';
return (int)di;
}
/* CRC-16/XMODEM (CRC-CCITT, poly 0x1021, init 0x0000) â what NATS NKeys
use. NOT CRC-16/IBM. Used for seed/pubkey integrity. */
static uint16_t nats_crc16(const unsigned char *data, size_t len)
{
uint16_t crc = 0;
size_t i;
int j;
for (i = 0; i < len; i++) {
crc ^= ((uint16_t)data[i]) << 8;
for (j = 0; j < 8; j++) {
if (crc & 0x8000) crc = (uint16_t)((crc << 1) ^ 0x1021);
else crc = (uint16_t)(crc << 1);
}
}
return crc;
}
static int nats_nkey_sign(const char *seed_encoded, const char *nonce, size_t nonce_len,
char *sig_out, size_t sig_out_size)
{
unsigned char raw[64];
size_t raw_len = 0;
if (nats_base32_decode(seed_encoded, strlen(seed_encoded), raw, &raw_len) != 0)
return -1;
if (raw_len < 36) return -1; /* prefix(2) + seed(32) + CRC(2) */
/* Validate CRC16 */
uint16_t expected_crc = (uint16_t)raw[raw_len - 2] | ((uint16_t)raw[raw_len - 1] << 8);
uint16_t actual_crc = nats_crc16(raw, raw_len - 2);
if (expected_crc != actual_crc) return -1;
unsigned char *seed = raw + 2;
EVP_PKEY *pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, seed, 32);
if (!pkey) return -1;
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
if (!ctx) { EVP_PKEY_free(pkey); return -1; }
unsigned char sig[64];
size_t sig_len = sizeof(sig);
int ok = (EVP_DigestSignInit(ctx, NULL, NULL, NULL, pkey) == 1 &&
EVP_DigestSign(ctx, sig, &sig_len, (const unsigned char *)nonce, nonce_len) == 1);
EVP_MD_CTX_free(ctx);
EVP_PKEY_free(pkey);
if (!ok) return -1;
return nats_base64url_encode(sig, sig_len, sig_out, sig_out_size);
}
static int nats_nkey_public(const char *seed_encoded, char *pub_out, size_t pub_out_size)
{
unsigned char raw[64];
size_t raw_len = 0;
if (nats_base32_decode(seed_encoded, strlen(seed_encoded), raw, &raw_len) != 0)
return -1;
if (raw_len < 36) return -1;
uint16_t expected_crc = (uint16_t)raw[raw_len - 2] | ((uint16_t)raw[raw_len - 1] << 8);
if (nats_crc16(raw, raw_len - 2) != expected_crc) return -1;
unsigned char *seed = raw + 2;
EVP_PKEY *pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, seed, 32);
if (!pkey) return -1;
unsigned char pub[32];
size_t pub_len = 32;
int ok = EVP_PKEY_get_raw_public_key(pkey, pub, &pub_len) == 1;
EVP_PKEY_free(pkey);
if (!ok) return -1;
/* Derive public-key prefix from the role embedded in the seed prefix.
Seed encoding (NATS Go nkeys EncodeSeed):
raw[0] = PrefixByteSeed | (role >> 5)
raw[1] = (role & 31) << 3
so role = ((raw[0] & 7) << 5) | (raw[1] >> 3). */
unsigned char role = (unsigned char)(((raw[0] & 0x07) << 5) | (raw[1] >> 3));
unsigned char pub_prefix;
switch (role) {
case 0xA0: pub_prefix = 0xA0; break; /* User */
case 0x70: pub_prefix = 0x70; break; /* Operator */
case 0x00: pub_prefix = 0x00; break; /* Account */
default: pub_prefix = 0xA0; break; /* default to User */
}
/* Build: prefix(1) + pubkey(32) + CRC16(2) = 35 bytes */
unsigned char full[35];
full[0] = pub_prefix;
memcpy(full + 1, pub, 32);
uint16_t crc = nats_crc16(full, 33);
full[33] = crc & 0xFF;
full[34] = (crc >> 8) & 0xFF;
/* Base32 encode 35 bytes -> 56 chars (35*8 = 280 bits, 280/5 = 56). */
int n = nats_base32_encode(full, sizeof(full), pub_out,
pub_out_size > 0 ? pub_out_size - 1 : 0);
if (n < 0) return -1;
if ((size_t)n < pub_out_size) pub_out[n] = '\0';
return n;
}
#endif
/* ================================================================
* I/O helpers (with optional TLS)
* ================================================================ */
static ssize_t nats_io_read(nats_t *self, void *buf, size_t len)
{
#ifdef HAVE_OPENSSL
if (self->ssl) {
int n = SSL_read(self->ssl, buf, (len > (size_t)INT_MAX) ? INT_MAX : (int)len);
if (n <= 0) {
int err = SSL_get_error(self->ssl, n);
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
errno = EAGAIN;
return -1;
}
if (err == SSL_ERROR_ZERO_RETURN) return 0;
errno = EIO;
return -1;
}
return n;
}
( run in 0.737 second using v1.01-cache-2.11-cpan-5735350b133 )