GCC Code Coverage Report | |||||||||||||||||||||
|
|||||||||||||||||||||
Line | Branch | Exec | Source |
1 |
/* $OpenBSD: signify.c,v 1.128 2017/07/11 23:27:13 tedu Exp $ */ |
||
2 |
/* |
||
3 |
* Copyright (c) 2013 Ted Unangst <tedu@openbsd.org> |
||
4 |
* |
||
5 |
* Permission to use, copy, modify, and distribute this software for any |
||
6 |
* purpose with or without fee is hereby granted, provided that the above |
||
7 |
* copyright notice and this permission notice appear in all copies. |
||
8 |
* |
||
9 |
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
||
10 |
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
||
11 |
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
||
12 |
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
||
13 |
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
||
14 |
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
||
15 |
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
||
16 |
*/ |
||
17 |
#include <sys/stat.h> |
||
18 |
|||
19 |
#include <netinet/in.h> |
||
20 |
#include <resolv.h> |
||
21 |
|||
22 |
#include <limits.h> |
||
23 |
#include <stdint.h> |
||
24 |
#include <fcntl.h> |
||
25 |
#include <string.h> |
||
26 |
#include <stdio.h> |
||
27 |
#include <stdlib.h> |
||
28 |
#include <stddef.h> |
||
29 |
#include <ohash.h> |
||
30 |
#include <err.h> |
||
31 |
#include <unistd.h> |
||
32 |
#include <readpassphrase.h> |
||
33 |
#include <util.h> |
||
34 |
#include <sha2.h> |
||
35 |
|||
36 |
#include "crypto_api.h" |
||
37 |
#include "signify.h" |
||
38 |
|||
39 |
#define SIGBYTES crypto_sign_ed25519_BYTES |
||
40 |
#define SECRETBYTES crypto_sign_ed25519_SECRETKEYBYTES |
||
41 |
#define PUBLICBYTES crypto_sign_ed25519_PUBLICKEYBYTES |
||
42 |
|||
43 |
#define PKALG "Ed" |
||
44 |
#define KDFALG "BK" |
||
45 |
#define KEYNUMLEN 8 |
||
46 |
|||
47 |
#define COMMENTHDR "untrusted comment: " |
||
48 |
#define COMMENTHDRLEN 19 |
||
49 |
#define COMMENTMAXLEN 1024 |
||
50 |
#define VERIFYWITH "verify with " |
||
51 |
|||
52 |
struct enckey { |
||
53 |
uint8_t pkalg[2]; |
||
54 |
uint8_t kdfalg[2]; |
||
55 |
uint32_t kdfrounds; |
||
56 |
uint8_t salt[16]; |
||
57 |
uint8_t checksum[8]; |
||
58 |
uint8_t keynum[KEYNUMLEN]; |
||
59 |
uint8_t seckey[SECRETBYTES]; |
||
60 |
}; |
||
61 |
|||
62 |
struct pubkey { |
||
63 |
uint8_t pkalg[2]; |
||
64 |
uint8_t keynum[KEYNUMLEN]; |
||
65 |
uint8_t pubkey[PUBLICBYTES]; |
||
66 |
}; |
||
67 |
|||
68 |
struct sig { |
||
69 |
uint8_t pkalg[2]; |
||
70 |
uint8_t keynum[KEYNUMLEN]; |
||
71 |
uint8_t sig[SIGBYTES]; |
||
72 |
}; |
||
73 |
|||
74 |
static void __dead |
||
75 |
usage(const char *error) |
||
76 |
{ |
||
77 |
if (error) |
||
78 |
fprintf(stderr, "%s\n", error); |
||
79 |
fprintf(stderr, "usage:" |
||
80 |
#ifndef VERIFYONLY |
||
81 |
"\t%1$s -C [-q] -p pubkey -x sigfile [file ...]\n" |
||
82 |
"\t%1$s -G [-n] [-c comment] -p pubkey -s seckey\n" |
||
83 |
"\t%1$s -S [-ez] [-x sigfile] -s seckey -m message\n" |
||
84 |
#endif |
||
85 |
"\t%1$s -V [-eqz] [-p pubkey] [-t keytype] [-x sigfile] -m message\n", |
||
86 |
getprogname()); |
||
87 |
exit(1); |
||
88 |
} |
||
89 |
|||
90 |
int |
||
91 |
xopen(const char *fname, int oflags, mode_t mode) |
||
92 |
{ |
||
93 |
214 |
struct stat sb; |
|
94 |
int fd; |
||
95 |
|||
96 |
✓✓ | 107 |
if (strcmp(fname, "-") == 0) { |
97 |
✓✓ | 14 |
if ((oflags & O_WRONLY)) |
98 |
5 |
fd = dup(STDOUT_FILENO); |
|
99 |
else |
||
100 |
9 |
fd = dup(STDIN_FILENO); |
|
101 |
✗✓ | 14 |
if (fd == -1) |
102 |
err(1, "dup failed"); |
||
103 |
} else { |
||
104 |
93 |
fd = open(fname, oflags, mode); |
|
105 |
✗✓ | 93 |
if (fd == -1) |
106 |
err(1, "can't open %s for %s", fname, |
||
107 |
(oflags & O_WRONLY) ? "writing" : "reading"); |
||
108 |
} |
||
109 |
✓✗✗✓ |
214 |
if (fstat(fd, &sb) == -1 || S_ISDIR(sb.st_mode)) |
110 |
errx(1, "not a valid file: %s", fname); |
||
111 |
107 |
return fd; |
|
112 |
107 |
} |
|
113 |
|||
114 |
void * |
||
115 |
xmalloc(size_t len) |
||
116 |
{ |
||
117 |
void *p; |
||
118 |
|||
119 |
✗✓ | 216 |
if (!(p = malloc(len))) |
120 |
err(1, "malloc %zu", len); |
||
121 |
108 |
return p; |
|
122 |
} |
||
123 |
|||
124 |
static size_t |
||
125 |
parseb64file(const char *filename, char *b64, void *buf, size_t buflen, |
||
126 |
char *comment) |
||
127 |
{ |
||
128 |
char *commentend, *b64end; |
||
129 |
|||
130 |
116 |
commentend = strchr(b64, '\n'); |
|
131 |
✓✗✓✗ ✗✓ |
174 |
if (!commentend || commentend - b64 <= COMMENTHDRLEN || |
132 |
58 |
memcmp(b64, COMMENTHDR, COMMENTHDRLEN) != 0) |
|
133 |
errx(1, "invalid comment in %s; must start with '%s'", |
||
134 |
filename, COMMENTHDR); |
||
135 |
58 |
*commentend = '\0'; |
|
136 |
✓✓ | 58 |
if (comment) { |
137 |
✗✓ | 74 |
if (strlcpy(comment, b64 + COMMENTHDRLEN, |
138 |
37 |
COMMENTMAXLEN) >= COMMENTMAXLEN) |
|
139 |
errx(1, "comment too long"); |
||
140 |
} |
||
141 |
✗✓ | 58 |
if (!(b64end = strchr(commentend + 1, '\n'))) |
142 |
errx(1, "missing new line after base64 in %s", filename); |
||
143 |
58 |
*b64end = '\0'; |
|
144 |
✗✓ | 58 |
if (b64_pton(commentend + 1, buf, buflen) != buflen) |
145 |
errx(1, "invalid base64 encoding in %s", filename); |
||
146 |
✗✓ | 58 |
if (memcmp(buf, PKALG, 2) != 0) |
147 |
errx(1, "unsupported file %s", filename); |
||
148 |
58 |
return b64end - b64 + 1; |
|
149 |
} |
||
150 |
|||
151 |
static void |
||
152 |
readb64file(const char *filename, void *buf, size_t buflen, char *comment) |
||
153 |
{ |
||
154 |
90 |
char b64[2048]; |
|
155 |
int rv, fd; |
||
156 |
|||
157 |
45 |
fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0); |
|
158 |
✗✓ | 45 |
if ((rv = read(fd, b64, sizeof(b64) - 1)) == -1) |
159 |
err(1, "read from %s", filename); |
||
160 |
45 |
b64[rv] = '\0'; |
|
161 |
45 |
parseb64file(filename, b64, buf, buflen, comment); |
|
162 |
45 |
explicit_bzero(b64, sizeof(b64)); |
|
163 |
45 |
close(fd); |
|
164 |
45 |
} |
|
165 |
|||
166 |
static uint8_t * |
||
167 |
readmsg(const char *filename, unsigned long long *msglenp) |
||
168 |
{ |
||
169 |
unsigned long long msglen = 0; |
||
170 |
uint8_t *msg = NULL; |
||
171 |
56 |
struct stat sb; |
|
172 |
ssize_t x, space; |
||
173 |
int fd; |
||
174 |
const unsigned long long maxmsgsize = 1UL << 30; |
||
175 |
|||
176 |
28 |
fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0); |
|
177 |
✓✗✓✗ |
56 |
if (fstat(fd, &sb) == 0 && S_ISREG(sb.st_mode)) { |
178 |
✗✓ | 28 |
if (sb.st_size > maxmsgsize) |
179 |
errx(1, "msg too large in %s", filename); |
||
180 |
28 |
space = sb.st_size + 1; |
|
181 |
28 |
} else { |
|
182 |
space = 64 * 1024 - 1; |
||
183 |
} |
||
184 |
|||
185 |
28 |
msg = xmalloc(space + 1); |
|
186 |
56 |
while (1) { |
|
187 |
✗✓ | 56 |
if (space == 0) { |
188 |
if (msglen * 2 > maxmsgsize) |
||
189 |
errx(1, "msg too large in %s", filename); |
||
190 |
space = msglen; |
||
191 |
if (!(msg = realloc(msg, msglen + space + 1))) |
||
192 |
err(1, "realloc"); |
||
193 |
} |
||
194 |
✗✓ | 56 |
if ((x = read(fd, msg + msglen, space)) == -1) |
195 |
err(1, "read from %s", filename); |
||
196 |
✓✓ | 56 |
if (x == 0) |
197 |
break; |
||
198 |
28 |
space -= x; |
|
199 |
28 |
msglen += x; |
|
200 |
} |
||
201 |
|||
202 |
28 |
msg[msglen] = '\0'; |
|
203 |
28 |
close(fd); |
|
204 |
|||
205 |
28 |
*msglenp = msglen; |
|
206 |
28 |
return msg; |
|
207 |
28 |
} |
|
208 |
|||
209 |
void |
||
210 |
writeall(int fd, const void *buf, size_t buflen, const char *filename) |
||
211 |
{ |
||
212 |
ssize_t x; |
||
213 |
|||
214 |
✓✓ | 275 |
while (buflen != 0) { |
215 |
✗✓ | 55 |
if ((x = write(fd, buf, buflen)) == -1) |
216 |
err(1, "write to %s", filename); |
||
217 |
55 |
buflen -= x; |
|
218 |
55 |
buf = (char *)buf + x; |
|
219 |
} |
||
220 |
55 |
} |
|
221 |
|||
222 |
#ifndef VERIFYONLY |
||
223 |
static char * |
||
224 |
createheader(const char *comment, const void *buf, size_t buflen) |
||
225 |
{ |
||
226 |
32 |
char *header; |
|
227 |
16 |
char b64[1024]; |
|
228 |
|||
229 |
✗✓ | 16 |
if (b64_ntop(buf, buflen, b64, sizeof(b64)) == -1) |
230 |
errx(1, "base64 encode failed"); |
||
231 |
✗✓ | 16 |
if (asprintf(&header, "%s%s\n%s\n", COMMENTHDR, comment, b64) == -1) |
232 |
err(1, "asprintf failed"); |
||
233 |
16 |
explicit_bzero(b64, sizeof(b64)); |
|
234 |
32 |
return header; |
|
235 |
16 |
} |
|
236 |
|||
237 |
static void |
||
238 |
writekeyfile(const char *filename, const char *comment, const void *buf, |
||
239 |
size_t buflen, int oflags, mode_t mode) |
||
240 |
{ |
||
241 |
char *header; |
||
242 |
int fd; |
||
243 |
|||
244 |
fd = xopen(filename, O_CREAT|oflags|O_NOFOLLOW|O_WRONLY, mode); |
||
245 |
header = createheader(comment, buf, buflen); |
||
246 |
writeall(fd, header, strlen(header), filename); |
||
247 |
freezero(header, strlen(header)); |
||
248 |
close(fd); |
||
249 |
} |
||
250 |
|||
251 |
static void |
||
252 |
kdf(uint8_t *salt, size_t saltlen, int rounds, int allowstdin, int confirm, |
||
253 |
uint8_t *key, size_t keylen) |
||
254 |
{ |
||
255 |
32 |
char pass[1024]; |
|
256 |
int rppflags = RPP_ECHO_OFF; |
||
257 |
|||
258 |
✓✗ | 16 |
if (rounds == 0) { |
259 |
16 |
memset(key, 0, keylen); |
|
260 |
16 |
return; |
|
261 |
} |
||
262 |
|||
263 |
if (allowstdin && !isatty(STDIN_FILENO)) |
||
264 |
rppflags |= RPP_STDIN; |
||
265 |
if (!readpassphrase("passphrase: ", pass, sizeof(pass), rppflags)) |
||
266 |
errx(1, "unable to read passphrase"); |
||
267 |
if (strlen(pass) == 0) |
||
268 |
errx(1, "please provide a password"); |
||
269 |
if (confirm && !(rppflags & RPP_STDIN)) { |
||
270 |
char pass2[1024]; |
||
271 |
if (!readpassphrase("confirm passphrase: ", pass2, |
||
272 |
sizeof(pass2), rppflags)) |
||
273 |
errx(1, "unable to read passphrase"); |
||
274 |
if (strcmp(pass, pass2) != 0) |
||
275 |
errx(1, "passwords don't match"); |
||
276 |
explicit_bzero(pass2, sizeof(pass2)); |
||
277 |
} |
||
278 |
if (bcrypt_pbkdf(pass, strlen(pass), salt, saltlen, key, |
||
279 |
keylen, rounds) == -1) |
||
280 |
errx(1, "bcrypt pbkdf"); |
||
281 |
explicit_bzero(pass, sizeof(pass)); |
||
282 |
16 |
} |
|
283 |
|||
284 |
static void |
||
285 |
signmsg(uint8_t *seckey, uint8_t *msg, unsigned long long msglen, |
||
286 |
uint8_t *sig) |
||
287 |
{ |
||
288 |
32 |
unsigned long long siglen; |
|
289 |
uint8_t *sigbuf; |
||
290 |
|||
291 |
16 |
sigbuf = xmalloc(msglen + SIGBYTES); |
|
292 |
16 |
crypto_sign_ed25519(sigbuf, &siglen, msg, msglen, seckey); |
|
293 |
16 |
memcpy(sig, sigbuf, SIGBYTES); |
|
294 |
16 |
free(sigbuf); |
|
295 |
16 |
} |
|
296 |
|||
297 |
static void |
||
298 |
generate(const char *pubkeyfile, const char *seckeyfile, int rounds, |
||
299 |
const char *comment) |
||
300 |
{ |
||
301 |
uint8_t digest[SHA512_DIGEST_LENGTH]; |
||
302 |
struct pubkey pubkey; |
||
303 |
struct enckey enckey; |
||
304 |
uint8_t xorkey[sizeof(enckey.seckey)]; |
||
305 |
uint8_t keynum[KEYNUMLEN]; |
||
306 |
char commentbuf[COMMENTMAXLEN]; |
||
307 |
SHA2_CTX ctx; |
||
308 |
int i, nr; |
||
309 |
|||
310 |
crypto_sign_ed25519_keypair(pubkey.pubkey, enckey.seckey); |
||
311 |
arc4random_buf(keynum, sizeof(keynum)); |
||
312 |
|||
313 |
SHA512Init(&ctx); |
||
314 |
SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey)); |
||
315 |
SHA512Final(digest, &ctx); |
||
316 |
|||
317 |
memcpy(enckey.pkalg, PKALG, 2); |
||
318 |
memcpy(enckey.kdfalg, KDFALG, 2); |
||
319 |
enckey.kdfrounds = htonl(rounds); |
||
320 |
memcpy(enckey.keynum, keynum, KEYNUMLEN); |
||
321 |
arc4random_buf(enckey.salt, sizeof(enckey.salt)); |
||
322 |
kdf(enckey.salt, sizeof(enckey.salt), rounds, 1, 1, xorkey, sizeof(xorkey)); |
||
323 |
memcpy(enckey.checksum, digest, sizeof(enckey.checksum)); |
||
324 |
for (i = 0; i < sizeof(enckey.seckey); i++) |
||
325 |
enckey.seckey[i] ^= xorkey[i]; |
||
326 |
explicit_bzero(digest, sizeof(digest)); |
||
327 |
explicit_bzero(xorkey, sizeof(xorkey)); |
||
328 |
|||
329 |
nr = snprintf(commentbuf, sizeof(commentbuf), "%s secret key", comment); |
||
330 |
if (nr == -1 || nr >= sizeof(commentbuf)) |
||
331 |
errx(1, "comment too long"); |
||
332 |
writekeyfile(seckeyfile, commentbuf, &enckey, |
||
333 |
sizeof(enckey), O_EXCL, 0600); |
||
334 |
explicit_bzero(&enckey, sizeof(enckey)); |
||
335 |
|||
336 |
memcpy(pubkey.pkalg, PKALG, 2); |
||
337 |
memcpy(pubkey.keynum, keynum, KEYNUMLEN); |
||
338 |
nr = snprintf(commentbuf, sizeof(commentbuf), "%s public key", comment); |
||
339 |
if (nr == -1 || nr >= sizeof(commentbuf)) |
||
340 |
errx(1, "comment too long"); |
||
341 |
writekeyfile(pubkeyfile, commentbuf, &pubkey, |
||
342 |
sizeof(pubkey), O_EXCL, 0666); |
||
343 |
} |
||
344 |
|||
345 |
static const char * |
||
346 |
check_keyname_compliance(const char *pubkeyfile, const char *seckeyfile) |
||
347 |
{ |
||
348 |
const char *pos; |
||
349 |
size_t len; |
||
350 |
|||
351 |
/* basename may or may not modify input */ |
||
352 |
24 |
pos = strrchr(seckeyfile, '/'); |
|
353 |
✓✗ | 12 |
if (pos != NULL) |
354 |
12 |
seckeyfile = pos + 1; |
|
355 |
|||
356 |
12 |
len = strlen(seckeyfile); |
|
357 |
✓✗ | 12 |
if (len < 5) /* ?.key */ |
358 |
goto bad; |
||
359 |
✓✗ | 12 |
if (strcmp(seckeyfile + len - 4, ".sec") != 0) |
360 |
goto bad; |
||
361 |
✗✓ | 12 |
if (pubkeyfile != NULL) { |
362 |
pos = strrchr(pubkeyfile, '/'); |
||
363 |
if (pos != NULL) |
||
364 |
pubkeyfile = pos + 1; |
||
365 |
|||
366 |
if (strlen(pubkeyfile) != len) |
||
367 |
goto bad; |
||
368 |
if (strcmp(pubkeyfile + len - 4, ".pub") != 0) |
||
369 |
goto bad; |
||
370 |
if (strncmp(pubkeyfile, seckeyfile, len - 4) != 0) |
||
371 |
goto bad; |
||
372 |
} |
||
373 |
|||
374 |
12 |
return seckeyfile; |
|
375 |
bad: |
||
376 |
errx(1, "please use naming scheme of keyname.pub and keyname.sec"); |
||
377 |
} |
||
378 |
|||
379 |
uint8_t * |
||
380 |
createsig(const char *seckeyfile, const char *msgfile, uint8_t *msg, |
||
381 |
unsigned long long msglen) |
||
382 |
{ |
||
383 |
32 |
struct enckey enckey; |
|
384 |
16 |
uint8_t xorkey[sizeof(enckey.seckey)]; |
|
385 |
16 |
struct sig sig; |
|
386 |
char *sighdr; |
||
387 |
16 |
uint8_t digest[SHA512_DIGEST_LENGTH]; |
|
388 |
int i, nr, rounds; |
||
389 |
16 |
SHA2_CTX ctx; |
|
390 |
16 |
char comment[COMMENTMAXLEN], sigcomment[COMMENTMAXLEN]; |
|
391 |
|||
392 |
16 |
readb64file(seckeyfile, &enckey, sizeof(enckey), comment); |
|
393 |
|||
394 |
✓✓ | 16 |
if (strcmp(seckeyfile, "-") == 0) { |
395 |
4 |
nr = snprintf(sigcomment, sizeof(sigcomment), |
|
396 |
"signature from %s", comment); |
||
397 |
4 |
} else { |
|
398 |
12 |
const char *keyname = check_keyname_compliance(NULL, |
|
399 |
seckeyfile); |
||
400 |
24 |
nr = snprintf(sigcomment, sizeof(sigcomment), |
|
401 |
12 |
VERIFYWITH "%.*s.pub", (int)strlen(keyname) - 4, keyname); |
|
402 |
} |
||
403 |
✓✗✗✓ |
32 |
if (nr == -1 || nr >= sizeof(sigcomment)) |
404 |
errx(1, "comment too long"); |
||
405 |
|||
406 |
✗✓ | 16 |
if (memcmp(enckey.kdfalg, KDFALG, 2) != 0) |
407 |
errx(1, "unsupported KDF"); |
||
408 |
16 |
rounds = ntohl(enckey.kdfrounds); |
|
409 |
32 |
kdf(enckey.salt, sizeof(enckey.salt), rounds, strcmp(msgfile, "-") != 0, |
|
410 |
16 |
0, xorkey, sizeof(xorkey)); |
|
411 |
✓✓ | 2080 |
for (i = 0; i < sizeof(enckey.seckey); i++) |
412 |
1024 |
enckey.seckey[i] ^= xorkey[i]; |
|
413 |
16 |
explicit_bzero(xorkey, sizeof(xorkey)); |
|
414 |
16 |
SHA512Init(&ctx); |
|
415 |
16 |
SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey)); |
|
416 |
16 |
SHA512Final(digest, &ctx); |
|
417 |
✗✓ | 16 |
if (memcmp(enckey.checksum, digest, sizeof(enckey.checksum)) != 0) |
418 |
errx(1, "incorrect passphrase"); |
||
419 |
16 |
explicit_bzero(digest, sizeof(digest)); |
|
420 |
|||
421 |
16 |
signmsg(enckey.seckey, msg, msglen, sig.sig); |
|
422 |
16 |
memcpy(sig.keynum, enckey.keynum, KEYNUMLEN); |
|
423 |
16 |
explicit_bzero(&enckey, sizeof(enckey)); |
|
424 |
|||
425 |
16 |
memcpy(sig.pkalg, PKALG, 2); |
|
426 |
|||
427 |
16 |
sighdr = createheader(sigcomment, &sig, sizeof(sig)); |
|
428 |
16 |
return sighdr; |
|
429 |
16 |
} |
|
430 |
|||
431 |
static void |
||
432 |
sign(const char *seckeyfile, const char *msgfile, const char *sigfile, |
||
433 |
int embedded) |
||
434 |
{ |
||
435 |
uint8_t *msg; |
||
436 |
char *sighdr; |
||
437 |
int fd; |
||
438 |
24 |
unsigned long long msglen; |
|
439 |
|||
440 |
12 |
msg = readmsg(msgfile, &msglen); |
|
441 |
|||
442 |
12 |
sighdr = createsig(seckeyfile, msgfile, msg, msglen); |
|
443 |
|||
444 |
12 |
fd = xopen(sigfile, O_CREAT|O_TRUNC|O_NOFOLLOW|O_WRONLY, 0666); |
|
445 |
12 |
writeall(fd, sighdr, strlen(sighdr), sigfile); |
|
446 |
12 |
free(sighdr); |
|
447 |
✓✓ | 12 |
if (embedded) |
448 |
8 |
writeall(fd, msg, msglen, sigfile); |
|
449 |
12 |
close(fd); |
|
450 |
|||
451 |
12 |
free(msg); |
|
452 |
12 |
} |
|
453 |
#endif |
||
454 |
|||
455 |
static void |
||
456 |
verifymsg(struct pubkey *pubkey, uint8_t *msg, unsigned long long msglen, |
||
457 |
struct sig *sig, int quiet) |
||
458 |
{ |
||
459 |
uint8_t *sigbuf, *dummybuf; |
||
460 |
42 |
unsigned long long siglen, dummylen; |
|
461 |
|||
462 |
✗✓ | 21 |
if (memcmp(pubkey->keynum, sig->keynum, KEYNUMLEN) != 0) |
463 |
errx(1, "verification failed: checked against wrong key"); |
||
464 |
|||
465 |
21 |
siglen = SIGBYTES + msglen; |
|
466 |
21 |
sigbuf = xmalloc(siglen); |
|
467 |
21 |
dummybuf = xmalloc(siglen); |
|
468 |
21 |
memcpy(sigbuf, sig->sig, SIGBYTES); |
|
469 |
21 |
memcpy(sigbuf + SIGBYTES, msg, msglen); |
|
470 |
✓✓ | 42 |
if (crypto_sign_ed25519_open(dummybuf, &dummylen, sigbuf, siglen, |
471 |
42 |
pubkey->pubkey) == -1) |
|
472 |
errx(1, "signature verification failed"); |
||
473 |
✗✓ | 17 |
if (!quiet) |
474 |
printf("Signature Verified\n"); |
||
475 |
17 |
free(sigbuf); |
|
476 |
17 |
free(dummybuf); |
|
477 |
17 |
} |
|
478 |
|||
479 |
static void |
||
480 |
check_keytype(const char *pubkeyfile, const char *keytype) |
||
481 |
{ |
||
482 |
const char *p; |
||
483 |
size_t typelen; |
||
484 |
|||
485 |
if (!(p = strrchr(pubkeyfile, '-'))) |
||
486 |
goto bad; |
||
487 |
p++; |
||
488 |
typelen = strlen(keytype); |
||
489 |
if (strncmp(p, keytype, typelen) != 0) |
||
490 |
goto bad; |
||
491 |
if (strcmp(p + typelen, ".pub") != 0) |
||
492 |
goto bad; |
||
493 |
return; |
||
494 |
|||
495 |
bad: |
||
496 |
errx(1, "incorrect keytype: %s is not %s", pubkeyfile, keytype); |
||
497 |
} |
||
498 |
|||
499 |
static void |
||
500 |
readpubkey(const char *pubkeyfile, struct pubkey *pubkey, |
||
501 |
const char *sigcomment, const char *keytype) |
||
502 |
{ |
||
503 |
const char *safepath = "/etc/signify"; |
||
504 |
42 |
char keypath[1024]; |
|
505 |
|||
506 |
✓✓ | 21 |
if (!pubkeyfile) { |
507 |
1 |
pubkeyfile = strstr(sigcomment, VERIFYWITH); |
|
508 |
✓✗✓✗ |
2 |
if (pubkeyfile && strchr(pubkeyfile, '/') == NULL) { |
509 |
1 |
pubkeyfile += strlen(VERIFYWITH); |
|
510 |
✗✓ | 1 |
if (keytype) |
511 |
check_keytype(pubkeyfile, keytype); |
||
512 |
✗✓ | 2 |
if (snprintf(keypath, sizeof(keypath), "%s/%s", |
513 |
1 |
safepath, pubkeyfile) >= sizeof(keypath)) |
|
514 |
errx(1, "name too long %s", pubkeyfile); |
||
515 |
pubkeyfile = keypath; |
||
516 |
} else |
||
517 |
usage("must specify pubkey"); |
||
518 |
1 |
} |
|
519 |
21 |
readb64file(pubkeyfile, pubkey, sizeof(*pubkey), NULL); |
|
520 |
21 |
} |
|
521 |
|||
522 |
static void |
||
523 |
verifysimple(const char *pubkeyfile, const char *msgfile, const char *sigfile, |
||
524 |
int quiet, const char *keytype) |
||
525 |
{ |
||
526 |
16 |
char sigcomment[COMMENTMAXLEN]; |
|
527 |
8 |
struct sig sig; |
|
528 |
8 |
struct pubkey pubkey; |
|
529 |
8 |
unsigned long long msglen; |
|
530 |
uint8_t *msg; |
||
531 |
|||
532 |
8 |
msg = readmsg(msgfile, &msglen); |
|
533 |
|||
534 |
8 |
readb64file(sigfile, &sig, sizeof(sig), sigcomment); |
|
535 |
8 |
readpubkey(pubkeyfile, &pubkey, sigcomment, keytype); |
|
536 |
|||
537 |
8 |
verifymsg(&pubkey, msg, msglen, &sig, quiet); |
|
538 |
|||
539 |
8 |
free(msg); |
|
540 |
8 |
} |
|
541 |
|||
542 |
static uint8_t * |
||
543 |
verifyembedded(const char *pubkeyfile, const char *sigfile, |
||
544 |
int quiet, unsigned long long *msglenp, const char *keytype) |
||
545 |
{ |
||
546 |
16 |
char sigcomment[COMMENTMAXLEN]; |
|
547 |
8 |
struct sig sig; |
|
548 |
8 |
struct pubkey pubkey; |
|
549 |
8 |
unsigned long long msglen, siglen; |
|
550 |
uint8_t *msg; |
||
551 |
|||
552 |
8 |
msg = readmsg(sigfile, &msglen); |
|
553 |
|||
554 |
8 |
siglen = parseb64file(sigfile, msg, &sig, sizeof(sig), sigcomment); |
|
555 |
8 |
readpubkey(pubkeyfile, &pubkey, sigcomment, keytype); |
|
556 |
|||
557 |
8 |
msglen -= siglen; |
|
558 |
8 |
memmove(msg, msg + siglen, msglen); |
|
559 |
8 |
msg[msglen] = 0; |
|
560 |
|||
561 |
8 |
verifymsg(&pubkey, msg, msglen, &sig, quiet); |
|
562 |
|||
563 |
8 |
*msglenp = msglen; |
|
564 |
8 |
return msg; |
|
565 |
8 |
} |
|
566 |
|||
567 |
static void |
||
568 |
verify(const char *pubkeyfile, const char *msgfile, const char *sigfile, |
||
569 |
int embedded, int quiet, const char *keytype) |
||
570 |
{ |
||
571 |
24 |
unsigned long long msglen; |
|
572 |
uint8_t *msg; |
||
573 |
int fd; |
||
574 |
|||
575 |
✓✓ | 12 |
if (embedded) { |
576 |
4 |
msg = verifyembedded(pubkeyfile, sigfile, quiet, &msglen, |
|
577 |
keytype); |
||
578 |
4 |
fd = xopen(msgfile, O_CREAT|O_TRUNC|O_NOFOLLOW|O_WRONLY, 0666); |
|
579 |
4 |
writeall(fd, msg, msglen, msgfile); |
|
580 |
4 |
free(msg); |
|
581 |
4 |
close(fd); |
|
582 |
4 |
} else { |
|
583 |
8 |
verifysimple(pubkeyfile, msgfile, sigfile, quiet, keytype); |
|
584 |
} |
||
585 |
8 |
} |
|
586 |
|||
587 |
#ifndef VERIFYONLY |
||
588 |
#define HASHBUFSIZE 224 |
||
589 |
struct checksum { |
||
590 |
char file[PATH_MAX]; |
||
591 |
char hash[HASHBUFSIZE]; |
||
592 |
char algo[32]; |
||
593 |
}; |
||
594 |
|||
595 |
static void * |
||
596 |
ecalloc(size_t s1, size_t s2, void *data) |
||
597 |
{ |
||
598 |
void *p; |
||
599 |
|||
600 |
✗✓ | 8 |
if (!(p = calloc(s1, s2))) |
601 |
err(1, "calloc"); |
||
602 |
4 |
return p; |
|
603 |
} |
||
604 |
|||
605 |
static void |
||
606 |
efree(void *p, void *data) |
||
607 |
{ |
||
608 |
8 |
free(p); |
|
609 |
4 |
} |
|
610 |
|||
611 |
static void |
||
612 |
recodehash(char *hash, size_t len) |
||
613 |
{ |
||
614 |
32 |
uint8_t data[HASHBUFSIZE / 2]; |
|
615 |
int i, rv; |
||
616 |
|||
617 |
✓✗ | 16 |
if (strlen(hash) == len) |
618 |
16 |
return; |
|
619 |
if ((rv = b64_pton(hash, data, sizeof(data))) == -1) |
||
620 |
errx(1, "invalid base64 encoding"); |
||
621 |
for (i = 0; i < rv; i++) |
||
622 |
snprintf(hash + i * 2, HASHBUFSIZE - i * 2, "%2.2x", data[i]); |
||
623 |
16 |
} |
|
624 |
|||
625 |
static int |
||
626 |
verifychecksum(struct checksum *c, int quiet) |
||
627 |
{ |
||
628 |
32 |
char buf[HASHBUFSIZE]; |
|
629 |
|||
630 |
✓✓ | 16 |
if (strcmp(c->algo, "SHA256") == 0) { |
631 |
8 |
recodehash(c->hash, SHA256_DIGEST_STRING_LENGTH-1); |
|
632 |
✗✓ | 8 |
if (!SHA256File(c->file, buf)) |
633 |
return 0; |
||
634 |
✓✗ | 8 |
} else if (strcmp(c->algo, "SHA512") == 0) { |
635 |
8 |
recodehash(c->hash, SHA512_DIGEST_STRING_LENGTH-1); |
|
636 |
✗✓ | 8 |
if (!SHA512File(c->file, buf)) |
637 |
return 0; |
||
638 |
} else { |
||
639 |
errx(1, "can't handle algorithm %s", c->algo); |
||
640 |
} |
||
641 |
✗✓ | 16 |
if (strcmp(c->hash, buf) != 0) |
642 |
return 0; |
||
643 |
✗✓ | 16 |
if (!quiet) |
644 |
printf("%s: OK\n", c->file); |
||
645 |
16 |
return 1; |
|
646 |
16 |
} |
|
647 |
|||
648 |
static void |
||
649 |
verifychecksums(char *msg, int argc, char **argv, int quiet) |
||
650 |
{ |
||
651 |
8 |
struct ohash_info info = { 0, NULL, ecalloc, efree, NULL }; |
|
652 |
4 |
struct ohash myh; |
|
653 |
4 |
struct checksum c; |
|
654 |
char *e, *line, *endline; |
||
655 |
int hasfailed = 0; |
||
656 |
int i, rv; |
||
657 |
4 |
unsigned int slot; |
|
658 |
|||
659 |
4 |
ohash_init(&myh, 6, &info); |
|
660 |
✗✓ | 4 |
if (argc) { |
661 |
for (i = 0; i < argc; i++) { |
||
662 |
slot = ohash_qlookup(&myh, argv[i]); |
||
663 |
e = ohash_find(&myh, slot); |
||
664 |
if (e == NULL) |
||
665 |
ohash_insert(&myh, slot, argv[i]); |
||
666 |
} |
||
667 |
} |
||
668 |
|||
669 |
line = msg; |
||
670 |
✓✗✓✓ |
60 |
while (line && *line) { |
671 |
✓✗ | 16 |
if ((endline = strchr(line, '\n'))) |
672 |
16 |
*endline++ = '\0'; |
|
673 |
#if PATH_MAX < 1024 || HASHBUFSIZE < 224 |
||
674 |
#error sizes are wrong |
||
675 |
#endif |
||
676 |
16 |
rv = sscanf(line, "%31s (%1023[^)]) = %223s", |
|
677 |
16 |
c.algo, c.file, c.hash); |
|
678 |
✗✓ | 16 |
if (rv != 3) |
679 |
errx(1, "unable to parse checksum line %s", line); |
||
680 |
line = endline; |
||
681 |
✗✓ | 16 |
if (argc) { |
682 |
slot = ohash_qlookup(&myh, c.file); |
||
683 |
e = ohash_find(&myh, slot); |
||
684 |
if (e != NULL) { |
||
685 |
if (verifychecksum(&c, quiet) != 0) |
||
686 |
ohash_remove(&myh, slot); |
||
687 |
} |
||
688 |
} else { |
||
689 |
✗✓ | 16 |
if (verifychecksum(&c, quiet) == 0) { |
690 |
slot = ohash_qlookup(&myh, c.file); |
||
691 |
e = ohash_find(&myh, slot); |
||
692 |
if (e == NULL) { |
||
693 |
if (!(e = strdup(c.file))) |
||
694 |
err(1, "strdup"); |
||
695 |
ohash_insert(&myh, slot, e); |
||
696 |
} |
||
697 |
} |
||
698 |
} |
||
699 |
} |
||
700 |
|||
701 |
✗✓ | 8 |
for (e = ohash_first(&myh, &slot); e != NULL; e = ohash_next(&myh, &slot)) { |
702 |
fprintf(stderr, "%s: FAIL\n", e); |
||
703 |
hasfailed = 1; |
||
704 |
if (argc == 0) |
||
705 |
free(e); |
||
706 |
} |
||
707 |
4 |
ohash_delete(&myh); |
|
708 |
✗✓ | 4 |
if (hasfailed) |
709 |
exit(1); |
||
710 |
4 |
} |
|
711 |
|||
712 |
static void |
||
713 |
check(const char *pubkeyfile, const char *sigfile, int quiet, int argc, |
||
714 |
char **argv) |
||
715 |
{ |
||
716 |
8 |
unsigned long long msglen; |
|
717 |
uint8_t *msg; |
||
718 |
|||
719 |
4 |
msg = verifyembedded(pubkeyfile, sigfile, quiet, &msglen, NULL); |
|
720 |
4 |
verifychecksums((char *)msg, argc, argv, quiet); |
|
721 |
|||
722 |
4 |
free(msg); |
|
723 |
4 |
} |
|
724 |
|||
725 |
void * |
||
726 |
verifyzdata(uint8_t *zdata, unsigned long long zdatalen, |
||
727 |
const char *filename, const char *pubkeyfile, const char *keytype) |
||
728 |
{ |
||
729 |
10 |
struct sig sig; |
|
730 |
5 |
char sigcomment[COMMENTMAXLEN]; |
|
731 |
unsigned long long siglen; |
||
732 |
5 |
struct pubkey pubkey; |
|
733 |
|||
734 |
✗✓ | 5 |
if (zdatalen < sizeof(sig)) |
735 |
errx(1, "signature too short in %s", filename); |
||
736 |
5 |
siglen = parseb64file(filename, zdata, &sig, sizeof(sig), |
|
737 |
5 |
sigcomment); |
|
738 |
5 |
readpubkey(pubkeyfile, &pubkey, sigcomment, keytype); |
|
739 |
5 |
zdata += siglen; |
|
740 |
5 |
zdatalen -= siglen; |
|
741 |
5 |
verifymsg(&pubkey, zdata, zdatalen, &sig, 1); |
|
742 |
5 |
return zdata; |
|
743 |
5 |
} |
|
744 |
#endif |
||
745 |
|||
746 |
int |
||
747 |
main(int argc, char **argv) |
||
748 |
{ |
||
749 |
const char *pubkeyfile = NULL, *seckeyfile = NULL, *msgfile = NULL, |
||
750 |
*sigfile = NULL; |
||
751 |
74 |
char sigfilebuf[PATH_MAX]; |
|
752 |
const char *comment = "signify"; |
||
753 |
char *keytype = NULL; |
||
754 |
int ch, rounds; |
||
755 |
int embedded = 0; |
||
756 |
int quiet = 0; |
||
757 |
int gzip = 0; |
||
758 |
enum { |
||
759 |
NONE, |
||
760 |
CHECK, |
||
761 |
GENERATE, |
||
762 |
SIGN, |
||
763 |
VERIFY |
||
764 |
} verb = NONE; |
||
765 |
|||
766 |
✗✓ | 37 |
if (pledge("stdio rpath wpath cpath tty flock", NULL) == -1) |
767 |
err(1, "pledge"); |
||
768 |
|||
769 |
rounds = 42; |
||
770 |
|||
771 |
✓✓ | 382 |
while ((ch = getopt(argc, argv, "CGSVzc:em:np:qs:t:x:")) != -1) { |
772 |
✓✗✓✓ ✓✗✓✓ ✗✓✓✓ ✗✓✗ |
154 |
switch (ch) { |
773 |
#ifndef VERIFYONLY |
||
774 |
case 'C': |
||
775 |
✗✓ | 4 |
if (verb) |
776 |
usage(NULL); |
||
777 |
verb = CHECK; |
||
778 |
4 |
break; |
|
779 |
case 'G': |
||
780 |
if (verb) |
||
781 |
usage(NULL); |
||
782 |
verb = GENERATE; |
||
783 |
break; |
||
784 |
case 'S': |
||
785 |
✗✓ | 16 |
if (verb) |
786 |
usage(NULL); |
||
787 |
verb = SIGN; |
||
788 |
16 |
break; |
|
789 |
case 'z': |
||
790 |
gzip = 1; |
||
791 |
9 |
break; |
|
792 |
#endif |
||
793 |
case 'V': |
||
794 |
✗✓ | 17 |
if (verb) |
795 |
usage(NULL); |
||
796 |
verb = VERIFY; |
||
797 |
17 |
break; |
|
798 |
case 'c': |
||
799 |
comment = optarg; |
||
800 |
break; |
||
801 |
case 'e': |
||
802 |
embedded = 1; |
||
803 |
12 |
break; |
|
804 |
case 'm': |
||
805 |
28 |
msgfile = optarg; |
|
806 |
28 |
break; |
|
807 |
case 'n': |
||
808 |
rounds = 0; |
||
809 |
break; |
||
810 |
case 'p': |
||
811 |
20 |
pubkeyfile = optarg; |
|
812 |
20 |
break; |
|
813 |
case 'q': |
||
814 |
quiet = 1; |
||
815 |
16 |
break; |
|
816 |
case 's': |
||
817 |
16 |
seckeyfile = optarg; |
|
818 |
16 |
break; |
|
819 |
case 't': |
||
820 |
keytype = optarg; |
||
821 |
break; |
||
822 |
case 'x': |
||
823 |
16 |
sigfile = optarg; |
|
824 |
16 |
break; |
|
825 |
default: |
||
826 |
usage(NULL); |
||
827 |
break; |
||
828 |
} |
||
829 |
} |
||
830 |
37 |
argc -= optind; |
|
831 |
37 |
argv += optind; |
|
832 |
|||
833 |
✗✓ | 37 |
if (embedded && gzip) |
834 |
errx(1, "can't combine -e and -z options"); |
||
835 |
|||
836 |
✗✓ | 37 |
if (setvbuf(stdout, NULL, _IOLBF, 0) != 0) |
837 |
err(1, "setvbuf"); |
||
838 |
|||
839 |
#ifndef VERIFYONLY |
||
840 |
✓✓ | 37 |
if (verb == CHECK) { |
841 |
✗✓ | 4 |
if (pledge("stdio rpath flock cpath wpath", NULL) == -1) |
842 |
err(1, "pledge"); |
||
843 |
✗✓ | 4 |
if (!sigfile) |
844 |
usage("must specify sigfile"); |
||
845 |
4 |
check(pubkeyfile, sigfile, quiet, argc, argv); |
|
846 |
4 |
return 0; |
|
847 |
} |
||
848 |
#endif |
||
849 |
|||
850 |
✗✓ | 33 |
if (argc != 0) |
851 |
usage(NULL); |
||
852 |
|||
853 |
✓✓ | 33 |
if (!sigfile && msgfile) { |
854 |
int nr; |
||
855 |
✗✓ | 16 |
if (strcmp(msgfile, "-") == 0) |
856 |
usage("must specify sigfile with - message"); |
||
857 |
16 |
nr = snprintf(sigfilebuf, sizeof(sigfilebuf), |
|
858 |
"%s.sig", msgfile); |
||
859 |
✓✗✗✓ |
32 |
if (nr == -1 || nr >= sizeof(sigfilebuf)) |
860 |
errx(1, "path too long"); |
||
861 |
sigfile = sigfilebuf; |
||
862 |
16 |
} |
|
863 |
|||
864 |
✗✓✓✗ |
33 |
switch (verb) { |
865 |
#ifndef VERIFYONLY |
||
866 |
case GENERATE: |
||
867 |
/* no pledge */ |
||
868 |
if (!pubkeyfile || !seckeyfile) |
||
869 |
usage("must specify pubkey and seckey"); |
||
870 |
check_keyname_compliance(pubkeyfile, seckeyfile); |
||
871 |
generate(pubkeyfile, seckeyfile, rounds, comment); |
||
872 |
break; |
||
873 |
case SIGN: |
||
874 |
/* no pledge */ |
||
875 |
✓✓ | 16 |
if (gzip) { |
876 |
✗✓ | 4 |
if (!msgfile || !seckeyfile || !sigfile) |
877 |
usage("must specify message sigfile seckey"); |
||
878 |
4 |
zsign(seckeyfile, msgfile, sigfile); |
|
879 |
4 |
} else { |
|
880 |
✗✓ | 12 |
if (!msgfile || !seckeyfile) |
881 |
usage("must specify message and seckey"); |
||
882 |
12 |
sign(seckeyfile, msgfile, sigfile, embedded); |
|
883 |
} |
||
884 |
break; |
||
885 |
#endif |
||
886 |
case VERIFY: |
||
887 |
✓✓✓✗ |
21 |
if ((embedded || gzip) && |
888 |
4 |
(msgfile && strcmp(msgfile, "-") != 0)) { |
|
889 |
/* will need to create output file */ |
||
890 |
✗✓ | 4 |
if (pledge("stdio rpath wpath cpath flock", NULL) == -1) |
891 |
err(1, "pledge"); |
||
892 |
} else { |
||
893 |
✗✓ | 13 |
if (pledge("stdio rpath flock cpath wpath", NULL) == -1) |
894 |
err(1, "pledge"); |
||
895 |
} |
||
896 |
✓✓ | 17 |
if (gzip) { |
897 |
5 |
zverify(pubkeyfile, msgfile, sigfile, keytype); |
|
898 |
5 |
} else { |
|
899 |
✗✓ | 12 |
if (!msgfile) |
900 |
usage("must specify message"); |
||
901 |
12 |
verify(pubkeyfile, msgfile, sigfile, embedded, |
|
902 |
quiet, keytype); |
||
903 |
} |
||
904 |
break; |
||
905 |
default: |
||
906 |
if (pledge("stdio flock rpath cpath wpath", NULL) == -1) |
||
907 |
err(1, "pledge"); |
||
908 |
usage(NULL); |
||
909 |
break; |
||
910 |
} |
||
911 |
|||
912 |
29 |
return 0; |
|
913 |
33 |
} |
Generated by: GCOVR (Version 3.3) |