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/types.h>
18 #include <sys/stat.h>
19 #include <sys/socket.h>
20 #include <sys/queue.h>
21 #include <sys/wait.h>
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <fcntl.h>
26 #include <unistd.h>
27 #include <string.h>
28 #include <err.h>
29 #include <assert.h>
30 #include <sha1.h>
32 #include "got_error.h"
33 #include "got_date.h"
34 #include "got_object.h"
35 #include "got_opentemp.h"
37 #include "got_sigs.h"
39 #include "buf.h"
41 #ifndef MIN
42 #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
43 #endif
45 #ifndef nitems
46 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
47 #endif
49 #ifndef GOT_TAG_PATH_SSH_KEYGEN
50 #define GOT_TAG_PATH_SSH_KEYGEN "/usr/bin/ssh-keygen"
51 #endif
53 #ifndef GOT_TAG_PATH_SIGNIFY
54 #define GOT_TAG_PATH_SIGNIFY "/usr/bin/signify"
55 #endif
57 const struct got_error *
58 got_sigs_apply_unveil()
59 {
60 if (unveil(GOT_TAG_PATH_SSH_KEYGEN, "x") != 0) {
61 return got_error_from_errno2("unveil",
62 GOT_TAG_PATH_SSH_KEYGEN);
63 }
64 if (unveil(GOT_TAG_PATH_SIGNIFY, "x") != 0) {
65 return got_error_from_errno2("unveil",
66 GOT_TAG_PATH_SIGNIFY);
67 }
69 return NULL;
70 }
72 const struct got_error *
73 got_sigs_sign_tag_ssh(pid_t *newpid, int *in_fd, int *out_fd,
74 const char* key_file, int verbosity)
75 {
76 const struct got_error *error = NULL;
77 int pid, in_pfd[2], out_pfd[2];
78 const char* argv[11];
79 int i = 0, j;
81 *newpid = -1;
82 *in_fd = -1;
83 *out_fd = -1;
85 argv[i++] = GOT_TAG_PATH_SSH_KEYGEN;
86 argv[i++] = "-Y";
87 argv[i++] = "sign";
88 argv[i++] = "-f";
89 argv[i++] = key_file;
90 argv[i++] = "-n";
91 argv[i++] = "git";
92 if (verbosity <= 0) {
93 argv[i++] = "-q";
94 } else {
95 /* ssh(1) allows up to 3 "-v" options. */
96 for (j = 0; j < MIN(3, verbosity); j++)
97 argv[i++] = "-v";
98 }
99 argv[i++] = NULL;
100 assert(i <= nitems(argv));
102 if (pipe(in_pfd) == -1)
103 return got_error_from_errno("pipe");
104 if (pipe(out_pfd) == -1)
105 return got_error_from_errno("pipe");
107 pid = fork();
108 if (pid == -1) {
109 error = got_error_from_errno("fork");
110 close(in_pfd[0]);
111 close(in_pfd[1]);
112 close(out_pfd[0]);
113 close(out_pfd[1]);
114 return error;
115 } else if (pid == 0) {
116 if (close(in_pfd[1]) == -1)
117 err(1, "close");
118 if (close(out_pfd[1]) == -1)
119 err(1, "close");
120 if (dup2(in_pfd[0], 0) == -1)
121 err(1, "dup2");
122 if (dup2(out_pfd[0], 1) == -1)
123 err(1, "dup2");
124 if (execv(GOT_TAG_PATH_SSH_KEYGEN, (char **const)argv) == -1)
125 err(1, "execv");
126 abort(); /* not reached */
128 if (close(in_pfd[0]) == -1)
129 return got_error_from_errno("close");
130 if (close(out_pfd[0]) == -1)
131 return got_error_from_errno("close");
132 *newpid = pid;
133 *in_fd = in_pfd[1];
134 *out_fd = out_pfd[1];
135 return NULL;
138 static char *
139 signer_identity(const char *tagger)
141 char *lt, *gt;
143 lt = strstr(tagger, " <");
144 gt = strrchr(tagger, '>');
145 if (lt && gt && lt+1 < gt)
146 return strndup(lt+2, gt-lt-2);
147 return NULL;
150 static const char* BEGIN_SSH_SIG = "-----BEGIN SSH SIGNATURE-----\n";
151 static const char* END_SSH_SIG = "-----END SSH SIGNATURE-----\n";
153 const char *
154 got_sigs_get_tagmsg_ssh_signature(const char *tagmsg)
156 const char *s = tagmsg, *begin = NULL, *end = NULL;
158 while ((s = strstr(s, BEGIN_SSH_SIG)) != NULL) {
159 begin = s;
160 s += strlen(BEGIN_SSH_SIG);
162 if (begin)
163 end = strstr(begin+strlen(BEGIN_SSH_SIG), END_SSH_SIG);
164 if (end == NULL)
165 return NULL;
166 return (end[strlen(END_SSH_SIG)] == '\0') ? begin : NULL;
169 static const struct got_error *
170 got_tag_write_signed_data(BUF *buf, struct got_tag_object *tag,
171 const char *start_sig)
173 const struct got_error *err = NULL;
174 struct got_object_id *id;
175 char *id_str = NULL;
176 char *tagger = NULL;
177 const char *tagmsg;
178 char gmtoff[6];
179 size_t len;
181 id = got_object_tag_get_object_id(tag);
182 err = got_object_id_str(&id_str, id);
183 if (err)
184 goto done;
186 const char *type_label = NULL;
187 switch (got_object_tag_get_object_type(tag)) {
188 case GOT_OBJ_TYPE_BLOB:
189 type_label = GOT_OBJ_LABEL_BLOB;
190 break;
191 case GOT_OBJ_TYPE_TREE:
192 type_label = GOT_OBJ_LABEL_TREE;
193 break;
194 case GOT_OBJ_TYPE_COMMIT:
195 type_label = GOT_OBJ_LABEL_COMMIT;
196 break;
197 case GOT_OBJ_TYPE_TAG:
198 type_label = GOT_OBJ_LABEL_TAG;
199 break;
200 default:
201 break;
203 got_date_format_gmtoff(gmtoff, sizeof(gmtoff),
204 got_object_tag_get_tagger_gmtoff(tag));
205 if (asprintf(&tagger, "%s %lld %s", got_object_tag_get_tagger(tag),
206 got_object_tag_get_tagger_time(tag), gmtoff) == -1) {
207 err = got_error_from_errno("asprintf");
208 goto done;
211 err = buf_puts(&len, buf, GOT_TAG_LABEL_OBJECT);
212 if (err)
213 goto done;
214 err = buf_puts(&len, buf, id_str);
215 if (err)
216 goto done;
217 err = buf_putc(buf, '\n');
218 if (err)
219 goto done;
220 err = buf_puts(&len, buf, GOT_TAG_LABEL_TYPE);
221 if (err)
222 goto done;
223 err = buf_puts(&len, buf, type_label);
224 if (err)
225 goto done;
226 err = buf_putc(buf, '\n');
227 if (err)
228 goto done;
229 err = buf_puts(&len, buf, GOT_TAG_LABEL_TAG);
230 if (err)
231 goto done;
232 err = buf_puts(&len, buf, got_object_tag_get_name(tag));
233 if (err)
234 goto done;
235 err = buf_putc(buf, '\n');
236 if (err)
237 goto done;
238 err = buf_puts(&len, buf, GOT_TAG_LABEL_TAGGER);
239 if (err)
240 goto done;
241 err = buf_puts(&len, buf, tagger);
242 if (err)
243 goto done;
244 err = buf_puts(&len, buf, "\n");
245 if (err)
246 goto done;
247 tagmsg = got_object_tag_get_message(tag);
248 err = buf_append(&len, buf, tagmsg, start_sig-tagmsg);
249 if (err)
250 goto done;
252 done:
253 free(id_str);
254 free(tagger);
255 return err;
258 const struct got_error *
259 got_sigs_verify_tag_ssh(char **msg, struct got_tag_object *tag,
260 const char *start_sig, const char* allowed_signers, const char* revoked,
261 int verbosity)
263 const struct got_error *error = NULL;
264 const char* argv[17];
265 int pid, status, in_pfd[2], out_pfd[2];
266 char* parsed_identity = NULL;
267 const char *identity;
268 char* tmppath = NULL;
269 FILE *tmpsig, *out = NULL;
270 BUF *buf;
271 int i = 0, j;
273 *msg = NULL;
275 error = got_opentemp_named(&tmppath, &tmpsig,
276 GOT_TMPDIR_STR "/got-tagsig");
277 if (error)
278 goto done;
280 identity = got_object_tag_get_tagger(tag);
281 parsed_identity = signer_identity(identity);
282 if (parsed_identity != NULL)
283 identity = parsed_identity;
285 if (fputs(start_sig, tmpsig) == EOF) {
286 error = got_error_from_errno("fputs");
287 goto done;
289 if (fflush(tmpsig) == EOF) {
290 error = got_error_from_errno("fflush");
291 goto done;
294 error = buf_alloc(&buf, 0);
295 if (error)
296 goto done;
297 error = got_tag_write_signed_data(buf, tag, start_sig);
298 if (error)
299 goto done;
301 argv[i++] = GOT_TAG_PATH_SSH_KEYGEN;
302 argv[i++] = "-Y";
303 argv[i++] = "verify";
304 argv[i++] = "-f";
305 argv[i++] = allowed_signers;
306 argv[i++] = "-I";
307 argv[i++] = identity;
308 argv[i++] = "-n";
309 argv[i++] = "git";
310 argv[i++] = "-s";
311 argv[i++] = tmppath;
312 if (revoked) {
313 argv[i++] = "-r";
314 argv[i++] = revoked;
316 if (verbosity > 0) {
317 /* ssh(1) allows up to 3 "-v" options. */
318 for (j = 0; j < MIN(3, verbosity); j++)
319 argv[i++] = "-v";
321 argv[i++] = NULL;
322 assert(i <= nitems(argv));
324 if (pipe(in_pfd) == -1) {
325 error = got_error_from_errno("pipe");
326 goto done;
328 if (pipe(out_pfd) == -1) {
329 error = got_error_from_errno("pipe");
330 goto done;
333 pid = fork();
334 if (pid == -1) {
335 error = got_error_from_errno("fork");
336 close(in_pfd[0]);
337 close(in_pfd[1]);
338 close(out_pfd[0]);
339 close(out_pfd[1]);
340 return error;
341 } else if (pid == 0) {
342 if (close(in_pfd[1]) == -1)
343 err(1, "close");
344 if (close(out_pfd[1]) == -1)
345 err(1, "close");
346 if (dup2(in_pfd[0], 0) == -1)
347 err(1, "dup2");
348 if (dup2(out_pfd[0], 1) == -1)
349 err(1, "dup2");
350 if (execv(GOT_TAG_PATH_SSH_KEYGEN, (char **const)argv) == -1)
351 err(1, "execv");
352 abort(); /* not reached */
354 if (close(in_pfd[0]) == -1) {
355 error = got_error_from_errno("close");
356 goto done;
358 if (close(out_pfd[0]) == -1) {
359 error = got_error_from_errno("close");
360 goto done;
362 if (buf_write_fd(buf, in_pfd[1]) == -1) {
363 error = got_error_from_errno("write");
364 goto done;
366 if (close(in_pfd[1]) == -1) {
367 error = got_error_from_errno("close");
368 goto done;
370 if (waitpid(pid, &status, 0) == -1) {
371 error = got_error_from_errno("waitpid");
372 goto done;
374 if (!WIFEXITED(status)) {
375 error = got_error(GOT_ERR_BAD_TAG_SIGNATURE);
376 goto done;
379 out = fdopen(out_pfd[1], "r");
380 if (out == NULL) {
381 error = got_error_from_errno("fdopen");
382 goto done;
384 error = buf_load(&buf, out);
385 if (error)
386 goto done;
387 error = buf_putc(buf, '\0');
388 if (error)
389 goto done;
390 if (close(out_pfd[1]) == -1) {
391 error = got_error_from_errno("close");
392 goto done;
394 out = NULL;
395 *msg = buf_get(buf);
396 if (WEXITSTATUS(status) != 0)
397 error = got_error(GOT_ERR_BAD_TAG_SIGNATURE);
399 done:
400 free(parsed_identity);
401 free(tmppath);
402 if (tmpsig && fclose(tmpsig) == EOF && error == NULL)
403 error = got_error_from_errno("fclose");
404 if (out && fclose(out) == EOF && error == NULL)
405 error = got_error_from_errno("fclose");
406 return error;