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