git-trace

romele / git-trace public

62 commits · 1 branch · 1 tag · 1 contributor (top: Romele) · first commit 3d ago · last push 11m ago

src/crypto.c

#include "crypto.h"
#include "util.h"

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/random.h>
#include <unistd.h>

/* ---------------- SHA-256 (FIPS 180-4) ---------------- */

struct s256 { uint32_t h[8]; uint64_t len; uint8_t b[64]; size_t bl; };

static const uint32_t K[64] = {
    0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,
    0x923f82a4,0xab1c5ed5,0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,
    0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,0xe49b69c1,0xefbe4786,
    0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
    0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,
    0x06ca6351,0x14292967,0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,
    0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,0xa2bfe8a1,0xa81a664b,
    0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
    0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,
    0x5b9cca4f,0x682e6ff3,0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,
    0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
};

#define RR(x,n) (((x) >> (n)) | ((x) << (32 - (n))))

static void s256_block(struct s256 *s, const uint8_t *p)
{
    uint32_t w[64];
    for (int i = 0; i < 16; i++)
        w[i] = (uint32_t)p[i*4] << 24 | (uint32_t)p[i*4+1] << 16 |
               (uint32_t)p[i*4+2] << 8 | (uint32_t)p[i*4+3];
    for (int i = 16; i < 64; i++) {
        uint32_t s0 = RR(w[i-15],7) ^ RR(w[i-15],18) ^ (w[i-15] >> 3);
        uint32_t s1 = RR(w[i-2],17) ^ RR(w[i-2],19) ^ (w[i-2] >> 10);
        w[i] = w[i-16] + s0 + w[i-7] + s1;
    }
    uint32_t a=s->h[0],b=s->h[1],c=s->h[2],d=s->h[3],
             e=s->h[4],f=s->h[5],g=s->h[6],h=s->h[7];
    for (int i = 0; i < 64; i++) {
        uint32_t S1 = RR(e,6) ^ RR(e,11) ^ RR(e,25);
        uint32_t ch = (e & f) ^ (~e & g);
        uint32_t t1 = h + S1 + ch + K[i] + w[i];
        uint32_t S0 = RR(a,2) ^ RR(a,13) ^ RR(a,22);
        uint32_t mj = (a & b) ^ (a & c) ^ (b & c);
        uint32_t t2 = S0 + mj;
        h=g; g=f; f=e; e=d+t1; d=c; c=b; b=a; a=t1+t2;
    }
    s->h[0]+=a; s->h[1]+=b; s->h[2]+=c; s->h[3]+=d;
    s->h[4]+=e; s->h[5]+=f; s->h[6]+=g; s->h[7]+=h;
}

static void s256_init(struct s256 *s)
{
    static const uint32_t iv[8] = {
        0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,
        0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19
    };
    memcpy(s->h, iv, sizeof iv);
    s->len = 0;
    s->bl = 0;
}

static void s256_update(struct s256 *s, const void *data, size_t len)
{
    const uint8_t *p = data;
    s->len += len;
    while (len) {
        size_t take = 64 - s->bl;
        if (take > len)
            take = len;
        memcpy(s->b + s->bl, p, take);
        s->bl += take;
        p += take;
        len -= take;
        if (s->bl == 64) {
            s256_block(s, s->b);
            s->bl = 0;
        }
    }
}

static void s256_final(struct s256 *s, uint8_t out[32])
{
    uint64_t bits = s->len * 8;
    uint8_t pad = 0x80;
    s256_update(s, &pad, 1);
    uint8_t z = 0;
    while (s->bl != 56)
        s256_update(s, &z, 1);
    uint8_t lb[8];
    for (int i = 0; i < 8; i++)
        lb[i] = (uint8_t)(bits >> (56 - i * 8));
    s256_update(s, lb, 8);
    for (int i = 0; i < 8; i++) {
        out[i*4]   = (uint8_t)(s->h[i] >> 24);
        out[i*4+1] = (uint8_t)(s->h[i] >> 16);
        out[i*4+2] = (uint8_t)(s->h[i] >> 8);
        out[i*4+3] = (uint8_t)s->h[i];
    }
}

void sha256(const void *data, size_t len, uint8_t out[32])
{
    struct s256 s;
    s256_init(&s);
    s256_update(&s, data, len);
    s256_final(&s, out);
}

static void hmac_sha256(const uint8_t *key, size_t klen,
                        const uint8_t *msg, size_t mlen, uint8_t out[32])
{
    uint8_t k[64];
    memset(k, 0, 64);
    if (klen > 64) {
        sha256(key, klen, k);
    } else {
        memcpy(k, key, klen);
    }
    uint8_t ipad[64], opad[64];
    for (int i = 0; i < 64; i++) {
        ipad[i] = k[i] ^ 0x36;
        opad[i] = k[i] ^ 0x5c;
    }
    struct s256 s;
    s256_init(&s);
    s256_update(&s, ipad, 64);
    s256_update(&s, msg, mlen);
    uint8_t inner[32];
    s256_final(&s, inner);
    s256_init(&s);
    s256_update(&s, opad, 64);
    s256_update(&s, inner, 32);
    s256_final(&s, out);
}

void gt_hmac_sha256(const uint8_t *key, size_t klen,
                    const uint8_t *msg, size_t mlen, uint8_t out[32])
{
    hmac_sha256(key, klen, msg, mlen, out);
}

/* ---------------- PBKDF2 ---------------- */

void pbkdf2_sha256(const char *password, const uint8_t *salt, size_t saltlen,
                   unsigned iters, uint8_t out[32])
{
    /* single block (dkLen == hLen) */
    struct buf m;
    buf_init(&m);
    buf_put(&m, salt, saltlen);
    uint8_t ctr[4] = { 0, 0, 0, 1 };
    buf_put(&m, ctr, 4);
    uint8_t u[32];
    hmac_sha256((const uint8_t *)password, strlen(password),
                (const uint8_t *)m.data, m.len, u);
    memcpy(out, u, 32);
    for (unsigned i = 1; i < iters; i++) {
        hmac_sha256((const uint8_t *)password, strlen(password), u, 32, u);
        for (int j = 0; j < 32; j++)
            out[j] ^= u[j];
    }
    buf_free(&m);
}

/* HMAC-SHA256 over two message parts (scrypt needs salt || counter) */
static void hmac_sha256_2(const uint8_t *key, size_t klen,
                          const uint8_t *m1, size_t m1len,
                          const uint8_t *m2, size_t m2len, uint8_t out[32])
{
    uint8_t k[64];
    memset(k, 0, 64);
    if (klen > 64) {
        sha256(key, klen, k);
    } else {
        memcpy(k, key, klen);
    }
    uint8_t ipad[64], opad[64];
    for (int i = 0; i < 64; i++) {
        ipad[i] = k[i] ^ 0x36;
        opad[i] = k[i] ^ 0x5c;
    }
    struct s256 s;
    s256_init(&s);
    s256_update(&s, ipad, 64);
    s256_update(&s, m1, m1len);
    s256_update(&s, m2, m2len);
    uint8_t inner[32];
    s256_final(&s, inner);
    s256_init(&s);
    s256_update(&s, opad, 64);
    s256_update(&s, inner, 32);
    s256_final(&s, out);
}

/* multi-block PBKDF2-HMAC-SHA256 (scrypt's outer KDF) */
static void pbkdf2_any(const uint8_t *pw, size_t plen,
                       const uint8_t *salt, size_t saltlen,
                       unsigned iters, uint8_t *out, size_t olen)
{
    uint8_t u[32];
    uint32_t blk = 1;
    while (olen) {
        uint8_t ctr[4] = { (uint8_t)(blk >> 24), (uint8_t)(blk >> 16),
                           (uint8_t)(blk >> 8), (uint8_t)blk };
        hmac_sha256_2(pw, plen, salt, saltlen, ctr, 4, u);
        uint8_t t[32];
        memcpy(t, u, 32);
        for (unsigned i = 1; i < iters; i++) {
            hmac_sha256(pw, plen, u, 32, u);
            for (int j = 0; j < 32; j++)
                t[j] ^= u[j];
        }
        size_t n = olen < 32 ? olen : 32;
        memcpy(out, t, n);
        out += n;
        olen -= n;
        blk++;
    }
}

/* ---------------- scrypt (RFC 7914) ---------------- */

#define R32(a, b) (((a) << (b)) | ((a) >> (32 - (b))))

static uint32_t le32d(const uint8_t *p)
{
    return (uint32_t)p[0] | (uint32_t)p[1] << 8 | (uint32_t)p[2] << 16 |
           (uint32_t)p[3] << 24;
}

static void le32e(uint8_t *p, uint32_t v)
{
    p[0] = (uint8_t)v;
    p[1] = (uint8_t)(v >> 8);
    p[2] = (uint8_t)(v >> 16);
    p[3] = (uint8_t)(v >> 24);
}

static void salsa20_8(uint32_t B[16])
{
    uint32_t x[16];
    memcpy(x, B, 64);
    for (int i = 0; i < 8; i += 2) {
        x[ 4] ^= R32(x[ 0] + x[12],  7);
        x[ 8] ^= R32(x[ 4] + x[ 0],  9);
        x[12] ^= R32(x[ 8] + x[ 4], 13);
        x[ 0] ^= R32(x[12] + x[ 8], 18);
        x[ 9] ^= R32(x[ 5] + x[ 1],  7);
        x[13] ^= R32(x[ 9] + x[ 5],  9);
        x[ 1] ^= R32(x[13] + x[ 9], 13);
        x[ 5] ^= R32(x[ 1] + x[13], 18);
        x[14] ^= R32(x[10] + x[ 6],  7);
        x[ 2] ^= R32(x[14] + x[10],  9);
        x[ 6] ^= R32(x[ 2] + x[14], 13);
        x[10] ^= R32(x[ 6] + x[ 2], 18);
        x[ 3] ^= R32(x[15] + x[11],  7);
        x[ 7] ^= R32(x[ 3] + x[15],  9);
        x[11] ^= R32(x[ 7] + x[ 3], 13);
        x[15] ^= R32(x[11] + x[ 7], 18);
        x[ 1] ^= R32(x[ 0] + x[ 3],  7);
        x[ 2] ^= R32(x[ 1] + x[ 0],  9);
        x[ 3] ^= R32(x[ 2] + x[ 1], 13);
        x[ 0] ^= R32(x[ 3] + x[ 2], 18);
        x[ 6] ^= R32(x[ 5] + x[ 4],  7);
        x[ 7] ^= R32(x[ 6] + x[ 5],  9);
        x[ 4] ^= R32(x[ 7] + x[ 6], 13);
        x[ 5] ^= R32(x[ 4] + x[ 7], 18);
        x[11] ^= R32(x[10] + x[ 9],  7);
        x[ 8] ^= R32(x[11] + x[10],  9);
        x[ 9] ^= R32(x[ 8] + x[11], 13);
        x[10] ^= R32(x[ 9] + x[ 8], 18);
        x[12] ^= R32(x[15] + x[14],  7);
        x[13] ^= R32(x[12] + x[15],  9);
        x[14] ^= R32(x[13] + x[12], 13);
        x[15] ^= R32(x[14] + x[13], 18);
    }
    for (int i = 0; i < 16; i++)
        B[i] += x[i];
}

/* BlockMix_salsa8: B is 2r 64-byte blocks (32r words), Y same size */
static void blockmix(uint32_t *B, uint32_t *Y, unsigned r)
{
    uint32_t X[16];
    memcpy(X, B + (size_t)(2 * r - 1) * 16, 64);
    for (unsigned i = 0; i < 2 * r; i++) {
        for (int j = 0; j < 16; j++)
            X[j] ^= B[i * 16 + j];
        salsa20_8(X);
        memcpy(Y + i * 16, X, 64);
    }
    for (unsigned i = 0; i < r; i++) {
        memcpy(B + i * 16, Y + (i * 2) * 16, 64);
        memcpy(B + (i + r) * 16, Y + (i * 2 + 1) * 16, 64);
    }
}

/* ROMix: B is 32r words, V is N * 32r words of memory-hard scratch */
static void romix(uint32_t *B, uint32_t *V, uint32_t *Y,
                  uint64_t N, unsigned r)
{
    size_t bw = (size_t)32 * r;
    uint32_t *X = B;
    for (uint64_t i = 0; i < N; i++) {
        memcpy(V + i * bw, X, bw * 4);
        blockmix(X, Y, r);
    }
    for (uint64_t i = 0; i < N; i++) {
        /* Integerify(B): last 64-byte block as a little-endian int */
        uint64_t j = (uint64_t)X[(size_t)(2 * r - 1) * 16] |
                     (uint64_t)X[(size_t)(2 * r - 1) * 16 + 1] << 32;
        j &= N - 1;                     /* N is a power of two */
        for (size_t k = 0; k < bw; k++)
            X[k] ^= V[j * bw + k];
        blockmix(X, Y, r);
    }
}

int scrypt_kdf(const uint8_t *pw, size_t pwlen,
               const uint8_t *salt, size_t saltlen,
               uint64_t N, unsigned r, unsigned p,
               uint8_t *out, size_t outlen)
{
    if (N < 2 || (N & (N - 1)) != 0 || r < 1 || p < 1 || r > 64 ||
        p > 64 || outlen == 0)
        return -1;
    size_t bw = (size_t)32 * r;
    size_t blen = bw * 4 * p;           /* 128 * r * p bytes */
    uint8_t  *B = malloc(blen);
    uint32_t *W = malloc(blen);
    uint32_t *V = malloc(bw * 4 * (size_t)N);
    uint32_t *Y = malloc(bw * 4);
    if (!B || !W || !V || !Y) {
        free(B); free(W); free(V); free(Y);
        return -1;
    }
    pbkdf2_any(pw, pwlen, salt, saltlen, 1, B, blen);
    for (size_t i = 0; i < bw * p; i++)
        W[i] = le32d(B + i * 4);
    for (unsigned i = 0; i < p; i++)
        romix(W + i * bw, V, Y, N, r);
    for (size_t i = 0; i < bw * p; i++)
        le32e(B + i * 4, W[i]);
    pbkdf2_any(pw, pwlen, B, blen, 1, out, outlen);
    gt_bzero(B, blen);
    gt_bzero(W, blen);
    gt_bzero(V, bw * 4 * (size_t)N);
    gt_bzero(Y, bw * 4);
    free(B); free(W); free(V); free(Y);
    return 0;
}

/* ---------------- hex / random / compare ---------------- */

void to_hex(const uint8_t *in, size_t n, char *out)
{
    static const char H[] = "0123456789abcdef";
    for (size_t i = 0; i < n; i++) {
        out[i*2]   = H[in[i] >> 4];
        out[i*2+1] = H[in[i] & 15];
    }
    out[n*2] = '\0';
}

static int hexv(int c)
{
    if (c >= '0' && c <= '9') return c - '0';
    if (c >= 'a' && c <= 'f') return c - 'a' + 10;
    if (c >= 'A' && c <= 'F') return c - 'A' + 10;
    return -1;
}

int from_hex(const char *hex, uint8_t *out, size_t outlen)
{
    for (size_t i = 0; i < outlen; i++) {
        int hi = hexv(hex[i*2]), lo = hexv(hex[i*2+1]);
        if (hi < 0 || lo < 0)
            return -1;
        out[i] = (uint8_t)(hi * 16 + lo);
    }
    return 0;
}

void rand_bytes(void *buf, size_t n)
{
    uint8_t *p = buf;
    while (n) {
        ssize_t r = getrandom(p, n, 0);
        if (r > 0) {
            p += r;
            n -= (size_t)r;
            continue;
        }
        /* fallback: /dev/urandom */
        int fd = open("/dev/urandom", O_RDONLY);
        if (fd < 0)
            die("getrandom");
        ssize_t q = read(fd, p, n);
        close(fd);
        if (q <= 0)
            die("urandom");
        p += q;
        n -= (size_t)q;
    }
}

void rand_hex(char *out, size_t n)
{
    uint8_t *tmp = xmalloc(n);
    rand_bytes(tmp, n);
    to_hex(tmp, n, out);
    memset(tmp, 0, n);
    free(tmp);
}

int consteq(const void *a, const void *b, size_t n)
{
    const uint8_t *x = a, *y = b;
    uint8_t d = 0;
    for (size_t i = 0; i < n; i++)
        d |= (uint8_t)(x[i] ^ y[i]);
    return d == 0;
}

/* memory wipe the compiler cannot elide */
void gt_bzero(void *p, size_t n)
{
#if defined(__GLIBC__) || defined(__MUSL__) || defined(__OpenBSD__) || \
    defined(__FreeBSD__)
    explicit_bzero(p, n);
#else
    volatile uint8_t *v = p;
    while (n--)
        *v++ = 0;
#endif
}

/* ---------------- password storage ----------------
 * Versioned formats, dispatched by prefix:
 *   scrypt$<N>$<r>$<p>$<32-hex salt>$<64-hex hash>   (current)
 *   pbkdf2$<iters>$<64-hex salt>$<64-hex hash>       (legacy)
 * Verify accepts both; hash always emits scrypt unless
 * GIT_TRACE_KDF=pbkdf2 forces the legacy writer (migration tests).
 * pw_needs_rehash() flags rows still on a legacy scheme. */

#define PW_ITERS 100000
#define SC_N     32768          /* ~32 MiB of ROMix scratch */
#define SC_R     8
#define SC_P     1
/* verify-side caps: a tampered DB row may not memory-bomb us */
#define SC_N_MAX (1u << 18)

int pw_hash(const char *password, char *out, size_t outlen)
{
    uint8_t salt[16], dk[32];
    rand_bytes(salt, sizeof salt);
    const char *kdf = getenv("GIT_TRACE_KDF");
    if (kdf && strcmp(kdf, "pbkdf2") == 0) {
        pbkdf2_sha256(password, salt, sizeof salt, PW_ITERS, dk);
        char sh[33], dh[65];
        to_hex(salt, 16, sh);
        to_hex(dk, 32, dh);
        int rc = snprintf(out, outlen, "pbkdf2$%u$%s$%s",
                          PW_ITERS, sh, dh) < (int)outlen ? 0 : -1;
        gt_bzero(dk, sizeof dk);
        return rc;
    }
    if (scrypt_kdf((const uint8_t *)password, strlen(password),
                   salt, sizeof salt, SC_N, SC_R, SC_P, dk, 32) != 0)
        return -1;
    char sh[33], dh[65];
    to_hex(salt, 16, sh);
    to_hex(dk, 32, dh);
    int rc = snprintf(out, outlen, "scrypt$%u$%u$%u$%s$%s",
                      (unsigned)SC_N, SC_R, SC_P, sh, dh)
             < (int)outlen ? 0 : -1;
    gt_bzero(dk, sizeof dk);
    return rc;
}

int pw_verify(const char *password, const char *stored)
{
    if (strncmp(stored, "scrypt$", 7) == 0) {
        unsigned long long N;
        unsigned r, p;
        char sh[65], dh[65];
        if (sscanf(stored, "scrypt$%llu$%u$%u$%64[0-9a-f]$%64[0-9a-f]",
                   &N, &r, &p, sh, dh) != 5)
            return 0;
        if (N < 2 || N > SC_N_MAX || (N & (N - 1)) != 0 ||
            r < 1 || r > 16 || p < 1 || p > 16)
            return 0;
        size_t sl = strlen(sh) / 2;
        if (sl == 0 || sl > 32 || strlen(dh) != 64)
            return 0;
        uint8_t salt[32], want[32], dk[32];
        if (from_hex(sh, salt, sl) != 0 || from_hex(dh, want, 32) != 0)
            return 0;
        if (scrypt_kdf((const uint8_t *)password, strlen(password),
                       salt, sl, N, r, p, dk, 32) != 0)
            return 0;
        int ok = consteq(dk, want, 32);
        gt_bzero(dk, sizeof dk);
        return ok;
    }
    unsigned iters;
    char sh[65], dh[65];
    if (sscanf(stored, "pbkdf2$%u$%64[0-9a-f]$%64[0-9a-f]",
               &iters, sh, dh) != 3)
        return 0;
    if (iters < 1 || iters > 10000000)
        return 0;
    size_t sl = strlen(sh) / 2;
    if (sl == 0 || sl > 32 || strlen(dh) != 64)
        return 0;
    uint8_t salt[32], want[32], dk[32];
    if (from_hex(sh, salt, sl) != 0 || from_hex(dh, want, 32) != 0)
        return 0;
    pbkdf2_sha256(password, salt, sl, iters, dk);
    int ok = consteq(dk, want, 32);
    gt_bzero(dk, sizeof dk);
    return ok;
}

int pw_needs_rehash(const char *stored)
{
    return strncmp(stored, "scrypt$", 7) != 0;
}

const char *pw_scheme(const char *stored)
{
    if (strncmp(stored, "scrypt$", 7) == 0)
        return "scrypt";
    if (strncmp(stored, "pbkdf2$", 7) == 0)
        return "pbkdf2";
    return "unknown";
}

/* ---------------- base64 ---------------- */

long b64_decode(const char *in, size_t inlen, void *outp, size_t outcap)
{
    uint8_t *out = outp;
    size_t w = 0;
    uint32_t acc = 0;
    int bits = 0;
    for (size_t i = 0; i < inlen; i++) {
        int c = (unsigned char)in[i];
        int v;
        if (c >= 'A' && c <= 'Z')      v = c - 'A';
        else if (c >= 'a' && c <= 'z') v = c - 'a' + 26;
        else if (c >= '0' && c <= '9') v = c - '0' + 52;
        else if (c == '+')             v = 62;
        else if (c == '/')             v = 63;
        else if (c == '=')             break;
        else                           return -1;
        acc = (acc << 6) | (uint32_t)v;
        bits += 6;
        if (bits >= 8) {
            bits -= 8;
            if (w >= outcap)
                return -1;
            out[w++] = (uint8_t)(acc >> bits);
        }
    }
    return (long)w;
}

void b64_encode(const void *inp, size_t inlen, char *out, size_t outcap,
                int strip_padding)
{
    static const char T[] =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    const uint8_t *in = inp;
    size_t w = 0;
    for (size_t i = 0; i + 3 <= inlen; i += 3) {
        uint32_t v = (uint32_t)in[i] << 16 | (uint32_t)in[i+1] << 8 | in[i+2];
        if (w + 4 >= outcap) { out[0] = 0; return; }
        out[w++] = T[v >> 18];
        out[w++] = T[(v >> 12) & 63];
        out[w++] = T[(v >> 6) & 63];
        out[w++] = T[v & 63];
    }
    size_t rem = inlen % 3;
    if (rem) {
        uint32_t v = (uint32_t)in[inlen - rem] << 16;
        if (rem == 2)
            v |= (uint32_t)in[inlen - 1] << 8;
        if (w + 4 >= outcap) { out[0] = 0; return; }
        out[w++] = T[v >> 18];
        out[w++] = T[(v >> 12) & 63];
        if (rem == 2)
            out[w++] = T[(v >> 6) & 63];
        if (!strip_padding)
            for (size_t k = rem; k < 3; k++)
                out[w++] = '=';
    }
    out[w] = '\0';
}