DragonFlyBSD Kernel Audit
DF-0220 / ref_keystream.c
← back to finding ↓ download raw
/*
 * DF-0220 reference: compute the pre-reseed "degenerate" ChaCha20 keystream
 * that the csprng emits when reseed_cnt == 0.
 *
 * CRITICAL DETAIL: csprng_init() does bzero(&state->cipher_ctx, ...) and
 * chacha_keysetup() is NOT called before the first chacha_encrypt_bytes().
 * Therefore the cipher context's full input[0..15] is ZERO -- including the
 * 4 sigma-constant words that chacha_keysetup() would normally write at
 * input[0..3].  So the keystream is NOT "chacha20(key=0^32, ctr=0)"; it is
 * the even-more-degenerate all-zero-state keystream.
 *
 * This program reproduces the kernel's exact transform (the same chacha.c
 * compiled with CHACHA_NONCE0_CTR128 + KEYSTREAM_ONLY, as subr_csprng.c does)
 * on the all-zero state, for 64 bytes, and prints it as hex.
 *
 * Build:  cc -O2 -o ref_keystream ref_keystream.c
 * Run:    ./ref_keystream [Nbytes=64]
 */
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

typedef uint8_t  u8;
typedef uint32_t u32;

#define U8V(v)  ((u8)(v) & 0xFFu)
#define U32V(v) ((u32)(v) & 0xFFFFFFFFu)
#define ROTL32(v,n) (U32V((v)<<(n)) | ((v)>>(32-(n))))
#define ROTATE(v,c) ROTL32(v,c)
#define XOR(v,w) ((v)^(w))
#define PLUS(v,w) U32V((v)+(w))
#define PLUSONE(v) PLUS((v),1)
#define U32TO8_LITTLE(p,v) do{ (p)[0]=U8V((v)); (p)[1]=U8V((v)>>8); \
    (p)[2]=U8V((v)>>16); (p)[3]=U8V((v)>>24); }while(0)

#define QR(a,b,c,d) \
  a=PLUS(a,b); d=ROTATE(XOR(d,a),16); \
  c=PLUS(c,d); b=ROTATE(XOR(b,c),12); \
  a=PLUS(a,b); d=ROTATE(XOR(d,a), 8); \
  c=PLUS(c,d); b=ROTATE(XOR(b,c), 7);

/* Reproduce the EXACT block transform from sys/crypto/chacha20/chacha.c
 * lines 149-241 (20-round chacha, no message XOR = KEYSTREAM_ONLY, 128-bit
 * counter increment via CHACHA_NONCE0_CTR128). */
static void
chacha_block_zero(u32 in[16], u8 out[64])
{
    u32 x[16];
    int i;
    memcpy(x, in, sizeof(x));
    for (i = 20; i > 0; i -= 2) {
        QR(x[0],x[4],x[8], x[12]); QR(x[1],x[5],x[9], x[13]);
        QR(x[2],x[6],x[10],x[14]); QR(x[3],x[7],x[11],x[15]);
        QR(x[0],x[5],x[10],x[15]); QR(x[1],x[6],x[11],x[12]);
        QR(x[2],x[7],x[8], x[13]); QR(x[3],x[4],x[9], x[14]);
    }
    for (i = 0; i < 16; i++) {
        x[i] = PLUS(x[i], in[i]);
        U32TO8_LITTLE(out + 4*i, x[i]);
    }
}

int main(int argc, char **argv)
{
    int nbytes = 64;
    if (argc > 1) nbytes = atoi(argv[1]);
    if (nbytes <= 0) nbytes = 64;

    /* All-zero state, exactly as csprng_init leaves cipher_ctx (and as
     * subr_csprng.c:155 chacha_encrypt_bytes runs on it pre-reseed). */
    u32 in[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
    u8 blk[64];
    int off = 0, i;

    printf("ref_preseed_keystream (%d bytes, all-zero chacha input[16]):", nbytes);
    while (off < nbytes) {
        chacha_block_zero(in, blk);
        for (i = 0; i < 64 && off < nbytes; i++, off++) {
            if (off % 16 == 0) printf("\n  %04x:", off);
            printf(" %02x", blk[i]);
        }
        /* CHACHA_NONCE0_CTR128 128-bit counter increment (chacha.c:211-220) */
        in[12] = PLUSONE(in[12]);
        if (!in[12]) { in[13]=PLUSONE(in[13]); if(!in[13]){ in[14]=PLUSONE(in[14]);
            if(!in[14]){ in[15]=PLUSONE(in[15]); } } }
    }
    printf("\n");
    return 0;
}