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

Reply via email to