Q: My application takes a filename for a client certificate on the command line. What is the OpenSSL function to load and use it?
A: Well, we make this lots of fun for you -- it would be boring if there was just one function which you could pass the filename to. You have to write 230 lines of code instead.... First you have to check for yourself what type of file it is -- is it a PKCS#12 file, is it a PEM file with a key in it, or is it a TPM key 'blob'? No, there's no function which determines that for you -- you have to do it yourself. And depending on the answer, you have to do three entirely different things to load the key. To make things even more fun, those three file types have _wildly_ different ways to handle their passphrase/PIN: For a PEM file, you can't tell OpenSSL the passphrase in advance -- if the user gave it on the command line, you have to manually override the user interface function that OpenSSL will call, and make your replacement function return the pre-set passphrase. Or if you _do_ ask the user, you've got no way to easily tell whether the user got the passphrase wrong; if they get it wrong (and type 4 or more characters) then the 'load key' function will fail and you have to compare against a special error code, which may differ from version to version of OpenSSL because it has internal function names. Just for variety, if the user enters a wrong passphrase with _fewer_ than 4 characters, they'll get _no_ feedback and will just be asked again. For a PKCS#12 file, it's the other way round -- you _have_ to give the passphrase in advance, so you have to ask the user for it yourself. Even if the file isn't actually encrypted -- because you don't know that yet. For a TPM file it's saner -- you can _either_ set the PIN in advance or otherwise OpenSSL will ask the user for it _if_ necessary. But you do have to jump through various other hoops to use the TPM 'engine', instead of just pointing OpenSSL at the file and having everything handled for you. Have I got any parts of the above answer wrong? Is there anyone out there who thinks that this is a _sensible_ state of affairs? This is my load_certificate() function -- could it be simpler? Surely OpenSSL ought to provide a function with basically equivalent functionality, rather than leaving it to the client application? Am I missing something? (Unless specified on the command line, vpninfo->cert_type will be CERT_TYPE_UNKNOWN. When the autodetection is _working_ there's no real need to specify it on the command line, of course.) static int pem_pw_cb(char *buf, int len, int w, void *v); static int load_pkcs12_certificate(struct openconnect_info *vpninfo, PKCS12 *p12); static int load_tpm_certificate(struct openconnect_info *vpninfo); static int load_certificate(struct openconnect_info *vpninfo) { vpninfo->progress(vpninfo, PRG_TRACE, "Using certificate file %s\n", vpninfo->cert); if (vpninfo->cert_type == CERT_TYPE_PKCS12 || vpninfo->cert_type == CERT_TYPE_UNKNOWN) { FILE *f; PKCS12 *p12; f = fopen(vpninfo->cert, "r"); if (!f) { vpninfo->progress(vpninfo, PRG_ERR, "Failed to open certificate file %s\n", vpninfo->cert); return -ENOENT; } p12 = d2i_PKCS12_fp(f, NULL); fclose(f); if (p12) return load_pkcs12_certificate(vpninfo, p12); /* Not PKCS#12 */ if (vpninfo->cert_type == CERT_TYPE_PKCS12) { vpninfo->progress(vpninfo, PRG_ERR, "Read PKCS#12 failed\n"); report_ssl_errors(vpninfo); return -EINVAL; } /* Clear error and fall through to see if it's a PEM file... */ ERR_clear_error(); } /* It's PEM or TPM now, and either way we need to load the plain cert: */ if (!SSL_CTX_use_certificate_file(vpninfo->https_ctx, vpninfo->cert, SSL_FILETYPE_PEM)) { vpninfo->progress(vpninfo, PRG_ERR, "Load certificate failed\n"); report_ssl_errors(vpninfo); return -EINVAL; } if (vpninfo->cert_type == CERT_TYPE_UNKNOWN) { FILE *f = fopen(vpninfo->sslkey, "r"); char buf[256]; if (!f) { vpninfo->progress(vpninfo, PRG_ERR, "Failed to open certificate file %s\n", vpninfo->cert); return -ENOENT; } buf[255] = 0; while (fgets(buf, 255, f)) { if (!strcmp(buf, "-----BEGIN TSS KEY BLOB-----\n")) { vpninfo->cert_type = CERT_TYPE_TPM; break; } else if (!strcmp(buf, "-----BEGIN RSA PRIVATE KEY-----\n") || !strcmp(buf, "-----BEGIN DSA PRIVATE KEY-----\n")) { vpninfo->cert_type = CERT_TYPE_PEM; break; } } fclose(f); if (vpninfo->cert_type == CERT_TYPE_UNKNOWN) { vpninfo->progress(vpninfo, PRG_ERR, "Failed to identify private key type in '%s'\n", vpninfo->sslkey); return -EINVAL; } } if (vpninfo->cert_type == CERT_TYPE_TPM) return load_tpm_certificate(vpninfo); /* Standard PEM certificate */ if (vpninfo->cert_password) { SSL_CTX_set_default_passwd_cb(vpninfo->https_ctx, pem_pw_cb); SSL_CTX_set_default_passwd_cb_userdata(vpninfo->https_ctx, vpninfo); } again: if (!SSL_CTX_use_RSAPrivateKey_file(vpninfo->https_ctx, vpninfo->sslkey, SSL_FILETYPE_PEM)) { unsigned long err = ERR_peek_error(); vpninfo->progress(vpninfo, PRG_ERR, "Private key failed\n"); report_ssl_errors(vpninfo); /* Compatibility with OpenSSL-0.9.7. Ick. */ #ifndef EVP_F_EVP_DECRYPTFINAL_EX #define EVP_F_EVP_DECRYPTFINAL_EX EVP_F_EVP_DECRYPTFINAL #endif /* If the user fat-fingered the passphrase, try again */ if (ERR_GET_LIB(err) == ERR_LIB_EVP && ERR_GET_FUNC(err) == EVP_F_EVP_DECRYPTFINAL_EX && ERR_GET_REASON(err) == EVP_R_BAD_DECRYPT) goto again; return -EINVAL; } return 0; } static int pem_pw_cb(char *buf, int len, int w, void *v) { struct openconnect_info *vpninfo = v; /* Only try the provided password once... */ SSL_CTX_set_default_passwd_cb(vpninfo->https_ctx, NULL); SSL_CTX_set_default_passwd_cb_userdata(vpninfo->https_ctx, NULL); if (len <= strlen(vpninfo->cert_password)) { vpninfo->progress(vpninfo, PRG_ERR, "PEM password too long (%zd >= %d)\n", strlen(vpninfo->cert_password), len); return -1; } strcpy(buf, vpninfo->cert_password); return strlen(vpninfo->cert_password); } static int load_pkcs12_certificate(struct openconnect_info *vpninfo, PKCS12 *p12) { EVP_PKEY *pkey = NULL; X509 *cert = NULL; STACK_OF(X509) *ca = sk_X509_new_null(); int ret = 0; char pass[PEM_BUFSIZE]; if (!vpninfo->cert_password) { if (EVP_read_pw_string(pass, PEM_BUFSIZE, "Enter PKCS#12 pass phrase:", 0)) return -EINVAL; } if (!PKCS12_parse(p12, vpninfo->cert_password?:pass, &pkey, &cert, &ca)) { vpninfo->progress(vpninfo, PRG_ERR, "Parse PKCS#12 failed\n"); report_ssl_errors(vpninfo); PKCS12_free(p12); return -EINVAL; } if (cert) { SSL_CTX_use_certificate(vpninfo->https_ctx, cert); X509_free(cert); } else { vpninfo->progress(vpninfo, PRG_ERR, "PKCS#12 contained no certificate!"); ret = -EINVAL; } if (pkey) { SSL_CTX_use_PrivateKey(vpninfo->https_ctx, pkey); EVP_PKEY_free(pkey); } else { vpninfo->progress(vpninfo, PRG_ERR, "PKCS#12 contained no private key!"); ret = -EINVAL; } /* Only include supporting certificates which are actually necessary, * to work around RT#1942 */ if (ca) { int i; next: for (i = 0; i < sk_X509_num(ca); i++) { X509 *cert2 = sk_X509_value(ca, i); if (X509_check_issued(cert2, cert) == X509_V_OK) { char buf[200]; if (cert2 == cert) break; X509_NAME_oneline(X509_get_subject_name(cert2), buf, sizeof(buf)); vpninfo->progress(vpninfo, PRG_DEBUG, "Extra cert from PKCS#12: '%s'\n", buf); SSL_CTX_add_extra_chain_cert(vpninfo->https_ctx, cert2); cert = cert2; goto next; } } sk_X509_free(ca); } PKCS12_free(p12); return ret; } static int load_tpm_certificate(struct openconnect_info *vpninfo) { ENGINE *e; EVP_PKEY *key; ENGINE_load_builtin_engines(); e = ENGINE_by_id("tpm"); if (!e) { vpninfo->progress(vpninfo, PRG_ERR, "Can't load TPM engine.\n"); report_ssl_errors(vpninfo); return -EINVAL; } if (!ENGINE_init(e) || !ENGINE_set_default_RSA(e) || !ENGINE_set_default_RAND(e)) { vpninfo->progress(vpninfo, PRG_ERR, "Failed to init TPM engine\n"); report_ssl_errors(vpninfo); ENGINE_free(e); return -EINVAL; } if (vpninfo->cert_password) { if (!ENGINE_ctrl_cmd(e, "PIN", strlen(vpninfo->cert_password), vpninfo->cert_password, NULL, 0)) { vpninfo->progress(vpninfo, PRG_ERR, "Failed to set TPM SRK password\n"); report_ssl_errors(vpninfo); } } key = ENGINE_load_private_key(e, vpninfo->sslkey, NULL, NULL); if (!key) { vpninfo->progress(vpninfo, PRG_ERR, "Failed to load TPM private key\n"); report_ssl_errors(vpninfo); ENGINE_free(e); ENGINE_finish(e); return -EINVAL; } if (!SSL_CTX_use_PrivateKey(vpninfo->https_ctx, key)) { vpninfo->progress(vpninfo, PRG_ERR, "Add key from TPM failed\n"); report_ssl_errors(vpninfo); ENGINE_free(e); ENGINE_finish(e); return -EINVAL; } return 0; } -- dwmw2 ______________________________________________________________________ OpenSSL Project http://www.openssl.org User Support Mailing List openssl-users@openssl.org Automated List Manager majord...@openssl.org