diff --git a/Changes.rst b/Changes.rst
index 0323a7f7..a556de44 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..ccc06a2e 100644
--- a/doc/man-sections/tls-options.rst
+++ b/doc/man-sections/tls-options.rst
@@ -116,6 +116,19 @@ 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. 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 (e.g., :code: `type=cert;` or :code: `type=private;` should not
+  be included)
+
 --crl-verify args
   Check peer certificate against a Certificate Revocation List.
 
@@ -208,11 +221,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 7e146db9..4933546c 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -530,6 +530,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"
@@ -913,6 +918,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)
 {
@@ -2606,6 +2617,17 @@ options_postprocess_verify_ce(const struct options *options,
     if (options->tls_server || options->tls_client)
     {
         check_ca_required(options);
+#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])
         {
@@ -3445,8 +3467,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,
@@ -3456,9 +3481,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,
@@ -8078,6 +8106,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])
     {
@@ -8137,6 +8173,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])
     {
@@ -8219,6 +8269,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 b0e40cb7..19911b01 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -658,6 +658,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 b2dc48be..7e8940c6 100644
--- a/src/openvpn/ssl.c
+++ b/src/openvpn/ssl.c
@@ -647,6 +647,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])
     {
@@ -678,7 +691,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 c877e194..dbab721e 100644
--- a/src/openvpn/ssl_backend.h
+++ b/src/openvpn/ssl_backend.h
@@ -569,4 +569,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 241206fb..137234a5 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 */
