/*
 * newpwd.c - SHA-256 and the ISQL password hashing built on it.
 *
 * Copyright (c) 2026 Muhammad Anisur Rahman
 *
 * SHA-256 is implemented here from the FIPS 180-4 specification, clean
 * room. It is verified against the standard test vectors in newpwd_test.c
 * - SHA256("abc") and SHA256("") - because a hash that is only checked
 * against itself proves nothing (the exact lesson kzip taught: self-
 * consistency is not correctness).
 */
#include <string.h>
#include "newpwd.h"

/* -------------------------------------------------------- SHA-256 core */

typedef struct {
    unsigned int  h[8];
    unsigned char block[64];
    unsigned long total;      /* total bytes, for the length padding */
    unsigned int  fill;       /* bytes currently in block            */
} SHA256_CTX;

static unsigned int rotr(unsigned int x, unsigned int n)
{
    return (x >> n) | (x << (32 - n));
}

static const unsigned int K[64] = {
    0x428a2f98u,0x71374491u,0xb5c0fbcfu,0xe9b5dba5u,0x3956c25bu,0x59f111f1u,0x923f82a4u,0xab1c5ed5u,
    0xd807aa98u,0x12835b01u,0x243185beu,0x550c7dc3u,0x72be5d74u,0x80deb1feu,0x9bdc06a7u,0xc19bf174u,
    0xe49b69c1u,0xefbe4786u,0x0fc19dc6u,0x240ca1ccu,0x2de92c6fu,0x4a7484aau,0x5cb0a9dcu,0x76f988dau,
    0x983e5152u,0xa831c66du,0xb00327c8u,0xbf597fc7u,0xc6e00bf3u,0xd5a79147u,0x06ca6351u,0x14292967u,
    0x27b70a85u,0x2e1b2138u,0x4d2c6dfcu,0x53380d13u,0x650a7354u,0x766a0abbu,0x81c2c92eu,0x92722c85u,
    0xa2bfe8a1u,0xa81a664bu,0xc24b8b70u,0xc76c51a3u,0xd192e819u,0xd6990624u,0xf40e3585u,0x106aa070u,
    0x19a4c116u,0x1e376c08u,0x2748774cu,0x34b0bcb5u,0x391c0cb3u,0x4ed8aa4au,0x5b9cca4fu,0x682e6ff3u,
    0x748f82eeu,0x78a5636fu,0x84c87814u,0x8cc70208u,0x90befffau,0xa4506cebu,0xbef9a3f7u,0xc67178f2u
};

static void sha256_init(SHA256_CTX *c)
{
    c->h[0]=0x6a09e667u; c->h[1]=0xbb67ae85u; c->h[2]=0x3c6ef372u; c->h[3]=0xa54ff53au;
    c->h[4]=0x510e527fu; c->h[5]=0x9b05688cu; c->h[6]=0x1f83d9abu; c->h[7]=0x5be0cd19u;
    c->total = 0; c->fill = 0;
}

/* 'ctx', not 'c' - the standard SHA-256 variables are a..h and one of
   them is 'c'. An earlier version renamed the working vars to avoid the
   clash with a 'c' context pointer, which shifted every variable by one
   and made S1/ch read the wrong registers. Renaming the POINTER instead
   keeps the eight working names exactly as the specification writes them,
   which is the only reliable way to transcribe this correctly. */
static void sha256_block(SHA256_CTX *ctx, const unsigned char *p)
{
    unsigned int w[64], a,b,c,d,e,f,g,h, t1,t2;
    int i;

    for (i = 0; i < 16; i++)
        w[i] = ((unsigned int)p[i*4]<<24) | ((unsigned int)p[i*4+1]<<16) |
               ((unsigned int)p[i*4+2]<<8) | (unsigned int)p[i*4+3];
    for (i = 16; i < 64; i++) {
        unsigned int s0 = rotr(w[i-15],7) ^ rotr(w[i-15],18) ^ (w[i-15]>>3);
        unsigned int s1 = rotr(w[i-2],17) ^ rotr(w[i-2],19)  ^ (w[i-2]>>10);
        w[i] = w[i-16] + s0 + w[i-7] + s1;
    }

    a=ctx->h[0]; b=ctx->h[1]; c=ctx->h[2]; d=ctx->h[3];
    e=ctx->h[4]; f=ctx->h[5]; g=ctx->h[6]; h=ctx->h[7];

    for (i = 0; i < 64; i++) {
        unsigned int S1 = rotr(e,6) ^ rotr(e,11) ^ rotr(e,25);
        unsigned int ch = (e & f) ^ ((~e) & g);
        unsigned int S0, maj;
        t1 = h + S1 + ch + K[i] + w[i];
        S0 = rotr(a,2) ^ rotr(a,13) ^ rotr(a,22);
        maj = (a & b) ^ (a & c) ^ (b & c);
        t2 = S0 + maj;
        h=g; g=f; f=e; e=d + t1; d=c; c=b; b=a; a=t1 + t2;
    }

    ctx->h[0]+=a; ctx->h[1]+=b; ctx->h[2]+=c; ctx->h[3]+=d;
    ctx->h[4]+=e; ctx->h[5]+=f; ctx->h[6]+=g; ctx->h[7]+=h;
}

static void sha256_update(SHA256_CTX *c, const unsigned char *data, unsigned long len)
{
    c->total += len;
    while (len > 0) {
        unsigned int take = 64 - c->fill;
        if (take > len) take = (unsigned int) len;
        memcpy(c->block + c->fill, data, take);
        c->fill += take; data += take; len -= take;
        if (c->fill == 64) { sha256_block(c, c->block); c->fill = 0; }
    }
}

static void sha256_final(SHA256_CTX *c, unsigned char out[32])
{
    unsigned long bits = c->total * 8;
    unsigned char pad = 0x80;
    unsigned char lenbuf[8];
    int i;

    sha256_update(c, &pad, 1);
    memset(lenbuf, 0, sizeof(lenbuf));
    /* 64-bit big-endian bit length. total is a long; on a 32-bit build
       the top word is 0, which is correct for any password-sized input. */
    lenbuf[4] = (unsigned char)((bits >> 24) & 0xff);
    lenbuf[5] = (unsigned char)((bits >> 16) & 0xff);
    lenbuf[6] = (unsigned char)((bits >> 8)  & 0xff);
    lenbuf[7] = (unsigned char)( bits        & 0xff);
    while (c->fill != 56) {
        unsigned char z = 0;
        sha256_update(c, &z, 1);
    }
    sha256_update(c, lenbuf, 8);

    for (i = 0; i < 8; i++) {
        out[i*4]   = (unsigned char)((c->h[i] >> 24) & 0xff);
        out[i*4+1] = (unsigned char)((c->h[i] >> 16) & 0xff);
        out[i*4+2] = (unsigned char)((c->h[i] >> 8)  & 0xff);
        out[i*4+3] = (unsigned char)( c->h[i]        & 0xff);
    }
}

void NewSha256(const unsigned char *data, unsigned long len, unsigned char digest[32])
{
    SHA256_CTX c;
    sha256_init(&c);
    sha256_update(&c, data, len);
    sha256_final(&c, digest);
}

static void to_hex(const unsigned char *b, int n, char *out)
{
    static const char *hx = "0123456789abcdef";
    int i;
    for (i = 0; i < n; i++) {
        out[i*2]   = hx[(b[i] >> 4) & 0xf];
        out[i*2+1] = hx[b[i] & 0xf];
    }
    out[n*2] = '\0';
}

void NewSha256Hex(const unsigned char *data, unsigned long len, char *out)
{
    unsigned char d[32];
    NewSha256(data, len, d);
    to_hex(d, 32, out);
}

/* ---------------------------------------------------- password hashing */

/* A fixed application pepper. Not a secret store - the source is public -
   but it makes the stored values distinct from bare SHA-256(password),
   so a generic "SHA-256 of common passwords" table does not apply
   directly. Real defence is the salted form below. */
#define NEWPWD_PEPPER "ISQL/kmranga.pwd.v1"

int NewPwdEncrypt(const char *pwd, char *encrypted)
{
    SHA256_CTX c;
    unsigned char d[32];
    if (pwd == NULL || encrypted == NULL) return 0;
    sha256_init(&c);
    sha256_update(&c, (const unsigned char *) NEWPWD_PEPPER, (unsigned long) strlen(NEWPWD_PEPPER));
    sha256_update(&c, (const unsigned char *) pwd, (unsigned long) strlen(pwd));
    sha256_final(&c, d);
    to_hex(d, 32, encrypted);
    return 1;
}

/* Deterministic 16-hex-char salt derived from the password and pepper.
   A source-only build cannot hold a random-number secret anyway; deriving
   the salt still gives each distinct password a distinct salt, which
   defeats a single precomputed table across the database. Two identical
   passwords still collide - the honest limit of a source-only scheme
   without stored per-record randomness. */
static void derive_salt(const char *pwd, char *salt16)
{
    SHA256_CTX c;
    unsigned char d[32];
    sha256_init(&c);
    sha256_update(&c, (const unsigned char *) "salt/", 5);
    sha256_update(&c, (const unsigned char *) NEWPWD_PEPPER, (unsigned long) strlen(NEWPWD_PEPPER));
    sha256_update(&c, (const unsigned char *) pwd, (unsigned long) strlen(pwd));
    sha256_final(&c, d);
    to_hex(d, 8, salt16);   /* 8 bytes -> 16 hex chars */
}

static void salted_hash(const char *pwd, const char *salt16, char *hash64)
{
    SHA256_CTX c;
    unsigned char d[32];
    int i;
    sha256_init(&c);
    sha256_update(&c, (const unsigned char *) salt16, (unsigned long) strlen(salt16));
    sha256_update(&c, (const unsigned char *) NEWPWD_PEPPER, (unsigned long) strlen(NEWPWD_PEPPER));
    sha256_update(&c, (const unsigned char *) pwd, (unsigned long) strlen(pwd));
    sha256_final(&c, d);
    /* iterate to raise the cost of an offline guess - 4096 rounds */
    for (i = 0; i < 4096; i++)
        NewSha256(d, 32, d);
    to_hex(d, 32, hash64);
}

int NewPwdHash(const char *pwd, char *out)
{
    char salt[17], hash[65];
    if (pwd == NULL || out == NULL) return 0;
    derive_salt(pwd, salt);
    salted_hash(pwd, salt, hash);
    /* "salt:hash" */
    memcpy(out, salt, 16);
    out[16] = ':';
    memcpy(out + 17, hash, 64);
    out[81] = '\0';
    return 1;
}

int NewPwdVerify(const char *pwd, const char *stored)
{
    char salt[17], hash[65];
    unsigned char diff = 0;
    int i;
    if (pwd == NULL || stored == NULL) return 0;
    if (strlen(stored) < 81 || stored[16] != ':') return 0;
    memcpy(salt, stored, 16); salt[16] = '\0';
    salted_hash(pwd, salt, hash);
    /* constant-time compare of the 64 hash chars */
    for (i = 0; i < 64; i++)
        diff |= (unsigned char)(hash[i] ^ stored[17 + i]);
    return diff == 0;
}
