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 }