NetBSD/usr.bin/nbsvtool/nbsvtool.c
joerg d40cb84328 If key usage validation is requested, ensure that all bits exist.
Check the certificates before printing that the signature is valid.
Improvide wording of error message as suggested by Love.
2008-06-11 16:31:09 +00:00

414 lines
10 KiB
C

/* $NetBSD: nbsvtool.c,v 1.2 2008/06/11 16:31:09 joerg Exp $ */
/*-
* Copyright (c) 2004, 2008 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Love Hörnquist Åstrand <lha@it.su.se>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <openssl/pkcs7.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/ui.h>
static int verbose_flag;
static unsigned long key_usage = 0;
/*
* openssl command line equivalents
*
* openssl smime -verify \
* -inform PEM -in nbsvtool.c.sig -content nbsvtool.c \
* -CAfile /secure/lha/su/CA/swupki-pca.crt -out /dev/null
* openssl smime -sign \
* -noattr -binary -outform PEM -out nbsvtool.c.sig \
* -in nbsvtool.c -signer /secure/lha/su/CA/lha.crt \
* -certfile /secure/lha/su/CA/lha-chain \
* -inkey /secure/lha/su/CA/lha.key
*/
/*
* Create a detach PEM signature of file `infile' and store it in
* `outfile'. The signer certificate `cert' and private key
* `private_key' must be given. An additional hint to the verifier how
* to find the path from the `cert' to the x509 anchor can be passed
* in `cert_chain'.
*/
static void
sign_file(X509 *cert, EVP_PKEY *private_key, STACK_OF(X509) *cert_chain,
const char *infile, const char *outfile)
{
BIO *out, *in;
PKCS7 *p7;
out = BIO_new_file(outfile, "w");
if (out == NULL)
err(EXIT_FAILURE, "Failed to open signature output file: %s",
outfile);
in = BIO_new_file(infile, "r");
if (in == NULL)
err(EXIT_FAILURE, "Failed to input file: %s", infile);
p7 = PKCS7_sign(cert, private_key, cert_chain, in,
PKCS7_DETACHED|PKCS7_NOATTR|PKCS7_BINARY);
if (p7 == NULL)
errx(EXIT_FAILURE, "Failed to create signature structure");
PEM_write_bio_PKCS7(out, p7);
PKCS7_free(p7);
BIO_free(in);
BIO_free_all(out);
}
/*
* Verifies a detached PEM signature in the file `sigfile' of file
* `infile'. The trust anchor file `anchor' to the trust anchors must
* be given. If its suspended that the sender didn't inlude the whole
* path from the signing certificate to the given trust anchor, extra
* certificates can be passed in `cert_chain'.
*/
static void
verify_file(STACK_OF(X509) *cert_chain, const char *anchor,
const char *infile, const char *sigfile)
{
STACK_OF(X509) *signers;
X509_STORE *store;
BIO *sig, *in;
PKCS7 *p7;
int ret, i;
X509_NAME *name;
char *subject;
store = X509_STORE_new();
if (store == NULL)
err(1, "Failed to create store");
X509_STORE_load_locations(store, anchor, NULL);
in = BIO_new_file(infile, "r");
if (in == NULL)
err(EXIT_FAILURE, "Failed to open input data file: %s", infile);
sig = BIO_new_file(sigfile, "r");
if (sig == NULL)
err(EXIT_FAILURE, "Failed to open signature input file: %s",
sigfile);
p7 = PEM_read_bio_PKCS7(sig, NULL, NULL, NULL);
if (p7 == NULL)
errx(EXIT_FAILURE, "Failed to parse the signature file %s",
sigfile);
ret = PKCS7_verify(p7, cert_chain, store, in, NULL, 0);
if (ret != 1)
errx(EXIT_FAILURE, "Failed to verify signature");
signers = PKCS7_get0_signers(p7, NULL, 0);
if (signers == NULL)
errx(EXIT_FAILURE, "Failed to get signers");
if (sk_X509_num(signers) == 0)
errx(EXIT_FAILURE, "No signers ?");
if (key_usage != 0) {
for (i = 0; i < sk_X509_num(signers); i++) {
if ((sk_X509_value(signers, i)->ex_xkusage & key_usage)
== key_usage)
continue;
name = X509_get_subject_name(sk_X509_value(signers, i));
subject = X509_NAME_oneline(name, NULL, 0);
errx(EXIT_FAILURE,
"Certificate doesn't match required key usage: %s",
subject);
}
}
if (verbose_flag)
printf("Sigature ok, signed by:\n");
for (i = 0; i < sk_X509_num(signers); i++) {
name = X509_get_subject_name(sk_X509_value(signers, i));
subject = X509_NAME_oneline(name, NULL, 0);
if (verbose_flag)
printf("\t%s\n", subject);
OPENSSL_free(subject);
}
PKCS7_free(p7);
BIO_free(in);
BIO_free(sig);
}
/*
* Parse and return a list PEM encoded certificates in the file
* `file'. In case of error or an empty file, and error text will be
* printed and the function will exit(3).
*/
static STACK_OF(X509) *
file_to_certs(const char *file)
{
STACK_OF(X509) *certs;
FILE *f;
f = fopen(file, "r");
if (f == NULL)
err(EXIT_FAILURE, "Cannot open certificate file %s", file);
certs = sk_X509_new_null();
while (1) {
X509 *cert;
cert = PEM_read_X509(f, NULL, NULL, NULL);
if (cert == NULL) {
unsigned long ret;
ret = ERR_GET_REASON(ERR_peek_error());
if (ret == PEM_R_NO_START_LINE) {
/* End of file reached. no error */
ERR_clear_error();
break;
}
errx(EXIT_FAILURE, "Can't read certificate file %s",
file);
}
sk_X509_insert(certs, cert, sk_X509_num(certs));
}
fclose(f);
if (sk_X509_num(certs) == 0)
errx(EXIT_FAILURE, "No certificate found file %s", file);
return certs;
}
static int
ssl_pass_cb(char *buf, int size, int rwflag, void *u)
{
if (UI_UTIL_read_pw_string(buf, size, "Passphrase: ", 0))
return 0;
return strlen(buf);
}
static struct {
X509 *certificate;
STACK_OF(X509) *cert_chain;
EVP_PKEY *private_key;
} crypto_state;
/*
* Load the certificate file `cert_file' with the associated private
* key file `key_file'. The private key is checked to make sure it
* matches the certificate. The optional hints for the path to the CA
* is stored in `chain_file'.
*/
static void
load_keys(const char *cert_file, const char *chain_file, const char *key_file)
{
STACK_OF(X509) *c;
FILE *f;
int ret;
if (cert_file == NULL)
errx(EXIT_FAILURE, "No certificate file given");
if (key_file == NULL)
errx(EXIT_FAILURE, "No private key file given");
c = file_to_certs(cert_file);
if (sk_X509_num(c) != 1)
errx(EXIT_FAILURE,
"More then one certificate in the certificate file");
crypto_state.certificate = sk_X509_value(c, 0);
if (chain_file)
crypto_state.cert_chain = file_to_certs(chain_file);
/* load private key */
f = fopen(key_file, "r");
if (f == NULL)
errx(1, "Failed to open private key file %s", key_file);
crypto_state.private_key =
PEM_read_PrivateKey(f, NULL, ssl_pass_cb, NULL);
fclose(f);
if (crypto_state.private_key == NULL)
errx(EXIT_FAILURE, "Can't read private key %s", key_file);
ret = X509_check_private_key(crypto_state.certificate,
crypto_state.private_key);
if (ret != 1)
errx(EXIT_FAILURE,
"The private key %s doesn't match the certificate %s",
key_file, cert_file);
}
static void __dead
usage(int exit_code)
{
printf("%s usage\n", getprogname());
printf("%s -k keyfile -c cert-chain [-f cert-chain] sign file\n",
getprogname());
printf("%s [-u code|...] [-a x509-anchor-file] verify filename.sp7\n",
getprogname());
printf("%s [-u code|...] [-a x509-anchor-file] verify filename otherfilename.sp7\n",
getprogname());
printf("%s [-u code|...] [-a x509-anchor-file] verify-code file ...\n",
getprogname());
exit(exit_code);
}
int
main(int argc, char **argv)
{
const char *anchors = NULL;
const char *cert_file = NULL, *key_file = NULL, *chain_file = NULL;
const char *file;
char *sigfile;
int ch;
setprogname(argv[0]);
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();
while ((ch = getopt(argc, argv, "a:c:f:hk:u:v")) != -1) {
switch (ch) {
case 'a':
anchors = optarg;
break;
case 'f':
chain_file = optarg;
break;
case 'k':
key_file = optarg;
break;
case 'c':
cert_file = optarg;
break;
case 'u':
if (strcmp("ssl-server", optarg) == 0)
key_usage |= XKU_SSL_SERVER;
else if (strcmp("ssl-client", optarg) == 0)
key_usage |= XKU_SSL_CLIENT;
else if (strcmp("code", optarg) == 0)
key_usage |= XKU_CODE_SIGN;
else if (strcmp("smime", optarg) == 0)
key_usage |= XKU_SMIME;
else
errx(1, "Unknown keyusage: %s", optarg);
break;
case 'v':
verbose_flag = 1;
break;
case 'h':
usage(EXIT_SUCCESS);
default:
usage(EXIT_FAILURE);
}
}
argc -= optind;
argv += optind;
if (argc < 1) {
fprintf(stderr, "Command missing [sign|verify]\n");
usage(EXIT_FAILURE);
}
if (strcmp(argv[0], "sign") == 0) {
if (argc < 2)
usage(1);
file = argv[1];
asprintf(&sigfile, "%s.sp7", file);
if (sigfile == NULL)
err(EXIT_FAILURE, "asprintf failed");
load_keys(cert_file, chain_file, key_file);
sign_file(crypto_state.certificate,
crypto_state.private_key,
crypto_state.cert_chain,
file,
sigfile);
} else if (strcmp(argv[0], "verify") == 0
|| strcmp(argv[0], "verify-code") == 0) {
if (strcmp(argv[0], "verify-code") == 0)
key_usage |= XKU_CODE_SIGN;
if (argc < 2)
usage(1);
else if (argc < 3) {
char *dot;
sigfile = argv[1];
file = strdup(sigfile);
if (file == NULL)
err(1, "strdup failed");
dot = strrchr(file, '.');
if (dot == NULL || strchr(dot, '/') != NULL)
errx(EXIT_FAILURE,
"File name missing suffix");
if (strcmp(".sp7", dot) != 0)
errx(EXIT_FAILURE,
"File name bad suffix (%s)", dot);
*dot = '\0';
} else {
file = argv[1];
sigfile = argv[2];
}
verify_file(crypto_state.cert_chain, anchors, file, sigfile);
} else {
fprintf(stderr, "Unknown command: %s\n", argv[0]);
usage(EXIT_FAILURE);
}
return 0;
}