[Openvpn-devel,v2] Allow PKCS#11 uri to be used as --cert and --key file names

Message ID 20210510013239.18087-1-selva.nair@gmail.com
State Superseded
Headers show
Series [Openvpn-devel,v2] Allow PKCS#11 uri to be used as --cert and --key file names | expand

Commit Message

Selva Nair May 9, 2021, 3:32 p.m. UTC
From: Selva Nair <selva.nair@gmail.com>

v2 changes
  - do not allow so-path embedded in cert and key uri
  - add --pkcs11-engine option to optionally specify the
        engine and provider module to use

If either --cert or --key is specified as a PKCS#11 uri, try to
load the certificate and key from any accessible PKCS#11 device.
This does not require linking with any pkcs11 library, but needs
pkcs11 engine to be available on the target machine.

In its simplest form, just have

--cert 'pkcs11:id=%01'

Either do not specify --key, or use the same uri for --key.
Do not include type=cert or type=private in the uri
as the same uri is used for both certificate and private key.

That's all what is required if pkcs11 engine is installed in the
right location and optionally set up to load any necessary provider
libraries (e.g., via openssl.cnf or via PKCS11_MODULE_PATH).

If both cert and key are specified, the last entry takes precedence
and is used to locate both the certificate and key. Use of different
uri's for the cert and key are not supported. Specifying --cert as
a file and --key as a uri or vice versa is treated as a usage error.

If the engine cannot be automatically loaded, or a custom engine object
has to be loaded, the engine name or shared library may be specified
using the newly added option

   --pkcs11-engine engine [module_path]

Here engine may the the engine-id that OpenSSL is configured to locate,
or the path to a shared object. The optional 'module_path' specifies
any provider module that must be loaded. It must be given as a path.
Use full path or relative path for these shared objects based on the
target system setup.

Requires building with OpenSSL engine support although the pkcs11 or
a compatible engine, and provider libraries are required only at
run time.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
---
 Changes.rst                      |   6 +
 doc/man-sections/tls-options.rst |  31 ++++++
 src/openvpn/options.c            |  68 +++++++++++-
 src/openvpn/options.h            |   7 ++
 src/openvpn/ssl.c                |  15 ++-
 src/openvpn/ssl_backend.h        |  10 ++
 src/openvpn/ssl_openssl.c        | 183 ++++++++++++++++++++++++++++++-
 7 files changed, 316 insertions(+), 4 deletions(-)

Comments

Selva Nair July 27, 2021, 4:56 a.m. UTC | #1
It seems no one is interested in this to elicit a review.. I thought this
would be a nifty feature ;)

On Sun, May 9, 2021 at 9:32 PM <selva.nair@gmail.com> wrote:

> From: Selva Nair <selva.nair@gmail.com>
>
> v2 changes
>   - do not allow so-path embedded in cert and key uri
>   - add --pkcs11-engine option to optionally specify the
>         engine and provider module to use
>
> If either --cert or --key is specified as a PKCS#11 uri, try to
> load the certificate and key from any accessible PKCS#11 device.
> This does not require linking with any pkcs11 library, but needs
> pkcs11 engine to be available on the target machine.
>
> In its simplest form, just have
>
> --cert 'pkcs11:id=%01'
>
> Either do not specify --key, or use the same uri for --key.
> Do not include type=cert or type=private in the uri
> as the same uri is used for both certificate and private key.
>
> That's all what is required if pkcs11 engine is installed in the
> right location and optionally set up to load any necessary provider
> libraries (e.g., via openssl.cnf or via PKCS11_MODULE_PATH).
>
> If both cert and key are specified, the last entry takes precedence
> and is used to locate both the certificate and key. Use of different
> uri's for the cert and key are not supported. Specifying --cert as
> a file and --key as a uri or vice versa is treated as a usage error.
>
> If the engine cannot be automatically loaded, or a custom engine object
> has to be loaded, the engine name or shared library may be specified
> using the newly added option
>
>    --pkcs11-engine engine [module_path]
>
> Here engine may the the engine-id that OpenSSL is configured to locate,
> or the path to a shared object. The optional 'module_path' specifies
> any provider module that must be loaded. It must be given as a path.
> Use full path or relative path for these shared objects based on the
> target system setup.
>
> Requires building with OpenSSL engine support although the pkcs11 or
> a compatible engine, and provider libraries are required only at
> run time.
>
> Signed-off-by: Selva Nair <selva.nair@gmail.com>
> ---
>  Changes.rst                      |   6 +
>  doc/man-sections/tls-options.rst |  31 ++++++
>  src/openvpn/options.c            |  68 +++++++++++-
>  src/openvpn/options.h            |   7 ++
>  src/openvpn/ssl.c                |  15 ++-
>  src/openvpn/ssl_backend.h        |  10 ++
>  src/openvpn/ssl_openssl.c        | 183 ++++++++++++++++++++++++++++++-
>  7 files changed, 316 insertions(+), 4 deletions(-)
>
> diff --git a/Changes.rst b/Changes.rst
> index 9185b55f..19d311e3 100644
> --- a/Changes.rst
> +++ b/Changes.rst
> @@ -4,6 +4,12 @@ Overview of changes in 2.6
>
>  New features
>  ------------
> +Specification of private key and certificates as PKCS#11 URI
> +    ``--cert`` and ``--key`` options can take RFC7512 PKCS#11
> +    URI's pointing to certificate and key in a token. Both cert
> +    and key must use the same URI. Requires OpenSSL with engine
> +    support and pkcs11 (or compatible) engine installed.
> +
>  Keying Material Exporters (RFC 5705) based key generation
>      As part of the cipher negotiation OpenVPN will automatically prefer
>      the RFC5705 based key material generation to the current custom
> diff --git a/doc/man-sections/tls-options.rst
> b/doc/man-sections/tls-options.rst
> index 00ea063a..7acfbdae 100644
> --- a/doc/man-sections/tls-options.rst
> +++ b/doc/man-sections/tls-options.rst
> @@ -116,6 +116,20 @@ certificates and keys:
> https://github.com/OpenVPN/easy-rsa
>    authority functions, you must set up the files :code:`index.txt` (may be
>    empty) and :code:`serial` (initialize to :code:`01`).
>
> +--cert pkcs11-uri
> +  The local peer's certificate in a PKCS#11 token specified as a RFC 7512
> +  uri with optional custom attributes described below. Cannot be used with
> +  ``--key file``. ``--key`` must be left unspecified or point to the same
> +  uri. All other requrements for the certificate described under
> +  ``--cert file`` applies.
> +
> +  Requires OpenSSL with pkcs11 engine installed and configured. Also see
> +  the option ``--pkcs11-engine``.
> +
> +  As the same uri is used for certificate and private key, do not include
> type
> +  attribute (i.e., :code: `type=cert;` or :code: `type=private;` should
> not
> +  be included)
> +
>  --crl-verify args
>    Check peer certificate against a Certificate Revocation List.
>
> @@ -208,11 +222,28 @@ certificates and keys:
> https://github.com/OpenVPN/easy-rsa
>    generated when you built your peer's certificate (see ``--cert file``
>    above).
>
> +--key pkcs11-uri
> +  See ``--cert pkcs11-uri`` above.
> +
>  --pkcs12 file
>    Specify a PKCS #12 file containing local private key, local certificate,
>    and root CA certificate. This option can be used instead of ``--ca``,
>    ``--cert``, and ``--key``.  Not available with mbed TLS.
>
> +--pkcs11-engine engine-name [module-path]
> +  Specifiy the pkcs11-engine and the provider module to load when
> +  certificate and private key are given as a pkcs11 URI.
> +
> +  If the option is unspecified, and a pkcs11 URI is used for cert/key,
> +  :code:`pkcs11` engine is loaded, if it can be automatically found by
> +  OpenSSL.
> +
> +  If specified, the cert/key must be given as a pkcs11 URI.
> +
> +  The engine name could be a valid engine-id or path to a shared object.
> +  The module-path should be the path to a shared object. Objects in
> +  non-standard locations would need to be specified as full paths.
> +
>  --remote-cert-eku oid
>    Require that peer certificate was signed with an explicit *extended key
>    usage*.
> diff --git a/src/openvpn/options.c b/src/openvpn/options.c
> index db460796..559782ec 100644
> --- a/src/openvpn/options.c
> +++ b/src/openvpn/options.c
> @@ -531,6 +531,11 @@ static const char usage_message[] =
>      "                   nonce_secret_len=nsl.  Set alg=none to disable
> PRNG.\n"
>  #ifndef ENABLE_CRYPTO_MBEDTLS
>      "--engine [name] : Enable OpenSSL hardware crypto engine
> functionality.\n"
> +#endif
> +#ifdef HAVE_OPENSSL_ENGINE
> +    "--pkcs11-engine name [module-path] : PKCS11 engine and provider
> module to use\n"
> +    "                                     for cert and key specified as a
> pkcs11 URI.\n"
> +    "                                     name could be an engine-id or a
> path.\n"
>  #endif
>      "--no-replay     : (DEPRECATED) Disable replay protection.\n"
>      "--mute-replay-warnings : Silence the output of replay warnings to
> log file.\n"
> @@ -915,6 +920,12 @@ struct pull_filter_list
>      struct pull_filter *tail;
>  };
>
> +static bool
> +is_pkcs11_uri(const char *uri)
> +{
> +    return (uri && !strncmp(uri, "pkcs11:", 7));
> +}
> +
>  static const char *
>  pull_filter_type_name(int type)
>  {
> @@ -2587,6 +2598,17 @@ options_postprocess_verify_ce(const struct options
> *options,
>
>      if (options->tls_server || options->tls_client)
>      {
> +#ifdef HAVE_OPENSSL_ENGINE
> +        if (is_pkcs11_uri(options->cert_file) !=
> is_pkcs11_uri(options->priv_key_file))
> +        {
> +            msg(M_USAGE, "Use of PKCS#11 uri for --cert or --key and file
> name for the other is not supported");
> +        }
> +        else
> +        if (options->pkcs11_engine && !is_pkcs11_uri(options->cert_file))
> +        {
> +            msg(M_USAGE, "Use of --pkcs11-engine expects --cert to be
> specified as a pkcs11: URI");
> +        }
> +#endif
>  #ifdef ENABLE_PKCS11
>          if (options->pkcs11_providers[0])
>          {
> @@ -3455,8 +3477,11 @@ options_postprocess_filechecks(struct options
> *options)
>      errs |= check_file_access_chroot(options->chroot_dir, CHKACC_FILE,
>                                       options->ca_path, R_OK, "--capath");
>
> -    errs |= check_file_access_inline(options->cert_file_inline,
> CHKACC_FILE,
> +    if (!is_pkcs11_uri(options->cert_file))
> +    {
> +        errs |= check_file_access_inline(options->cert_file_inline,
> CHKACC_FILE,
>                                       options->cert_file, R_OK, "--cert");
> +    }
>
>      errs |= check_file_access_inline(options->extra_certs_file,
> CHKACC_FILE,
>                                       options->extra_certs_file, R_OK,
> @@ -3466,9 +3491,12 @@ options_postprocess_filechecks(struct options
> *options)
>      if (!(options->management_flags & MF_EXTERNAL_KEY))
>  #endif
>      {
> -        errs |= check_file_access_inline(options->priv_key_file_inline,
> +        if (!is_pkcs11_uri(options->priv_key_file))
> +        {
> +            errs |=
> check_file_access_inline(options->priv_key_file_inline,
>                                           CHKACC_FILE|CHKACC_PRIVATE,
>                                           options->priv_key_file, R_OK,
> "--key");
> +        }
>      }
>
>      errs |= check_file_access_inline(options->pkcs12_file_inline,
> @@ -8097,6 +8125,14 @@ add_option(struct options *options,
>          }
>      }
>  #endif /* ENABLE_CRYPTO_MBEDTLS */
> +#ifdef HAVE_OPENSSL_ENGINE
> +    else if (streq(p[0], "pkcs11-engine") && p[1] && !p[3])
> +    {
> +        VERIFY_PERMISSION(OPT_P_GENERAL);
> +        options->pkcs11_engine = p[1];
> +        options->pkcs11_engine_module = p[2]; /* may be NULL */
> +    }
> +#endif /* HAVE_OPENSSL_ENGINE */
>  #ifdef ENABLE_PREDICTION_RESISTANCE
>      else if (streq(p[0], "use-prediction-resistance") && !p[1])
>      {
> @@ -8156,6 +8192,20 @@ add_option(struct options *options,
>          VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INLINE);
>          options->cert_file = p[1];
>          options->cert_file_inline = is_inline;
> +        if (is_pkcs11_uri(p[1]))
> +        {
> +#ifndef HAVE_OPENSSL_ENGINE
> +            msg(msglevel, "Use of PKCS11 uri as cert and key file names
> requires OpenSSL "
> +                          "ENGINE support which is missing in this
> binary.")
> +#else
> +            options->priv_key_file = p[1];
> +            options->cert_file_is_pkcs11_uri = true;
> +        }
> +        else
> +        {
> +            options->cert_file_is_pkcs11_uri = false;
> +#endif /* HAVE_OPENSSL_ENGINE */
> +        }
>      }
>      else if (streq(p[0], "extra-certs") && p[1] && !p[2])
>      {
> @@ -8238,6 +8288,20 @@ add_option(struct options *options,
>          VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INLINE);
>          options->priv_key_file = p[1];
>          options->priv_key_file_inline = is_inline;
> +        if (is_pkcs11_uri(p[1]))
> +        {
> +#ifndef HAVE_OPENSSL_ENGINE
> +            msg(msglevel, "Use of PKCS11 uri as cert and key file names
> requires OpenSSL "
> +                          "ENGINE support which is missing in this
> binary.")
> +#else
> +            options->cert_file = p[1];
> +            options->cert_file_is_pkcs11_uri = true;
> +        }
> +        else
> +        {
> +            options->cert_file_is_pkcs11_uri = false;
> +#endif /* HAVE_OPENSSL_ENGINE */
> +        }
>      }
>      else if (streq(p[0], "tls-version-min") && p[1] && !p[3])
>      {
> diff --git a/src/openvpn/options.h b/src/openvpn/options.h
> index 41e84f7e..15567980 100644
> --- a/src/openvpn/options.h
> +++ b/src/openvpn/options.h
> @@ -659,6 +659,13 @@ struct options
>
>      /* data channel crypto flags set by push/pull. Reuses the CO_*
> crypto_flags */
>      unsigned int data_channel_crypto_flags;
> +
> +#ifdef HAVE_OPENSSL_ENGINE
> +    const char *pkcs11_engine;
> +    const char *pkcs11_engine_module;
> +    /* flag to indicate cert and key files are specified as pkcs11 uri */
> +    bool cert_file_is_pkcs11_uri;
> +#endif
>  };
>
>  #define streq(x, y) (!strcmp((x), (y)))
> diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c
> index b16f6bcc..dedfe8ad 100644
> --- a/src/openvpn/ssl.c
> +++ b/src/openvpn/ssl.c
> @@ -641,6 +641,19 @@ init_ssl(const struct options *options, struct
> tls_root_ctx *new_ctx, bool in_ch
>              goto err;
>          }
>      }
> +#ifdef HAVE_OPENSSL_ENGINE
> +    else if (options->cert_file_is_pkcs11_uri)
> +    {
> +        if (!tls_ctx_use_pkcs11_engine(new_ctx, options->cert_file,
> +                                       options->pkcs11_engine,
> +                                       options->pkcs11_engine_module))
> +        {
> +            msg(M_WARN, "Cannot load certificate \"%s\" using PKCS#11
> engine",
> +                options->cert_file);
> +            goto err;
> +        }
> +    }
> +#endif
>  #ifdef ENABLE_PKCS11
>      else if (options->pkcs11_providers[0])
>      {
> @@ -672,7 +685,7 @@ init_ssl(const struct options *options, struct
> tls_root_ctx *new_ctx, bool in_ch
>          tls_ctx_load_cert_file(new_ctx, options->cert_file,
> options->cert_file_inline);
>      }
>
> -    if (options->priv_key_file)
> +    if (options->priv_key_file && !options->cert_file_is_pkcs11_uri)
>      {
>          if (0 != tls_ctx_load_priv_file(new_ctx, options->priv_key_file,
>                                          options->priv_key_file_inline))
> diff --git a/src/openvpn/ssl_backend.h b/src/openvpn/ssl_backend.h
> index c3d12e5b..556fc2c8 100644
> --- a/src/openvpn/ssl_backend.h
> +++ b/src/openvpn/ssl_backend.h
> @@ -568,4 +568,14 @@ void get_highest_preference_tls_cipher(char *buf, int
> size);
>   */
>  const char *get_ssl_library_version(void);
>
> +/**
> + * Load certificate and key into TLS context using pkcs11 engine
> + * @param ctx       TLS context
> + * @param cert_id   ceritificate and proivate key spec as pkcs11 URI
> + * @param engine    id or path of OpenSSL pkcs11 engine object (default:
> pkcs11)
> + * @param module    path of optional provider module to load with the
> engine
> + */
> +int tls_ctx_use_pkcs11_engine(struct tls_root_ctx *tls_ctx, const char
> *cert_id,
> +                          const char *engine, const char *module);
> +
>  #endif /* SSL_BACKEND_H_ */
> diff --git a/src/openvpn/ssl_openssl.c b/src/openvpn/ssl_openssl.c
> index 3120c51a..58fbde93 100644
> --- a/src/openvpn/ssl_openssl.c
> +++ b/src/openvpn/ssl_openssl.c
> @@ -2251,4 +2251,185 @@ get_ssl_library_version(void)
>      return OpenSSL_version(OPENSSL_VERSION);
>  }
>
> -#endif /* defined(ENABLE_CRYPTO_OPENSSL) */
> +#if HAVE_OPENSSL_ENGINE
> +#include <openssl/ui.h>
> +#include <openssl/engine.h>
> +
> +/* Call back method for user interface with pkcs11 engine
> + * used for PIN prompt and possibly token insertion request.
> + */
> +static int
> +ui_reader(UI *ui, UI_STRING *uis)
> +{
> +    struct user_pass token_pass;
> +    int ret = 0;
> +
> +    const char *uri = UI_get0_user_data(ui);
> +    const char *prompt = UI_get0_output_string(uis);
> +
> +    token_pass.defined = false;
> +    token_pass.nocache = true;
> +
> +    switch(UI_get_string_type(uis))
> +    {
> +        case UIT_PROMPT:
> +        case UIT_VERIFY:
> +            if (get_user_pass(&token_pass, NULL, prompt,
> +                GET_USER_PASS_MANAGEMENT|GET_USER_PASS_PASSWORD_ONLY
> +                |GET_USER_PASS_NOFATAL))
> +            {
> +                ret = 1;
> +                UI_set_result(ui, uis, token_pass.password);
> +            }
> +            break;
> +       case UIT_BOOLEAN:
> +            if (get_user_pass(&token_pass, NULL,
> UI_get0_output_string(uis),
> +                GET_USER_PASS_MANAGEMENT|GET_USER_PASS_NEED_OK
> +                |GET_USER_PASS_NOFATAL))
> +            {
> +                ret = (strcmp(token_pass.password, "ok") == 0);
> +                UI_set_result(ui, uis, token_pass.password);
> +            }
> +       case UIT_INFO:
> +            msg(M_INFO, "INFO prompt from token: <%s>", prompt);
> +            break;
> +       case UIT_ERROR:
> +            msg(M_INFO, "ERROR prompt from token: <%s>", prompt);
> +            break;
> +       default:
> +            break;
> +    }
> +
> +    return ret;
> +}
> +
> +static char *
> +ui_prompt_constructor(UI *ui, const char *desc, const char *name)
> +{
> +    int len =  strlen(desc) + strlen(name) + 6;
> +    char *s = malloc(len);
> +    openvpn_snprintf(s, len, "%s for %s", desc, name);
> +    return s;
> +}
> +
> +static ENGINE *
> +load_pkcs11_engine(const char *engine_id)
> +{
> +    ENGINE *e = ENGINE_by_id(engine_id);
> +
> +    if (e) {
> +        return e;
> +    }
> +
> +    /* try dynamic engine with engine-id as path to the engine shared
> object */
> +    e = ENGINE_by_id("dynamic");
> +    if (e)
> +    {
> +        if (!ENGINE_ctrl_cmd_string(e, "SO_PATH", engine_id, 0)
> +            || !ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0))
> +        {
> +            ENGINE_free(e);
> +            e = NULL;
> +        }
> +    }
> +    if (!e)
> +    {
> +        msg(M_WARN, "PKCS11 engine <%s> not available", engine_id);
> +    }
> +    return e;
> +}
> +
> +static ENGINE *
> +setup_pkcs11_engine(const char *engine_id, const char *module_path,
> UI_METHOD *ui)
> +{
> +    if (!engine_id)
> +    {
> +        engine_id = "pkcs11";
> +    }
> +
> +    msg(D_SHOW_PKCS11, "Loading pkcs11 engine <%s> with module <%s>",
> +        engine_id, (module_path ? module_path : "unspecified"));
> +
> +    ENGINE *e = load_pkcs11_engine(engine_id);
> +
> +    if (e)
> +    {
> +       if (module_path)
> +        {
> +            ENGINE_ctrl_cmd_string(e, "MODULE_PATH", module_path, 0);
> +        }
> +        ENGINE_ctrl_cmd(e, "SET_USER_INTERFACE", 0, ui, NULL, 0);
> +    }
> +
> +    return e;
> +}
> +
> +/**
> + * Load certificate and key into TLS context using pkcs11 engine
> + * @param ctx       TLS context
> + * @param cert_id   ceritificate and proivate key spec as pkcs11 URI
> + * @param engine    id or path of OpenSSL pkcs11 engine object (default:
> pkcs11)
> + * @param module    path of optional provider module to load with the
> engine
> + */
> +int
> +tls_ctx_use_pkcs11_engine(struct tls_root_ctx *tls_ctx, const char
> *cert_id,
> +                          const char *engine, const char *module)
> +{
> +    int ret = 0;
> +    EVP_PKEY *pkey = NULL;
> +
> +    UI_METHOD *ui = UI_create_method("openvpn");
> +    if (!ui)
> +    {
> +        msg(M_WARN, "Failed to setup UI callback for engine");
> +        return ret;
> +    }
> +    UI_method_set_reader(ui, ui_reader);
> +    UI_method_set_prompt_constructor(ui, ui_prompt_constructor);
> +
> +    struct
> +    {
> +        const char *cert_id;
> +        X509* cert;
> +    } params = {cert_id, NULL};
> +
> +    ENGINE *e = setup_pkcs11_engine(engine, module, ui);
> +    if (!e || !ENGINE_init(e))
> +    {
> +        goto cleanup;
> +    }
> +    ENGINE_ctrl_cmd(e, "SET_CALLBACK_DATA", 0, (void *)cert_id, NULL, 0);
> +
> +    msg (D_SHOW_PKCS11, "Loading certificate <%s> using engine",
> params.cert_id);
> +
> +    if (!ENGINE_ctrl_cmd(e, "LOAD_CERT_CTRL", 0, &params, NULL, 0)
> +        || !params.cert || !SSL_CTX_use_certificate(tls_ctx->ctx,
> params.cert))
> +    {
> +        msg (M_WARN, "Failed to load certificate <%s>", cert_id);
> +        goto finish;
> +    }
> +
> +    msg (D_SHOW_PKCS11, "Loading private key <%s> using engine",
> params.cert_id);
> +
> +    pkey = ENGINE_load_private_key(e, cert_id, ui, (void *)cert_id);
> +    if (!pkey || !SSL_CTX_use_PrivateKey(tls_ctx->ctx, pkey))
> +    {
> +        msg (M_WARN, "Failed to set private key <%s> using engine",
> cert_id);
> +        goto finish;
> +    }
> +    ret = 1;
> +
> +finish:
> +    ENGINE_finish(e);
> +
> +cleanup:
> +    ENGINE_free(e);
> +    X509_free(params.cert);
> +    UI_destroy_method(ui);
> +
> +    return ret;
> +}
> +
> +#endif /* HAVE_OPENSSL_ENGINE */
> +
> +#endif /* ENABLE_CRYPTO_OPENSSL */
> --
> 2.20.1
>
>
<div dir="ltr"><div><br></div>It seems no one is interested in this to elicit a review.. I thought this would be a nifty feature ;)</div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Sun, May 9, 2021 at 9:32 PM &lt;<a href="mailto:selva.nair@gmail.com">selva.nair@gmail.com</a>&gt; wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">From: Selva Nair &lt;<a href="mailto:selva.nair@gmail.com" target="_blank">selva.nair@gmail.com</a>&gt;<br>
<br>
v2 changes<br>
  - do not allow so-path embedded in cert and key uri<br>
  - add --pkcs11-engine option to optionally specify the<br>
        engine and provider module to use<br>
<br>
If either --cert or --key is specified as a PKCS#11 uri, try to<br>
load the certificate and key from any accessible PKCS#11 device.<br>
This does not require linking with any pkcs11 library, but needs<br>
pkcs11 engine to be available on the target machine.<br>
<br>
In its simplest form, just have<br>
<br>
--cert &#39;pkcs11:id=%01&#39;<br>
<br>
Either do not specify --key, or use the same uri for --key.<br>
Do not include type=cert or type=private in the uri<br>
as the same uri is used for both certificate and private key.<br>
<br>
That&#39;s all what is required if pkcs11 engine is installed in the<br>
right location and optionally set up to load any necessary provider<br>
libraries (e.g., via openssl.cnf or via PKCS11_MODULE_PATH).<br>
<br>
If both cert and key are specified, the last entry takes precedence<br>
and is used to locate both the certificate and key. Use of different<br>
uri&#39;s for the cert and key are not supported. Specifying --cert as<br>
a file and --key as a uri or vice versa is treated as a usage error.<br>
<br>
If the engine cannot be automatically loaded, or a custom engine object<br>
has to be loaded, the engine name or shared library may be specified<br>
using the newly added option<br>
<br>
   --pkcs11-engine engine [module_path]<br>
<br>
Here engine may the the engine-id that OpenSSL is configured to locate,<br>
or the path to a shared object. The optional &#39;module_path&#39; specifies<br>
any provider module that must be loaded. It must be given as a path.<br>
Use full path or relative path for these shared objects based on the<br>
target system setup.<br>
<br>
Requires building with OpenSSL engine support although the pkcs11 or<br>
a compatible engine, and provider libraries are required only at<br>
run time.<br>
<br>
Signed-off-by: Selva Nair &lt;<a href="mailto:selva.nair@gmail.com" target="_blank">selva.nair@gmail.com</a>&gt;<br>
---<br>
 Changes.rst                      |   6 +<br>
 doc/man-sections/tls-options.rst |  31 ++++++<br>
 src/openvpn/options.c            |  68 +++++++++++-<br>
 src/openvpn/options.h            |   7 ++<br>
 src/openvpn/ssl.c                |  15 ++-<br>
 src/openvpn/ssl_backend.h        |  10 ++<br>
 src/openvpn/ssl_openssl.c        | 183 ++++++++++++++++++++++++++++++-<br>
 7 files changed, 316 insertions(+), 4 deletions(-)<br>
<br>
diff --git a/Changes.rst b/Changes.rst<br>
index 9185b55f..19d311e3 100644<br>
--- a/Changes.rst<br>
+++ b/Changes.rst<br>
@@ -4,6 +4,12 @@ Overview of changes in 2.6<br>
<br>
 New features<br>
 ------------<br>
+Specification of private key and certificates as PKCS#11 URI<br>
+    ``--cert`` and ``--key`` options can take RFC7512 PKCS#11<br>
+    URI&#39;s pointing to certificate and key in a token. Both cert<br>
+    and key must use the same URI. Requires OpenSSL with engine<br>
+    support and pkcs11 (or compatible) engine installed.<br>
+<br>
 Keying Material Exporters (RFC 5705) based key generation<br>
     As part of the cipher negotiation OpenVPN will automatically prefer<br>
     the RFC5705 based key material generation to the current custom<br>
diff --git a/doc/man-sections/tls-options.rst b/doc/man-sections/tls-options.rst<br>
index 00ea063a..7acfbdae 100644<br>
--- a/doc/man-sections/tls-options.rst<br>
+++ b/doc/man-sections/tls-options.rst<br>
@@ -116,6 +116,20 @@ certificates and keys: <a href="https://github.com/OpenVPN/easy-rsa" rel="noreferrer" target="_blank">https://github.com/OpenVPN/easy-rsa</a><br>
   authority functions, you must set up the files :code:`index.txt` (may be<br>
   empty) and :code:`serial` (initialize to :code:`01`).<br>
<br>
+--cert pkcs11-uri<br>
+  The local peer&#39;s certificate in a PKCS#11 token specified as a RFC 7512<br>
+  uri with optional custom attributes described below. Cannot be used with<br>
+  ``--key file``. ``--key`` must be left unspecified or point to the same<br>
+  uri. All other requrements for the certificate described under<br>
+  ``--cert file`` applies.<br>
+<br>
+  Requires OpenSSL with pkcs11 engine installed and configured. Also see<br>
+  the option ``--pkcs11-engine``.<br>
+<br>
+  As the same uri is used for certificate and private key, do not include type<br>
+  attribute (i.e., :code: `type=cert;` or :code: `type=private;` should not<br>
+  be included)<br>
+<br>
 --crl-verify args<br>
   Check peer certificate against a Certificate Revocation List.<br>
<br>
@@ -208,11 +222,28 @@ certificates and keys: <a href="https://github.com/OpenVPN/easy-rsa" rel="noreferrer" target="_blank">https://github.com/OpenVPN/easy-rsa</a><br>
   generated when you built your peer&#39;s certificate (see ``--cert file``<br>
   above).<br>
<br>
+--key pkcs11-uri<br>
+  See ``--cert pkcs11-uri`` above.<br>
+<br>
 --pkcs12 file<br>
   Specify a PKCS #12 file containing local private key, local certificate,<br>
   and root CA certificate. This option can be used instead of ``--ca``,<br>
   ``--cert``, and ``--key``.  Not available with mbed TLS.<br>
<br>
+--pkcs11-engine engine-name [module-path]<br>
+  Specifiy the pkcs11-engine and the provider module to load when<br>
+  certificate and private key are given as a pkcs11 URI.<br>
+<br>
+  If the option is unspecified, and a pkcs11 URI is used for cert/key,<br>
+  :code:`pkcs11` engine is loaded, if it can be automatically found by<br>
+  OpenSSL.<br>
+<br>
+  If specified, the cert/key must be given as a pkcs11 URI.<br>
+<br>
+  The engine name could be a valid engine-id or path to a shared object.<br>
+  The module-path should be the path to a shared object. Objects in<br>
+  non-standard locations would need to be specified as full paths.<br>
+<br>
 --remote-cert-eku oid<br>
   Require that peer certificate was signed with an explicit *extended key<br>
   usage*.<br>
diff --git a/src/openvpn/options.c b/src/openvpn/options.c<br>
index db460796..559782ec 100644<br>
--- a/src/openvpn/options.c<br>
+++ b/src/openvpn/options.c<br>
@@ -531,6 +531,11 @@ static const char usage_message[] =<br>
     &quot;                   nonce_secret_len=nsl.  Set alg=none to disable PRNG.\n&quot;<br>
 #ifndef ENABLE_CRYPTO_MBEDTLS<br>
     &quot;--engine [name] : Enable OpenSSL hardware crypto engine functionality.\n&quot;<br>
+#endif<br>
+#ifdef HAVE_OPENSSL_ENGINE<br>
+    &quot;--pkcs11-engine name [module-path] : PKCS11 engine and provider module to use\n&quot;<br>
+    &quot;                                     for cert and key specified as a pkcs11 URI.\n&quot;<br>
+    &quot;                                     name could be an engine-id or a path.\n&quot;<br>
 #endif<br>
     &quot;--no-replay     : (DEPRECATED) Disable replay protection.\n&quot;<br>
     &quot;--mute-replay-warnings : Silence the output of replay warnings to log file.\n&quot;<br>
@@ -915,6 +920,12 @@ struct pull_filter_list<br>
     struct pull_filter *tail;<br>
 };<br>
<br>
+static bool<br>
+is_pkcs11_uri(const char *uri)<br>
+{<br>
+    return (uri &amp;&amp; !strncmp(uri, &quot;pkcs11:&quot;, 7));<br>
+}<br>
+<br>
 static const char *<br>
 pull_filter_type_name(int type)<br>
 {<br>
@@ -2587,6 +2598,17 @@ options_postprocess_verify_ce(const struct options *options,<br>
<br>
     if (options-&gt;tls_server || options-&gt;tls_client)<br>
     {<br>
+#ifdef HAVE_OPENSSL_ENGINE<br>
+        if (is_pkcs11_uri(options-&gt;cert_file) != is_pkcs11_uri(options-&gt;priv_key_file))<br>
+        {<br>
+            msg(M_USAGE, &quot;Use of PKCS#11 uri for --cert or --key and file name for the other is not supported&quot;);<br>
+        }<br>
+        else<br>
+        if (options-&gt;pkcs11_engine &amp;&amp; !is_pkcs11_uri(options-&gt;cert_file))<br>
+        {<br>
+            msg(M_USAGE, &quot;Use of --pkcs11-engine expects --cert to be specified as a pkcs11: URI&quot;);<br>
+        }<br>
+#endif<br>
 #ifdef ENABLE_PKCS11<br>
         if (options-&gt;pkcs11_providers[0])<br>
         {<br>
@@ -3455,8 +3477,11 @@ options_postprocess_filechecks(struct options *options)<br>
     errs |= check_file_access_chroot(options-&gt;chroot_dir, CHKACC_FILE,<br>
                                      options-&gt;ca_path, R_OK, &quot;--capath&quot;);<br>
<br>
-    errs |= check_file_access_inline(options-&gt;cert_file_inline, CHKACC_FILE,<br>
+    if (!is_pkcs11_uri(options-&gt;cert_file))<br>
+    {<br>
+        errs |= check_file_access_inline(options-&gt;cert_file_inline, CHKACC_FILE,<br>
                                      options-&gt;cert_file, R_OK, &quot;--cert&quot;);<br>
+    }<br>
<br>
     errs |= check_file_access_inline(options-&gt;extra_certs_file, CHKACC_FILE,<br>
                                      options-&gt;extra_certs_file, R_OK,<br>
@@ -3466,9 +3491,12 @@ options_postprocess_filechecks(struct options *options)<br>
     if (!(options-&gt;management_flags &amp; MF_EXTERNAL_KEY))<br>
 #endif<br>
     {<br>
-        errs |= check_file_access_inline(options-&gt;priv_key_file_inline,<br>
+        if (!is_pkcs11_uri(options-&gt;priv_key_file))<br>
+        {<br>
+            errs |= check_file_access_inline(options-&gt;priv_key_file_inline,<br>
                                          CHKACC_FILE|CHKACC_PRIVATE,<br>
                                          options-&gt;priv_key_file, R_OK, &quot;--key&quot;);<br>
+        }<br>
     }<br>
<br>
     errs |= check_file_access_inline(options-&gt;pkcs12_file_inline,<br>
@@ -8097,6 +8125,14 @@ add_option(struct options *options,<br>
         }<br>
     }<br>
 #endif /* ENABLE_CRYPTO_MBEDTLS */<br>
+#ifdef HAVE_OPENSSL_ENGINE<br>
+    else if (streq(p[0], &quot;pkcs11-engine&quot;) &amp;&amp; p[1] &amp;&amp; !p[3])<br>
+    {<br>
+        VERIFY_PERMISSION(OPT_P_GENERAL);<br>
+        options-&gt;pkcs11_engine = p[1];<br>
+        options-&gt;pkcs11_engine_module = p[2]; /* may be NULL */<br>
+    }<br>
+#endif /* HAVE_OPENSSL_ENGINE */<br>
 #ifdef ENABLE_PREDICTION_RESISTANCE<br>
     else if (streq(p[0], &quot;use-prediction-resistance&quot;) &amp;&amp; !p[1])<br>
     {<br>
@@ -8156,6 +8192,20 @@ add_option(struct options *options,<br>
         VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INLINE);<br>
         options-&gt;cert_file = p[1];<br>
         options-&gt;cert_file_inline = is_inline;<br>
+        if (is_pkcs11_uri(p[1]))<br>
+        {<br>
+#ifndef HAVE_OPENSSL_ENGINE<br>
+            msg(msglevel, &quot;Use of PKCS11 uri as cert and key file names requires OpenSSL &quot;<br>
+                          &quot;ENGINE support which is missing in this binary.&quot;)<br>
+#else<br>
+            options-&gt;priv_key_file = p[1];<br>
+            options-&gt;cert_file_is_pkcs11_uri = true;<br>
+        }<br>
+        else<br>
+        {<br>
+            options-&gt;cert_file_is_pkcs11_uri = false;<br>
+#endif /* HAVE_OPENSSL_ENGINE */<br>
+        }<br>
     }<br>
     else if (streq(p[0], &quot;extra-certs&quot;) &amp;&amp; p[1] &amp;&amp; !p[2])<br>
     {<br>
@@ -8238,6 +8288,20 @@ add_option(struct options *options,<br>
         VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INLINE);<br>
         options-&gt;priv_key_file = p[1];<br>
         options-&gt;priv_key_file_inline = is_inline;<br>
+        if (is_pkcs11_uri(p[1]))<br>
+        {<br>
+#ifndef HAVE_OPENSSL_ENGINE<br>
+            msg(msglevel, &quot;Use of PKCS11 uri as cert and key file names requires OpenSSL &quot;<br>
+                          &quot;ENGINE support which is missing in this binary.&quot;)<br>
+#else<br>
+            options-&gt;cert_file = p[1];<br>
+            options-&gt;cert_file_is_pkcs11_uri = true;<br>
+        }<br>
+        else<br>
+        {<br>
+            options-&gt;cert_file_is_pkcs11_uri = false;<br>
+#endif /* HAVE_OPENSSL_ENGINE */<br>
+        }<br>
     }<br>
     else if (streq(p[0], &quot;tls-version-min&quot;) &amp;&amp; p[1] &amp;&amp; !p[3])<br>
     {<br>
diff --git a/src/openvpn/options.h b/src/openvpn/options.h<br>
index 41e84f7e..15567980 100644<br>
--- a/src/openvpn/options.h<br>
+++ b/src/openvpn/options.h<br>
@@ -659,6 +659,13 @@ struct options<br>
<br>
     /* data channel crypto flags set by push/pull. Reuses the CO_* crypto_flags */<br>
     unsigned int data_channel_crypto_flags;<br>
+<br>
+#ifdef HAVE_OPENSSL_ENGINE<br>
+    const char *pkcs11_engine;<br>
+    const char *pkcs11_engine_module;<br>
+    /* flag to indicate cert and key files are specified as pkcs11 uri */<br>
+    bool cert_file_is_pkcs11_uri;<br>
+#endif<br>
 };<br>
<br>
 #define streq(x, y) (!strcmp((x), (y)))<br>
diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c<br>
index b16f6bcc..dedfe8ad 100644<br>
--- a/src/openvpn/ssl.c<br>
+++ b/src/openvpn/ssl.c<br>
@@ -641,6 +641,19 @@ init_ssl(const struct options *options, struct tls_root_ctx *new_ctx, bool in_ch<br>
             goto err;<br>
         }<br>
     }<br>
+#ifdef HAVE_OPENSSL_ENGINE<br>
+    else if (options-&gt;cert_file_is_pkcs11_uri)<br>
+    {<br>
+        if (!tls_ctx_use_pkcs11_engine(new_ctx, options-&gt;cert_file,<br>
+                                       options-&gt;pkcs11_engine,<br>
+                                       options-&gt;pkcs11_engine_module))<br>
+        {<br>
+            msg(M_WARN, &quot;Cannot load certificate \&quot;%s\&quot; using PKCS#11 engine&quot;,<br>
+                options-&gt;cert_file);<br>
+            goto err;<br>
+        }<br>
+    }<br>
+#endif<br>
 #ifdef ENABLE_PKCS11<br>
     else if (options-&gt;pkcs11_providers[0])<br>
     {<br>
@@ -672,7 +685,7 @@ init_ssl(const struct options *options, struct tls_root_ctx *new_ctx, bool in_ch<br>
         tls_ctx_load_cert_file(new_ctx, options-&gt;cert_file, options-&gt;cert_file_inline);<br>
     }<br>
<br>
-    if (options-&gt;priv_key_file)<br>
+    if (options-&gt;priv_key_file &amp;&amp; !options-&gt;cert_file_is_pkcs11_uri)<br>
     {<br>
         if (0 != tls_ctx_load_priv_file(new_ctx, options-&gt;priv_key_file,<br>
                                         options-&gt;priv_key_file_inline))<br>
diff --git a/src/openvpn/ssl_backend.h b/src/openvpn/ssl_backend.h<br>
index c3d12e5b..556fc2c8 100644<br>
--- a/src/openvpn/ssl_backend.h<br>
+++ b/src/openvpn/ssl_backend.h<br>
@@ -568,4 +568,14 @@ void get_highest_preference_tls_cipher(char *buf, int size);<br>
  */<br>
 const char *get_ssl_library_version(void);<br>
<br>
+/**<br>
+ * Load certificate and key into TLS context using pkcs11 engine<br>
+ * @param ctx       TLS context<br>
+ * @param cert_id   ceritificate and proivate key spec as pkcs11 URI<br>
+ * @param engine    id or path of OpenSSL pkcs11 engine object (default: pkcs11)<br>
+ * @param module    path of optional provider module to load with the engine<br>
+ */<br>
+int tls_ctx_use_pkcs11_engine(struct tls_root_ctx *tls_ctx, const char *cert_id,<br>
+                          const char *engine, const char *module);<br>
+<br>
 #endif /* SSL_BACKEND_H_ */<br>
diff --git a/src/openvpn/ssl_openssl.c b/src/openvpn/ssl_openssl.c<br>
index 3120c51a..58fbde93 100644<br>
--- a/src/openvpn/ssl_openssl.c<br>
+++ b/src/openvpn/ssl_openssl.c<br>
@@ -2251,4 +2251,185 @@ get_ssl_library_version(void)<br>
     return OpenSSL_version(OPENSSL_VERSION);<br>
 }<br>
<br>
-#endif /* defined(ENABLE_CRYPTO_OPENSSL) */<br>
+#if HAVE_OPENSSL_ENGINE<br>
+#include &lt;openssl/ui.h&gt;<br>
+#include &lt;openssl/engine.h&gt;<br>
+<br>
+/* Call back method for user interface with pkcs11 engine<br>
+ * used for PIN prompt and possibly token insertion request.<br>
+ */<br>
+static int<br>
+ui_reader(UI *ui, UI_STRING *uis)<br>
+{<br>
+    struct user_pass token_pass;<br>
+    int ret = 0;<br>
+<br>
+    const char *uri = UI_get0_user_data(ui);<br>
+    const char *prompt = UI_get0_output_string(uis);<br>
+<br>
+    token_pass.defined = false;<br>
+    token_pass.nocache = true;<br>
+<br>
+    switch(UI_get_string_type(uis))<br>
+    {<br>
+        case UIT_PROMPT:<br>
+        case UIT_VERIFY:<br>
+            if (get_user_pass(&amp;token_pass, NULL, prompt,<br>
+                GET_USER_PASS_MANAGEMENT|GET_USER_PASS_PASSWORD_ONLY<br>
+                |GET_USER_PASS_NOFATAL))<br>
+            {<br>
+                ret = 1;<br>
+                UI_set_result(ui, uis, token_pass.password);<br>
+            }<br>
+            break;<br>
+       case UIT_BOOLEAN:<br>
+            if (get_user_pass(&amp;token_pass, NULL, UI_get0_output_string(uis),<br>
+                GET_USER_PASS_MANAGEMENT|GET_USER_PASS_NEED_OK<br>
+                |GET_USER_PASS_NOFATAL))<br>
+            {<br>
+                ret = (strcmp(token_pass.password, &quot;ok&quot;) == 0);<br>
+                UI_set_result(ui, uis, token_pass.password);<br>
+            }<br>
+       case UIT_INFO:<br>
+            msg(M_INFO, &quot;INFO prompt from token: &lt;%s&gt;&quot;, prompt);<br>
+            break;<br>
+       case UIT_ERROR:<br>
+            msg(M_INFO, &quot;ERROR prompt from token: &lt;%s&gt;&quot;, prompt);<br>
+            break;<br>
+       default:<br>
+            break;<br>
+    }<br>
+<br>
+    return ret;<br>
+}<br>
+<br>
+static char *<br>
+ui_prompt_constructor(UI *ui, const char *desc, const char *name)<br>
+{<br>
+    int len =  strlen(desc) + strlen(name) + 6;<br>
+    char *s = malloc(len);<br>
+    openvpn_snprintf(s, len, &quot;%s for %s&quot;, desc, name);<br>
+    return s;<br>
+}<br>
+<br>
+static ENGINE *<br>
+load_pkcs11_engine(const char *engine_id)<br>
+{<br>
+    ENGINE *e = ENGINE_by_id(engine_id);<br>
+<br>
+    if (e) {<br>
+        return e;<br>
+    }<br>
+<br>
+    /* try dynamic engine with engine-id as path to the engine shared object */<br>
+    e = ENGINE_by_id(&quot;dynamic&quot;);<br>
+    if (e)<br>
+    {<br>
+        if (!ENGINE_ctrl_cmd_string(e, &quot;SO_PATH&quot;, engine_id, 0)<br>
+            || !ENGINE_ctrl_cmd_string(e, &quot;LOAD&quot;, NULL, 0))<br>
+        {<br>
+            ENGINE_free(e);<br>
+            e = NULL;<br>
+        }<br>
+    }<br>
+    if (!e)<br>
+    {<br>
+        msg(M_WARN, &quot;PKCS11 engine &lt;%s&gt; not available&quot;, engine_id);<br>
+    }<br>
+    return e;<br>
+}<br>
+<br>
+static ENGINE *<br>
+setup_pkcs11_engine(const char *engine_id, const char *module_path, UI_METHOD *ui)<br>
+{<br>
+    if (!engine_id)<br>
+    {<br>
+        engine_id = &quot;pkcs11&quot;;<br>
+    }<br>
+<br>
+    msg(D_SHOW_PKCS11, &quot;Loading pkcs11 engine &lt;%s&gt; with module &lt;%s&gt;&quot;,<br>
+        engine_id, (module_path ? module_path : &quot;unspecified&quot;));<br>
+<br>
+    ENGINE *e = load_pkcs11_engine(engine_id);<br>
+<br>
+    if (e)<br>
+    {<br>
+       if (module_path)<br>
+        {<br>
+            ENGINE_ctrl_cmd_string(e, &quot;MODULE_PATH&quot;, module_path, 0);<br>
+        }<br>
+        ENGINE_ctrl_cmd(e, &quot;SET_USER_INTERFACE&quot;, 0, ui, NULL, 0);<br>
+    }<br>
+<br>
+    return e;<br>
+}<br>
+<br>
+/**<br>
+ * Load certificate and key into TLS context using pkcs11 engine<br>
+ * @param ctx       TLS context<br>
+ * @param cert_id   ceritificate and proivate key spec as pkcs11 URI<br>
+ * @param engine    id or path of OpenSSL pkcs11 engine object (default: pkcs11)<br>
+ * @param module    path of optional provider module to load with the engine<br>
+ */<br>
+int<br>
+tls_ctx_use_pkcs11_engine(struct tls_root_ctx *tls_ctx, const char *cert_id,<br>
+                          const char *engine, const char *module)<br>
+{<br>
+    int ret = 0;<br>
+    EVP_PKEY *pkey = NULL;<br>
+<br>
+    UI_METHOD *ui = UI_create_method(&quot;openvpn&quot;);<br>
+    if (!ui)<br>
+    {<br>
+        msg(M_WARN, &quot;Failed to setup UI callback for engine&quot;);<br>
+        return ret;<br>
+    }<br>
+    UI_method_set_reader(ui, ui_reader);<br>
+    UI_method_set_prompt_constructor(ui, ui_prompt_constructor);<br>
+<br>
+    struct<br>
+    {<br>
+        const char *cert_id;<br>
+        X509* cert;<br>
+    } params = {cert_id, NULL};<br>
+<br>
+    ENGINE *e = setup_pkcs11_engine(engine, module, ui);<br>
+    if (!e || !ENGINE_init(e))<br>
+    {<br>
+        goto cleanup;<br>
+    }<br>
+    ENGINE_ctrl_cmd(e, &quot;SET_CALLBACK_DATA&quot;, 0, (void *)cert_id, NULL, 0);<br>
+<br>
+    msg (D_SHOW_PKCS11, &quot;Loading certificate &lt;%s&gt; using engine&quot;, params.cert_id);<br>
+<br>
+    if (!ENGINE_ctrl_cmd(e, &quot;LOAD_CERT_CTRL&quot;, 0, &amp;params, NULL, 0)<br>
+        || !params.cert || !SSL_CTX_use_certificate(tls_ctx-&gt;ctx, params.cert))<br>
+    {<br>
+        msg (M_WARN, &quot;Failed to load certificate &lt;%s&gt;&quot;, cert_id);<br>
+        goto finish;<br>
+    }<br>
+<br>
+    msg (D_SHOW_PKCS11, &quot;Loading private key &lt;%s&gt; using engine&quot;, params.cert_id);<br>
+<br>
+    pkey = ENGINE_load_private_key(e, cert_id, ui, (void *)cert_id);<br>
+    if (!pkey || !SSL_CTX_use_PrivateKey(tls_ctx-&gt;ctx, pkey))<br>
+    {<br>
+        msg (M_WARN, &quot;Failed to set private key &lt;%s&gt; using engine&quot;, cert_id);<br>
+        goto finish;<br>
+    }<br>
+    ret = 1;<br>
+<br>
+finish:<br>
+    ENGINE_finish(e);<br>
+<br>
+cleanup:<br>
+    ENGINE_free(e);<br>
+    X509_free(params.cert);<br>
+    UI_destroy_method(ui);<br>
+<br>
+    return ret;<br>
+}<br>
+<br>
+#endif /* HAVE_OPENSSL_ENGINE */<br>
+<br>
+#endif /* ENABLE_CRYPTO_OPENSSL */<br>
-- <br>
2.20.1<br>
<br>
</blockquote></div>
mike tancsa July 27, 2021, 5:18 a.m. UTC | #2
That would be VERY handy to have for our use case

    ---Mike

On 7/27/2021 10:56 AM, Selva Nair wrote:
>
> It seems no one is interested in this to elicit a review.. I thought
> this would be a nifty feature ;)
>
> On Sun, May 9, 2021 at 9:32 PM <selva.nair@gmail.com
> <mailto:selva.nair@gmail.com>> wrote:
>
>     From: Selva Nair <selva.nair@gmail.com <mailto:selva.nair@gmail.com>>
>
>     v2 changes
>       - do not allow so-path embedded in cert and key uri
>       - add --pkcs11-engine option to optionally specify the
>             engine and provider module to use
>
>     If either --cert or --key is specified as a PKCS#11 uri, try to
>     load the certificate and key from any accessible PKCS#11 device.
>     This does not require linking with any pkcs11 library, but needs
>     pkcs11 engine to be available on the target machine.
>
>     In its simplest form, just have
>
>     --cert 'pkcs11:id=%01'
>
>     Either do not specify --key, or use the same uri for --key.
>     Do not include type=cert or type=private in the uri
>     as the same uri is used for both certificate and private key.
>
>     That's all what is required if pkcs11 engine is installed in the
>     right location and optionally set up to load any necessary provider
>     libraries (e.g., via openssl.cnf or via PKCS11_MODULE_PATH).
>
>     If both cert and key are specified, the last entry takes precedence
>     and is used to locate both the certificate and key. Use of different
>     uri's for the cert and key are not supported. Specifying --cert as
>     a file and --key as a uri or vice versa is treated as a usage error.
>
>     If the engine cannot be automatically loaded, or a custom engine
>     object
>     has to be loaded, the engine name or shared library may be specified
>     using the newly added option
>
>        --pkcs11-engine engine [module_path]
>
>     Here engine may the the engine-id that OpenSSL is configured to
>     locate,
>     or the path to a shared object. The optional 'module_path' specifies
>     any provider module that must be loaded. It must be given as a path.
>     Use full path or relative path for these shared objects based on the
>     target system setup.
>
>     Requires building with OpenSSL engine support although the pkcs11 or
>     a compatible engine, and provider libraries are required only at
>     run time.
>
>     Signed-off-by: Selva Nair <selva.nair@gmail.com
>     <mailto:selva.nair@gmail.com>>
>     ---
>      Changes.rst                      |   6 +
>      doc/man-sections/tls-options.rst |  31 ++++++
>      src/openvpn/options.c            |  68 +++++++++++-
>      src/openvpn/options.h            |   7 ++
>      src/openvpn/ssl.c                |  15 ++-
>      src/openvpn/ssl_backend.h        |  10 ++
>      src/openvpn/ssl_openssl.c        | 183
>     ++++++++++++++++++++++++++++++-
>      7 files changed, 316 insertions(+), 4 deletions(-)
>
>     diff --git a/Changes.rst b/Changes.rst
>     index 9185b55f..19d311e3 100644
>     --- a/Changes.rst
>     +++ b/Changes.rst
>     @@ -4,6 +4,12 @@ Overview of changes in 2.6
>
>      New features
>      ------------
>     +Specification of private key and certificates as PKCS#11 URI
>     +    ``--cert`` and ``--key`` options can take RFC7512 PKCS#11
>     +    URI's pointing to certificate and key in a token. Both cert
>     +    and key must use the same URI. Requires OpenSSL with engine
>     +    support and pkcs11 (or compatible) engine installed.
>     +
>      Keying Material Exporters (RFC 5705) based key generation
>          As part of the cipher negotiation OpenVPN will automatically
>     prefer
>          the RFC5705 based key material generation to the current custom
>     diff --git a/doc/man-sections/tls-options.rst
>     b/doc/man-sections/tls-options.rst
>     index 00ea063a..7acfbdae 100644
>     --- a/doc/man-sections/tls-options.rst
>     +++ b/doc/man-sections/tls-options.rst
>     @@ -116,6 +116,20 @@ certificates and keys:
>     https://github.com/OpenVPN/easy-rsa
>     <https://github.com/OpenVPN/easy-rsa>
>        authority functions, you must set up the files
>     :code:`index.txt` (may be
>        empty) and :code:`serial` (initialize to :code:`01`).
>
>     +--cert pkcs11-uri
>     +  The local peer's certificate in a PKCS#11 token specified as a
>     RFC 7512
>     +  uri with optional custom attributes described below. Cannot be
>     used with
>     +  ``--key file``. ``--key`` must be left unspecified or point to
>     the same
>     +  uri. All other requrements for the certificate described under
>     +  ``--cert file`` applies.
>     +
>     +  Requires OpenSSL with pkcs11 engine installed and configured.
>     Also see
>     +  the option ``--pkcs11-engine``.
>     +
>     +  As the same uri is used for certificate and private key, do not
>     include type
>     +  attribute (i.e., :code: `type=cert;` or :code: `type=private;`
>     should not
>     +  be included)
>     +
>      --crl-verify args
>        Check peer certificate against a Certificate Revocation List.
>
>     @@ -208,11 +222,28 @@ certificates and keys:
>     https://github.com/OpenVPN/easy-rsa
>     <https://github.com/OpenVPN/easy-rsa>
>        generated when you built your peer's certificate (see ``--cert
>     file``
>        above).
>
>     +--key pkcs11-uri
>     +  See ``--cert pkcs11-uri`` above.
>     +
>      --pkcs12 file
>        Specify a PKCS #12 file containing local private key, local
>     certificate,
>        and root CA certificate. This option can be used instead of
>     ``--ca``,
>        ``--cert``, and ``--key``.  Not available with mbed TLS.
>
>     +--pkcs11-engine engine-name [module-path]
>     +  Specifiy the pkcs11-engine and the provider module to load when
>     +  certificate and private key are given as a pkcs11 URI.
>     +
>     +  If the option is unspecified, and a pkcs11 URI is used for
>     cert/key,
>     +  :code:`pkcs11` engine is loaded, if it can be automatically
>     found by
>     +  OpenSSL.
>     +
>     +  If specified, the cert/key must be given as a pkcs11 URI.
>     +
>     +  The engine name could be a valid engine-id or path to a shared
>     object.
>     +  The module-path should be the path to a shared object. Objects in
>     +  non-standard locations would need to be specified as full paths.
>     +
>      --remote-cert-eku oid
>        Require that peer certificate was signed with an explicit
>     *extended key
>        usage*.
>     diff --git a/src/openvpn/options.c b/src/openvpn/options.c
>     index db460796..559782ec 100644
>     --- a/src/openvpn/options.c
>     +++ b/src/openvpn/options.c
>     @@ -531,6 +531,11 @@ static const char usage_message[] =
>          "                   nonce_secret_len=nsl.  Set alg=none to
>     disable PRNG.\n"
>      #ifndef ENABLE_CRYPTO_MBEDTLS
>          "--engine [name] : Enable OpenSSL hardware crypto engine
>     functionality.\n"
>     +#endif
>     +#ifdef HAVE_OPENSSL_ENGINE
>     +    "--pkcs11-engine name [module-path] : PKCS11 engine and
>     provider module to use\n"
>     +    "                                     for cert and key
>     specified as a pkcs11 URI.\n"
>     +    "                                     name could be an
>     engine-id or a path.\n"
>      #endif
>          "--no-replay     : (DEPRECATED) Disable replay protection.\n"
>          "--mute-replay-warnings : Silence the output of replay
>     warnings to log file.\n"
>     @@ -915,6 +920,12 @@ struct pull_filter_list
>          struct pull_filter *tail;
>      };
>
>     +static bool
>     +is_pkcs11_uri(const char *uri)
>     +{
>     +    return (uri && !strncmp(uri, "pkcs11:", 7));
>     +}
>     +
>      static const char *
>      pull_filter_type_name(int type)
>      {
>     @@ -2587,6 +2598,17 @@ options_postprocess_verify_ce(const struct
>     options *options,
>
>          if (options->tls_server || options->tls_client)
>          {
>     +#ifdef HAVE_OPENSSL_ENGINE
>     +        if (is_pkcs11_uri(options->cert_file) !=
>     is_pkcs11_uri(options->priv_key_file))
>     +        {
>     +            msg(M_USAGE, "Use of PKCS#11 uri for --cert or --key
>     and file name for the other is not supported");
>     +        }
>     +        else
>     +        if (options->pkcs11_engine &&
>     !is_pkcs11_uri(options->cert_file))
>     +        {
>     +            msg(M_USAGE, "Use of --pkcs11-engine expects --cert
>     to be specified as a pkcs11: URI");
>     +        }
>     +#endif
>      #ifdef ENABLE_PKCS11
>              if (options->pkcs11_providers[0])
>              {
>     @@ -3455,8 +3477,11 @@ options_postprocess_filechecks(struct
>     options *options)
>          errs |= check_file_access_chroot(options->chroot_dir,
>     CHKACC_FILE,
>                                           options->ca_path, R_OK,
>     "--capath");
>
>     -    errs |= check_file_access_inline(options->cert_file_inline,
>     CHKACC_FILE,
>     +    if (!is_pkcs11_uri(options->cert_file))
>     +    {
>     +        errs |=
>     check_file_access_inline(options->cert_file_inline, CHKACC_FILE,
>                                           options->cert_file, R_OK,
>     "--cert");
>     +    }
>
>          errs |= check_file_access_inline(options->extra_certs_file,
>     CHKACC_FILE,
>                                           options->extra_certs_file, R_OK,
>     @@ -3466,9 +3491,12 @@ options_postprocess_filechecks(struct
>     options *options)
>          if (!(options->management_flags & MF_EXTERNAL_KEY))
>      #endif
>          {
>     -        errs |=
>     check_file_access_inline(options->priv_key_file_inline,
>     +        if (!is_pkcs11_uri(options->priv_key_file))
>     +        {
>     +            errs |=
>     check_file_access_inline(options->priv_key_file_inline,
>                                               CHKACC_FILE|CHKACC_PRIVATE,
>                                               options->priv_key_file,
>     R_OK, "--key");
>     +        }
>          }
>
>          errs |= check_file_access_inline(options->pkcs12_file_inline,
>     @@ -8097,6 +8125,14 @@ add_option(struct options *options,
>              }
>          }
>      #endif /* ENABLE_CRYPTO_MBEDTLS */
>     +#ifdef HAVE_OPENSSL_ENGINE
>     +    else if (streq(p[0], "pkcs11-engine") && p[1] && !p[3])
>     +    {
>     +        VERIFY_PERMISSION(OPT_P_GENERAL);
>     +        options->pkcs11_engine = p[1];
>     +        options->pkcs11_engine_module = p[2]; /* may be NULL */
>     +    }
>     +#endif /* HAVE_OPENSSL_ENGINE */
>      #ifdef ENABLE_PREDICTION_RESISTANCE
>          else if (streq(p[0], "use-prediction-resistance") && !p[1])
>          {
>     @@ -8156,6 +8192,20 @@ add_option(struct options *options,
>              VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INLINE);
>              options->cert_file = p[1];
>              options->cert_file_inline = is_inline;
>     +        if (is_pkcs11_uri(p[1]))
>     +        {
>     +#ifndef HAVE_OPENSSL_ENGINE
>     +            msg(msglevel, "Use of PKCS11 uri as cert and key file
>     names requires OpenSSL "
>     +                          "ENGINE support which is missing in
>     this binary.")
>     +#else
>     +            options->priv_key_file = p[1];
>     +            options->cert_file_is_pkcs11_uri = true;
>     +        }
>     +        else
>     +        {
>     +            options->cert_file_is_pkcs11_uri = false;
>     +#endif /* HAVE_OPENSSL_ENGINE */
>     +        }
>          }
>          else if (streq(p[0], "extra-certs") && p[1] && !p[2])
>          {
>     @@ -8238,6 +8288,20 @@ add_option(struct options *options,
>              VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INLINE);
>              options->priv_key_file = p[1];
>              options->priv_key_file_inline = is_inline;
>     +        if (is_pkcs11_uri(p[1]))
>     +        {
>     +#ifndef HAVE_OPENSSL_ENGINE
>     +            msg(msglevel, "Use of PKCS11 uri as cert and key file
>     names requires OpenSSL "
>     +                          "ENGINE support which is missing in
>     this binary.")
>     +#else
>     +            options->cert_file = p[1];
>     +            options->cert_file_is_pkcs11_uri = true;
>     +        }
>     +        else
>     +        {
>     +            options->cert_file_is_pkcs11_uri = false;
>     +#endif /* HAVE_OPENSSL_ENGINE */
>     +        }
>          }
>          else if (streq(p[0], "tls-version-min") && p[1] && !p[3])
>          {
>     diff --git a/src/openvpn/options.h b/src/openvpn/options.h
>     index 41e84f7e..15567980 100644
>     --- a/src/openvpn/options.h
>     +++ b/src/openvpn/options.h
>     @@ -659,6 +659,13 @@ struct options
>
>          /* data channel crypto flags set by push/pull. Reuses the
>     CO_* crypto_flags */
>          unsigned int data_channel_crypto_flags;
>     +
>     +#ifdef HAVE_OPENSSL_ENGINE
>     +    const char *pkcs11_engine;
>     +    const char *pkcs11_engine_module;
>     +    /* flag to indicate cert and key files are specified as
>     pkcs11 uri */
>     +    bool cert_file_is_pkcs11_uri;
>     +#endif
>      };
>
>      #define streq(x, y) (!strcmp((x), (y)))
>     diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c
>     index b16f6bcc..dedfe8ad 100644
>     --- a/src/openvpn/ssl.c
>     +++ b/src/openvpn/ssl.c
>     @@ -641,6 +641,19 @@ init_ssl(const struct options *options,
>     struct tls_root_ctx *new_ctx, bool in_ch
>                  goto err;
>              }
>          }
>     +#ifdef HAVE_OPENSSL_ENGINE
>     +    else if (options->cert_file_is_pkcs11_uri)
>     +    {
>     +        if (!tls_ctx_use_pkcs11_engine(new_ctx, options->cert_file,
>     +                                       options->pkcs11_engine,
>     +                                     
>      options->pkcs11_engine_module))
>     +        {
>     +            msg(M_WARN, "Cannot load certificate \"%s\" using
>     PKCS#11 engine",
>     +                options->cert_file);
>     +            goto err;
>     +        }
>     +    }
>     +#endif
>      #ifdef ENABLE_PKCS11
>          else if (options->pkcs11_providers[0])
>          {
>     @@ -672,7 +685,7 @@ init_ssl(const struct options *options, struct
>     tls_root_ctx *new_ctx, bool in_ch
>              tls_ctx_load_cert_file(new_ctx, options->cert_file,
>     options->cert_file_inline);
>          }
>
>     -    if (options->priv_key_file)
>     +    if (options->priv_key_file && !options->cert_file_is_pkcs11_uri)
>          {
>              if (0 != tls_ctx_load_priv_file(new_ctx,
>     options->priv_key_file,
>                                            
>      options->priv_key_file_inline))
>     diff --git a/src/openvpn/ssl_backend.h b/src/openvpn/ssl_backend.h
>     index c3d12e5b..556fc2c8 100644
>     --- a/src/openvpn/ssl_backend.h
>     +++ b/src/openvpn/ssl_backend.h
>     @@ -568,4 +568,14 @@ void get_highest_preference_tls_cipher(char
>     *buf, int size);
>       */
>      const char *get_ssl_library_version(void);
>
>     +/**
>     + * Load certificate and key into TLS context using pkcs11 engine
>     + * @param ctx       TLS context
>     + * @param cert_id   ceritificate and proivate key spec as pkcs11 URI
>     + * @param engine    id or path of OpenSSL pkcs11 engine object
>     (default: pkcs11)
>     + * @param module    path of optional provider module to load with
>     the engine
>     + */
>     +int tls_ctx_use_pkcs11_engine(struct tls_root_ctx *tls_ctx, const
>     char *cert_id,
>     +                          const char *engine, const char *module);
>     +
>      #endif /* SSL_BACKEND_H_ */
>     diff --git a/src/openvpn/ssl_openssl.c b/src/openvpn/ssl_openssl.c
>     index 3120c51a..58fbde93 100644
>     --- a/src/openvpn/ssl_openssl.c
>     +++ b/src/openvpn/ssl_openssl.c
>     @@ -2251,4 +2251,185 @@ get_ssl_library_version(void)
>          return OpenSSL_version(OPENSSL_VERSION);
>      }
>
>     -#endif /* defined(ENABLE_CRYPTO_OPENSSL) */
>     +#if HAVE_OPENSSL_ENGINE
>     +#include <openssl/ui.h>
>     +#include <openssl/engine.h>
>     +
>     +/* Call back method for user interface with pkcs11 engine
>     + * used for PIN prompt and possibly token insertion request.
>     + */
>     +static int
>     +ui_reader(UI *ui, UI_STRING *uis)
>     +{
>     +    struct user_pass token_pass;
>     +    int ret = 0;
>     +
>     +    const char *uri = UI_get0_user_data(ui);
>     +    const char *prompt = UI_get0_output_string(uis);
>     +
>     +    token_pass.defined = false;
>     +    token_pass.nocache = true;
>     +
>     +    switch(UI_get_string_type(uis))
>     +    {
>     +        case UIT_PROMPT:
>     +        case UIT_VERIFY:
>     +            if (get_user_pass(&token_pass, NULL, prompt,
>     +                GET_USER_PASS_MANAGEMENT|GET_USER_PASS_PASSWORD_ONLY
>     +                |GET_USER_PASS_NOFATAL))
>     +            {
>     +                ret = 1;
>     +                UI_set_result(ui, uis, token_pass.password);
>     +            }
>     +            break;
>     +       case UIT_BOOLEAN:
>     +            if (get_user_pass(&token_pass, NULL,
>     UI_get0_output_string(uis),
>     +                GET_USER_PASS_MANAGEMENT|GET_USER_PASS_NEED_OK
>     +                |GET_USER_PASS_NOFATAL))
>     +            {
>     +                ret = (strcmp(token_pass.password, "ok") == 0);
>     +                UI_set_result(ui, uis, token_pass.password);
>     +            }
>     +       case UIT_INFO:
>     +            msg(M_INFO, "INFO prompt from token: <%s>", prompt);
>     +            break;
>     +       case UIT_ERROR:
>     +            msg(M_INFO, "ERROR prompt from token: <%s>", prompt);
>     +            break;
>     +       default:
>     +            break;
>     +    }
>     +
>     +    return ret;
>     +}
>     +
>     +static char *
>     +ui_prompt_constructor(UI *ui, const char *desc, const char *name)
>     +{
>     +    int len =  strlen(desc) + strlen(name) + 6;
>     +    char *s = malloc(len);
>     +    openvpn_snprintf(s, len, "%s for %s", desc, name);
>     +    return s;
>     +}
>     +
>     +static ENGINE *
>     +load_pkcs11_engine(const char *engine_id)
>     +{
>     +    ENGINE *e = ENGINE_by_id(engine_id);
>     +
>     +    if (e) {
>     +        return e;
>     +    }
>     +
>     +    /* try dynamic engine with engine-id as path to the engine
>     shared object */
>     +    e = ENGINE_by_id("dynamic");
>     +    if (e)
>     +    {
>     +        if (!ENGINE_ctrl_cmd_string(e, "SO_PATH", engine_id, 0)
>     +            || !ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0))
>     +        {
>     +            ENGINE_free(e);
>     +            e = NULL;
>     +        }
>     +    }
>     +    if (!e)
>     +    {
>     +        msg(M_WARN, "PKCS11 engine <%s> not available", engine_id);
>     +    }
>     +    return e;
>     +}
>     +
>     +static ENGINE *
>     +setup_pkcs11_engine(const char *engine_id, const char
>     *module_path, UI_METHOD *ui)
>     +{
>     +    if (!engine_id)
>     +    {
>     +        engine_id = "pkcs11";
>     +    }
>     +
>     +    msg(D_SHOW_PKCS11, "Loading pkcs11 engine <%s> with module <%s>",
>     +        engine_id, (module_path ? module_path : "unspecified"));
>     +
>     +    ENGINE *e = load_pkcs11_engine(engine_id);
>     +
>     +    if (e)
>     +    {
>     +       if (module_path)
>     +        {
>     +            ENGINE_ctrl_cmd_string(e, "MODULE_PATH", module_path, 0);
>     +        }
>     +        ENGINE_ctrl_cmd(e, "SET_USER_INTERFACE", 0, ui, NULL, 0);
>     +    }
>     +
>     +    return e;
>     +}
>     +
>     +/**
>     + * Load certificate and key into TLS context using pkcs11 engine
>     + * @param ctx       TLS context
>     + * @param cert_id   ceritificate and proivate key spec as pkcs11 URI
>     + * @param engine    id or path of OpenSSL pkcs11 engine object
>     (default: pkcs11)
>     + * @param module    path of optional provider module to load with
>     the engine
>     + */
>     +int
>     +tls_ctx_use_pkcs11_engine(struct tls_root_ctx *tls_ctx, const
>     char *cert_id,
>     +                          const char *engine, const char *module)
>     +{
>     +    int ret = 0;
>     +    EVP_PKEY *pkey = NULL;
>     +
>     +    UI_METHOD *ui = UI_create_method("openvpn");
>     +    if (!ui)
>     +    {
>     +        msg(M_WARN, "Failed to setup UI callback for engine");
>     +        return ret;
>     +    }
>     +    UI_method_set_reader(ui, ui_reader);
>     +    UI_method_set_prompt_constructor(ui, ui_prompt_constructor);
>     +
>     +    struct
>     +    {
>     +        const char *cert_id;
>     +        X509* cert;
>     +    } params = {cert_id, NULL};
>     +
>     +    ENGINE *e = setup_pkcs11_engine(engine, module, ui);
>     +    if (!e || !ENGINE_init(e))
>     +    {
>     +        goto cleanup;
>     +    }
>     +    ENGINE_ctrl_cmd(e, "SET_CALLBACK_DATA", 0, (void *)cert_id,
>     NULL, 0);
>     +
>     +    msg (D_SHOW_PKCS11, "Loading certificate <%s> using engine",
>     params.cert_id);
>     +
>     +    if (!ENGINE_ctrl_cmd(e, "LOAD_CERT_CTRL", 0, &params, NULL, 0)
>     +        || !params.cert || !SSL_CTX_use_certificate(tls_ctx->ctx,
>     params.cert))
>     +    {
>     +        msg (M_WARN, "Failed to load certificate <%s>", cert_id);
>     +        goto finish;
>     +    }
>     +
>     +    msg (D_SHOW_PKCS11, "Loading private key <%s> using engine",
>     params.cert_id);
>     +
>     +    pkey = ENGINE_load_private_key(e, cert_id, ui, (void *)cert_id);
>     +    if (!pkey || !SSL_CTX_use_PrivateKey(tls_ctx->ctx, pkey))
>     +    {
>     +        msg (M_WARN, "Failed to set private key <%s> using
>     engine", cert_id);
>     +        goto finish;
>     +    }
>     +    ret = 1;
>     +
>     +finish:
>     +    ENGINE_finish(e);
>     +
>     +cleanup:
>     +    ENGINE_free(e);
>     +    X509_free(params.cert);
>     +    UI_destroy_method(ui);
>     +
>     +    return ret;
>     +}
>     +
>     +#endif /* HAVE_OPENSSL_ENGINE */
>     +
>     +#endif /* ENABLE_CRYPTO_OPENSSL */
>     -- 
>     2.20.1
>
>
>
> _______________________________________________
> Openvpn-devel mailing list
> Openvpn-devel@lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/openvpn-devel
Gert Doering July 27, 2021, 6:03 a.m. UTC | #3
Hi,

On Tue, Jul 27, 2021 at 11:18:53AM -0400, mike tancsa wrote:
> That would be VERY handy to have for our use case

So could you *test* it?

I'm happy to stare at the code a bit, but have no test environment to
verify that it actually works.  Thus, test reports by actual users are
more than welcome.

gert

Patch

diff --git a/Changes.rst b/Changes.rst
index 9185b55f..19d311e3 100644
--- a/Changes.rst
+++ b/Changes.rst
@@ -4,6 +4,12 @@  Overview of changes in 2.6
 
 New features
 ------------
+Specification of private key and certificates as PKCS#11 URI
+    ``--cert`` and ``--key`` options can take RFC7512 PKCS#11
+    URI's pointing to certificate and key in a token. Both cert
+    and key must use the same URI. Requires OpenSSL with engine
+    support and pkcs11 (or compatible) engine installed.
+
 Keying Material Exporters (RFC 5705) based key generation
     As part of the cipher negotiation OpenVPN will automatically prefer
     the RFC5705 based key material generation to the current custom
diff --git a/doc/man-sections/tls-options.rst b/doc/man-sections/tls-options.rst
index 00ea063a..7acfbdae 100644
--- a/doc/man-sections/tls-options.rst
+++ b/doc/man-sections/tls-options.rst
@@ -116,6 +116,20 @@  certificates and keys: https://github.com/OpenVPN/easy-rsa
   authority functions, you must set up the files :code:`index.txt` (may be
   empty) and :code:`serial` (initialize to :code:`01`).
 
+--cert pkcs11-uri
+  The local peer's certificate in a PKCS#11 token specified as a RFC 7512
+  uri with optional custom attributes described below. Cannot be used with
+  ``--key file``. ``--key`` must be left unspecified or point to the same
+  uri. All other requrements for the certificate described under
+  ``--cert file`` applies.
+
+  Requires OpenSSL with pkcs11 engine installed and configured. Also see
+  the option ``--pkcs11-engine``.
+
+  As the same uri is used for certificate and private key, do not include type
+  attribute (i.e., :code: `type=cert;` or :code: `type=private;` should not
+  be included)
+
 --crl-verify args
   Check peer certificate against a Certificate Revocation List.
 
@@ -208,11 +222,28 @@  certificates and keys: https://github.com/OpenVPN/easy-rsa
   generated when you built your peer's certificate (see ``--cert file``
   above).
 
+--key pkcs11-uri
+  See ``--cert pkcs11-uri`` above.
+
 --pkcs12 file
   Specify a PKCS #12 file containing local private key, local certificate,
   and root CA certificate. This option can be used instead of ``--ca``,
   ``--cert``, and ``--key``.  Not available with mbed TLS.
 
+--pkcs11-engine engine-name [module-path]
+  Specifiy the pkcs11-engine and the provider module to load when
+  certificate and private key are given as a pkcs11 URI.
+
+  If the option is unspecified, and a pkcs11 URI is used for cert/key,
+  :code:`pkcs11` engine is loaded, if it can be automatically found by
+  OpenSSL.
+
+  If specified, the cert/key must be given as a pkcs11 URI.
+
+  The engine name could be a valid engine-id or path to a shared object.
+  The module-path should be the path to a shared object. Objects in
+  non-standard locations would need to be specified as full paths.
+
 --remote-cert-eku oid
   Require that peer certificate was signed with an explicit *extended key
   usage*.
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index db460796..559782ec 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -531,6 +531,11 @@  static const char usage_message[] =
     "                   nonce_secret_len=nsl.  Set alg=none to disable PRNG.\n"
 #ifndef ENABLE_CRYPTO_MBEDTLS
     "--engine [name] : Enable OpenSSL hardware crypto engine functionality.\n"
+#endif
+#ifdef HAVE_OPENSSL_ENGINE
+    "--pkcs11-engine name [module-path] : PKCS11 engine and provider module to use\n"
+    "                                     for cert and key specified as a pkcs11 URI.\n"
+    "                                     name could be an engine-id or a path.\n"
 #endif
     "--no-replay     : (DEPRECATED) Disable replay protection.\n"
     "--mute-replay-warnings : Silence the output of replay warnings to log file.\n"
@@ -915,6 +920,12 @@  struct pull_filter_list
     struct pull_filter *tail;
 };
 
+static bool
+is_pkcs11_uri(const char *uri)
+{
+    return (uri && !strncmp(uri, "pkcs11:", 7));
+}
+
 static const char *
 pull_filter_type_name(int type)
 {
@@ -2587,6 +2598,17 @@  options_postprocess_verify_ce(const struct options *options,
 
     if (options->tls_server || options->tls_client)
     {
+#ifdef HAVE_OPENSSL_ENGINE
+        if (is_pkcs11_uri(options->cert_file) != is_pkcs11_uri(options->priv_key_file))
+        {
+            msg(M_USAGE, "Use of PKCS#11 uri for --cert or --key and file name for the other is not supported");
+        }
+        else
+        if (options->pkcs11_engine && !is_pkcs11_uri(options->cert_file))
+        {
+            msg(M_USAGE, "Use of --pkcs11-engine expects --cert to be specified as a pkcs11: URI");
+        }
+#endif
 #ifdef ENABLE_PKCS11
         if (options->pkcs11_providers[0])
         {
@@ -3455,8 +3477,11 @@  options_postprocess_filechecks(struct options *options)
     errs |= check_file_access_chroot(options->chroot_dir, CHKACC_FILE,
                                      options->ca_path, R_OK, "--capath");
 
-    errs |= check_file_access_inline(options->cert_file_inline, CHKACC_FILE,
+    if (!is_pkcs11_uri(options->cert_file))
+    {
+        errs |= check_file_access_inline(options->cert_file_inline, CHKACC_FILE,
                                      options->cert_file, R_OK, "--cert");
+    }
 
     errs |= check_file_access_inline(options->extra_certs_file, CHKACC_FILE,
                                      options->extra_certs_file, R_OK,
@@ -3466,9 +3491,12 @@  options_postprocess_filechecks(struct options *options)
     if (!(options->management_flags & MF_EXTERNAL_KEY))
 #endif
     {
-        errs |= check_file_access_inline(options->priv_key_file_inline,
+        if (!is_pkcs11_uri(options->priv_key_file))
+        {
+            errs |= check_file_access_inline(options->priv_key_file_inline,
                                          CHKACC_FILE|CHKACC_PRIVATE,
                                          options->priv_key_file, R_OK, "--key");
+        }
     }
 
     errs |= check_file_access_inline(options->pkcs12_file_inline,
@@ -8097,6 +8125,14 @@  add_option(struct options *options,
         }
     }
 #endif /* ENABLE_CRYPTO_MBEDTLS */
+#ifdef HAVE_OPENSSL_ENGINE
+    else if (streq(p[0], "pkcs11-engine") && p[1] && !p[3])
+    {
+        VERIFY_PERMISSION(OPT_P_GENERAL);
+        options->pkcs11_engine = p[1];
+        options->pkcs11_engine_module = p[2]; /* may be NULL */
+    }
+#endif /* HAVE_OPENSSL_ENGINE */
 #ifdef ENABLE_PREDICTION_RESISTANCE
     else if (streq(p[0], "use-prediction-resistance") && !p[1])
     {
@@ -8156,6 +8192,20 @@  add_option(struct options *options,
         VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INLINE);
         options->cert_file = p[1];
         options->cert_file_inline = is_inline;
+        if (is_pkcs11_uri(p[1]))
+        {
+#ifndef HAVE_OPENSSL_ENGINE
+            msg(msglevel, "Use of PKCS11 uri as cert and key file names requires OpenSSL "
+                          "ENGINE support which is missing in this binary.")
+#else
+            options->priv_key_file = p[1];
+            options->cert_file_is_pkcs11_uri = true;
+        }
+        else
+        {
+            options->cert_file_is_pkcs11_uri = false;
+#endif /* HAVE_OPENSSL_ENGINE */
+        }
     }
     else if (streq(p[0], "extra-certs") && p[1] && !p[2])
     {
@@ -8238,6 +8288,20 @@  add_option(struct options *options,
         VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INLINE);
         options->priv_key_file = p[1];
         options->priv_key_file_inline = is_inline;
+        if (is_pkcs11_uri(p[1]))
+        {
+#ifndef HAVE_OPENSSL_ENGINE
+            msg(msglevel, "Use of PKCS11 uri as cert and key file names requires OpenSSL "
+                          "ENGINE support which is missing in this binary.")
+#else
+            options->cert_file = p[1];
+            options->cert_file_is_pkcs11_uri = true;
+        }
+        else
+        {
+            options->cert_file_is_pkcs11_uri = false;
+#endif /* HAVE_OPENSSL_ENGINE */
+        }
     }
     else if (streq(p[0], "tls-version-min") && p[1] && !p[3])
     {
diff --git a/src/openvpn/options.h b/src/openvpn/options.h
index 41e84f7e..15567980 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -659,6 +659,13 @@  struct options
 
     /* data channel crypto flags set by push/pull. Reuses the CO_* crypto_flags */
     unsigned int data_channel_crypto_flags;
+
+#ifdef HAVE_OPENSSL_ENGINE
+    const char *pkcs11_engine;
+    const char *pkcs11_engine_module;
+    /* flag to indicate cert and key files are specified as pkcs11 uri */
+    bool cert_file_is_pkcs11_uri;
+#endif
 };
 
 #define streq(x, y) (!strcmp((x), (y)))
diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c
index b16f6bcc..dedfe8ad 100644
--- a/src/openvpn/ssl.c
+++ b/src/openvpn/ssl.c
@@ -641,6 +641,19 @@  init_ssl(const struct options *options, struct tls_root_ctx *new_ctx, bool in_ch
             goto err;
         }
     }
+#ifdef HAVE_OPENSSL_ENGINE
+    else if (options->cert_file_is_pkcs11_uri)
+    {
+        if (!tls_ctx_use_pkcs11_engine(new_ctx, options->cert_file,
+                                       options->pkcs11_engine,
+                                       options->pkcs11_engine_module))
+        {
+            msg(M_WARN, "Cannot load certificate \"%s\" using PKCS#11 engine",
+                options->cert_file);
+            goto err;
+        }
+    }
+#endif
 #ifdef ENABLE_PKCS11
     else if (options->pkcs11_providers[0])
     {
@@ -672,7 +685,7 @@  init_ssl(const struct options *options, struct tls_root_ctx *new_ctx, bool in_ch
         tls_ctx_load_cert_file(new_ctx, options->cert_file, options->cert_file_inline);
     }
 
-    if (options->priv_key_file)
+    if (options->priv_key_file && !options->cert_file_is_pkcs11_uri)
     {
         if (0 != tls_ctx_load_priv_file(new_ctx, options->priv_key_file,
                                         options->priv_key_file_inline))
diff --git a/src/openvpn/ssl_backend.h b/src/openvpn/ssl_backend.h
index c3d12e5b..556fc2c8 100644
--- a/src/openvpn/ssl_backend.h
+++ b/src/openvpn/ssl_backend.h
@@ -568,4 +568,14 @@  void get_highest_preference_tls_cipher(char *buf, int size);
  */
 const char *get_ssl_library_version(void);
 
+/**
+ * Load certificate and key into TLS context using pkcs11 engine
+ * @param ctx       TLS context
+ * @param cert_id   ceritificate and proivate key spec as pkcs11 URI
+ * @param engine    id or path of OpenSSL pkcs11 engine object (default: pkcs11)
+ * @param module    path of optional provider module to load with the engine
+ */
+int tls_ctx_use_pkcs11_engine(struct tls_root_ctx *tls_ctx, const char *cert_id,
+                          const char *engine, const char *module);
+
 #endif /* SSL_BACKEND_H_ */
diff --git a/src/openvpn/ssl_openssl.c b/src/openvpn/ssl_openssl.c
index 3120c51a..58fbde93 100644
--- a/src/openvpn/ssl_openssl.c
+++ b/src/openvpn/ssl_openssl.c
@@ -2251,4 +2251,185 @@  get_ssl_library_version(void)
     return OpenSSL_version(OPENSSL_VERSION);
 }
 
-#endif /* defined(ENABLE_CRYPTO_OPENSSL) */
+#if HAVE_OPENSSL_ENGINE
+#include <openssl/ui.h>
+#include <openssl/engine.h>
+
+/* Call back method for user interface with pkcs11 engine
+ * used for PIN prompt and possibly token insertion request.
+ */
+static int
+ui_reader(UI *ui, UI_STRING *uis)
+{
+    struct user_pass token_pass;
+    int ret = 0;
+
+    const char *uri = UI_get0_user_data(ui);
+    const char *prompt = UI_get0_output_string(uis);
+
+    token_pass.defined = false;
+    token_pass.nocache = true;
+
+    switch(UI_get_string_type(uis))
+    {
+        case UIT_PROMPT:
+        case UIT_VERIFY:
+            if (get_user_pass(&token_pass, NULL, prompt,
+                GET_USER_PASS_MANAGEMENT|GET_USER_PASS_PASSWORD_ONLY
+                |GET_USER_PASS_NOFATAL))
+            {
+                ret = 1;
+                UI_set_result(ui, uis, token_pass.password);
+            }
+            break;
+       case UIT_BOOLEAN:
+            if (get_user_pass(&token_pass, NULL, UI_get0_output_string(uis),
+                GET_USER_PASS_MANAGEMENT|GET_USER_PASS_NEED_OK
+                |GET_USER_PASS_NOFATAL))
+            {
+                ret = (strcmp(token_pass.password, "ok") == 0);
+                UI_set_result(ui, uis, token_pass.password);
+            }
+       case UIT_INFO:
+            msg(M_INFO, "INFO prompt from token: <%s>", prompt);
+            break;
+       case UIT_ERROR:
+            msg(M_INFO, "ERROR prompt from token: <%s>", prompt);
+            break;
+       default:
+            break;
+    }
+
+    return ret;
+}
+
+static char *
+ui_prompt_constructor(UI *ui, const char *desc, const char *name)
+{
+    int len =  strlen(desc) + strlen(name) + 6;
+    char *s = malloc(len);
+    openvpn_snprintf(s, len, "%s for %s", desc, name);
+    return s;
+}
+
+static ENGINE *
+load_pkcs11_engine(const char *engine_id)
+{
+    ENGINE *e = ENGINE_by_id(engine_id);
+
+    if (e) {
+        return e;
+    }
+
+    /* try dynamic engine with engine-id as path to the engine shared object */
+    e = ENGINE_by_id("dynamic");
+    if (e)
+    {
+        if (!ENGINE_ctrl_cmd_string(e, "SO_PATH", engine_id, 0)
+            || !ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0))
+        {
+            ENGINE_free(e);
+            e = NULL;
+        }
+    }
+    if (!e)
+    {
+        msg(M_WARN, "PKCS11 engine <%s> not available", engine_id);
+    }
+    return e;
+}
+
+static ENGINE *
+setup_pkcs11_engine(const char *engine_id, const char *module_path, UI_METHOD *ui)
+{
+    if (!engine_id)
+    {
+        engine_id = "pkcs11";
+    }
+
+    msg(D_SHOW_PKCS11, "Loading pkcs11 engine <%s> with module <%s>",
+        engine_id, (module_path ? module_path : "unspecified"));
+
+    ENGINE *e = load_pkcs11_engine(engine_id);
+
+    if (e)
+    {
+	if (module_path)
+        {
+            ENGINE_ctrl_cmd_string(e, "MODULE_PATH", module_path, 0);
+        }
+        ENGINE_ctrl_cmd(e, "SET_USER_INTERFACE", 0, ui, NULL, 0);
+    }
+
+    return e;
+}
+
+/**
+ * Load certificate and key into TLS context using pkcs11 engine
+ * @param ctx       TLS context
+ * @param cert_id   ceritificate and proivate key spec as pkcs11 URI
+ * @param engine    id or path of OpenSSL pkcs11 engine object (default: pkcs11)
+ * @param module    path of optional provider module to load with the engine
+ */
+int
+tls_ctx_use_pkcs11_engine(struct tls_root_ctx *tls_ctx, const char *cert_id,
+                          const char *engine, const char *module)
+{
+    int ret = 0;
+    EVP_PKEY *pkey = NULL;
+
+    UI_METHOD *ui = UI_create_method("openvpn");
+    if (!ui)
+    {
+        msg(M_WARN, "Failed to setup UI callback for engine");
+        return ret;
+    }
+    UI_method_set_reader(ui, ui_reader);
+    UI_method_set_prompt_constructor(ui, ui_prompt_constructor);
+
+    struct
+    {
+        const char *cert_id;
+        X509* cert;
+    } params = {cert_id, NULL};
+
+    ENGINE *e = setup_pkcs11_engine(engine, module, ui);
+    if (!e || !ENGINE_init(e))
+    {
+        goto cleanup;
+    }
+    ENGINE_ctrl_cmd(e, "SET_CALLBACK_DATA", 0, (void *)cert_id, NULL, 0);
+
+    msg (D_SHOW_PKCS11, "Loading certificate <%s> using engine", params.cert_id);
+
+    if (!ENGINE_ctrl_cmd(e, "LOAD_CERT_CTRL", 0, &params, NULL, 0)
+        || !params.cert || !SSL_CTX_use_certificate(tls_ctx->ctx, params.cert))
+    {
+        msg (M_WARN, "Failed to load certificate <%s>", cert_id);
+        goto finish;
+    }
+
+    msg (D_SHOW_PKCS11, "Loading private key <%s> using engine", params.cert_id);
+
+    pkey = ENGINE_load_private_key(e, cert_id, ui, (void *)cert_id);
+    if (!pkey || !SSL_CTX_use_PrivateKey(tls_ctx->ctx, pkey))
+    {
+        msg (M_WARN, "Failed to set private key <%s> using engine", cert_id);
+        goto finish;
+    }
+    ret = 1;
+
+finish:
+    ENGINE_finish(e);
+
+cleanup:
+    ENGINE_free(e);
+    X509_free(params.cert);
+    UI_destroy_method(ui);
+
+    return ret;
+}
+
+#endif /* HAVE_OPENSSL_ENGINE */
+
+#endif /* ENABLE_CRYPTO_OPENSSL */