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 )