kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

minisig.c (10769B)


      1 #include "minisig.h"
      2 
      3 #include <stdio.h>
      4 #include <string.h>
      5 
      6 #include "b64.h"
      7 #include "blake2b.h"
      8 #include "ed25519.h"
      9 
     10 #define U_PREFIX "untrusted comment: "
     11 #define T_PREFIX "trusted comment: "
     12 
     13 /* minisign algorithm tags. The key's sig_alg is "Ed"; signatures we emit are
     14  * prehashed, so their tag is "ED". */
     15 #define MS_SIGALG "Ed"
     16 #define MS_SIGALG_HASHED "ED"
     17 #define MS_KDFALG_SCRYPT "Sc"
     18 #define MS_CHKALG "B2"
     19 #define MS_ALG_LEN 2u
     20 #define MS_SALT_LEN 32u
     21 #define MS_LIMIT_LEN 8u
     22 #define MS_CHK_LEN 32u /* BLAKE2b-256 checksum */
     23 
     24 /* Public-key payload: sig_alg || keyid || pk. */
     25 #define KEY_BLOB_LEN (MS_ALG_LEN + DIST_KEYID_LEN + DIST_ED25519_PK_LEN)
     26 /* Signature line-1 payload: sig_alg || keyid || sig. */
     27 #define SIG_LINE1_LEN (MS_ALG_LEN + DIST_KEYID_LEN + DIST_ED25519_SIG_LEN)
     28 /* Secret-key payload (passwordless layout). */
     29 #define SK_KDF_OFF MS_ALG_LEN
     30 #define SK_CHKALG_OFF (SK_KDF_OFF + MS_ALG_LEN)
     31 #define SK_SALT_OFF (SK_CHKALG_OFF + MS_ALG_LEN)
     32 #define SK_OPS_OFF (SK_SALT_OFF + MS_SALT_LEN)
     33 #define SK_MEM_OFF (SK_OPS_OFF + MS_LIMIT_LEN)
     34 #define SK_KEYID_OFF (SK_MEM_OFF + MS_LIMIT_LEN)
     35 #define SK_SK_OFF (SK_KEYID_OFF + DIST_KEYID_LEN)
     36 #define SK_CHK_OFF (SK_SK_OFF + DIST_ED25519_SK_LEN)
     37 #define SKEY_BLOB_LEN (SK_CHK_OFF + MS_CHK_LEN)
     38 #define SIG_LINE_MAX 1024u
     39 #define B64_SCRATCH 320u
     40 
     41 void dist_minisig_keygen(DistKeypair* out,
     42                          const uint8_t seed[DIST_ED25519_SEED_LEN],
     43                          const uint8_t keyid[DIST_KEYID_LEN]) {
     44   dist_ed25519_keypair(out->pk, out->sk, seed);
     45   memcpy(out->keyid, keyid, DIST_KEYID_LEN);
     46 }
     47 
     48 /* The minisign secret-key checksum: BLAKE2b-256 over sig_alg || keyid || sk. */
     49 static void seckey_checksum(uint8_t out[MS_CHK_LEN],
     50                             const uint8_t keyid[DIST_KEYID_LEN],
     51                             const uint8_t sk[DIST_ED25519_SK_LEN]) {
     52   uint8_t buf[MS_ALG_LEN + DIST_KEYID_LEN + DIST_ED25519_SK_LEN];
     53   DistBlake2b h;
     54   memcpy(buf, MS_SIGALG, MS_ALG_LEN);
     55   memcpy(buf + MS_ALG_LEN, keyid, DIST_KEYID_LEN);
     56   memcpy(buf + MS_ALG_LEN + DIST_KEYID_LEN, sk, DIST_ED25519_SK_LEN);
     57   dist_blake2b_init(&h, MS_CHK_LEN);
     58   dist_blake2b_update(&h, buf, sizeof buf);
     59   dist_blake2b_final(&h, out);
     60 }
     61 
     62 static int emit_str(KitWriter* out, const char* s) {
     63   return kit_writer_write(out, s, strlen(s)) == KIT_OK ? DIST_OK : DIST_ERR;
     64 }
     65 
     66 static int emit_b64_line(KitWriter* out, const uint8_t* blob, size_t n) {
     67   char b64[B64_SCRATCH];
     68   dist_b64_encode(b64, blob, n);
     69   if (emit_str(out, b64) != DIST_OK) return DIST_ERR;
     70   return emit_str(out, "\n");
     71 }
     72 
     73 int dist_minisig_emit_pubkey(KitWriter* out, const DistKeypair* kp) {
     74   char line[SIG_LINE_MAX];
     75   char hex[2 * DIST_KEYID_LEN + 1];
     76   uint8_t blob[KEY_BLOB_LEN];
     77   dist_hex_encode(hex, kp->keyid, DIST_KEYID_LEN);
     78   snprintf(line, sizeof line, U_PREFIX "kit public key %s\n", hex);
     79   if (emit_str(out, line) != DIST_OK) return DIST_ERR;
     80   memcpy(blob, MS_SIGALG, MS_ALG_LEN);
     81   memcpy(blob + MS_ALG_LEN, kp->keyid, DIST_KEYID_LEN);
     82   memcpy(blob + MS_ALG_LEN + DIST_KEYID_LEN, kp->pk, DIST_ED25519_PK_LEN);
     83   return emit_b64_line(out, blob, sizeof blob);
     84 }
     85 
     86 int dist_minisig_emit_seckey(KitWriter* out, const DistKeypair* kp) {
     87   char line[SIG_LINE_MAX];
     88   char hex[2 * DIST_KEYID_LEN + 1];
     89   uint8_t blob[SKEY_BLOB_LEN];
     90 
     91   dist_hex_encode(hex, kp->keyid, DIST_KEYID_LEN);
     92   snprintf(line, sizeof line, U_PREFIX "kit secret key %s\n", hex);
     93   if (emit_str(out, line) != DIST_OK) return DIST_ERR;
     94 
     95   memset(blob, 0, sizeof blob);
     96   memcpy(blob, MS_SIGALG, MS_ALG_LEN);
     97   /* kdf_alg [SK_KDF_OFF] left zero: passwordless, no scrypt. salt/opslimit/
     98    * memlimit are unused when kdf_alg is zero, so they stay zero. */
     99   memcpy(blob + SK_CHKALG_OFF, MS_CHKALG, MS_ALG_LEN);
    100   memcpy(blob + SK_KEYID_OFF, kp->keyid, DIST_KEYID_LEN);
    101   memcpy(blob + SK_SK_OFF, kp->sk, DIST_ED25519_SK_LEN);
    102   seckey_checksum(blob + SK_CHK_OFF, kp->keyid, kp->sk);
    103   return emit_b64_line(out, blob, sizeof blob);
    104 }
    105 
    106 /* Copy the idx-th line (0-based) into `out`, trimmed of trailing CR/LF. */
    107 static int sig_line(const uint8_t* data, size_t len, size_t idx, char* out,
    108                     size_t cap) {
    109   size_t pos = 0, cur = 0;
    110   while (pos < len) {
    111     size_t end = pos;
    112     while (end < len && data[end] != '\n') ++end;
    113     if (cur == idx) {
    114       size_t n = end - pos;
    115       if (n >= cap) return DIST_ERR;
    116       memcpy(out, data + pos, n);
    117       out[n] = '\0';
    118       while (n && (out[n - 1] == '\r')) out[--n] = '\0';
    119       return DIST_OK;
    120     }
    121     pos = (end < len) ? end + 1 : end;
    122     ++cur;
    123   }
    124   return DIST_ERR;
    125 }
    126 
    127 /* Decode the base64 on line `idx` into `out`, requiring exactly `want` bytes.
    128  */
    129 static int decode_line(const uint8_t* data, size_t len, size_t idx,
    130                        uint8_t* out, size_t want) {
    131   char line[SIG_LINE_MAX];
    132   size_t got = 0;
    133   if (sig_line(data, len, idx, line, sizeof line) != DIST_OK) return DIST_ERR;
    134   if (dist_b64_decode(out, &got, line, strlen(line)) != DIST_OK)
    135     return DIST_ERR;
    136   return got == want ? DIST_OK : DIST_ERR;
    137 }
    138 
    139 static void minisig_prehash(uint8_t out[DIST_MINISIG_PREHASH_LEN],
    140                             const uint8_t* msg, size_t msglen) {
    141   DistBlake2b h;
    142   dist_blake2b_init(&h, DIST_MINISIG_PREHASH_LEN);
    143   dist_blake2b_update(&h, msg, msglen);
    144   dist_blake2b_final(&h, out);
    145 }
    146 
    147 int dist_minisig_parse_pubkey(const uint8_t* data, size_t len,
    148                               uint8_t pk[DIST_ED25519_PK_LEN],
    149                               uint8_t keyid[DIST_KEYID_LEN]) {
    150   uint8_t blob[KEY_BLOB_LEN];
    151   if (decode_line(data, len, 1, blob, sizeof blob) != DIST_OK) return DIST_ERR;
    152   if (memcmp(blob, MS_SIGALG, MS_ALG_LEN) != 0) return DIST_ERR;
    153   memcpy(keyid, blob + MS_ALG_LEN, DIST_KEYID_LEN);
    154   memcpy(pk, blob + MS_ALG_LEN + DIST_KEYID_LEN, DIST_ED25519_PK_LEN);
    155   return DIST_OK;
    156 }
    157 
    158 int dist_minisig_parse_seckey(const uint8_t* data, size_t len,
    159                               uint8_t sk[DIST_ED25519_SK_LEN],
    160                               uint8_t keyid[DIST_KEYID_LEN]) {
    161   uint8_t blob[SKEY_BLOB_LEN];
    162   uint8_t chk[MS_CHK_LEN];
    163   if (decode_line(data, len, 1, blob, sizeof blob) != DIST_OK) return DIST_ERR;
    164   if (memcmp(blob, MS_SIGALG, MS_ALG_LEN) != 0) return DIST_ERR;
    165   /* Encrypted secret keys (kdf_alg = "Sc") need scrypt, not yet vendored. */
    166   if (memcmp(blob + SK_KDF_OFF, MS_KDFALG_SCRYPT, MS_ALG_LEN) == 0)
    167     return DIST_ENCRYPTED;
    168   /* Otherwise require the passwordless form (kdf_alg = {0,0}). */
    169   if (blob[SK_KDF_OFF] != 0 || blob[SK_KDF_OFF + 1] != 0) return DIST_ERR;
    170   memcpy(keyid, blob + SK_KEYID_OFF, DIST_KEYID_LEN);
    171   memcpy(sk, blob + SK_SK_OFF, DIST_ED25519_SK_LEN);
    172   /* Verify the BLAKE2b checksum over the (cleartext) key material. */
    173   seckey_checksum(chk, keyid, sk);
    174   if (memcmp(chk, blob + SK_CHK_OFF, MS_CHK_LEN) != 0) return DIST_ERR;
    175   return DIST_OK;
    176 }
    177 
    178 int dist_minisig_sign(KitWriter* out, const uint8_t* msg, size_t msglen,
    179                       const uint8_t sk[DIST_ED25519_SK_LEN],
    180                       const uint8_t keyid[DIST_KEYID_LEN],
    181                       const char* untrusted_comment,
    182                       const char* trusted_comment) {
    183   uint8_t prehash[DIST_MINISIG_PREHASH_LEN];
    184   uint8_t sig[DIST_ED25519_SIG_LEN];
    185   uint8_t gsig[DIST_ED25519_SIG_LEN];
    186   uint8_t line1[SIG_LINE1_LEN];
    187   uint8_t gmsg[DIST_ED25519_SIG_LEN + DIST_TRUSTED_COMMENT_MAX];
    188   size_t tclen = strlen(trusted_comment);
    189   char line[SIG_LINE_MAX];
    190 
    191   if (tclen >= DIST_TRUSTED_COMMENT_MAX) return DIST_ERR;
    192 
    193   /* Signature over stock minisign's 64-byte BLAKE2b prehash. */
    194   minisig_prehash(prehash, msg, msglen);
    195   dist_ed25519_sign(sig, prehash, sizeof prehash, sk);
    196 
    197   /* Global signature also covers the trusted comment. */
    198   memcpy(gmsg, sig, DIST_ED25519_SIG_LEN);
    199   memcpy(gmsg + DIST_ED25519_SIG_LEN, trusted_comment, tclen);
    200   dist_ed25519_sign(gsig, gmsg, DIST_ED25519_SIG_LEN + tclen, sk);
    201 
    202   snprintf(line, sizeof line, U_PREFIX "%s\n", untrusted_comment);
    203   if (emit_str(out, line) != DIST_OK) return DIST_ERR;
    204 
    205   memcpy(line1, MS_SIGALG_HASHED, MS_ALG_LEN); /* "ED": prehashed */
    206   memcpy(line1 + MS_ALG_LEN, keyid, DIST_KEYID_LEN);
    207   memcpy(line1 + MS_ALG_LEN + DIST_KEYID_LEN, sig, DIST_ED25519_SIG_LEN);
    208   if (emit_b64_line(out, line1, sizeof line1) != DIST_OK) return DIST_ERR;
    209 
    210   snprintf(line, sizeof line, T_PREFIX "%s\n", trusted_comment);
    211   if (emit_str(out, line) != DIST_OK) return DIST_ERR;
    212 
    213   return emit_b64_line(out, gsig, sizeof gsig);
    214 }
    215 
    216 int dist_minisig_sig_keyid(const uint8_t* sig, size_t siglen,
    217                            uint8_t out_keyid[DIST_KEYID_LEN]) {
    218   uint8_t blob[SIG_LINE1_LEN];
    219   if (decode_line(sig, siglen, 1, blob, sizeof blob) != DIST_OK)
    220     return DIST_ERR;
    221   /* Accept either the prehashed ("ED") or legacy ("Ed") tag. */
    222   if (memcmp(blob, MS_SIGALG_HASHED, MS_ALG_LEN) != 0 &&
    223       memcmp(blob, MS_SIGALG, MS_ALG_LEN) != 0)
    224     return DIST_ERR;
    225   memcpy(out_keyid, blob + MS_ALG_LEN, DIST_KEYID_LEN);
    226   return DIST_OK;
    227 }
    228 
    229 int dist_minisig_verify(const uint8_t* sig, size_t siglen, const uint8_t* msg,
    230                         size_t msglen, const uint8_t pk[DIST_ED25519_PK_LEN],
    231                         char* out_trusted, size_t trusted_cap) {
    232   uint8_t blob[SIG_LINE1_LEN];
    233   uint8_t gsig[DIST_ED25519_SIG_LEN];
    234   uint8_t prehash[DIST_MINISIG_PREHASH_LEN];
    235   uint8_t gmsg[DIST_ED25519_SIG_LEN + DIST_TRUSTED_COMMENT_MAX];
    236   char tcline[SIG_LINE_MAX];
    237   const char* tc;
    238   size_t tclen;
    239 
    240   int prehashed;
    241   const uint8_t* sigbytes = blob + MS_ALG_LEN + DIST_KEYID_LEN;
    242 
    243   if (decode_line(sig, siglen, 1, blob, sizeof blob) != DIST_OK)
    244     return DIST_ERR;
    245   prehashed = (memcmp(blob, MS_SIGALG_HASHED, MS_ALG_LEN) == 0);
    246   if (!prehashed && memcmp(blob, MS_SIGALG, MS_ALG_LEN) != 0) return DIST_ERR;
    247 
    248   /* "ED" signs stock minisign's 64-byte BLAKE2b prehash; "Ed" signs raw. */
    249   if (prehashed) {
    250     minisig_prehash(prehash, msg, msglen);
    251     if (!dist_ed25519_verify(sigbytes, prehash, sizeof prehash, pk))
    252       return DIST_ERR;
    253   } else if (!dist_ed25519_verify(sigbytes, msg, msglen, pk)) {
    254     return DIST_ERR;
    255   }
    256 
    257   /* Recover and verify the trusted comment via the global signature. */
    258   if (sig_line(sig, siglen, 2, tcline, sizeof tcline) != DIST_OK)
    259     return DIST_ERR;
    260   if (strncmp(tcline, T_PREFIX, strlen(T_PREFIX)) != 0) return DIST_ERR;
    261   tc = tcline + strlen(T_PREFIX);
    262   tclen = strlen(tc);
    263   if (tclen >= DIST_TRUSTED_COMMENT_MAX) return DIST_ERR;
    264   if (decode_line(sig, siglen, 3, gsig, sizeof gsig) != DIST_OK)
    265     return DIST_ERR;
    266 
    267   memcpy(gmsg, sigbytes, DIST_ED25519_SIG_LEN);
    268   memcpy(gmsg + DIST_ED25519_SIG_LEN, tc, tclen);
    269   if (!dist_ed25519_verify(gsig, gmsg, DIST_ED25519_SIG_LEN + tclen, pk))
    270     return DIST_ERR;
    271 
    272   if (out_trusted && trusted_cap) snprintf(out_trusted, trusted_cap, "%s", tc);
    273   return DIST_OK;
    274 }