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 "got_compat.h"
19 #include <sys/time.h>
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <sys/socket.h>
23 #include <sys/queue.h>
24 #include <sys/wait.h>
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <fcntl.h>
29 #include <unistd.h>
30 #include <string.h>
31 #include <err.h>
32 #include <assert.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"
40 #include "buf.h"
42 #ifndef MIN
43 #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
44 #endif
46 #ifndef nitems
47 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
48 #endif
50 #ifndef GOT_TAG_PATH_SSH_KEYGEN
51 #define GOT_TAG_PATH_SSH_KEYGEN "/usr/bin/ssh-keygen"
52 #endif
54 #ifndef GOT_TAG_PATH_SIGNIFY
55 #define GOT_TAG_PATH_SIGNIFY "/usr/bin/signify"
56 #endif
58 const struct got_error *
59 got_sigs_apply_unveil(void)
60 {
61 if (unveil(GOT_TAG_PATH_SSH_KEYGEN, "x") != 0) {
62 return got_error_from_errno2("unveil",
63 GOT_TAG_PATH_SSH_KEYGEN);
64 }
65 if (unveil(GOT_TAG_PATH_SIGNIFY, "x") != 0) {
66 return got_error_from_errno2("unveil",
67 GOT_TAG_PATH_SIGNIFY);
68 }
70 return NULL;
71 }
73 const struct got_error *
74 got_sigs_sign_tag_ssh(pid_t *newpid, int *in_fd, int *out_fd,
75 const char* key_file, int verbosity)
76 {
77 const struct got_error *error = NULL;
78 int pid, in_pfd[2], out_pfd[2];
79 const char* argv[11];
80 int i = 0, j;
82 *newpid = -1;
83 *in_fd = -1;
84 *out_fd = -1;
86 argv[i++] = GOT_TAG_PATH_SSH_KEYGEN;
87 argv[i++] = "-Y";
88 argv[i++] = "sign";
89 argv[i++] = "-f";
90 argv[i++] = key_file;
91 argv[i++] = "-n";
92 argv[i++] = "git";
93 if (verbosity <= 0) {
94 argv[i++] = "-q";
95 } else {
96 /* ssh(1) allows up to 3 "-v" options. */
97 for (j = 0; j < MIN(3, verbosity); j++)
98 argv[i++] = "-v";
99 }
100 argv[i++] = NULL;
101 assert(i <= nitems(argv));
103 if (pipe(in_pfd) == -1)
104 return got_error_from_errno("pipe");
105 if (pipe(out_pfd) == -1)
106 return got_error_from_errno("pipe");
108 pid = fork();
109 if (pid == -1) {
110 error = got_error_from_errno("fork");
111 close(in_pfd[0]);
112 close(in_pfd[1]);
113 close(out_pfd[0]);
114 close(out_pfd[1]);
115 return error;
116 } else if (pid == 0) {
117 if (close(in_pfd[1]) == -1)
118 err(1, "close");
119 if (close(out_pfd[0]) == -1)
120 err(1, "close");
121 if (dup2(in_pfd[0], 0) == -1)
122 err(1, "dup2");
123 if (dup2(out_pfd[1], 1) == -1)
124 err(1, "dup2");
125 if (execv(GOT_TAG_PATH_SSH_KEYGEN, (char **const)argv) == -1)
126 err(1, "execv");
127 abort(); /* not reached */
129 if (close(in_pfd[0]) == -1)
130 return got_error_from_errno("close");
131 if (close(out_pfd[1]) == -1)
132 return got_error_from_errno("close");
133 *newpid = pid;
134 *in_fd = in_pfd[1];
135 *out_fd = out_pfd[0];
136 return NULL;
139 static char *
140 signer_identity(const char *tagger)
142 char *lt, *gt;
144 lt = strstr(tagger, " <");
145 gt = strrchr(tagger, '>');
146 if (lt && gt && lt+1 < gt)
147 return strndup(lt+2, gt-lt-2);
148 return NULL;
151 static const char* BEGIN_SSH_SIG = "-----BEGIN SSH SIGNATURE-----\n";
152 static const char* END_SSH_SIG = "-----END SSH SIGNATURE-----\n";
154 const char *
155 got_sigs_get_tagmsg_ssh_signature(const char *tagmsg)
157 const char *s = tagmsg, *begin = NULL, *end = NULL;
159 while ((s = strstr(s, BEGIN_SSH_SIG)) != NULL) {
160 begin = s;
161 s += strlen(BEGIN_SSH_SIG);
163 if (begin)
164 end = strstr(begin+strlen(BEGIN_SSH_SIG), END_SSH_SIG);
165 if (end == NULL)
166 return NULL;
167 return (end[strlen(END_SSH_SIG)] == '\0') ? begin : NULL;
170 static const struct got_error *
171 got_tag_write_signed_data(BUF *buf, struct got_tag_object *tag,
172 const char *start_sig)
174 const struct got_error *err = NULL;
175 struct got_object_id *id;
176 char *id_str = NULL;
177 char *tagger = NULL;
178 const char *tagmsg;
179 char gmtoff[6];
180 size_t len;
182 id = got_object_tag_get_object_id(tag);
183 err = got_object_id_str(&id_str, id);
184 if (err)
185 goto done;
187 const char *type_label = NULL;
188 switch (got_object_tag_get_object_type(tag)) {
189 case GOT_OBJ_TYPE_BLOB:
190 type_label = GOT_OBJ_LABEL_BLOB;
191 break;
192 case GOT_OBJ_TYPE_TREE:
193 type_label = GOT_OBJ_LABEL_TREE;
194 break;
195 case GOT_OBJ_TYPE_COMMIT:
196 type_label = GOT_OBJ_LABEL_COMMIT;
197 break;
198 case GOT_OBJ_TYPE_TAG:
199 type_label = GOT_OBJ_LABEL_TAG;
200 break;
201 default:
202 break;
204 got_date_format_gmtoff(gmtoff, sizeof(gmtoff),
205 got_object_tag_get_tagger_gmtoff(tag));
206 if (asprintf(&tagger, "%s %lld %s", got_object_tag_get_tagger(tag),
207 (long long)got_object_tag_get_tagger_time(tag), gmtoff) == -1) {
208 err = got_error_from_errno("asprintf");
209 goto done;
212 err = buf_puts(&len, buf, GOT_TAG_LABEL_OBJECT);
213 if (err)
214 goto done;
215 err = buf_puts(&len, buf, id_str);
216 if (err)
217 goto done;
218 err = buf_putc(buf, '\n');
219 if (err)
220 goto done;
221 err = buf_puts(&len, buf, GOT_TAG_LABEL_TYPE);
222 if (err)
223 goto done;
224 err = buf_puts(&len, buf, type_label);
225 if (err)
226 goto done;
227 err = buf_putc(buf, '\n');
228 if (err)
229 goto done;
230 err = buf_puts(&len, buf, GOT_TAG_LABEL_TAG);
231 if (err)
232 goto done;
233 err = buf_puts(&len, buf, got_object_tag_get_name(tag));
234 if (err)
235 goto done;
236 err = buf_putc(buf, '\n');
237 if (err)
238 goto done;
239 err = buf_puts(&len, buf, GOT_TAG_LABEL_TAGGER);
240 if (err)
241 goto done;
242 err = buf_puts(&len, buf, tagger);
243 if (err)
244 goto done;
245 err = buf_puts(&len, buf, "\n");
246 if (err)
247 goto done;
248 tagmsg = got_object_tag_get_message(tag);
249 err = buf_append(&len, buf, tagmsg, start_sig-tagmsg);
250 if (err)
251 goto done;
253 done:
254 free(id_str);
255 free(tagger);
256 return err;
259 const struct got_error *
260 got_sigs_verify_tag_ssh(char **msg, struct got_tag_object *tag,
261 const char *start_sig, const char* allowed_signers, const char* revoked,
262 int verbosity)
264 const struct got_error *error = NULL;
265 const char* argv[17];
266 int pid, status, in_pfd[2], out_pfd[2];
267 char* parsed_identity = NULL;
268 const char *identity;
269 char *tmppath = NULL;
270 FILE *tmpsig = NULL;
271 BUF *buf;
272 int i = 0, j;
274 *msg = NULL;
276 error = got_opentemp_named(&tmppath, &tmpsig,
277 GOT_TMPDIR_STR "/got-tagsig", "");
278 if (error)
279 goto done;
281 identity = got_object_tag_get_tagger(tag);
282 parsed_identity = signer_identity(identity);
283 if (parsed_identity != NULL)
284 identity = parsed_identity;
286 if (fputs(start_sig, tmpsig) == EOF) {
287 error = got_error_from_errno("fputs");
288 goto done;
290 if (fflush(tmpsig) == EOF) {
291 error = got_error_from_errno("fflush");
292 goto done;
295 error = buf_alloc(&buf, 0);
296 if (error)
297 goto done;
298 error = got_tag_write_signed_data(buf, tag, start_sig);
299 if (error)
300 goto done;
302 argv[i++] = GOT_TAG_PATH_SSH_KEYGEN;
303 argv[i++] = "-Y";
304 argv[i++] = "verify";
305 argv[i++] = "-f";
306 argv[i++] = allowed_signers;
307 argv[i++] = "-I";
308 argv[i++] = identity;
309 argv[i++] = "-n";
310 argv[i++] = "git";
311 argv[i++] = "-s";
312 argv[i++] = tmppath;
313 if (revoked) {
314 argv[i++] = "-r";
315 argv[i++] = revoked;
317 if (verbosity > 0) {
318 /* ssh(1) allows up to 3 "-v" options. */
319 for (j = 0; j < MIN(3, verbosity); j++)
320 argv[i++] = "-v";
322 argv[i++] = NULL;
323 assert(i <= nitems(argv));
325 if (pipe(in_pfd) == -1) {
326 error = got_error_from_errno("pipe");
327 goto done;
329 if (pipe(out_pfd) == -1) {
330 error = got_error_from_errno("pipe");
331 goto done;
334 pid = fork();
335 if (pid == -1) {
336 error = got_error_from_errno("fork");
337 close(in_pfd[0]);
338 close(in_pfd[1]);
339 close(out_pfd[0]);
340 close(out_pfd[1]);
341 return error;
342 } else if (pid == 0) {
343 if (close(in_pfd[1]) == -1)
344 err(1, "close");
345 if (close(out_pfd[0]) == -1)
346 err(1, "close");
347 if (dup2(in_pfd[0], 0) == -1)
348 err(1, "dup2");
349 if (dup2(out_pfd[1], 1) == -1)
350 err(1, "dup2");
351 if (execv(GOT_TAG_PATH_SSH_KEYGEN, (char **const)argv) == -1)
352 err(1, "execv");
353 abort(); /* not reached */
355 if (close(in_pfd[0]) == -1) {
356 error = got_error_from_errno("close");
357 goto done;
359 if (close(out_pfd[1]) == -1) {
360 error = got_error_from_errno("close");
361 goto done;
363 if (buf_write_fd(buf, in_pfd[1]) == -1) {
364 error = got_error_from_errno("write");
365 goto done;
367 if (close(in_pfd[1]) == -1) {
368 error = got_error_from_errno("close");
369 goto done;
371 if (waitpid(pid, &status, 0) == -1) {
372 error = got_error_from_errno("waitpid");
373 goto done;
375 if (!WIFEXITED(status)) {
376 error = got_error(GOT_ERR_BAD_TAG_SIGNATURE);
377 goto done;
380 error = buf_load_fd(&buf, out_pfd[0]);
381 if (error)
382 goto done;
383 error = buf_putc(buf, '\0');
384 if (error)
385 goto done;
386 if (close(out_pfd[0]) == -1) {
387 error = got_error_from_errno("close");
388 goto done;
390 *msg = buf_get(buf);
391 if (WEXITSTATUS(status) != 0)
392 error = got_error(GOT_ERR_BAD_TAG_SIGNATURE);
394 done:
395 free(parsed_identity);
396 if (tmppath && unlink(tmppath) == -1 && error == NULL)
397 error = got_error_from_errno("unlink");
398 free(tmppath);
399 close(out_pfd[0]);
400 if (tmpsig && fclose(tmpsig) == EOF && error == NULL)
401 error = got_error_from_errno("fclose");
402 return error;