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 |
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, ¶ms, 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 <<a href="mailto:selva.nair@gmail.com">selva.nair@gmail.com</a>> 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 <<a href="mailto:selva.nair@gmail.com" target="_blank">selva.nair@gmail.com</a>><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 'pkcs11:id=%01'<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'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'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 'module_path' 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 <<a href="mailto:selva.nair@gmail.com" target="_blank">selva.nair@gmail.com</a>><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'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'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'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> " nonce_secret_len=nsl. Set alg=none to disable PRNG.\n"<br> #ifndef ENABLE_CRYPTO_MBEDTLS<br> "--engine [name] : Enable OpenSSL hardware crypto engine functionality.\n"<br> +#endif<br> +#ifdef HAVE_OPENSSL_ENGINE<br> + "--pkcs11-engine name [module-path] : PKCS11 engine and provider module to use\n"<br> + " for cert and key specified as a pkcs11 URI.\n"<br> + " name could be an engine-id or a path.\n"<br> #endif<br> "--no-replay : (DEPRECATED) Disable replay protection.\n"<br> "--mute-replay-warnings : Silence the output of replay warnings to log file.\n"<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 && !strncmp(uri, "pkcs11:", 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->tls_server || options->tls_client)<br> {<br> +#ifdef HAVE_OPENSSL_ENGINE<br> + if (is_pkcs11_uri(options->cert_file) != is_pkcs11_uri(options->priv_key_file))<br> + {<br> + msg(M_USAGE, "Use of PKCS#11 uri for --cert or --key and file name for the other is not supported");<br> + }<br> + else<br> + if (options->pkcs11_engine && !is_pkcs11_uri(options->cert_file))<br> + {<br> + msg(M_USAGE, "Use of --pkcs11-engine expects --cert to be specified as a pkcs11: URI");<br> + }<br> +#endif<br> #ifdef ENABLE_PKCS11<br> if (options->pkcs11_providers[0])<br> {<br> @@ -3455,8 +3477,11 @@ options_postprocess_filechecks(struct options *options)<br> errs |= check_file_access_chroot(options->chroot_dir, CHKACC_FILE,<br> options->ca_path, R_OK, "--capath");<br> <br> - errs |= check_file_access_inline(options->cert_file_inline, CHKACC_FILE,<br> + if (!is_pkcs11_uri(options->cert_file))<br> + {<br> + errs |= check_file_access_inline(options->cert_file_inline, CHKACC_FILE,<br> options->cert_file, R_OK, "--cert");<br> + }<br> <br> errs |= check_file_access_inline(options->extra_certs_file, CHKACC_FILE,<br> options->extra_certs_file, R_OK,<br> @@ -3466,9 +3491,12 @@ options_postprocess_filechecks(struct options *options)<br> if (!(options->management_flags & MF_EXTERNAL_KEY))<br> #endif<br> {<br> - errs |= check_file_access_inline(options->priv_key_file_inline,<br> + if (!is_pkcs11_uri(options->priv_key_file))<br> + {<br> + errs |= check_file_access_inline(options->priv_key_file_inline,<br> CHKACC_FILE|CHKACC_PRIVATE,<br> options->priv_key_file, R_OK, "--key");<br> + }<br> }<br> <br> errs |= check_file_access_inline(options->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], "pkcs11-engine") && p[1] && !p[3])<br> + {<br> + VERIFY_PERMISSION(OPT_P_GENERAL);<br> + options->pkcs11_engine = p[1];<br> + options->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], "use-prediction-resistance") && !p[1])<br> {<br> @@ -8156,6 +8192,20 @@ add_option(struct options *options,<br> VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INLINE);<br> options->cert_file = p[1];<br> options->cert_file_inline = is_inline;<br> + if (is_pkcs11_uri(p[1]))<br> + {<br> +#ifndef HAVE_OPENSSL_ENGINE<br> + msg(msglevel, "Use of PKCS11 uri as cert and key file names requires OpenSSL "<br> + "ENGINE support which is missing in this binary.")<br> +#else<br> + options->priv_key_file = p[1];<br> + options->cert_file_is_pkcs11_uri = true;<br> + }<br> + else<br> + {<br> + options->cert_file_is_pkcs11_uri = false;<br> +#endif /* HAVE_OPENSSL_ENGINE */<br> + }<br> }<br> else if (streq(p[0], "extra-certs") && p[1] && !p[2])<br> {<br> @@ -8238,6 +8288,20 @@ add_option(struct options *options,<br> VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INLINE);<br> options->priv_key_file = p[1];<br> options->priv_key_file_inline = is_inline;<br> + if (is_pkcs11_uri(p[1]))<br> + {<br> +#ifndef HAVE_OPENSSL_ENGINE<br> + msg(msglevel, "Use of PKCS11 uri as cert and key file names requires OpenSSL "<br> + "ENGINE support which is missing in this binary.")<br> +#else<br> + options->cert_file = p[1];<br> + options->cert_file_is_pkcs11_uri = true;<br> + }<br> + else<br> + {<br> + options->cert_file_is_pkcs11_uri = false;<br> +#endif /* HAVE_OPENSSL_ENGINE */<br> + }<br> }<br> else if (streq(p[0], "tls-version-min") && p[1] && !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->cert_file_is_pkcs11_uri)<br> + {<br> + if (!tls_ctx_use_pkcs11_engine(new_ctx, options->cert_file,<br> + options->pkcs11_engine,<br> + options->pkcs11_engine_module))<br> + {<br> + msg(M_WARN, "Cannot load certificate \"%s\" using PKCS#11 engine",<br> + options->cert_file);<br> + goto err;<br> + }<br> + }<br> +#endif<br> #ifdef ENABLE_PKCS11<br> else if (options->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->cert_file, options->cert_file_inline);<br> }<br> <br> - if (options->priv_key_file)<br> + if (options->priv_key_file && !options->cert_file_is_pkcs11_uri)<br> {<br> if (0 != tls_ctx_load_priv_file(new_ctx, options->priv_key_file,<br> options->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 <openssl/ui.h><br> +#include <openssl/engine.h><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(&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(&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, "ok") == 0);<br> + UI_set_result(ui, uis, token_pass.password);<br> + }<br> + case UIT_INFO:<br> + msg(M_INFO, "INFO prompt from token: <%s>", prompt);<br> + break;<br> + case UIT_ERROR:<br> + msg(M_INFO, "ERROR prompt from token: <%s>", 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, "%s for %s", 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("dynamic");<br> + if (e)<br> + {<br> + if (!ENGINE_ctrl_cmd_string(e, "SO_PATH", engine_id, 0)<br> + || !ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0))<br> + {<br> + ENGINE_free(e);<br> + e = NULL;<br> + }<br> + }<br> + if (!e)<br> + {<br> + msg(M_WARN, "PKCS11 engine <%s> not available", 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 = "pkcs11";<br> + }<br> +<br> + msg(D_SHOW_PKCS11, "Loading pkcs11 engine <%s> with module <%s>",<br> + engine_id, (module_path ? module_path : "unspecified"));<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, "MODULE_PATH", module_path, 0);<br> + }<br> + ENGINE_ctrl_cmd(e, "SET_USER_INTERFACE", 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("openvpn");<br> + if (!ui)<br> + {<br> + msg(M_WARN, "Failed to setup UI callback for engine");<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, "SET_CALLBACK_DATA", 0, (void *)cert_id, NULL, 0);<br> +<br> + msg (D_SHOW_PKCS11, "Loading certificate <%s> using engine", params.cert_id);<br> +<br> + if (!ENGINE_ctrl_cmd(e, "LOAD_CERT_CTRL", 0, &params, NULL, 0)<br> + || !params.cert || !SSL_CTX_use_certificate(tls_ctx->ctx, params.cert))<br> + {<br> + msg (M_WARN, "Failed to load certificate <%s>", cert_id);<br> + goto finish;<br> + }<br> +<br> + msg (D_SHOW_PKCS11, "Loading private key <%s> using engine", 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->ctx, pkey))<br> + {<br> + msg (M_WARN, "Failed to set private key <%s> using engine", 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>
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, ¶ms, 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
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
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, ¶ms, 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 */