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.n...@gmail.com
> <mailto:selva.n...@gmail.com>> wrote:
>
>     From: Selva Nair <selva.n...@gmail.com <mailto:selva.n...@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.n...@gmail.com
>     <mailto:selva.n...@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


_______________________________________________
Openvpn-devel mailing list
Openvpn-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/openvpn-devel

Reply via email to