1 |
|
|
/* $OpenBSD: signify.c,v 1.106 2016/06/08 04:16:06 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 |
|
|
|
38 |
|
|
#define SIGBYTES crypto_sign_ed25519_BYTES |
39 |
|
|
#define SECRETBYTES crypto_sign_ed25519_SECRETKEYBYTES |
40 |
|
|
#define PUBLICBYTES crypto_sign_ed25519_PUBLICKEYBYTES |
41 |
|
|
|
42 |
|
|
#define PKALG "Ed" |
43 |
|
|
#define KDFALG "BK" |
44 |
|
|
#define KEYNUMLEN 8 |
45 |
|
|
|
46 |
|
|
#define COMMENTHDR "untrusted comment: " |
47 |
|
|
#define COMMENTHDRLEN 19 |
48 |
|
|
#define COMMENTMAXLEN 1024 |
49 |
|
|
#define VERIFYWITH "verify with " |
50 |
|
|
|
51 |
|
|
struct enckey { |
52 |
|
|
uint8_t pkalg[2]; |
53 |
|
|
uint8_t kdfalg[2]; |
54 |
|
|
uint32_t kdfrounds; |
55 |
|
|
uint8_t salt[16]; |
56 |
|
|
uint8_t checksum[8]; |
57 |
|
|
uint8_t keynum[KEYNUMLEN]; |
58 |
|
|
uint8_t seckey[SECRETBYTES]; |
59 |
|
|
}; |
60 |
|
|
|
61 |
|
|
struct pubkey { |
62 |
|
|
uint8_t pkalg[2]; |
63 |
|
|
uint8_t keynum[KEYNUMLEN]; |
64 |
|
|
uint8_t pubkey[PUBLICBYTES]; |
65 |
|
|
}; |
66 |
|
|
|
67 |
|
|
struct sig { |
68 |
|
|
uint8_t pkalg[2]; |
69 |
|
|
uint8_t keynum[KEYNUMLEN]; |
70 |
|
|
uint8_t sig[SIGBYTES]; |
71 |
|
|
}; |
72 |
|
|
|
73 |
|
|
static void __dead |
74 |
|
|
usage(const char *error) |
75 |
|
|
{ |
76 |
|
|
if (error) |
77 |
|
|
fprintf(stderr, "%s\n", error); |
78 |
|
|
fprintf(stderr, "usage:" |
79 |
|
|
#ifndef VERIFYONLY |
80 |
|
|
"\t%1$s -C [-q] -p pubkey -x sigfile [file ...]\n" |
81 |
|
|
"\t%1$s -G [-n] [-c comment] -p pubkey -s seckey\n" |
82 |
|
|
"\t%1$s -S [-e] [-x sigfile] -s seckey -m message\n" |
83 |
|
|
#endif |
84 |
|
|
"\t%1$s -V [-eq] [-x sigfile] -p pubkey -m message\n", |
85 |
|
|
getprogname()); |
86 |
|
|
exit(1); |
87 |
|
|
} |
88 |
|
|
|
89 |
|
|
static int |
90 |
|
|
xopen(const char *fname, int oflags, mode_t mode) |
91 |
|
|
{ |
92 |
|
|
struct stat sb; |
93 |
|
|
int fd; |
94 |
|
|
|
95 |
|
|
if (strcmp(fname, "-") == 0) { |
96 |
|
|
if ((oflags & O_WRONLY)) |
97 |
|
|
fd = dup(STDOUT_FILENO); |
98 |
|
|
else |
99 |
|
|
fd = dup(STDIN_FILENO); |
100 |
|
|
if (fd == -1) |
101 |
|
|
err(1, "dup failed"); |
102 |
|
|
} else { |
103 |
|
|
fd = open(fname, oflags, mode); |
104 |
|
|
if (fd == -1) |
105 |
|
|
err(1, "can't open %s for %s", fname, |
106 |
|
|
(oflags & O_WRONLY) ? "writing" : "reading"); |
107 |
|
|
} |
108 |
|
|
if (fstat(fd, &sb) == -1 || S_ISDIR(sb.st_mode)) |
109 |
|
|
errx(1, "not a valid file: %s", fname); |
110 |
|
|
return fd; |
111 |
|
|
} |
112 |
|
|
|
113 |
|
|
static void * |
114 |
|
|
xmalloc(size_t len) |
115 |
|
|
{ |
116 |
|
|
void *p; |
117 |
|
|
|
118 |
|
|
if (!(p = malloc(len))) |
119 |
|
|
err(1, "malloc %zu", len); |
120 |
|
|
return p; |
121 |
|
|
} |
122 |
|
|
|
123 |
|
|
static size_t |
124 |
|
|
parseb64file(const char *filename, char *b64, void *buf, size_t buflen, |
125 |
|
|
char *comment) |
126 |
|
|
{ |
127 |
|
|
char *commentend, *b64end; |
128 |
|
|
|
129 |
|
|
commentend = strchr(b64, '\n'); |
130 |
|
|
if (!commentend || commentend - b64 <= COMMENTHDRLEN || |
131 |
|
|
memcmp(b64, COMMENTHDR, COMMENTHDRLEN) != 0) |
132 |
|
|
errx(1, "invalid comment in %s; must start with '%s'", |
133 |
|
|
filename, COMMENTHDR); |
134 |
|
|
*commentend = '\0'; |
135 |
|
|
if (comment) { |
136 |
|
|
if (strlcpy(comment, b64 + COMMENTHDRLEN, |
137 |
|
|
COMMENTMAXLEN) >= COMMENTMAXLEN) |
138 |
|
|
errx(1, "comment too long"); |
139 |
|
|
} |
140 |
|
|
if (!(b64end = strchr(commentend + 1, '\n'))) |
141 |
|
|
errx(1, "missing new line after base64 in %s", filename); |
142 |
|
|
*b64end = '\0'; |
143 |
|
|
if (b64_pton(commentend + 1, buf, buflen) != buflen) |
144 |
|
|
errx(1, "invalid base64 encoding in %s", filename); |
145 |
|
|
if (memcmp(buf, PKALG, 2) != 0) |
146 |
|
|
errx(1, "unsupported file %s", filename); |
147 |
|
|
return b64end - b64 + 1; |
148 |
|
|
} |
149 |
|
|
|
150 |
|
|
static void |
151 |
|
|
readb64file(const char *filename, void *buf, size_t buflen, char *comment) |
152 |
|
|
{ |
153 |
|
|
char b64[2048]; |
154 |
|
|
int rv, fd; |
155 |
|
|
|
156 |
|
|
fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0); |
157 |
|
|
if ((rv = read(fd, b64, sizeof(b64) - 1)) == -1) |
158 |
|
|
err(1, "read from %s", filename); |
159 |
|
|
b64[rv] = '\0'; |
160 |
|
|
parseb64file(filename, b64, buf, buflen, comment); |
161 |
|
|
explicit_bzero(b64, sizeof(b64)); |
162 |
|
|
close(fd); |
163 |
|
|
} |
164 |
|
|
|
165 |
|
|
static uint8_t * |
166 |
|
|
readmsg(const char *filename, unsigned long long *msglenp) |
167 |
|
|
{ |
168 |
|
|
unsigned long long msglen = 0; |
169 |
|
|
uint8_t *msg = NULL; |
170 |
|
|
struct stat sb; |
171 |
|
|
ssize_t x, space; |
172 |
|
|
int fd; |
173 |
|
|
const unsigned long long maxmsgsize = 1UL << 30; |
174 |
|
|
|
175 |
|
|
fd = xopen(filename, O_RDONLY | O_NOFOLLOW, 0); |
176 |
|
|
if (fstat(fd, &sb) == 0 && S_ISREG(sb.st_mode)) { |
177 |
|
|
if (sb.st_size > maxmsgsize) |
178 |
|
|
errx(1, "msg too large in %s", filename); |
179 |
|
|
space = sb.st_size + 1; |
180 |
|
|
} else { |
181 |
|
|
space = 64 * 1024 - 1; |
182 |
|
|
} |
183 |
|
|
|
184 |
|
|
msg = xmalloc(space + 1); |
185 |
|
|
while (1) { |
186 |
|
|
if (space == 0) { |
187 |
|
|
if (msglen * 2 > maxmsgsize) |
188 |
|
|
errx(1, "msg too large in %s", filename); |
189 |
|
|
space = msglen; |
190 |
|
|
if (!(msg = realloc(msg, msglen + space + 1))) |
191 |
|
|
errx(1, "realloc"); |
192 |
|
|
} |
193 |
|
|
if ((x = read(fd, msg + msglen, space)) == -1) |
194 |
|
|
err(1, "read from %s", filename); |
195 |
|
|
if (x == 0) |
196 |
|
|
break; |
197 |
|
|
space -= x; |
198 |
|
|
msglen += x; |
199 |
|
|
} |
200 |
|
|
|
201 |
|
|
msg[msglen] = '\0'; |
202 |
|
|
close(fd); |
203 |
|
|
|
204 |
|
|
*msglenp = msglen; |
205 |
|
|
return msg; |
206 |
|
|
} |
207 |
|
|
|
208 |
|
|
static void |
209 |
|
|
writeall(int fd, const void *buf, size_t buflen, const char *filename) |
210 |
|
|
{ |
211 |
|
|
ssize_t x; |
212 |
|
|
|
213 |
|
|
while (buflen != 0) { |
214 |
|
|
if ((x = write(fd, buf, buflen)) == -1) |
215 |
|
|
err(1, "write to %s", filename); |
216 |
|
|
buflen -= x; |
217 |
|
|
buf = (char *)buf + x; |
218 |
|
|
} |
219 |
|
|
} |
220 |
|
|
|
221 |
|
|
#ifndef VERIFYONLY |
222 |
|
|
static void |
223 |
|
|
writeb64file(const char *filename, const char *comment, const void *buf, |
224 |
|
|
size_t buflen, const void *msg, size_t msglen, int oflags, mode_t mode) |
225 |
|
|
{ |
226 |
|
|
char header[1024]; |
227 |
|
|
char b64[1024]; |
228 |
|
|
int fd, rv, nr; |
229 |
|
|
|
230 |
|
|
fd = xopen(filename, O_CREAT|oflags|O_NOFOLLOW|O_WRONLY, mode); |
231 |
|
|
if ((nr = snprintf(header, sizeof(header), "%s%s\n", |
232 |
|
|
COMMENTHDR, comment)) == -1 || nr >= sizeof(header)) |
233 |
|
|
errx(1, "comment too long"); |
234 |
|
|
writeall(fd, header, strlen(header), filename); |
235 |
|
|
if ((rv = b64_ntop(buf, buflen, b64, sizeof(b64))) == -1) |
236 |
|
|
errx(1, "base64 encode failed"); |
237 |
|
|
b64[rv++] = '\n'; |
238 |
|
|
writeall(fd, b64, rv, filename); |
239 |
|
|
explicit_bzero(b64, sizeof(b64)); |
240 |
|
|
if (msg) |
241 |
|
|
writeall(fd, msg, msglen, filename); |
242 |
|
|
close(fd); |
243 |
|
|
} |
244 |
|
|
|
245 |
|
|
static void |
246 |
|
|
kdf(uint8_t *salt, size_t saltlen, int rounds, int allowstdin, int confirm, |
247 |
|
|
uint8_t *key, size_t keylen) |
248 |
|
|
{ |
249 |
|
|
char pass[1024]; |
250 |
|
|
int rppflags = RPP_ECHO_OFF; |
251 |
|
|
|
252 |
|
|
if (rounds == 0) { |
253 |
|
|
memset(key, 0, keylen); |
254 |
|
|
return; |
255 |
|
|
} |
256 |
|
|
|
257 |
|
|
if (allowstdin && !isatty(STDIN_FILENO)) |
258 |
|
|
rppflags |= RPP_STDIN; |
259 |
|
|
if (!readpassphrase("passphrase: ", pass, sizeof(pass), rppflags)) |
260 |
|
|
errx(1, "unable to read passphrase"); |
261 |
|
|
if (strlen(pass) == 0) |
262 |
|
|
errx(1, "please provide a password"); |
263 |
|
|
if (confirm && !(rppflags & RPP_STDIN)) { |
264 |
|
|
char pass2[1024]; |
265 |
|
|
if (!readpassphrase("confirm passphrase: ", pass2, |
266 |
|
|
sizeof(pass2), rppflags)) |
267 |
|
|
errx(1, "unable to read passphrase"); |
268 |
|
|
if (strcmp(pass, pass2) != 0) |
269 |
|
|
errx(1, "passwords don't match"); |
270 |
|
|
explicit_bzero(pass2, sizeof(pass2)); |
271 |
|
|
} |
272 |
|
|
if (bcrypt_pbkdf(pass, strlen(pass), salt, saltlen, key, |
273 |
|
|
keylen, rounds) == -1) |
274 |
|
|
errx(1, "bcrypt pbkdf"); |
275 |
|
|
explicit_bzero(pass, sizeof(pass)); |
276 |
|
|
} |
277 |
|
|
|
278 |
|
|
static void |
279 |
|
|
signmsg(uint8_t *seckey, uint8_t *msg, unsigned long long msglen, |
280 |
|
|
uint8_t *sig) |
281 |
|
|
{ |
282 |
|
|
unsigned long long siglen; |
283 |
|
|
uint8_t *sigbuf; |
284 |
|
|
|
285 |
|
|
sigbuf = xmalloc(msglen + SIGBYTES); |
286 |
|
|
crypto_sign_ed25519(sigbuf, &siglen, msg, msglen, seckey); |
287 |
|
|
memcpy(sig, sigbuf, SIGBYTES); |
288 |
|
|
free(sigbuf); |
289 |
|
|
} |
290 |
|
|
|
291 |
|
|
static void |
292 |
|
|
generate(const char *pubkeyfile, const char *seckeyfile, int rounds, |
293 |
|
|
const char *comment) |
294 |
|
|
{ |
295 |
|
|
uint8_t digest[SHA512_DIGEST_LENGTH]; |
296 |
|
|
struct pubkey pubkey; |
297 |
|
|
struct enckey enckey; |
298 |
|
|
uint8_t xorkey[sizeof(enckey.seckey)]; |
299 |
|
|
uint8_t keynum[KEYNUMLEN]; |
300 |
|
|
char commentbuf[COMMENTMAXLEN]; |
301 |
|
|
SHA2_CTX ctx; |
302 |
|
|
int i, nr; |
303 |
|
|
|
304 |
|
|
crypto_sign_ed25519_keypair(pubkey.pubkey, enckey.seckey); |
305 |
|
|
arc4random_buf(keynum, sizeof(keynum)); |
306 |
|
|
|
307 |
|
|
SHA512Init(&ctx); |
308 |
|
|
SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey)); |
309 |
|
|
SHA512Final(digest, &ctx); |
310 |
|
|
|
311 |
|
|
memcpy(enckey.pkalg, PKALG, 2); |
312 |
|
|
memcpy(enckey.kdfalg, KDFALG, 2); |
313 |
|
|
enckey.kdfrounds = htonl(rounds); |
314 |
|
|
memcpy(enckey.keynum, keynum, KEYNUMLEN); |
315 |
|
|
arc4random_buf(enckey.salt, sizeof(enckey.salt)); |
316 |
|
|
kdf(enckey.salt, sizeof(enckey.salt), rounds, 1, 1, xorkey, sizeof(xorkey)); |
317 |
|
|
memcpy(enckey.checksum, digest, sizeof(enckey.checksum)); |
318 |
|
|
for (i = 0; i < sizeof(enckey.seckey); i++) |
319 |
|
|
enckey.seckey[i] ^= xorkey[i]; |
320 |
|
|
explicit_bzero(digest, sizeof(digest)); |
321 |
|
|
explicit_bzero(xorkey, sizeof(xorkey)); |
322 |
|
|
|
323 |
|
|
if ((nr = snprintf(commentbuf, sizeof(commentbuf), "%s secret key", |
324 |
|
|
comment)) == -1 || nr >= sizeof(commentbuf)) |
325 |
|
|
errx(1, "comment too long"); |
326 |
|
|
writeb64file(seckeyfile, commentbuf, &enckey, |
327 |
|
|
sizeof(enckey), NULL, 0, O_EXCL, 0600); |
328 |
|
|
explicit_bzero(&enckey, sizeof(enckey)); |
329 |
|
|
|
330 |
|
|
memcpy(pubkey.pkalg, PKALG, 2); |
331 |
|
|
memcpy(pubkey.keynum, keynum, KEYNUMLEN); |
332 |
|
|
if ((nr = snprintf(commentbuf, sizeof(commentbuf), "%s public key", |
333 |
|
|
comment)) == -1 || nr >= sizeof(commentbuf)) |
334 |
|
|
errx(1, "comment too long"); |
335 |
|
|
writeb64file(pubkeyfile, commentbuf, &pubkey, |
336 |
|
|
sizeof(pubkey), NULL, 0, O_EXCL, 0666); |
337 |
|
|
} |
338 |
|
|
|
339 |
|
|
static void |
340 |
|
|
sign(const char *seckeyfile, const char *msgfile, const char *sigfile, |
341 |
|
|
int embedded) |
342 |
|
|
{ |
343 |
|
|
struct sig sig; |
344 |
|
|
uint8_t digest[SHA512_DIGEST_LENGTH]; |
345 |
|
|
struct enckey enckey; |
346 |
|
|
uint8_t xorkey[sizeof(enckey.seckey)]; |
347 |
|
|
uint8_t *msg; |
348 |
|
|
char comment[COMMENTMAXLEN], sigcomment[COMMENTMAXLEN]; |
349 |
|
|
char *secname; |
350 |
|
|
unsigned long long msglen; |
351 |
|
|
int i, rounds, nr; |
352 |
|
|
SHA2_CTX ctx; |
353 |
|
|
|
354 |
|
|
readb64file(seckeyfile, &enckey, sizeof(enckey), comment); |
355 |
|
|
|
356 |
|
|
if (memcmp(enckey.kdfalg, KDFALG, 2) != 0) |
357 |
|
|
errx(1, "unsupported KDF"); |
358 |
|
|
rounds = ntohl(enckey.kdfrounds); |
359 |
|
|
kdf(enckey.salt, sizeof(enckey.salt), rounds, strcmp(msgfile, "-") != 0, |
360 |
|
|
0, xorkey, sizeof(xorkey)); |
361 |
|
|
for (i = 0; i < sizeof(enckey.seckey); i++) |
362 |
|
|
enckey.seckey[i] ^= xorkey[i]; |
363 |
|
|
explicit_bzero(xorkey, sizeof(xorkey)); |
364 |
|
|
SHA512Init(&ctx); |
365 |
|
|
SHA512Update(&ctx, enckey.seckey, sizeof(enckey.seckey)); |
366 |
|
|
SHA512Final(digest, &ctx); |
367 |
|
|
if (memcmp(enckey.checksum, digest, sizeof(enckey.checksum)) != 0) |
368 |
|
|
errx(1, "incorrect passphrase"); |
369 |
|
|
explicit_bzero(digest, sizeof(digest)); |
370 |
|
|
|
371 |
|
|
msg = readmsg(msgfile, &msglen); |
372 |
|
|
|
373 |
|
|
signmsg(enckey.seckey, msg, msglen, sig.sig); |
374 |
|
|
memcpy(sig.keynum, enckey.keynum, KEYNUMLEN); |
375 |
|
|
explicit_bzero(&enckey, sizeof(enckey)); |
376 |
|
|
|
377 |
|
|
memcpy(sig.pkalg, PKALG, 2); |
378 |
|
|
secname = strstr(seckeyfile, ".sec"); |
379 |
|
|
if (secname && strlen(secname) == 4) { |
380 |
|
|
if ((nr = snprintf(sigcomment, sizeof(sigcomment), VERIFYWITH "%.*s.pub", |
381 |
|
|
(int)strlen(seckeyfile) - 4, seckeyfile)) == -1 || nr >= sizeof(sigcomment)) |
382 |
|
|
errx(1, "comment too long"); |
383 |
|
|
} else { |
384 |
|
|
if ((nr = snprintf(sigcomment, sizeof(sigcomment), "signature from %s", |
385 |
|
|
comment)) == -1 || nr >= sizeof(sigcomment)) |
386 |
|
|
errx(1, "comment too long"); |
387 |
|
|
} |
388 |
|
|
if (embedded) |
389 |
|
|
writeb64file(sigfile, sigcomment, &sig, sizeof(sig), msg, |
390 |
|
|
msglen, O_TRUNC, 0666); |
391 |
|
|
else |
392 |
|
|
writeb64file(sigfile, sigcomment, &sig, sizeof(sig), NULL, |
393 |
|
|
0, O_TRUNC, 0666); |
394 |
|
|
|
395 |
|
|
free(msg); |
396 |
|
|
} |
397 |
|
|
#endif |
398 |
|
|
|
399 |
|
|
static void |
400 |
|
|
verifymsg(struct pubkey *pubkey, uint8_t *msg, unsigned long long msglen, |
401 |
|
|
struct sig *sig, int quiet) |
402 |
|
|
{ |
403 |
|
|
uint8_t *sigbuf, *dummybuf; |
404 |
|
|
unsigned long long siglen, dummylen; |
405 |
|
|
|
406 |
|
|
if (memcmp(pubkey->keynum, sig->keynum, KEYNUMLEN) != 0) |
407 |
|
|
errx(1, "verification failed: checked against wrong key"); |
408 |
|
|
|
409 |
|
|
siglen = SIGBYTES + msglen; |
410 |
|
|
sigbuf = xmalloc(siglen); |
411 |
|
|
dummybuf = xmalloc(siglen); |
412 |
|
|
memcpy(sigbuf, sig->sig, SIGBYTES); |
413 |
|
|
memcpy(sigbuf + SIGBYTES, msg, msglen); |
414 |
|
|
if (crypto_sign_ed25519_open(dummybuf, &dummylen, sigbuf, siglen, |
415 |
|
|
pubkey->pubkey) == -1) |
416 |
|
|
errx(1, "signature verification failed"); |
417 |
|
|
if (!quiet) |
418 |
|
|
printf("Signature Verified\n"); |
419 |
|
|
free(sigbuf); |
420 |
|
|
free(dummybuf); |
421 |
|
|
} |
422 |
|
|
|
423 |
|
|
static void |
424 |
|
|
readpubkey(const char *pubkeyfile, struct pubkey *pubkey, |
425 |
|
|
const char *sigcomment) |
426 |
|
|
{ |
427 |
|
|
const char *safepath = "/etc/signify/"; |
428 |
|
|
|
429 |
|
|
if (!pubkeyfile) { |
430 |
|
|
pubkeyfile = strstr(sigcomment, VERIFYWITH); |
431 |
|
|
if (pubkeyfile) { |
432 |
|
|
pubkeyfile += strlen(VERIFYWITH); |
433 |
|
|
if (strncmp(pubkeyfile, safepath, strlen(safepath)) != 0 || |
434 |
|
|
strstr(pubkeyfile, "/../") != NULL) |
435 |
|
|
errx(1, "untrusted path %s", pubkeyfile); |
436 |
|
|
} else |
437 |
|
|
usage("must specify pubkey"); |
438 |
|
|
} |
439 |
|
|
readb64file(pubkeyfile, pubkey, sizeof(*pubkey), NULL); |
440 |
|
|
} |
441 |
|
|
|
442 |
|
|
static void |
443 |
|
|
verifysimple(const char *pubkeyfile, const char *msgfile, const char *sigfile, |
444 |
|
|
int quiet) |
445 |
|
|
{ |
446 |
|
|
char sigcomment[COMMENTMAXLEN]; |
447 |
|
|
struct sig sig; |
448 |
|
|
struct pubkey pubkey; |
449 |
|
|
unsigned long long msglen; |
450 |
|
|
uint8_t *msg; |
451 |
|
|
|
452 |
|
|
msg = readmsg(msgfile, &msglen); |
453 |
|
|
|
454 |
|
|
readb64file(sigfile, &sig, sizeof(sig), sigcomment); |
455 |
|
|
readpubkey(pubkeyfile, &pubkey, sigcomment); |
456 |
|
|
|
457 |
|
|
verifymsg(&pubkey, msg, msglen, &sig, quiet); |
458 |
|
|
|
459 |
|
|
free(msg); |
460 |
|
|
} |
461 |
|
|
|
462 |
|
|
static uint8_t * |
463 |
|
|
verifyembedded(const char *pubkeyfile, const char *sigfile, |
464 |
|
|
int quiet, unsigned long long *msglenp) |
465 |
|
|
{ |
466 |
|
|
char sigcomment[COMMENTMAXLEN]; |
467 |
|
|
struct sig sig; |
468 |
|
|
struct pubkey pubkey; |
469 |
|
|
unsigned long long msglen, siglen; |
470 |
|
|
uint8_t *msg; |
471 |
|
|
|
472 |
|
|
msg = readmsg(sigfile, &msglen); |
473 |
|
|
|
474 |
|
|
siglen = parseb64file(sigfile, msg, &sig, sizeof(sig), sigcomment); |
475 |
|
|
readpubkey(pubkeyfile, &pubkey, sigcomment); |
476 |
|
|
|
477 |
|
|
msglen -= siglen; |
478 |
|
|
memmove(msg, msg + siglen, msglen); |
479 |
|
|
msg[msglen] = 0; |
480 |
|
|
|
481 |
|
|
verifymsg(&pubkey, msg, msglen, &sig, quiet); |
482 |
|
|
|
483 |
|
|
*msglenp = msglen; |
484 |
|
|
return msg; |
485 |
|
|
} |
486 |
|
|
|
487 |
|
|
static void |
488 |
|
|
verify(const char *pubkeyfile, const char *msgfile, const char *sigfile, |
489 |
|
|
int embedded, int quiet) |
490 |
|
|
{ |
491 |
|
|
unsigned long long msglen; |
492 |
|
|
uint8_t *msg; |
493 |
|
|
int fd; |
494 |
|
|
|
495 |
|
|
if (embedded) { |
496 |
|
|
msg = verifyembedded(pubkeyfile, sigfile, quiet, &msglen); |
497 |
|
|
fd = xopen(msgfile, O_CREAT|O_TRUNC|O_NOFOLLOW|O_WRONLY, 0666); |
498 |
|
|
writeall(fd, msg, msglen, msgfile); |
499 |
|
|
free(msg); |
500 |
|
|
close(fd); |
501 |
|
|
} else { |
502 |
|
|
verifysimple(pubkeyfile, msgfile, sigfile, quiet); |
503 |
|
|
} |
504 |
|
|
} |
505 |
|
|
|
506 |
|
|
#ifndef VERIFYONLY |
507 |
|
|
#define HASHBUFSIZE 224 |
508 |
|
|
struct checksum { |
509 |
|
|
char file[PATH_MAX]; |
510 |
|
|
char hash[HASHBUFSIZE]; |
511 |
|
|
char algo[32]; |
512 |
|
|
}; |
513 |
|
|
|
514 |
|
|
static void * |
515 |
|
|
ecalloc(size_t s1, size_t s2, void *data) |
516 |
|
|
{ |
517 |
|
|
void *p; |
518 |
|
|
|
519 |
|
|
if (!(p = calloc(s1, s2))) |
520 |
|
|
err(1, "calloc"); |
521 |
|
|
return p; |
522 |
|
|
} |
523 |
|
|
|
524 |
|
|
static void |
525 |
|
|
efree(void *p, void *data) |
526 |
|
|
{ |
527 |
|
|
free(p); |
528 |
|
|
} |
529 |
|
|
|
530 |
|
|
static void |
531 |
|
|
recodehash(char *hash, size_t len) |
532 |
|
|
{ |
533 |
|
|
uint8_t data[HASHBUFSIZE / 2]; |
534 |
|
|
int i, rv; |
535 |
|
|
|
536 |
|
|
if (strlen(hash) == len) |
537 |
|
|
return; |
538 |
|
|
if ((rv = b64_pton(hash, data, sizeof(data))) == -1) |
539 |
|
|
errx(1, "invalid base64 encoding"); |
540 |
|
|
for (i = 0; i < rv; i++) |
541 |
|
|
snprintf(hash + i * 2, HASHBUFSIZE - i * 2, "%2.2x", data[i]); |
542 |
|
|
} |
543 |
|
|
|
544 |
|
|
static int |
545 |
|
|
verifychecksum(struct checksum *c, int quiet) |
546 |
|
|
{ |
547 |
|
|
char buf[HASHBUFSIZE]; |
548 |
|
|
|
549 |
|
|
if (strcmp(c->algo, "SHA256") == 0) { |
550 |
|
|
recodehash(c->hash, SHA256_DIGEST_STRING_LENGTH-1); |
551 |
|
|
if (!SHA256File(c->file, buf)) |
552 |
|
|
return 0; |
553 |
|
|
} else if (strcmp(c->algo, "SHA512") == 0) { |
554 |
|
|
recodehash(c->hash, SHA512_DIGEST_STRING_LENGTH-1); |
555 |
|
|
if (!SHA512File(c->file, buf)) |
556 |
|
|
return 0; |
557 |
|
|
} else { |
558 |
|
|
errx(1, "can't handle algorithm %s", c->algo); |
559 |
|
|
} |
560 |
|
|
if (strcmp(c->hash, buf) != 0) |
561 |
|
|
return 0; |
562 |
|
|
if (!quiet) |
563 |
|
|
printf("%s: OK\n", c->file); |
564 |
|
|
return 1; |
565 |
|
|
} |
566 |
|
|
|
567 |
|
|
static void |
568 |
|
|
verifychecksums(char *msg, int argc, char **argv, int quiet) |
569 |
|
|
{ |
570 |
|
|
struct ohash_info info = { 0, NULL, ecalloc, efree, NULL }; |
571 |
|
|
struct ohash myh; |
572 |
|
|
struct checksum c; |
573 |
|
|
char *e, *line, *endline; |
574 |
|
|
int hasfailed = 0; |
575 |
|
|
int i, rv; |
576 |
|
|
unsigned int slot; |
577 |
|
|
|
578 |
|
|
ohash_init(&myh, 6, &info); |
579 |
|
|
if (argc) { |
580 |
|
|
for (i = 0; i < argc; i++) { |
581 |
|
|
slot = ohash_qlookup(&myh, argv[i]); |
582 |
|
|
e = ohash_find(&myh, slot); |
583 |
|
|
if (e == NULL) |
584 |
|
|
ohash_insert(&myh, slot, argv[i]); |
585 |
|
|
} |
586 |
|
|
} |
587 |
|
|
|
588 |
|
|
line = msg; |
589 |
|
|
while (line && *line) { |
590 |
|
|
if ((endline = strchr(line, '\n'))) |
591 |
|
|
*endline++ = '\0'; |
592 |
|
|
#if PATH_MAX < 1024 || HASHBUFSIZE < 224 |
593 |
|
|
#error sizes are wrong |
594 |
|
|
#endif |
595 |
|
|
rv = sscanf(line, "%31s (%1023[^)]) = %223s", |
596 |
|
|
c.algo, c.file, c.hash); |
597 |
|
|
if (rv != 3) |
598 |
|
|
errx(1, "unable to parse checksum line %s", line); |
599 |
|
|
line = endline; |
600 |
|
|
if (argc) { |
601 |
|
|
slot = ohash_qlookup(&myh, c.file); |
602 |
|
|
e = ohash_find(&myh, slot); |
603 |
|
|
if (e != NULL) { |
604 |
|
|
if (verifychecksum(&c, quiet) != 0) |
605 |
|
|
ohash_remove(&myh, slot); |
606 |
|
|
} |
607 |
|
|
} else { |
608 |
|
|
if (verifychecksum(&c, quiet) == 0) { |
609 |
|
|
slot = ohash_qlookup(&myh, c.file); |
610 |
|
|
e = ohash_find(&myh, slot); |
611 |
|
|
if (e == NULL) { |
612 |
|
|
if (!(e = strdup(c.file))) |
613 |
|
|
err(1, "strdup"); |
614 |
|
|
ohash_insert(&myh, slot, e); |
615 |
|
|
} |
616 |
|
|
} |
617 |
|
|
} |
618 |
|
|
} |
619 |
|
|
|
620 |
|
|
for (e = ohash_first(&myh, &slot); e != NULL; e = ohash_next(&myh, &slot)) { |
621 |
|
|
fprintf(stderr, "%s: FAIL\n", e); |
622 |
|
|
hasfailed = 1; |
623 |
|
|
if (argc == 0) |
624 |
|
|
free(e); |
625 |
|
|
} |
626 |
|
|
ohash_delete(&myh); |
627 |
|
|
if (hasfailed) |
628 |
|
|
exit(1); |
629 |
|
|
} |
630 |
|
|
|
631 |
|
|
static void |
632 |
|
|
check(const char *pubkeyfile, const char *sigfile, int quiet, int argc, |
633 |
|
|
char **argv) |
634 |
|
|
{ |
635 |
|
|
unsigned long long msglen; |
636 |
|
|
uint8_t *msg; |
637 |
|
|
|
638 |
|
|
msg = verifyembedded(pubkeyfile, sigfile, quiet, &msglen); |
639 |
|
|
verifychecksums((char *)msg, argc, argv, quiet); |
640 |
|
|
|
641 |
|
|
free(msg); |
642 |
|
|
} |
643 |
|
|
#endif |
644 |
|
|
|
645 |
|
|
int |
646 |
|
|
main(int argc, char **argv) |
647 |
|
|
{ |
648 |
|
|
const char *pubkeyfile = NULL, *seckeyfile = NULL, *msgfile = NULL, |
649 |
|
|
*sigfile = NULL; |
650 |
|
|
char sigfilebuf[PATH_MAX]; |
651 |
|
|
const char *comment = "signify"; |
652 |
|
|
int ch, rounds; |
653 |
|
|
int embedded = 0; |
654 |
|
|
int quiet = 0; |
655 |
|
|
enum { |
656 |
|
|
NONE, |
657 |
|
|
CHECK, |
658 |
|
|
GENERATE, |
659 |
|
|
SIGN, |
660 |
|
|
VERIFY |
661 |
|
|
} verb = NONE; |
662 |
|
|
|
663 |
|
|
if (pledge("stdio rpath wpath cpath tty", NULL) == -1) |
664 |
|
|
err(1, "pledge"); |
665 |
|
|
|
666 |
|
|
rounds = 42; |
667 |
|
|
|
668 |
|
|
while ((ch = getopt(argc, argv, "CGSVc:em:np:qs:x:")) != -1) { |
669 |
|
|
switch (ch) { |
670 |
|
|
#ifndef VERIFYONLY |
671 |
|
|
case 'C': |
672 |
|
|
if (verb) |
673 |
|
|
usage(NULL); |
674 |
|
|
verb = CHECK; |
675 |
|
|
break; |
676 |
|
|
case 'G': |
677 |
|
|
if (verb) |
678 |
|
|
usage(NULL); |
679 |
|
|
verb = GENERATE; |
680 |
|
|
break; |
681 |
|
|
case 'S': |
682 |
|
|
if (verb) |
683 |
|
|
usage(NULL); |
684 |
|
|
verb = SIGN; |
685 |
|
|
break; |
686 |
|
|
#endif |
687 |
|
|
case 'V': |
688 |
|
|
if (verb) |
689 |
|
|
usage(NULL); |
690 |
|
|
verb = VERIFY; |
691 |
|
|
break; |
692 |
|
|
case 'c': |
693 |
|
|
comment = optarg; |
694 |
|
|
break; |
695 |
|
|
case 'e': |
696 |
|
|
embedded = 1; |
697 |
|
|
break; |
698 |
|
|
case 'm': |
699 |
|
|
msgfile = optarg; |
700 |
|
|
break; |
701 |
|
|
case 'n': |
702 |
|
|
rounds = 0; |
703 |
|
|
break; |
704 |
|
|
case 'p': |
705 |
|
|
pubkeyfile = optarg; |
706 |
|
|
break; |
707 |
|
|
case 'q': |
708 |
|
|
quiet = 1; |
709 |
|
|
break; |
710 |
|
|
case 's': |
711 |
|
|
seckeyfile = optarg; |
712 |
|
|
break; |
713 |
|
|
case 'x': |
714 |
|
|
sigfile = optarg; |
715 |
|
|
break; |
716 |
|
|
default: |
717 |
|
|
usage(NULL); |
718 |
|
|
break; |
719 |
|
|
} |
720 |
|
|
} |
721 |
|
|
argc -= optind; |
722 |
|
|
argv += optind; |
723 |
|
|
|
724 |
|
|
if (setvbuf(stdout, NULL, _IOLBF, 0) != 0) |
725 |
|
|
err(1, "setvbuf"); |
726 |
|
|
|
727 |
|
|
switch (verb) { |
728 |
|
|
case GENERATE: |
729 |
|
|
case SIGN: |
730 |
|
|
/* keep it all */ |
731 |
|
|
break; |
732 |
|
|
case CHECK: |
733 |
|
|
if (pledge("stdio rpath wpath cpath", NULL) == -1) |
734 |
|
|
err(1, "pledge"); |
735 |
|
|
break; |
736 |
|
|
case VERIFY: |
737 |
|
|
if (embedded && (!msgfile || strcmp(msgfile, "-") != 0)) { |
738 |
|
|
if (pledge("stdio rpath wpath cpath", NULL) == -1) |
739 |
|
|
err(1, "pledge"); |
740 |
|
|
} else { |
741 |
|
|
if (pledge("stdio rpath wpath cpath", NULL) == -1) |
742 |
|
|
err(1, "pledge"); |
743 |
|
|
} |
744 |
|
|
break; |
745 |
|
|
default: |
746 |
|
|
if (pledge("stdio wpath cpath rpath", NULL) == -1) |
747 |
|
|
err(1, "pledge"); |
748 |
|
|
break; |
749 |
|
|
} |
750 |
|
|
|
751 |
|
|
#ifndef VERIFYONLY |
752 |
|
|
if (verb == CHECK) { |
753 |
|
|
if (!sigfile) |
754 |
|
|
usage("must specify sigfile"); |
755 |
|
|
check(pubkeyfile, sigfile, quiet, argc, argv); |
756 |
|
|
return 0; |
757 |
|
|
} |
758 |
|
|
#endif |
759 |
|
|
|
760 |
|
|
if (argc != 0) |
761 |
|
|
usage(NULL); |
762 |
|
|
|
763 |
|
|
if (!sigfile && msgfile) { |
764 |
|
|
int nr; |
765 |
|
|
if (strcmp(msgfile, "-") == 0) |
766 |
|
|
usage("must specify sigfile with - message"); |
767 |
|
|
if ((nr = snprintf(sigfilebuf, sizeof(sigfilebuf), "%s.sig", |
768 |
|
|
msgfile)) == -1 || nr >= sizeof(sigfilebuf)) |
769 |
|
|
errx(1, "path too long"); |
770 |
|
|
sigfile = sigfilebuf; |
771 |
|
|
} |
772 |
|
|
|
773 |
|
|
switch (verb) { |
774 |
|
|
#ifndef VERIFYONLY |
775 |
|
|
case GENERATE: |
776 |
|
|
if (!pubkeyfile || !seckeyfile) |
777 |
|
|
usage("must specify pubkey and seckey"); |
778 |
|
|
generate(pubkeyfile, seckeyfile, rounds, comment); |
779 |
|
|
break; |
780 |
|
|
case SIGN: |
781 |
|
|
if (!msgfile || !seckeyfile) |
782 |
|
|
usage("must specify message and seckey"); |
783 |
|
|
sign(seckeyfile, msgfile, sigfile, embedded); |
784 |
|
|
break; |
785 |
|
|
#endif |
786 |
|
|
case VERIFY: |
787 |
|
|
if (!msgfile) |
788 |
|
|
usage("must specify message"); |
789 |
|
|
verify(pubkeyfile, msgfile, sigfile, embedded, quiet); |
790 |
|
|
break; |
791 |
|
|
default: |
792 |
|
|
usage(NULL); |
793 |
|
|
break; |
794 |
|
|
} |
795 |
|
|
|
796 |
|
|
return 0; |
797 |
|
|
} |