Blob


1 /*
2 * Copyright (c) 2022 Josh Rickmar <jrick@zettaport.com>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
17 #include <sys/time.h>
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <sys/socket.h>
21 #include <sys/queue.h>
22 #include <sys/wait.h>
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <fcntl.h>
27 #include <unistd.h>
28 #include <string.h>
29 #include <err.h>
30 #include <assert.h>
31 #include <sha1.h>
32 #include <sha2.h>
34 #include "got_error.h"
35 #include "got_date.h"
36 #include "got_object.h"
37 #include "got_opentemp.h"
39 #include "got_sigs.h"
41 #include "buf.h"
43 #ifndef MIN
44 #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
45 #endif
47 #ifndef nitems
48 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
49 #endif
51 #ifndef GOT_TAG_PATH_SSH_KEYGEN
52 #define GOT_TAG_PATH_SSH_KEYGEN "/usr/bin/ssh-keygen"
53 #endif
55 #ifndef GOT_TAG_PATH_SIGNIFY
56 #define GOT_TAG_PATH_SIGNIFY "/usr/bin/signify"
57 #endif
59 const struct got_error *
60 got_sigs_apply_unveil(void)
61 {
62 if (unveil(GOT_TAG_PATH_SSH_KEYGEN, "x") != 0) {
63 return got_error_from_errno2("unveil",
64 GOT_TAG_PATH_SSH_KEYGEN);
65 }
66 if (unveil(GOT_TAG_PATH_SIGNIFY, "x") != 0) {
67 return got_error_from_errno2("unveil",
68 GOT_TAG_PATH_SIGNIFY);
69 }
71 return NULL;
72 }
74 const struct got_error *
75 got_sigs_sign_tag_ssh(pid_t *newpid, int *in_fd, int *out_fd,
76 const char* key_file, int verbosity)
77 {
78 const struct got_error *error = NULL;
79 int pid, in_pfd[2], out_pfd[2];
80 const char* argv[11];
81 int i = 0, j;
83 *newpid = -1;
84 *in_fd = -1;
85 *out_fd = -1;
87 argv[i++] = GOT_TAG_PATH_SSH_KEYGEN;
88 argv[i++] = "-Y";
89 argv[i++] = "sign";
90 argv[i++] = "-f";
91 argv[i++] = key_file;
92 argv[i++] = "-n";
93 argv[i++] = "git";
94 if (verbosity <= 0) {
95 argv[i++] = "-q";
96 } else {
97 /* ssh(1) allows up to 3 "-v" options. */
98 for (j = 0; j < MIN(3, verbosity); j++)
99 argv[i++] = "-v";
101 argv[i++] = NULL;
102 assert(i <= nitems(argv));
104 if (pipe(in_pfd) == -1)
105 return got_error_from_errno("pipe");
106 if (pipe(out_pfd) == -1)
107 return got_error_from_errno("pipe");
109 pid = fork();
110 if (pid == -1) {
111 error = got_error_from_errno("fork");
112 close(in_pfd[0]);
113 close(in_pfd[1]);
114 close(out_pfd[0]);
115 close(out_pfd[1]);
116 return error;
117 } else if (pid == 0) {
118 if (close(in_pfd[1]) == -1)
119 err(1, "close");
120 if (close(out_pfd[0]) == -1)
121 err(1, "close");
122 if (dup2(in_pfd[0], 0) == -1)
123 err(1, "dup2");
124 if (dup2(out_pfd[1], 1) == -1)
125 err(1, "dup2");
126 if (execv(GOT_TAG_PATH_SSH_KEYGEN, (char **const)argv) == -1)
127 err(1, "execv");
128 abort(); /* not reached */
130 if (close(in_pfd[0]) == -1)
131 return got_error_from_errno("close");
132 if (close(out_pfd[1]) == -1)
133 return got_error_from_errno("close");
134 *newpid = pid;
135 *in_fd = in_pfd[1];
136 *out_fd = out_pfd[0];
137 return NULL;
140 static char *
141 signer_identity(const char *tagger)
143 char *lt, *gt;
145 lt = strstr(tagger, " <");
146 gt = strrchr(tagger, '>');
147 if (lt && gt && lt+1 < gt)
148 return strndup(lt+2, gt-lt-2);
149 return NULL;
152 static const char* BEGIN_SSH_SIG = "-----BEGIN SSH SIGNATURE-----\n";
153 static const char* END_SSH_SIG = "-----END SSH SIGNATURE-----\n";
155 const char *
156 got_sigs_get_tagmsg_ssh_signature(const char *tagmsg)
158 const char *s = tagmsg, *begin = NULL, *end = NULL;
160 while ((s = strstr(s, BEGIN_SSH_SIG)) != NULL) {
161 begin = s;
162 s += strlen(BEGIN_SSH_SIG);
164 if (begin)
165 end = strstr(begin+strlen(BEGIN_SSH_SIG), END_SSH_SIG);
166 if (end == NULL)
167 return NULL;
168 return (end[strlen(END_SSH_SIG)] == '\0') ? begin : NULL;
171 static const struct got_error *
172 got_tag_write_signed_data(BUF *buf, struct got_tag_object *tag,
173 const char *start_sig)
175 const struct got_error *err = NULL;
176 struct got_object_id *id;
177 char *id_str = NULL;
178 char *tagger = NULL;
179 const char *tagmsg;
180 char gmtoff[6];
181 size_t len;
183 id = got_object_tag_get_object_id(tag);
184 err = got_object_id_str(&id_str, id);
185 if (err)
186 goto done;
188 const char *type_label = NULL;
189 switch (got_object_tag_get_object_type(tag)) {
190 case GOT_OBJ_TYPE_BLOB:
191 type_label = GOT_OBJ_LABEL_BLOB;
192 break;
193 case GOT_OBJ_TYPE_TREE:
194 type_label = GOT_OBJ_LABEL_TREE;
195 break;
196 case GOT_OBJ_TYPE_COMMIT:
197 type_label = GOT_OBJ_LABEL_COMMIT;
198 break;
199 case GOT_OBJ_TYPE_TAG:
200 type_label = GOT_OBJ_LABEL_TAG;
201 break;
202 default:
203 break;
205 got_date_format_gmtoff(gmtoff, sizeof(gmtoff),
206 got_object_tag_get_tagger_gmtoff(tag));
207 if (asprintf(&tagger, "%s %lld %s", got_object_tag_get_tagger(tag),
208 (long long)got_object_tag_get_tagger_time(tag), gmtoff) == -1) {
209 err = got_error_from_errno("asprintf");
210 goto done;
213 err = buf_puts(&len, buf, GOT_TAG_LABEL_OBJECT);
214 if (err)
215 goto done;
216 err = buf_puts(&len, buf, id_str);
217 if (err)
218 goto done;
219 err = buf_putc(buf, '\n');
220 if (err)
221 goto done;
222 err = buf_puts(&len, buf, GOT_TAG_LABEL_TYPE);
223 if (err)
224 goto done;
225 err = buf_puts(&len, buf, type_label);
226 if (err)
227 goto done;
228 err = buf_putc(buf, '\n');
229 if (err)
230 goto done;
231 err = buf_puts(&len, buf, GOT_TAG_LABEL_TAG);
232 if (err)
233 goto done;
234 err = buf_puts(&len, buf, got_object_tag_get_name(tag));
235 if (err)
236 goto done;
237 err = buf_putc(buf, '\n');
238 if (err)
239 goto done;
240 err = buf_puts(&len, buf, GOT_TAG_LABEL_TAGGER);
241 if (err)
242 goto done;
243 err = buf_puts(&len, buf, tagger);
244 if (err)
245 goto done;
246 err = buf_puts(&len, buf, "\n");
247 if (err)
248 goto done;
249 tagmsg = got_object_tag_get_message(tag);
250 err = buf_append(&len, buf, tagmsg, start_sig-tagmsg);
251 if (err)
252 goto done;
254 done:
255 free(id_str);
256 free(tagger);
257 return err;
260 const struct got_error *
261 got_sigs_verify_tag_ssh(char **msg, struct got_tag_object *tag,
262 const char *start_sig, const char* allowed_signers, const char* revoked,
263 int verbosity)
265 const struct got_error *error = NULL;
266 const char* argv[17];
267 int pid, status, in_pfd[2], out_pfd[2];
268 char* parsed_identity = NULL;
269 const char *identity;
270 char *tmppath = NULL;
271 FILE *tmpsig = NULL;
272 BUF *buf;
273 int i = 0, j;
275 *msg = NULL;
277 error = got_opentemp_named(&tmppath, &tmpsig,
278 GOT_TMPDIR_STR "/got-tagsig", "");
279 if (error)
280 goto done;
282 identity = got_object_tag_get_tagger(tag);
283 parsed_identity = signer_identity(identity);
284 if (parsed_identity != NULL)
285 identity = parsed_identity;
287 if (fputs(start_sig, tmpsig) == EOF) {
288 error = got_error_from_errno("fputs");
289 goto done;
291 if (fflush(tmpsig) == EOF) {
292 error = got_error_from_errno("fflush");
293 goto done;
296 error = buf_alloc(&buf, 0);
297 if (error)
298 goto done;
299 error = got_tag_write_signed_data(buf, tag, start_sig);
300 if (error)
301 goto done;
303 argv[i++] = GOT_TAG_PATH_SSH_KEYGEN;
304 argv[i++] = "-Y";
305 argv[i++] = "verify";
306 argv[i++] = "-f";
307 argv[i++] = allowed_signers;
308 argv[i++] = "-I";
309 argv[i++] = identity;
310 argv[i++] = "-n";
311 argv[i++] = "git";
312 argv[i++] = "-s";
313 argv[i++] = tmppath;
314 if (revoked) {
315 argv[i++] = "-r";
316 argv[i++] = revoked;
318 if (verbosity > 0) {
319 /* ssh(1) allows up to 3 "-v" options. */
320 for (j = 0; j < MIN(3, verbosity); j++)
321 argv[i++] = "-v";
323 argv[i++] = NULL;
324 assert(i <= nitems(argv));
326 if (pipe(in_pfd) == -1) {
327 error = got_error_from_errno("pipe");
328 goto done;
330 if (pipe(out_pfd) == -1) {
331 error = got_error_from_errno("pipe");
332 goto done;
335 pid = fork();
336 if (pid == -1) {
337 error = got_error_from_errno("fork");
338 close(in_pfd[0]);
339 close(in_pfd[1]);
340 close(out_pfd[0]);
341 close(out_pfd[1]);
342 return error;
343 } else if (pid == 0) {
344 if (close(in_pfd[1]) == -1)
345 err(1, "close");
346 if (close(out_pfd[0]) == -1)
347 err(1, "close");
348 if (dup2(in_pfd[0], 0) == -1)
349 err(1, "dup2");
350 if (dup2(out_pfd[1], 1) == -1)
351 err(1, "dup2");
352 if (execv(GOT_TAG_PATH_SSH_KEYGEN, (char **const)argv) == -1)
353 err(1, "execv");
354 abort(); /* not reached */
356 if (close(in_pfd[0]) == -1) {
357 error = got_error_from_errno("close");
358 goto done;
360 if (close(out_pfd[1]) == -1) {
361 error = got_error_from_errno("close");
362 goto done;
364 if (buf_write_fd(buf, in_pfd[1]) == -1) {
365 error = got_error_from_errno("write");
366 goto done;
368 if (close(in_pfd[1]) == -1) {
369 error = got_error_from_errno("close");
370 goto done;
372 if (waitpid(pid, &status, 0) == -1) {
373 error = got_error_from_errno("waitpid");
374 goto done;
376 if (!WIFEXITED(status)) {
377 error = got_error(GOT_ERR_BAD_TAG_SIGNATURE);
378 goto done;
381 error = buf_load_fd(&buf, out_pfd[0]);
382 if (error)
383 goto done;
384 error = buf_putc(buf, '\0');
385 if (error)
386 goto done;
387 if (close(out_pfd[0]) == -1) {
388 error = got_error_from_errno("close");
389 goto done;
391 *msg = buf_get(buf);
392 if (WEXITSTATUS(status) != 0)
393 error = got_error(GOT_ERR_BAD_TAG_SIGNATURE);
395 done:
396 free(parsed_identity);
397 if (tmppath && unlink(tmppath) == -1 && error == NULL)
398 error = got_error_from_errno("unlink");
399 free(tmppath);
400 close(out_pfd[0]);
401 if (tmpsig && fclose(tmpsig) == EOF && error == NULL)
402 error = got_error_from_errno("fclose");
403 return error;