nbsvtool.c revision 1.4
1/* $NetBSD: nbsvtool.c,v 1.4 2018/02/06 20:15:39 christos Exp $ */ 2 3/*- 4 * Copyright (c) 2004, 2008 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Love H�rnquist �strand <lha@it.su.se> 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32#include <err.h> 33#include <stdio.h> 34#include <stdlib.h> 35#include <string.h> 36#include <unistd.h> 37 38#include <openssl/pkcs7.h> 39#include <openssl/evp.h> 40#include <openssl/x509.h> 41#include <openssl/x509v3.h> 42#include <openssl/pem.h> 43#include <openssl/err.h> 44#include <openssl/ui.h> 45 46static int verbose_flag; 47static unsigned long key_usage = 0; 48 49/* 50 * openssl command line equivalents 51 * 52 * openssl smime -verify \ 53 * -inform PEM -in nbsvtool.c.sig -content nbsvtool.c \ 54 * -CAfile /secure/lha/su/CA/swupki-pca.crt -out /dev/null 55 * openssl smime -sign \ 56 * -noattr -binary -outform PEM -out nbsvtool.c.sig \ 57 * -in nbsvtool.c -signer /secure/lha/su/CA/lha.crt \ 58 * -certfile /secure/lha/su/CA/lha-chain \ 59 * -inkey /secure/lha/su/CA/lha.key 60 */ 61 62/* 63 * Create a detach PEM signature of file `infile' and store it in 64 * `outfile'. The signer certificate `cert' and private key 65 * `private_key' must be given. An additional hint to the verifier how 66 * to find the path from the `cert' to the x509 anchor can be passed 67 * in `cert_chain'. 68 */ 69 70static void 71sign_file(X509 *cert, EVP_PKEY *private_key, STACK_OF(X509) *cert_chain, 72 const char *infile, const char *outfile) 73{ 74 BIO *out, *in; 75 PKCS7 *p7; 76 77 out = BIO_new_file(outfile, "w"); 78 if (out == NULL) 79 err(EXIT_FAILURE, "Failed to open signature output file: %s", 80 outfile); 81 82 in = BIO_new_file(infile, "r"); 83 if (in == NULL) 84 err(EXIT_FAILURE, "Failed to input file: %s", infile); 85 86 p7 = PKCS7_sign(cert, private_key, cert_chain, in, 87 PKCS7_DETACHED|PKCS7_NOATTR|PKCS7_BINARY); 88 if (p7 == NULL) 89 errx(EXIT_FAILURE, "Failed to create signature structure"); 90 91 PEM_write_bio_PKCS7(out, p7); 92 93 PKCS7_free(p7); 94 BIO_free(in); 95 BIO_free_all(out); 96} 97 98/* 99 * Verifies a detached PEM signature in the file `sigfile' of file 100 * `infile'. The trust anchor file `anchor' to the trust anchors must 101 * be given. If its suspended that the sender didn't inlude the whole 102 * path from the signing certificate to the given trust anchor, extra 103 * certificates can be passed in `cert_chain'. 104 */ 105 106static void 107verify_file(STACK_OF(X509) *cert_chain, const char *anchor, 108 const char *infile, const char *sigfile) 109{ 110 STACK_OF(X509) *signers; 111 X509_STORE *store; 112 BIO *sig, *in; 113 PKCS7 *p7; 114 int ret, i; 115 X509_NAME *name; 116 char *subject; 117 118 store = X509_STORE_new(); 119 if (store == NULL) 120 err(1, "Failed to create store"); 121 122 X509_STORE_load_locations(store, anchor, NULL); 123 124 in = BIO_new_file(infile, "r"); 125 if (in == NULL) 126 err(EXIT_FAILURE, "Failed to open input data file: %s", infile); 127 128 sig = BIO_new_file(sigfile, "r"); 129 if (sig == NULL) 130 err(EXIT_FAILURE, "Failed to open signature input file: %s", 131 sigfile); 132 133 p7 = PEM_read_bio_PKCS7(sig, NULL, NULL, NULL); 134 if (p7 == NULL) 135 errx(EXIT_FAILURE, "Failed to parse the signature file %s", 136 sigfile); 137 138 ret = PKCS7_verify(p7, cert_chain, store, in, NULL, 0); 139 if (ret != 1) 140 errx(EXIT_FAILURE, "Failed to verify signature"); 141 142 signers = PKCS7_get0_signers(p7, NULL, 0); 143 if (signers == NULL) 144 errx(EXIT_FAILURE, "Failed to get signers"); 145 146 if (sk_X509_num(signers) == 0) 147 errx(EXIT_FAILURE, "No signers ?"); 148 149 if (key_usage != 0) { 150 for (i = 0; i < sk_X509_num(signers); i++) { 151 X509 *x = sk_X509_value(signers, i); 152 if ((X509_get_extended_key_usage(x) & key_usage) 153 == key_usage) 154 continue; 155 name = X509_get_subject_name(x); 156 subject = X509_NAME_oneline(name, NULL, 0); 157 errx(EXIT_FAILURE, 158 "Certificate doesn't match required key usage: %s", 159 subject); 160 } 161 } 162 163 if (verbose_flag) 164 printf("Sigature ok, signed by:\n"); 165 166 for (i = 0; i < sk_X509_num(signers); i++) { 167 name = X509_get_subject_name(sk_X509_value(signers, i)); 168 subject = X509_NAME_oneline(name, NULL, 0); 169 170 if (verbose_flag) 171 printf("\t%s\n", subject); 172 173 OPENSSL_free(subject); 174 } 175 176 PKCS7_free(p7); 177 BIO_free(in); 178 BIO_free(sig); 179} 180 181/* 182 * Parse and return a list PEM encoded certificates in the file 183 * `file'. In case of error or an empty file, and error text will be 184 * printed and the function will exit(3). 185 */ 186 187static STACK_OF(X509) * 188file_to_certs(const char *file) 189{ 190 STACK_OF(X509) *certs; 191 FILE *f; 192 193 f = fopen(file, "r"); 194 if (f == NULL) 195 err(EXIT_FAILURE, "Cannot open certificate file %s", file); 196 certs = sk_X509_new_null(); 197 while (1) { 198 X509 *cert; 199 200 cert = PEM_read_X509(f, NULL, NULL, NULL); 201 if (cert == NULL) { 202 unsigned long ret; 203 204 ret = ERR_GET_REASON(ERR_peek_error()); 205 if (ret == PEM_R_NO_START_LINE) { 206 /* End of file reached. no error */ 207 ERR_clear_error(); 208 break; 209 } 210 errx(EXIT_FAILURE, "Can't read certificate file %s", 211 file); 212 } 213 sk_X509_insert(certs, cert, sk_X509_num(certs)); 214 } 215 fclose(f); 216 if (sk_X509_num(certs) == 0) 217 errx(EXIT_FAILURE, "No certificate found file %s", file); 218 219 return certs; 220} 221 222static int 223ssl_pass_cb(char *buf, int size, int rwflag, void *u) 224{ 225 226 if (UI_UTIL_read_pw_string(buf, size, "Passphrase: ", 0)) 227 return 0; 228 return strlen(buf); 229} 230 231static struct { 232 X509 *certificate; 233 STACK_OF(X509) *cert_chain; 234 EVP_PKEY *private_key; 235} crypto_state; 236 237/* 238 * Load the certificate file `cert_file' with the associated private 239 * key file `key_file'. The private key is checked to make sure it 240 * matches the certificate. The optional hints for the path to the CA 241 * is stored in `chain_file'. 242 */ 243 244static void 245load_keys(const char *cert_file, const char *chain_file, const char *key_file) 246{ 247 STACK_OF(X509) *c; 248 FILE *f; 249 int ret; 250 251 if (cert_file == NULL) 252 errx(EXIT_FAILURE, "No certificate file given"); 253 if (key_file == NULL) 254 errx(EXIT_FAILURE, "No private key file given"); 255 256 c = file_to_certs(cert_file); 257 258 if (sk_X509_num(c) != 1) 259 errx(EXIT_FAILURE, 260 "More then one certificate in the certificate file"); 261 crypto_state.certificate = sk_X509_value(c, 0); 262 263 if (chain_file) 264 crypto_state.cert_chain = file_to_certs(chain_file); 265 266 /* load private key */ 267 f = fopen(key_file, "r"); 268 if (f == NULL) 269 errx(1, "Failed to open private key file %s", key_file); 270 271 crypto_state.private_key = 272 PEM_read_PrivateKey(f, NULL, ssl_pass_cb, NULL); 273 fclose(f); 274 if (crypto_state.private_key == NULL) 275 errx(EXIT_FAILURE, "Can't read private key %s", key_file); 276 277 ret = X509_check_private_key(crypto_state.certificate, 278 crypto_state.private_key); 279 if (ret != 1) 280 errx(EXIT_FAILURE, 281 "The private key %s doesn't match the certificate %s", 282 key_file, cert_file); 283} 284 285static void __dead 286usage(int exit_code) 287{ 288 289 printf("%s usage\n", getprogname()); 290 printf("%s -k keyfile -c cert-chain [-f cert-chain] sign file\n", 291 getprogname()); 292 printf("%s [-u code|...] [-a x509-anchor-file] verify filename.sp7\n", 293 getprogname()); 294 printf("%s [-u code|...] [-a x509-anchor-file] verify filename otherfilename.sp7\n", 295 getprogname()); 296 printf("%s [-u code|...] [-a x509-anchor-file] verify-code file ...\n", 297 getprogname()); 298 exit(exit_code); 299} 300 301int 302main(int argc, char **argv) 303{ 304 const char *anchors = NULL; 305 const char *cert_file = NULL, *key_file = NULL, *chain_file = NULL; 306 const char *file; 307 char *sigfile; 308 int ch; 309 310 setprogname(argv[0]); 311 312#if OPENSSL_VERSION_NUMBER < 0x10100000L 313 OpenSSL_add_all_algorithms(); 314 ERR_load_crypto_strings(); 315#endif 316 317 while ((ch = getopt(argc, argv, "a:c:f:hk:u:v")) != -1) { 318 switch (ch) { 319 case 'a': 320 anchors = optarg; 321 break; 322 case 'f': 323 chain_file = optarg; 324 break; 325 case 'k': 326 key_file = optarg; 327 break; 328 case 'c': 329 cert_file = optarg; 330 break; 331 case 'u': 332 if (strcmp("ssl-server", optarg) == 0) 333 key_usage |= XKU_SSL_SERVER; 334 else if (strcmp("ssl-client", optarg) == 0) 335 key_usage |= XKU_SSL_CLIENT; 336 else if (strcmp("code", optarg) == 0) 337 key_usage |= XKU_CODE_SIGN; 338 else if (strcmp("smime", optarg) == 0) 339 key_usage |= XKU_SMIME; 340 else 341 errx(1, "Unknown keyusage: %s", optarg); 342 break; 343 case 'v': 344 verbose_flag = 1; 345 break; 346 case 'h': 347 usage(EXIT_SUCCESS); 348 default: 349 usage(EXIT_FAILURE); 350 } 351 } 352 353 argc -= optind; 354 argv += optind; 355 356 if (argc < 1) { 357 fprintf(stderr, "Command missing [sign|verify]\n"); 358 usage(EXIT_FAILURE); 359 } 360 361 if (strcmp(argv[0], "sign") == 0) { 362 363 if (argc < 2) 364 usage(1); 365 366 file = argv[1]; 367 368 asprintf(&sigfile, "%s.sp7", file); 369 if (sigfile == NULL) 370 err(EXIT_FAILURE, "asprintf failed"); 371 372 load_keys(cert_file, chain_file, key_file); 373 374 sign_file(crypto_state.certificate, 375 crypto_state.private_key, 376 crypto_state.cert_chain, 377 file, 378 sigfile); 379 380 } else if (strcmp(argv[0], "verify") == 0 381 || strcmp(argv[0], "verify-code") == 0) { 382 383 if (strcmp(argv[0], "verify-code") == 0) 384 key_usage |= XKU_CODE_SIGN; 385 386 if (argc < 2) 387 usage(1); 388 else if (argc < 3) { 389 char *dot; 390 391 sigfile = argv[1]; 392 393 file = strdup(sigfile); 394 if (file == NULL) 395 err(1, "strdup failed"); 396 397 dot = strrchr(file, '.'); 398 if (dot == NULL || strchr(dot, '/') != NULL) 399 errx(EXIT_FAILURE, 400 "File name missing suffix"); 401 if (strcmp(".sp7", dot) != 0) 402 errx(EXIT_FAILURE, 403 "File name bad suffix (%s)", dot); 404 *dot = '\0'; 405 } else { 406 file = argv[1]; 407 sigfile = argv[2]; 408 } 409 verify_file(crypto_state.cert_chain, anchors, file, sigfile); 410 } else { 411 fprintf(stderr, "Unknown command: %s\n", argv[0]); 412 usage(EXIT_FAILURE); 413 } 414 415 return 0; 416} 417