1 /* $NetBSD: nbsvtool.c,v 1.1 2008/05/11 17:58:09 joerg 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 46 static int verbose_flag; 47 static 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 70 static void 71 sign_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 106 static void 107 verify_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 116 store = X509_STORE_new(); 117 if (store == NULL) 118 err(1, "Failed to create store"); 119 120 X509_STORE_load_locations(store, anchor, NULL); 121 122 in = BIO_new_file(infile, "r"); 123 if (in == NULL) 124 err(EXIT_FAILURE, "Failed to open input data file: %s", infile); 125 126 sig = BIO_new_file(sigfile, "r"); 127 if (sig == NULL) 128 err(EXIT_FAILURE, "Failed to open signature input file: %s", 129 sigfile); 130 131 p7 = PEM_read_bio_PKCS7(sig, NULL, NULL, NULL); 132 if (p7 == NULL) 133 errx(EXIT_FAILURE, "Failed to parse the signature file %s", 134 sigfile); 135 136 ret = PKCS7_verify(p7, cert_chain, store, in, NULL, 0); 137 if (ret != 1) 138 errx(EXIT_FAILURE, "Failed to verify signature"); 139 140 signers = PKCS7_get0_signers(p7, NULL, 0); 141 if (signers == NULL) 142 errx(EXIT_FAILURE, "Failed to get signers"); 143 144 if (sk_X509_num(signers) == 0) 145 errx(EXIT_FAILURE, "No signers ?"); 146 147 if (verbose_flag) 148 printf("Sigature ok, signed by:\n"); 149 150 for (i = 0; i < sk_X509_num(signers); i++) { 151 X509_NAME *name; 152 char *subject; 153 name = X509_get_subject_name(sk_X509_value(signers, i)); 154 subject = X509_NAME_oneline(name, NULL, 0); 155 156 if (key_usage != 0 && 157 (sk_X509_value(signers, i)->ex_xkusage & key_usage) == 0) 158 errx(EXIT_FAILURE, 159 "Key doesn't match required key usage: %s", 160 subject); 161 162 if (verbose_flag) 163 printf("\t%s\n", subject); 164 165 OPENSSL_free(subject); 166 } 167 168 PKCS7_free(p7); 169 BIO_free(in); 170 BIO_free(sig); 171 } 172 173 /* 174 * Parse and return a list PEM encoded certificates in the file 175 * `file'. In case of error or an empty file, and error text will be 176 * printed and the function will exit(3). 177 */ 178 179 static STACK_OF(X509) * 180 file_to_certs(const char *file) 181 { 182 STACK_OF(X509) *certs; 183 FILE *f; 184 185 f = fopen(file, "r"); 186 if (f == NULL) 187 err(EXIT_FAILURE, "Cannot open certificate file %s", file); 188 certs = sk_X509_new_null(); 189 while (1) { 190 X509 *cert; 191 192 cert = PEM_read_X509(f, NULL, NULL, NULL); 193 if (cert == NULL) { 194 unsigned long ret; 195 196 ret = ERR_GET_REASON(ERR_peek_error()); 197 if (ret == PEM_R_NO_START_LINE) { 198 /* End of file reached. no error */ 199 ERR_clear_error(); 200 break; 201 } 202 errx(EXIT_FAILURE, "Can't read certificate file %s", 203 file); 204 } 205 sk_X509_insert(certs, cert, sk_X509_num(certs)); 206 } 207 fclose(f); 208 if (sk_X509_num(certs) == 0) 209 errx(EXIT_FAILURE, "No certificate found file %s", file); 210 211 return certs; 212 } 213 214 static int 215 ssl_pass_cb(char *buf, int size, int rwflag, void *u) 216 { 217 218 if (UI_UTIL_read_pw_string(buf, size, "Passphrase: ", 0)) 219 return 0; 220 return strlen(buf); 221 } 222 223 static struct { 224 X509 *certificate; 225 STACK_OF(X509) *cert_chain; 226 EVP_PKEY *private_key; 227 } crypto_state; 228 229 /* 230 * Load the certificate file `cert_file' with the associated private 231 * key file `key_file'. The private key is checked to make sure it 232 * matches the certificate. The optional hints for the path to the CA 233 * is stored in `chain_file'. 234 */ 235 236 static void 237 load_keys(const char *cert_file, const char *chain_file, const char *key_file) 238 { 239 STACK_OF(X509) *c; 240 FILE *f; 241 int ret; 242 243 if (cert_file == NULL) 244 errx(EXIT_FAILURE, "No certificate file given"); 245 if (key_file == NULL) 246 errx(EXIT_FAILURE, "No private key file given"); 247 248 c = file_to_certs(cert_file); 249 250 if (sk_X509_num(c) != 1) 251 errx(EXIT_FAILURE, 252 "More then one certificate in the certificate file"); 253 crypto_state.certificate = sk_X509_value(c, 0); 254 255 if (chain_file) 256 crypto_state.cert_chain = file_to_certs(chain_file); 257 258 /* load private key */ 259 f = fopen(key_file, "r"); 260 if (f == NULL) 261 errx(1, "Failed to open private key file %s", key_file); 262 263 crypto_state.private_key = 264 PEM_read_PrivateKey(f, NULL, ssl_pass_cb, NULL); 265 fclose(f); 266 if (crypto_state.private_key == NULL) 267 errx(EXIT_FAILURE, "Can't read private key %s", key_file); 268 269 ret = X509_check_private_key(crypto_state.certificate, 270 crypto_state.private_key); 271 if (ret != 1) 272 errx(EXIT_FAILURE, 273 "The private key %s doesn't match the certificate %s", 274 key_file, cert_file); 275 } 276 277 static void __dead 278 usage(int exit_code) 279 { 280 281 printf("%s usage\n", getprogname()); 282 printf("%s -k keyfile -c cert-chain [-f cert-chain] sign file\n", 283 getprogname()); 284 printf("%s [-u code|...] [-a x509-anchor-file] verify filename.sp7\n", 285 getprogname()); 286 printf("%s [-u code|...] [-a x509-anchor-file] verify filename otherfilename.sp7\n", 287 getprogname()); 288 printf("%s [-u code|...] [-a x509-anchor-file] verify-code file ...\n", 289 getprogname()); 290 exit(exit_code); 291 } 292 293 int 294 main(int argc, char **argv) 295 { 296 const char *anchors = NULL; 297 const char *cert_file = NULL, *key_file = NULL, *chain_file = NULL; 298 const char *file; 299 char *sigfile; 300 int ch; 301 302 setprogname(argv[0]); 303 304 OpenSSL_add_all_algorithms(); 305 ERR_load_crypto_strings(); 306 307 while ((ch = getopt(argc, argv, "a:c:f:hk:u:v")) != -1) { 308 switch (ch) { 309 case 'a': 310 anchors = optarg; 311 break; 312 case 'f': 313 chain_file = optarg; 314 break; 315 case 'k': 316 key_file = optarg; 317 break; 318 case 'c': 319 cert_file = optarg; 320 break; 321 case 'u': 322 if (strcmp("ssl-server", optarg) == 0) 323 key_usage |= XKU_SSL_SERVER; 324 else if (strcmp("ssl-client", optarg) == 0) 325 key_usage |= XKU_SSL_CLIENT; 326 else if (strcmp("code", optarg) == 0) 327 key_usage |= XKU_CODE_SIGN; 328 else if (strcmp("smime", optarg) == 0) 329 key_usage |= XKU_SMIME; 330 else 331 errx(1, "Unknown keyusage: %s", optarg); 332 break; 333 case 'v': 334 verbose_flag = 1; 335 break; 336 case 'h': 337 usage(EXIT_SUCCESS); 338 default: 339 usage(EXIT_FAILURE); 340 } 341 } 342 343 argc -= optind; 344 argv += optind; 345 346 if (argc < 1) { 347 fprintf(stderr, "Command missing [sign|verify]\n"); 348 usage(EXIT_FAILURE); 349 } 350 351 if (strcmp(argv[0], "sign") == 0) { 352 353 if (argc < 2) 354 usage(1); 355 356 file = argv[1]; 357 358 asprintf(&sigfile, "%s.sp7", file); 359 if (sigfile == NULL) 360 err(EXIT_FAILURE, "asprintf failed"); 361 362 load_keys(cert_file, chain_file, key_file); 363 364 sign_file(crypto_state.certificate, 365 crypto_state.private_key, 366 crypto_state.cert_chain, 367 file, 368 sigfile); 369 370 } else if (strcmp(argv[0], "verify") == 0 371 || strcmp(argv[0], "verify-code") == 0) { 372 373 if (strcmp(argv[0], "verify-code") == 0) 374 key_usage |= XKU_CODE_SIGN; 375 376 if (argc < 2) 377 usage(1); 378 else if (argc < 3) { 379 char *dot; 380 381 sigfile = argv[1]; 382 383 file = strdup(sigfile); 384 if (file == NULL) 385 err(1, "strdup failed"); 386 387 dot = strrchr(file, '.'); 388 if (dot == NULL || strchr(dot, '/') != NULL) 389 errx(EXIT_FAILURE, 390 "File name missing suffix"); 391 if (strcmp(".sp7", dot) != 0) 392 errx(EXIT_FAILURE, 393 "File name bad suffix (%s)", dot); 394 *dot = '\0'; 395 } else { 396 file = argv[1]; 397 sigfile = argv[2]; 398 } 399 verify_file(crypto_state.cert_chain, anchors, file, sigfile); 400 } else { 401 fprintf(stderr, "Unknown command: %s\n", argv[0]); 402 usage(EXIT_FAILURE); 403 } 404 405 return 0; 406 } 407