/*++ /* NAME /* tls_certkey 3 /* SUMMARY /* public key certificate and private key loader /* SYNOPSIS /* #define TLS_INTERNAL /* #include /* /* int tls_set_ca_certificate_info(ctx, CAfile, CApath) /* SSL_CTX *ctx; /* const char *CAfile; /* const char *CApath; /* /* int tls_set_my_certificate_key_info(ctx, chain_files, /* cert_file, key_file, /* dcert_file, dkey_file, /* eccert_file, eckey_file) /* SSL_CTX *ctx; /* const char *chain_files; /* const char *cert_file; /* const char *key_file; /* const char *dcert_file; /* const char *dkey_file; /* const char *eccert_file; /* const char *eckey_file; /* /* int tls_load_pem_chain(ssl, pem, origin); /* SSL *ssl; /* const char *pem; /* const char *origin; /* DESCRIPTION /* OpenSSL supports two options to specify CA certificates: /* either one file CAfile that contains all CA certificates, /* or a directory CApath with separate files for each /* individual CA, with symbolic links named after the hash /* values of the certificates. The second option is not /* convenient with a chrooted process. /* /* tls_set_ca_certificate_info() loads the CA certificate /* information for the specified TLS server or client context. /* The result is -1 on failure, 0 on success. /* /* tls_set_my_certificate_key_info() loads the public key /* certificates and private keys for the specified TLS server /* or client context. Up to 3 pairs of key pairs (RSA, DSA and /* ECDSA) may be specified; each certificate and key pair must /* match. The chain_files argument makes it possible to load /* keys and certificates for more than 3 algorithms, via either /* a single file, or a list of multiple files. The result is -1 /* on failure, 0 on success. /* /* tls_load_pem_chain() loads one or more (key, cert, [chain]) /* triples from an in-memory PEM blob. The "origin" argument /* is used for error logging, to identify the provenance of the /* PEM blob. "ssl" must be non-zero, and the keys and certificates /* will be loaded into that object. /* LICENSE /* .ad /* .fi /* This software is free. You can do with it whatever you want. /* The original author kindly requests that you acknowledge /* the use of his software. /* AUTHOR(S) /* Originally written by: /* Lutz Jaenicke /* BTU Cottbus /* Allgemeine Elektrotechnik /* Universitaetsplatz 3-4 /* D-03044 Cottbus, Germany /* /* Updated by: /* Wietse Venema /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA /* /* Wietse Venema /* Google, Inc. /* 111 8th Avenue /* New York, NY 10011, USA /*--*/ /* System library. */ #include #ifdef USE_TLS /* Utility library. */ #include /* Global library. */ #include /* TLS library. */ #define TLS_INTERNAL #include #define PEM_LOAD_STATE_NOGO -2 /* Unusable object or sequence */ #define PEM_LOAD_STATE_FAIL -1 /* Error in libcrypto */ #define PEM_LOAD_STATE_DONE 0 /* End of PEM file, return value only */ #define PEM_LOAD_STATE_INIT 1 /* No PEM objects seen */ #define PEM_LOAD_STATE_PKEY 2 /* Last object was a private key */ #define PEM_LOAD_STATE_CERT 3 /* Last object was a certificate */ #define PEM_LOAD_STATE_BOTH 4 /* Unordered, key + first cert seen */ #define PEM_LOAD_READ_LAST 0 /* Reading last file */ #define PEM_LOAD_READ_MORE 1 /* More files to be read */ typedef struct pem_load_state_t { const char *origin; /* PEM chain origin description */ const char *source; /* PEM BIO origin description */ const char *keysrc; /* Source of last key */ BIO *pembio; /* PEM input stream */ SSL_CTX *ctx; /* SSL connection factory */ SSL *ssl; /* SSL connection handle */ EVP_PKEY *pkey; /* current key */ X509 *cert; /* current certificate */ x509_stack_t *chain; /* current chain */ int keynum; /* Index of last key */ int objnum; /* Index in current source */ int state; /* Current state, never "DONE" */ int mixed; /* Single file with key anywhere */ } pem_load_state_t; /* init_pem_load_state - fill in initial pem_load_state structure */ static void init_pem_load_state(pem_load_state_t *st, SSL_CTX *ctx, SSL *ssl, const char *origin) { st->origin = origin; st->source = origin; st->keysrc = 0; st->pembio = 0; st->ctx = ctx; st->ssl = ssl; st->pkey = 0; st->cert = 0; st->chain = 0; st->keynum = 0; st->objnum = 0; st->state = PEM_LOAD_STATE_INIT; st->mixed = 0; } /* use_chain - load cert, key and chain into ctx or ssl */ static int use_chain(pem_load_state_t *st) { int ret; int replace = 0; /* * With replace == 0, an error is returned if the algorithm slot is * already taken, and a previous key + chain of the same type would be * clobbered. */ if (st->ctx) ret = SSL_CTX_use_cert_and_key(st->ctx, st->cert, st->pkey, st->chain, replace); else ret = SSL_use_cert_and_key(st->ssl, st->cert, st->pkey, st->chain, replace); /* * SSL_[CTX_]_use_cert_key() uprefs all the objects in question, so we * must free ours. */ X509_free(st->cert); st->cert = 0; EVP_PKEY_free(st->pkey); st->pkey = 0; sk_X509_pop_free(st->chain, X509_free); st->chain = 0; return ret; } /* load_cert - decode and load a DER-encoded X509 certificate */ static void load_cert(pem_load_state_t *st, unsigned char *buf, long buflen) { const unsigned char *p = buf; X509 *cert = d2i_X509(0, &p, buflen); /* * When expecting one or more keys, each key must precede the associated * certificate (chain). */ if (!st->mixed && st->state == PEM_LOAD_STATE_INIT) { msg_warn("error loading chain from %s: key not first", st->source); if (cert) X509_free(cert); st->state = PEM_LOAD_STATE_NOGO; return; } if (!cert) { msg_warn("error loading certificate (PEM object number %d) from %s", st->objnum, st->source); st->state = PEM_LOAD_STATE_FAIL; return; } if (p - buf != buflen) { msg_warn("error loading certificate (PEM object number %d) from %s:" " excess data", st->objnum, st->source); X509_free(cert); st->state = PEM_LOAD_STATE_NOGO; return; } /* * The first certificate after a new key becomes the leaf certificate for * that key. Subsequent certificates are added to the issuer chain. * * In "mixed" mode, the first certificate is either after the key, or else * comes first. */ switch (st->state) { case PEM_LOAD_STATE_PKEY: st->cert = cert; st->state = st->mixed ? PEM_LOAD_STATE_BOTH : PEM_LOAD_STATE_CERT; return; case PEM_LOAD_STATE_INIT: st->cert = cert; st->state = PEM_LOAD_STATE_CERT; return; case PEM_LOAD_STATE_CERT: case PEM_LOAD_STATE_BOTH: if ((!st->chain && (st->chain = sk_X509_new_null()) == 0) || !sk_X509_push(st->chain, cert)) { X509_free(cert); st->state = PEM_LOAD_STATE_FAIL; } return; } } /* load_pkey - decode and load a DER-encoded private key */ static void load_pkey(pem_load_state_t *st, int pkey_type, unsigned char *buf, long buflen) { const char *myname = "load_pkey"; const unsigned char *p = buf; PKCS8_PRIV_KEY_INFO *p8; EVP_PKEY *pkey = 0; /* * Keys are either algorithm-specific, or else (ideally) algorithm * agnostic, in which case they are wrapped as PKCS#8 objects with an * algorithm OID. */ if (pkey_type != NID_undef) { pkey = d2i_PrivateKey(pkey_type, 0, &p, buflen); } else { p8 = d2i_PKCS8_PRIV_KEY_INFO(NULL, &p, buflen); if (p8) { pkey = EVP_PKCS82PKEY(p8); PKCS8_PRIV_KEY_INFO_free(p8); } } /* * Except in "mixed" mode, where a single key appears anywhere in a file * with multiple certificates, a given key is either at the first object * we process, or occurs after a previous key and one or more associated * certificates. Thus, encountering a key in a state other than "INIT" * or "CERT" is an error, except in "mixed" mode where a second key is * ignored with a warning. */ switch (st->state) { case PEM_LOAD_STATE_CERT: /* * When processing the key of a "next" chain, we're in the "CERT" * state, and first complete the processing of the previous chain. */ if (!st->mixed && !use_chain(st)) { msg_warn("error loading certificate chain: " "key at index %d in %s does not match the certificate", st->keynum, st->keysrc); st->state = PEM_LOAD_STATE_FAIL; return; } /* FALLTHROUGH */ case PEM_LOAD_STATE_INIT: if (!pkey) { msg_warn("error loading private key (PEM object number %d) from %s", st->objnum, st->source); st->state = PEM_LOAD_STATE_FAIL; return; } /* Reject unexpected data beyond the end of the DER-encoded object */ if (p - buf != buflen) { msg_warn("error loading private key (PEM object number %d) from" " %s: excess data", st->objnum, st->source); EVP_PKEY_free(pkey); st->state = PEM_LOAD_STATE_NOGO; return; } /* All's well, update the state */ st->pkey = pkey; if (st->state == PEM_LOAD_STATE_INIT) st->state = PEM_LOAD_STATE_PKEY; else if (st->mixed) st->state = PEM_LOAD_STATE_BOTH; else st->state = PEM_LOAD_STATE_PKEY; return; case PEM_LOAD_STATE_PKEY: case PEM_LOAD_STATE_BOTH: if (pkey) EVP_PKEY_free(pkey); /* XXX: Legacy behavior was silent, should we stay silent? */ if (st->mixed) { msg_warn("ignoring 2nd key at index %d in %s after 1st at %d", st->objnum, st->source, st->keynum); return; } /* else back-to-back keys */ msg_warn("error loading certificate chain: " "key at index %d in %s not followed by a certificate", st->keynum, st->keysrc); st->state = PEM_LOAD_STATE_NOGO; return; default: msg_error("%s: internal error: bad state: %d", myname, st->state); st->state = PEM_LOAD_STATE_NOGO; return; } } /* load_pem_object - load next pkey or cert from open BIO */ static int load_pem_object(pem_load_state_t *st) { char *name = 0; char *header = 0; unsigned char *buf = 0; long buflen; int pkey_type = NID_undef; if (!PEM_read_bio(st->pembio, &name, &header, &buf, &buflen)) { if (ERR_GET_REASON(ERR_peek_last_error()) != PEM_R_NO_START_LINE) return (st->state = PEM_LOAD_STATE_FAIL); ERR_clear_error(); /* Clean EOF, preserve stored state for any next input file */ return (PEM_LOAD_STATE_DONE); } if (strcmp(name, PEM_STRING_X509) == 0 || strcmp(name, PEM_STRING_X509_OLD) == 0) { load_cert(st, buf, buflen); } else if (strcmp(name, PEM_STRING_PKCS8INF) == 0 || ((pkey_type = EVP_PKEY_RSA) != NID_undef && strcmp(name, PEM_STRING_RSA) == 0) || ((pkey_type = EVP_PKEY_EC) != NID_undef && strcmp(name, PEM_STRING_ECPRIVATEKEY) == 0) || ((pkey_type = EVP_PKEY_DSA) != NID_undef && strcmp(name, PEM_STRING_DSA) == 0)) { load_pkey(st, pkey_type, buf, buflen); } else if (!st->mixed) { msg_warn("loading %s: ignoring PEM type: %s", st->source, name); } OPENSSL_free(name); OPENSSL_free(header); OPENSSL_free(buf); return (st->state); } /* load_pem_bio - load all key/certs from bio and free the bio */ static int load_pem_bio(pem_load_state_t *st, int more) { int state = st->state; /* Don't report old news */ ERR_clear_error(); /* * When "more" is PEM_LOAD_READ_MORE, more files will be loaded after the * current file, and final processing for the last key and chain is * deferred. * * When "more" is PEM_LOAD_READ_LAST, this is the last file in the list, and * we validate the final chain. * * When st->mixed is true, this is the only file, and its key can occur at * any location. In this case we load at most one key. */ for (st->objnum = 1; state > PEM_LOAD_STATE_DONE; ++st->objnum) { state = load_pem_object(st); if ((st->mixed && st->keynum == 0 && (state == PEM_LOAD_STATE_PKEY || state == PEM_LOAD_STATE_BOTH)) || (!st->mixed && state == PEM_LOAD_STATE_PKEY)) { /* Squirrel-away the current key location */ st->keynum = st->objnum; st->keysrc = st->source; } } /* We're responsible for unconditionally freeing the BIO */ BIO_free(st->pembio); /* Success with current file, go back for more? */ if (more == PEM_LOAD_READ_MORE && state >= PEM_LOAD_STATE_DONE) return 0; /* * If all is well so far, complete processing for the final chain. */ switch (st->state) { case PEM_LOAD_STATE_FAIL: tls_print_errors(); break; default: break; case PEM_LOAD_STATE_INIT: msg_warn("No PEM data in %s", st->origin); break; case PEM_LOAD_STATE_PKEY: msg_warn("No certs for key at index %d in %s", st->keynum, st->keysrc); break; case PEM_LOAD_STATE_CERT: if (st->mixed) { msg_warn("No private key found in %s", st->origin); break; } /* FALLTHROUGH */ case PEM_LOAD_STATE_BOTH: /* use_chain() frees the key and certs, and zeroes the pointers */ if (use_chain(st)) return (0); msg_warn("key at index %d in %s does not match next certificate", st->keynum, st->keysrc); tls_print_errors(); break; } /* Free any left-over unused keys and certs */ EVP_PKEY_free(st->pkey); X509_free(st->cert); sk_X509_pop_free(st->chain, X509_free); msg_warn("error loading private keys and certificates from: %s: %s", st->origin, st->ctx ? "disabling TLS support" : "aborting TLS handshake"); return (-1); } /* load_chain_files - load sequence of (key, cert, [chain]) from files */ static int load_chain_files(SSL_CTX *ctx, const char *chain_files) { pem_load_state_t st; ARGV *files = argv_split(chain_files, CHARS_COMMA_SP); char **filep; int ret = 0; int more; init_pem_load_state(&st, ctx, 0, chain_files); for (filep = files->argv; ret == 0 && *filep; ++filep) { st.source = *filep; if ((st.pembio = BIO_new_file(st.source, "r")) == NULL) { msg_warn("error opening chain file: %s: %m", st.source); st.state = PEM_LOAD_STATE_NOGO; break; } more = filep[1] ? PEM_LOAD_READ_MORE : PEM_LOAD_READ_LAST; /* load_pem_bio() frees the BIO */ ret = load_pem_bio(&st, more); } argv_free(files); return (ret); } /* load_mixed_file - load certs with single key anywhere in the file */ static int load_mixed_file(SSL_CTX *ctx, const char *file) { pem_load_state_t st; init_pem_load_state(&st, ctx, 0, file); if ((st.pembio = BIO_new_file(st.source, "r")) == NULL) { msg_warn("error opening chain file: %s: %m", st.source); return (-1); } st.mixed = 1; /* load_pem_bio() frees the BIO */ return load_pem_bio(&st, PEM_LOAD_READ_LAST); } /* tls_set_ca_certificate_info - load Certification Authority certificates */ int tls_set_ca_certificate_info(SSL_CTX *ctx, const char *CAfile, const char *CApath) { if (*CAfile == 0) CAfile = 0; if (*CApath == 0) CApath = 0; #define CA_PATH_FMT "%s%s%s" #define CA_PATH_ARGS(var, nextvar) \ var ? #var "=\"" : "", \ var ? var : "", \ var ? (nextvar ? "\", " : "\"") : "" if (CAfile || CApath) { if (!SSL_CTX_load_verify_locations(ctx, CAfile, CApath)) { msg_info("cannot load Certification Authority data, " CA_PATH_FMT CA_PATH_FMT ": disabling TLS support", CA_PATH_ARGS(CAfile, CApath), CA_PATH_ARGS(CApath, 0)); tls_print_errors(); return (-1); } if (var_tls_append_def_CA && !SSL_CTX_set_default_verify_paths(ctx)) { msg_info("cannot set default OpenSSL certificate verification " "paths: disabling TLS support"); tls_print_errors(); return (-1); } } return (0); } /* set_cert_stuff - specify certificate and key information */ static int set_cert_stuff(SSL_CTX *ctx, const char *cert_type, const char *cert_file, const char *key_file) { /* * When the certfile and keyfile are one and the same, load both in a * single pass, avoiding potential race conditions during key rollover. */ if (strcmp(cert_file, key_file) == 0) return (load_mixed_file(ctx, cert_file) == 0); /* * We need both the private key (in key_file) and the public key * certificate (in cert_file). * * Code adapted from OpenSSL apps/s_cb.c. */ ERR_clear_error(); if (SSL_CTX_use_certificate_chain_file(ctx, cert_file) <= 0) { msg_warn("cannot get %s certificate from file \"%s\": " "disabling TLS support", cert_type, cert_file); tls_print_errors(); return (0); } if (SSL_CTX_use_PrivateKey_file(ctx, key_file, SSL_FILETYPE_PEM) <= 0) { msg_warn("cannot get %s private key from file \"%s\": " "disabling TLS support", cert_type, key_file); tls_print_errors(); return (0); } /* * Sanity check. */ if (!SSL_CTX_check_private_key(ctx)) { msg_warn("%s private key in %s does not match public key in %s: " "disabling TLS support", cert_type, key_file, cert_file); return (0); } return (1); } /* tls_set_my_certificate_key_info - load client or server certificates/keys */ int tls_set_my_certificate_key_info(SSL_CTX *ctx, const char *chain_files, const char *cert_file, const char *key_file, const char *dcert_file, const char *dkey_file, const char *eccert_file, const char *eckey_file) { /* The "chain_files" parameter overrides all the legacy parameters */ if (chain_files && *chain_files) return load_chain_files(ctx, chain_files); /* * Lack of certificates is fine so long as we are prepared to use * anonymous ciphers. */ if (*cert_file && !set_cert_stuff(ctx, "RSA", cert_file, key_file)) return (-1); /* logged */ if (*dcert_file && !set_cert_stuff(ctx, "DSA", dcert_file, dkey_file)) return (-1); /* logged */ #ifndef OPENSSL_NO_ECDH if (*eccert_file && !set_cert_stuff(ctx, "ECDSA", eccert_file, eckey_file)) return (-1); /* logged */ #else if (*eccert_file) msg_warn("ECDSA not supported. Ignoring ECDSA certificate file \"%s\"", eccert_file); #endif return (0); } /* tls_load_pem_chain - load in-memory PEM client or server chain */ int tls_load_pem_chain(SSL *ssl, const char *pem, const char *origin) { static VSTRING *obuf; pem_load_state_t st; if (!obuf) obuf = vstring_alloc(100); vstring_sprintf(obuf, "SNI data for %s", origin); init_pem_load_state(&st, 0, ssl, vstring_str(obuf)); if ((st.pembio = BIO_new_mem_buf(pem, -1)) == NULL) { msg_warn("error opening memory BIO for %s", st.origin); tls_print_errors(); return (-1); } /* load_pem_bio() frees the BIO */ return (load_pem_bio(&st, PEM_LOAD_READ_LAST)); } #ifdef TEST static NORETURN usage(void) { fprintf(stderr, "usage: tls_certkey [-m] \n"); exit(1); } int main(int argc, char *argv[]) { int ch; int mixed = 0; int ret; char *key_file = 0; SSL_CTX *ctx; if (!(ctx = SSL_CTX_new(TLS_client_method()))) { tls_print_errors(); exit(1); } while ((ch = GETOPT(argc, argv, "mk:")) > 0) { switch (ch) { case 'k': key_file = optarg; break; case 'm': mixed = 1; break; default: usage(); } } argc -= optind; argv += optind; if (argc < 1) usage(); if (key_file) ret = set_cert_stuff(ctx, "any", argv[0], key_file) == 0; else if (mixed) ret = load_mixed_file(ctx, argv[0]); else ret = load_chain_files(ctx, argv[0]); if (ret != 0) exit(1); if (SSL_CTX_set_current_cert(ctx, SSL_CERT_SET_FIRST) != 1) { fprintf(stderr, "error selecting first certificate\n"); tls_print_errors(); exit(1); } do { STACK_OF(X509) *chain; int i; if (SSL_CTX_get0_chain_certs(ctx, &chain) != 1) { fprintf(stderr, "error locating certificate chain\n"); tls_print_errors(); exit(1); } for (i = 0; i <= sk_X509_num(chain); ++i) { char buf[CCERT_BUFSIZ]; X509 *cert; if (i > 0) cert = sk_X509_value(chain, i - 1); else cert = SSL_CTX_get0_certificate(ctx); printf("depth = %d\n", i); X509_NAME_oneline(X509_get_issuer_name(cert), buf, sizeof(buf)); printf("issuer = %s\n", buf); X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf)); printf("subject = %s\n\n", buf); } } while (SSL_CTX_set_current_cert(ctx, SSL_CERT_SET_NEXT) != 0); exit(0); } #endif #endif