1 /* Copyright 2011-2014 Red Hat, Inc.
2 * All rights reserved.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 2 of the License.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Author(s): Peter Jones <pjones@redhat.com>
17 */
18
19 #include <err.h>
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <popt.h>
23 #include <pwd.h>
24 #include <stddef.h>
25 #include <sys/socket.h>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 #include <sys/un.h>
29
30 #include "pesign.h"
31
32 #define NO_FLAGS 0x00
33 #define UNLOCK_TOKEN 0x01
34 #define KILL_DAEMON 0x02
35 #define SIGN_BINARY 0x04
36 #define IS_TOKEN_UNLOCKED 0x08
37 #define FLAG_LIST_END 0x10
38
39 static struct {
40 int flag;
41 const char *name;
42 } flag_names[] = {
43 {UNLOCK_TOKEN, "unlock"},
44 {KILL_DAEMON, "kill"},
45 {SIGN_BINARY, "sign"},
46 {IS_TOKEN_UNLOCKED, "is-unlocked"},
47 {FLAG_LIST_END, NULL},
48 };
49
50 static void
print_flag_name(FILE * f,int flag)51 print_flag_name(FILE *f, int flag)
52 {
53 for (int i = 0; flag_names[i].flag != FLAG_LIST_END; i++) {
54 if (flag_names[i].flag == flag)
55 fprintf(f, "%s ", flag_names[i].name);
56 }
57 }
58
59 static int
connect_to_server(void)60 connect_to_server(void)
61 {
62 int rc = access(SOCKPATH, R_OK);
63 if (rc != 0) {
64 fprintf(stderr, "pesign-client: could not connect to server: "
65 "%m\n");
66 exit(1);
67 }
68
69 struct sockaddr_un addr_un = {
70 .sun_family = AF_UNIX,
71 .sun_path = SOCKPATH,
72 };
73
74 int sd = socket(AF_UNIX, SOCK_STREAM, 0);
75 if (sd < 0) {
76 fprintf(stderr, "pesign-client: could not open socket: %m\n");
77 exit(1);
78 }
79
80 socklen_t len = strlen(addr_un.sun_path) +
81 sizeof(addr_un.sun_family);
82
83 rc = connect(sd, (struct sockaddr *)&addr_un, len);
84 if (rc < 0) {
85 fprintf(stderr, "pesign-client: could not connect to daemon: "
86 "%m\n");
87 exit(1);
88 }
89
90 return sd;
91 }
92
93 static int32_t
94 check_response(int sd, char **srvmsg);
95
96 static void
check_cmd_version(int sd,uint32_t command,char * name,int32_t version)97 check_cmd_version(int sd, uint32_t command, char *name, int32_t version)
98 {
99 struct msghdr msg;
100 struct iovec iov[1];
101 pesignd_msghdr pm;
102
103 pm.version = PESIGND_VERSION;
104 pm.command = CMD_GET_CMD_VERSION;
105 pm.size = sizeof(command);
106 iov[0].iov_base = ±
107 iov[0].iov_len = sizeof(pm);
108
109 memset(&msg, '\0', sizeof(msg));
110 msg.msg_iov = iov;
111 msg.msg_iovlen = 1;
112
113 ssize_t n;
114 n = sendmsg(sd, &msg, 0);
115 if (n < 0) {
116 fprintf(stderr, "check-cmd-version: kill daemon failed: %m\n");
117 exit(1);
118 }
119
120 iov[0].iov_base = &command;
121 iov[0].iov_len = sizeof(command);
122
123 msg.msg_iov = iov;
124 msg.msg_iovlen = 1;
125
126 n = sendmsg(sd, &msg, 0);
127 if (n < 0)
128 err(1, "check-cmd-version: sendmsg failed");
129
130 char *srvmsg = NULL;
131 int32_t rc = check_response(sd, &srvmsg);
132 if (rc < 0)
133 errx(1, "command \"%s\" not known by server", name);
134 if (rc != version)
135 errx(1, "command \"%s\": client version %d, server version %d",
136 name, version, rc);
137 }
138
139 static void
send_kill_daemon(int sd)140 send_kill_daemon(int sd)
141 {
142 struct msghdr msg;
143 struct iovec iov;
144 pesignd_msghdr pm;
145
146 check_cmd_version(sd, CMD_KILL_DAEMON, "kill-daemon", 0);
147
148 pm.version = PESIGND_VERSION;
149 pm.command = CMD_KILL_DAEMON;
150 pm.size = 0;
151
152 iov.iov_base = ±
153 iov.iov_len = sizeof(pm);
154
155 memset(&msg, '\0', sizeof(msg));
156 msg.msg_iov = &iov;
157 msg.msg_iovlen = 1;
158
159 ssize_t n;
160
161 n = sendmsg(sd, &msg, 0);
162 if (n < 0) {
163 fprintf(stderr, "pesign-client: kill daemon failed: %m\n");
164 exit(1);
165 }
166 }
167
168 static int32_t
check_response(int sd,char ** srvmsg)169 check_response(int sd, char **srvmsg)
170 {
171 ssize_t n;
172 struct msghdr msg;
173 struct iovec iov;
174 char buffer[1024];
175
176 pesignd_msghdr *pm;
177
178 msg.msg_name = NULL;
179 msg.msg_namelen = 0;
180
181 memset(&msg, '\0', sizeof(msg));
182 memset(buffer, '\0', sizeof(buffer));
183
184 iov.iov_base = buffer;
185 iov.iov_len = 1023;
186 msg.msg_iov = &iov;
187 msg.msg_iovlen = 1;
188
189 n = recvmsg(sd, &msg, 0);
190 if (n < 0) {
191 fprintf(stderr, "pesign-client: could not get response from "
192 "server: %m\n");
193 exit(1);
194 }
195
196 pm = (pesignd_msghdr *)buffer;
197
198 if (pm->version != PESIGND_VERSION) {
199 fprintf(stderr, "pesign-client: got version %d, "
200 "expected version %d\n", pm->version, PESIGND_VERSION);
201 exit(1);
202 }
203
204 if (pm->command != CMD_RESPONSE) {
205 fprintf(stderr, "pesign-client: got unexpected response: %d\n",
206 pm->command);
207 exit(1);
208 }
209
210 pesignd_cmd_response *resp = (pesignd_cmd_response *)((uint8_t *)pm +
211 offsetof(pesignd_msghdr, size) +
212 sizeof(pm->size));
213
214 if (resp->rc == 0)
215 return 0;
216
217 *srvmsg = strdup((char *)resp->errmsg);
218 return resp->rc;
219 }
220
221 static char *
get_token_pin(int pinfd,char * pinfile,char * envname)222 get_token_pin(int pinfd, char *pinfile, char *envname)
223 {
224 char *pin = NULL;
225 FILE *pinf = NULL;
226
227 errno = 0;
228 /* validate that the fd we got is real... */
229 if (pinfd >= 0) {
230 pinf = fdopen(pinfd, "r");
231 if (!pinf) {
232 if (errno != EBADF)
233 close(pinfd);
234 return NULL;
235 }
236
237 ssize_t n = getline(&pin, 0, pinf);
238 if (n < 0 || !pin) {
239 fclose(pinf);
240 close(pinfd);
241 return NULL;
242 }
243
244 char *c = strchrnul(pin, '\n');
245 *c = '\0';
246
247 fclose(pinf);
248 close(pinfd);
249 return pin;
250 } else if (pinfile) {
251 pinf = fopen(pinfile, "r");
252 if (!pinf)
253 return NULL;
254
255 size_t len;
256 ssize_t n = getline(&pin, &len, pinf);
257 if (n < 0 || !pin) {
258 fclose(pinf);
259 return NULL;
260 }
261
262 char *c = strchrnul(pin, '\n');
263 *c = '\0';
264
265 fclose(pinf);
266 return pin;
267 } else {
268 pin = getenv(envname);
269 if (pin)
270 return strdup(pin);
271 }
272
273 pin = readpw(NULL, PR_FALSE, NULL);
274 return pin;
275 }
276
277 static void
unlock_token(int sd,char * tokenname,char * pin)278 unlock_token(int sd, char *tokenname, char *pin)
279 {
280 struct msghdr msg;
281 struct iovec iov[2];
282 pesignd_msghdr pm;
283
284 uint32_t size0 = pesignd_string_size(tokenname);
285
286 uint32_t size1 = pesignd_string_size(pin);
287
288 check_cmd_version(sd, CMD_UNLOCK_TOKEN, "unlock-token", 0);
289
290 pm.version = PESIGND_VERSION;
291 pm.command = CMD_UNLOCK_TOKEN;
292 pm.size = size0 + size1;
293 iov[0].iov_base = ±
294 iov[0].iov_len = sizeof (pm);
295
296 memset(&msg, '\0', sizeof(msg));
297 msg.msg_iov = iov;
298 msg.msg_iovlen = 1;
299
300 ssize_t n;
301 n = sendmsg(sd, &msg, 0);
302 if (n < 0) {
303 fprintf(stderr, "pesign-client: unlock token: sendmsg failed: "
304 "%m\n");
305 exit(1);
306 }
307
308 uint8_t *buffer = NULL;
309 buffer = calloc(1, size0 + size1);
310 if (!buffer) {
311 fprintf(stderr, "pesign-client: could not allocate memory: "
312 "%m\n");
313 exit(1);
314 }
315
316 pesignd_string *tn = (pesignd_string *)buffer;
317 pesignd_string_set(tn, tokenname);
318 iov[0].iov_base = tn;
319 iov[0].iov_len = size0;
320
321 pesignd_string *tp = pesignd_string_next(tn);
322 pesignd_string_set(tp, pin);
323
324 iov[1].iov_base = tp;
325 iov[1].iov_len = size1;
326
327 msg.msg_iov = iov;
328 msg.msg_iovlen = 2;
329
330 n = sendmsg(sd, &msg, 0);
331 if (n < 0) {
332 fprintf(stderr, "pesign-client: unlock token: sendmsg failed: "
333 "%m\n");
334 exit(1);
335 }
336
337 char *srvmsg = NULL;
338 int rc = check_response(sd, &srvmsg);
339 if (rc < 0) {
340 fprintf(stderr, "pesign-client: %s\n",
341 srvmsg);
342 exit(1);
343 }
344
345 free(buffer);
346 }
347
348 static void
is_token_unlocked(int sd,char * tokenname)349 is_token_unlocked(int sd, char *tokenname)
350 {
351 struct msghdr msg;
352 struct iovec iov[1];
353 pesignd_msghdr pm;
354
355 uint32_t size0 = pesignd_string_size(tokenname);
356
357 check_cmd_version(sd, CMD_IS_TOKEN_UNLOCKED, "is-token-unlocked", 0);
358
359 pm.version = PESIGND_VERSION;
360 pm.command = CMD_IS_TOKEN_UNLOCKED;
361 pm.size = size0;
362 iov[0].iov_base = ±
363 iov[0].iov_len = sizeof (pm);
364
365 memset(&msg, '\0', sizeof(msg));
366 msg.msg_iov = iov;
367 msg.msg_iovlen = 1;
368
369 ssize_t n;
370 n = sendmsg(sd, &msg, 0);
371 if (n < 0)
372 err(1, "is_token_unlocked: sendmsg failed");
373
374 uint8_t *buffer = NULL;
375 buffer = calloc(1, size0);
376 if (!buffer)
377 err(1, "is_token_unlocked: Could not allocate memory");
378
379 pesignd_string *tn = (pesignd_string *)buffer;
380 pesignd_string_set(tn, tokenname);
381 iov[0].iov_base = tn;
382 iov[0].iov_len = size0;
383
384 msg.msg_iov = iov;
385 msg.msg_iovlen = 1;
386
387 n = sendmsg(sd, &msg, 0);
388 if (n < 0)
389 err(1, "is_token_unlocked: sendmsg failed");
390
391 char *srvmsg = NULL;
392 int rc = check_response(sd, &srvmsg);
393 if (rc < 0)
394 errx(1, "%s", srvmsg);
395 printf("token \"%s\" is %slocked\n", tokenname, rc == 1 ? "" : "un");
396
397 free(buffer);
398 }
399
400 static void
send_fd(int sd,int fd)401 send_fd(int sd, int fd)
402 {
403 struct msghdr msg;
404 struct iovec iov;
405 char buf[2] = "\0";
406
407 memset(&msg, '\0', sizeof(msg));
408
409 iov.iov_base = buf;
410 iov.iov_len = sizeof(buf);
411
412 msg.msg_iov = &iov;
413 msg.msg_iovlen = 1;
414
415 size_t controllen = CMSG_SPACE(sizeof(int));
416 struct cmsghdr *cm = malloc(controllen);
417 if (!cm) {
418 fprintf(stderr, "pesign-client: could not allocate memory: "
419 "%m\n");
420 exit(1);
421 }
422
423 msg.msg_control = cm;
424 msg.msg_controllen = controllen;
425
426 struct cmsghdr *cme;
427
428 cme = CMSG_FIRSTHDR(&msg);
429 cme->cmsg_len = CMSG_LEN(sizeof(int));
430 cme->cmsg_level = SOL_SOCKET;
431 cme->cmsg_type = SCM_RIGHTS;
432 *(int *)CMSG_DATA(cme) = fd;
433
434 ssize_t n;
435 n = sendmsg(sd, &msg, 0);
436 if (n < 0) {
437 fprintf(stderr, "pesign-client: sign: sendmsg failed: "
438 "%m\n");
439 exit(1);
440 }
441 }
442
443 static void
sign(int sd,char * infile,char * outfile,char * tokenname,char * certname,int attached)444 sign(int sd, char *infile, char *outfile, char *tokenname, char *certname,
445 int attached)
446 {
447 int infd = open(infile, O_RDONLY);
448 if (infd < 0) {
449 fprintf(stderr, "pesign-client: could not open input file "
450 "\"%s\": %m\n", infile);
451 exit(1);
452 }
453
454 int outfd = open(outfile, O_RDWR|O_CREAT, 0600);
455 if (outfd < 0) {
456 fprintf(stderr, "pesign-client: could not open output file "
457 "\"%s\": %m\n", outfile);
458 exit(1);
459 }
460
461 struct msghdr msg;
462 struct iovec iov[2];
463
464 uint32_t size0 = pesignd_string_size(tokenname);
465 uint32_t size1 = pesignd_string_size(certname);
466
467 pesignd_msghdr *pm;
468 pm = calloc(1, sizeof(*pm));
469 if (!pm) {
470 oom:
471 fprintf(stderr, "pesign-client: could not allocate memory: "
472 "%m\n");
473 exit(1);
474 }
475
476 check_cmd_version(sd, attached ? CMD_SIGN_ATTACHED : CMD_SIGN_DETACHED,
477 attached ? "sign-attached" : "sign-detached", 0);
478
479 pm->version = PESIGND_VERSION;
480 pm->command = attached ? CMD_SIGN_ATTACHED : CMD_SIGN_DETACHED;
481 pm->size = size0 + size1;
482 iov[0].iov_base = pm;
483 iov[0].iov_len = sizeof (*pm);
484
485 memset(&msg, '\0', sizeof(msg));
486 msg.msg_iov = iov;
487 msg.msg_iovlen = 1;
488
489 ssize_t n;
490 n = sendmsg(sd, &msg, 0);
491 if (n < 0) {
492 fprintf(stderr, "pesign-client: sign: sendmsg failed: "
493 "%m\n");
494 exit(1);
495 }
496
497 char *buffer;
498 buffer = malloc(size0 + size1);
499 if (!buffer)
500 goto oom;
501
502 pesignd_string *tn = (pesignd_string *)buffer;
503 pesignd_string_set(tn, tokenname);
504 iov[0].iov_base = tn;
505 iov[0].iov_len = size0;
506
507 pesignd_string *cn = pesignd_string_next(tn);
508 pesignd_string_set(cn, certname);
509 iov[1].iov_base = cn;
510 iov[1].iov_len = size1;
511
512 msg.msg_iov = iov;
513 msg.msg_iovlen = 2;
514
515 n = sendmsg(sd, &msg, 0);
516 if (n < 0) {
517 fprintf(stderr, "pesign-client: sign: sendmsg failed: "
518 "%m\n");
519 exit(1);
520 }
521 free(buffer);
522
523 send_fd(sd, infd);
524 send_fd(sd, outfd);
525
526 char *srvmsg = NULL;
527 int rc = check_response(sd, &srvmsg);
528 if (rc < 0) {
529 fprintf(stderr, "pesign-client: signing failed: \"%s\"\n",
530 srvmsg);
531 exit(1);
532 }
533
534 close(infd);
535 close(outfd);
536
537 return;
538 }
539
540 int
main(int argc,char * argv[])541 main(int argc, char *argv[])
542 {
543 char *tokenname = "NSS Certificate DB";
544 char *certname = NULL;
545 poptContext optCon;
546 int rc;
547 int action = NO_FLAGS;
548 char *infile = NULL;
549 char *outfile = NULL;
550 char *exportfile = NULL;
551 int attached = 1;
552 int pinfd = -1;
553 char *pinfile = NULL;
554 char *tokenpin = NULL;
555
556 struct poptOption options[] = {
557 {NULL, '\0', POPT_ARG_INTL_DOMAIN, "pesign" },
558 {"token", 't', POPT_ARG_STRING|POPT_ARGFLAG_SHOW_DEFAULT,
559 &tokenname, 0, "NSS token holding signing key",
560 "<token>" },
561 {"certificate", 'c', POPT_ARG_STRING,
562 &certname, 0, "NSS certificate name", "<nickname>" },
563 {"unlock", 'u', POPT_ARG_VAL|POPT_ARGFLAG_OR, &action,
564 UNLOCK_TOKEN, "unlock nss token", NULL },
565 {"is-unlocked", 'q', POPT_ARG_VAL|POPT_ARGFLAG_OR, &action,
566 IS_TOKEN_UNLOCKED, "query if an nss token is unlocked",
567 NULL},
568 {"kill", 'k', POPT_ARG_VAL|POPT_ARGFLAG_OR, &action,
569 KILL_DAEMON, "kill running daemon", NULL },
570 {"sign", 's', POPT_ARG_VAL|POPT_ARGFLAG_OR, &action,
571 SIGN_BINARY, "sign binary", NULL },
572 {"infile", 'i', POPT_ARG_STRING,
573 &infile, 0, "input filename", "<infile>" },
574 {"outfile", 'o', POPT_ARG_STRING,
575 &outfile, 0, "output filename", "<outfile>" },
576 {"export", 'e', POPT_ARG_STRING,
577 &exportfile, 0, "create detached signature",
578 "<outfile>" },
579 {"pinfd", 'f', POPT_ARG_INT, &pinfd, -1,
580 "read file descriptor for pin information",
581 "<file descriptor>" },
582 {"pinfile", 'F', POPT_ARG_STRING, &pinfile, 0,
583 "read named file for pin information",
584 "<pin file name>" },
585 POPT_AUTOALIAS
586 POPT_AUTOHELP
587 POPT_TABLEEND
588 };
589
590 optCon = poptGetContext("pesign", argc, (const char **)argv, options,0);
591
592 rc = poptReadDefaultConfig(optCon, 0);
593 if (rc < 0 && !(rc == POPT_ERROR_ERRNO && errno == ENOENT)) {
594 fprintf(stderr,
595 "pesign-client: poptReadDefaultConfig failed: %s\n",
596 poptStrerror(rc));
597 exit(1);
598 }
599
600 while ((rc = poptGetNextOpt(optCon)) > 0)
601 ;
602
603 if (rc < -1) {
604 fprintf(stderr, "pesign-client: Invalid argument: %s: %s\n",
605 poptBadOption(optCon, 0), poptStrerror(rc));
606 exit(1);
607 }
608
609 if (poptPeekArg(optCon)) {
610 fprintf(stderr, "pesign-client: Invalid Argument: \"%s\"\n",
611 poptPeekArg(optCon));
612 exit(1);
613 }
614
615 if (action == NO_FLAGS) {
616 poptPrintUsage(optCon, stdout, 0);
617 poptFreeContext(optCon);
618 exit(0);
619 }
620
621 if (action & SIGN_BINARY && (!outfile && !exportfile)) {
622 fprintf(stderr, "pesign-client: neither --outfile nor --export "
623 "specified\n");
624 exit(1);
625 }
626
627 if (outfile && exportfile) {
628 fprintf(stderr, "pesign-client: both --outfile and --export "
629 "specified\n");
630 exit(1);
631 }
632 if (exportfile) {
633 outfile = exportfile;
634 attached = 0;
635 }
636
637 poptFreeContext(optCon);
638
639 int sd = -1;
640
641 switch (action) {
642 case UNLOCK_TOKEN:
643 tokenpin = get_token_pin(pinfd, pinfile, "PESIGN_TOKEN_PIN");
644 if (tokenpin == NULL) {
645 if (errno)
646 fprintf(stderr, "pesign-client: could not "
647 "get token pin: %m\n");
648 else
649 fprintf(stderr, "pesign-client: no token pin "
650 "specified");
651 exit(1);
652 }
653 sd = connect_to_server();
654 unlock_token(sd, tokenname, tokenpin);
655 free(tokenpin);
656 break;
657 case IS_TOKEN_UNLOCKED:
658 sd = connect_to_server();
659 is_token_unlocked(sd, tokenname);
660 break;
661 case KILL_DAEMON:
662 sd = connect_to_server();
663 send_kill_daemon(sd);
664 break;
665 case SIGN_BINARY:
666 if (!infile) {
667 fprintf(stderr, "pesign-client: no input file "
668 "specified\n");
669 exit(1);
670 }
671 if (!outfile) {
672 fprintf(stderr, "pesign-client: no output file "
673 "specified\n");
674 exit(1);
675 }
676 if (!certname) {
677 fprintf(stderr, "pesign-client: no certificate name "
678 "spefified\n");
679 exit(1);
680 }
681 sd = connect_to_server();
682 sign(sd, infile, outfile, tokenname, certname, attached);
683 break;
684 default:
685 fprintf(stderr, "Incompatible flags (0x%08x): ", action);
686 for (int i = 1; i < FLAG_LIST_END; i <<= 1) {
687 if (action & i)
688 print_flag_name(stderr, i);
689 }
690 fprintf(stderr, "\n");
691 exit(1);
692 }
693
694 return 0;
695 }
696