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/wait.h>
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <fcntl.h>
25 #include <unistd.h>
26 #include <string.h>
27 #include <err.h>
28 #include <assert.h>
30 #include "got_error.h"
31 #include "got_date.h"
32 #include "got_object.h"
33 #include "got_opentemp.h"
35 #include "got_sigs.h"
36 #include "got_compat.h"
37 #include "buf.h"
39 #ifndef MIN
40 #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
41 #endif
43 #ifndef nitems
44 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
45 #endif
47 #ifndef GOT_TAG_PATH_SSH_KEYGEN
48 #define GOT_TAG_PATH_SSH_KEYGEN "/usr/bin/ssh-keygen"
49 #endif
51 #ifndef GOT_TAG_PATH_SIGNIFY
52 #define GOT_TAG_PATH_SIGNIFY "/usr/bin/signify"
53 #endif
55 const struct got_error *
56 got_sigs_apply_unveil()
57 {
58 if (unveil(GOT_TAG_PATH_SSH_KEYGEN, "x") != 0) {
59 return got_error_from_errno2("unveil",
60 GOT_TAG_PATH_SSH_KEYGEN);
61 }
62 if (unveil(GOT_TAG_PATH_SIGNIFY, "x") != 0) {
63 return got_error_from_errno2("unveil",
64 GOT_TAG_PATH_SIGNIFY);
65 }
67 return NULL;
68 }
70 const struct got_error *
71 got_sigs_sign_tag_ssh(pid_t *newpid, int *in_fd, int *out_fd,
72 const char* key_file, int verbosity)
73 {
74 const struct got_error *error = NULL;
75 int pid, in_pfd[2], out_pfd[2];
76 const char* argv[11];
77 int i = 0, j;
79 *newpid = -1;
80 *in_fd = -1;
81 *out_fd = -1;
83 argv[i++] = GOT_TAG_PATH_SSH_KEYGEN;
84 argv[i++] = "-Y";
85 argv[i++] = "sign";
86 argv[i++] = "-f";
87 argv[i++] = key_file;
88 argv[i++] = "-n";
89 argv[i++] = "git";
90 if (verbosity <= 0) {
91 argv[i++] = "-q";
92 } else {
93 /* ssh(1) allows up to 3 "-v" options. */
94 for (j = 0; j < MIN(3, verbosity); j++)
95 argv[i++] = "-v";
96 }
97 argv[i++] = NULL;
98 assert(i <= nitems(argv));
100 if (pipe(in_pfd) == -1)
101 return got_error_from_errno("pipe");
102 if (pipe(out_pfd) == -1)
103 return got_error_from_errno("pipe");
105 pid = fork();
106 if (pid == -1) {
107 error = got_error_from_errno("fork");
108 close(in_pfd[0]);
109 close(in_pfd[1]);
110 close(out_pfd[0]);
111 close(out_pfd[1]);
112 return error;
113 } else if (pid == 0) {
114 if (close(in_pfd[1]) == -1)
115 err(1, "close");
116 if (close(out_pfd[1]) == -1)
117 err(1, "close");
118 if (dup2(in_pfd[0], 0) == -1)
119 err(1, "dup2");
120 if (dup2(out_pfd[0], 1) == -1)
121 err(1, "dup2");
122 if (execv(GOT_TAG_PATH_SSH_KEYGEN, (char **const)argv) == -1)
123 err(1, "execv");
124 abort(); /* not reached */
126 if (close(in_pfd[0]) == -1)
127 return got_error_from_errno("close");
128 if (close(out_pfd[0]) == -1)
129 return got_error_from_errno("close");
130 *newpid = pid;
131 *in_fd = in_pfd[1];
132 *out_fd = out_pfd[1];
133 return NULL;
136 static char *
137 signer_identity(const char *tagger)
139 char *lt, *gt;
141 lt = strstr(tagger, " <");
142 gt = strrchr(tagger, '>');
143 if (lt && gt && lt+1 < gt)
144 return strndup(lt+2, gt-lt-2);
145 return NULL;
148 static const char* BEGIN_SSH_SIG = "-----BEGIN SSH SIGNATURE-----\n";
149 static const char* END_SSH_SIG = "-----END SSH SIGNATURE-----\n";
151 const char *
152 got_sigs_get_tagmsg_ssh_signature(const char *tagmsg)
154 const char *s = tagmsg, *begin = NULL, *end = NULL;
156 while ((s = strstr(s, BEGIN_SSH_SIG)) != NULL) {
157 begin = s;
158 s += strlen(BEGIN_SSH_SIG);
160 if (begin)
161 end = strstr(begin+strlen(BEGIN_SSH_SIG), END_SSH_SIG);
162 if (end == NULL)
163 return NULL;
164 return (end[strlen(END_SSH_SIG)] == '\0') ? begin : NULL;
167 static const struct got_error *
168 got_tag_write_signed_data(BUF *buf, struct got_tag_object *tag,
169 const char *start_sig)
171 const struct got_error *err = NULL;
172 struct got_object_id *id;
173 char *id_str = NULL;
174 char *tagger = NULL;
175 const char *tagmsg;
176 char gmtoff[6];
177 size_t len;
179 id = got_object_tag_get_object_id(tag);
180 err = got_object_id_str(&id_str, id);
181 if (err)
182 goto done;
184 const char *type_label = NULL;
185 switch (got_object_tag_get_object_type(tag)) {
186 case GOT_OBJ_TYPE_BLOB:
187 type_label = GOT_OBJ_LABEL_BLOB;
188 break;
189 case GOT_OBJ_TYPE_TREE:
190 type_label = GOT_OBJ_LABEL_TREE;
191 break;
192 case GOT_OBJ_TYPE_COMMIT:
193 type_label = GOT_OBJ_LABEL_COMMIT;
194 break;
195 case GOT_OBJ_TYPE_TAG:
196 type_label = GOT_OBJ_LABEL_TAG;
197 break;
198 default:
199 break;
201 got_date_format_gmtoff(gmtoff, sizeof(gmtoff),
202 got_object_tag_get_tagger_gmtoff(tag));
203 if (asprintf(&tagger, "%s %lld %s", got_object_tag_get_tagger(tag),
204 got_object_tag_get_tagger_time(tag), gmtoff) == -1) {
205 err = got_error_from_errno("asprintf");
206 goto done;
209 err = buf_puts(&len, buf, GOT_TAG_LABEL_OBJECT);
210 if (err)
211 goto done;
212 err = buf_puts(&len, buf, id_str);
213 if (err)
214 goto done;
215 err = buf_putc(buf, '\n');
216 if (err)
217 goto done;
218 err = buf_puts(&len, buf, GOT_TAG_LABEL_TYPE);
219 if (err)
220 goto done;
221 err = buf_puts(&len, buf, type_label);
222 if (err)
223 goto done;
224 err = buf_putc(buf, '\n');
225 if (err)
226 goto done;
227 err = buf_puts(&len, buf, GOT_TAG_LABEL_TAG);
228 if (err)
229 goto done;
230 err = buf_puts(&len, buf, got_object_tag_get_name(tag));
231 if (err)
232 goto done;
233 err = buf_putc(buf, '\n');
234 if (err)
235 goto done;
236 err = buf_puts(&len, buf, GOT_TAG_LABEL_TAGGER);
237 if (err)
238 goto done;
239 err = buf_puts(&len, buf, tagger);
240 if (err)
241 goto done;
242 err = buf_puts(&len, buf, "\n");
243 if (err)
244 goto done;
245 tagmsg = got_object_tag_get_message(tag);
246 err = buf_append(&len, buf, tagmsg, start_sig-tagmsg);
247 if (err)
248 goto done;
250 done:
251 free(id_str);
252 free(tagger);
253 return err;
256 const struct got_error *
257 got_sigs_verify_tag_ssh(char **msg, struct got_tag_object *tag,
258 const char *start_sig, const char* allowed_signers, const char* revoked,
259 int verbosity)
261 const struct got_error *error = NULL;
262 const char* argv[17];
263 int pid, status, in_pfd[2], out_pfd[2];
264 char* parsed_identity = NULL;
265 const char *identity;
266 char* tmppath = NULL;
267 FILE *tmpsig, *out = NULL;
268 BUF *buf;
269 int i = 0, j;
271 *msg = NULL;
273 error = got_opentemp_named(&tmppath, &tmpsig,
274 GOT_TMPDIR_STR "/got-tagsig");
275 if (error)
276 goto done;
278 identity = got_object_tag_get_tagger(tag);
279 parsed_identity = signer_identity(identity);
280 if (parsed_identity != NULL)
281 identity = parsed_identity;
283 if (fputs(start_sig, tmpsig) == EOF) {
284 error = got_error_from_errno("fputs");
285 goto done;
287 if (fflush(tmpsig) == EOF) {
288 error = got_error_from_errno("fflush");
289 goto done;
292 error = buf_alloc(&buf, 0);
293 if (error)
294 goto done;
295 error = got_tag_write_signed_data(buf, tag, start_sig);
296 if (error)
297 goto done;
299 argv[i++] = GOT_TAG_PATH_SSH_KEYGEN;
300 argv[i++] = "-Y";
301 argv[i++] = "verify";
302 argv[i++] = "-f";
303 argv[i++] = allowed_signers;
304 argv[i++] = "-I";
305 argv[i++] = identity;
306 argv[i++] = "-n";
307 argv[i++] = "git";
308 argv[i++] = "-s";
309 argv[i++] = tmppath;
310 if (revoked) {
311 argv[i++] = "-r";
312 argv[i++] = revoked;
314 if (verbosity > 0) {
315 /* ssh(1) allows up to 3 "-v" options. */
316 for (j = 0; j < MIN(3, verbosity); j++)
317 argv[i++] = "-v";
319 argv[i++] = NULL;
320 assert(i <= nitems(argv));
322 if (pipe(in_pfd) == -1) {
323 error = got_error_from_errno("pipe");
324 goto done;
326 if (pipe(out_pfd) == -1) {
327 error = got_error_from_errno("pipe");
328 goto done;
331 pid = fork();
332 if (pid == -1) {
333 error = got_error_from_errno("fork");
334 close(in_pfd[0]);
335 close(in_pfd[1]);
336 close(out_pfd[0]);
337 close(out_pfd[1]);
338 return error;
339 } else if (pid == 0) {
340 if (close(in_pfd[1]) == -1)
341 err(1, "close");
342 if (close(out_pfd[1]) == -1)
343 err(1, "close");
344 if (dup2(in_pfd[0], 0) == -1)
345 err(1, "dup2");
346 if (dup2(out_pfd[0], 1) == -1)
347 err(1, "dup2");
348 if (execv(GOT_TAG_PATH_SSH_KEYGEN, (char **const)argv) == -1)
349 err(1, "execv");
350 abort(); /* not reached */
352 if (close(in_pfd[0]) == -1) {
353 error = got_error_from_errno("close");
354 goto done;
356 if (close(out_pfd[0]) == -1) {
357 error = got_error_from_errno("close");
358 goto done;
360 if (buf_write_fd(buf, in_pfd[1]) == -1) {
361 error = got_error_from_errno("write");
362 goto done;
364 if (close(in_pfd[1]) == -1) {
365 error = got_error_from_errno("close");
366 goto done;
368 if (waitpid(pid, &status, 0) == -1) {
369 error = got_error_from_errno("waitpid");
370 goto done;
372 if (!WIFEXITED(status)) {
373 error = got_error(GOT_ERR_BAD_TAG_SIGNATURE);
374 goto done;
377 out = fdopen(out_pfd[1], "r");
378 if (out == NULL) {
379 error = got_error_from_errno("fdopen");
380 goto done;
382 error = buf_load(&buf, out);
383 if (error)
384 goto done;
385 error = buf_putc(buf, '\0');
386 if (error)
387 goto done;
388 if (close(out_pfd[1]) == -1) {
389 error = got_error_from_errno("close");
390 goto done;
392 out = NULL;
393 *msg = buf_get(buf);
394 if (WEXITSTATUS(status) != 0)
395 error = got_error(GOT_ERR_BAD_TAG_SIGNATURE);
397 done:
398 free(parsed_identity);
399 free(tmppath);
400 if (tmpsig && fclose(tmpsig) == EOF && error == NULL)
401 error = got_error_from_errno("fclose");
402 if (out && fclose(out) == EOF && error == NULL)
403 error = got_error_from_errno("fclose");
404 return error;