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..f41d12f3 100644
--- a/doc/man-sections/tls-options.rst
+++ b/doc/man-sections/tls-options.rst
@@ -116,6 +116,29 @@ 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. Optionally,
+  the engine and provider module may be included as custom attributes in the
+  uri as in the example below:
+
+  ::
+
+    --cert 'pkcs11:token=foo;serial=bar;id=nnn;engine=pkcs11;provider=p11-kit-proxy.so'
+
+  Here the engine name :code:`pkcs11` could be a valid engine-id or path to a
+  shared object. Shared objects in non-standard locations would need to be
+  specified with full path.
+
+  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,6 +231,9 @@ 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``,
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index db460796..21241846 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -915,6 +915,12 @@ struct pull_filter_list
     struct pull_filter *tail;
 };
 
+static bool
+is_pkcs11_uri(const char *uri)
+{
+    return (!strncmp(uri, "pkcs11:", 7));
+}
+
 static const char *
 pull_filter_type_name(int type)
 {
@@ -2587,6 +2593,13 @@ 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
+#endif
 #ifdef ENABLE_PKCS11
         if (options->pkcs11_providers[0])
         {
@@ -3455,8 +3468,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 +3482,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,
@@ -8156,6 +8175,19 @@ 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.")
+#endif
+            options->priv_key_file = p[1];
+            options->cert_file_is_pkcs11_uri = true;
+        }
+        else
+        {
+            options->cert_file_is_pkcs11_uri = false;
+        }
     }
     else if (streq(p[0], "extra-certs") && p[1] && !p[2])
     {
@@ -8238,6 +8270,19 @@ 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.")
+#endif
+            options->cert_file = p[1];
+            options->cert_file_is_pkcs11_uri = true;
+        }
+        else
+        {
+            options->cert_file_is_pkcs11_uri = false;
+        }
     }
     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..ab7b2f62 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -659,6 +659,9 @@ struct options
 
     /* data channel crypto flags set by push/pull. Reuses the CO_* crypto_flags */
     unsigned int data_channel_crypto_flags;
+
+    /* cert and key files are specified as pkcs11 uri */
+    bool cert_file_is_pkcs11_uri;
 };
 
 #define streq(x, y) (!strcmp((x), (y)))
diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c
index b16f6bcc..93101e7c 100644
--- a/src/openvpn/ssl.c
+++ b/src/openvpn/ssl.c
@@ -641,6 +641,17 @@ 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))
+        {
+            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 +683,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..96d6e64d 100644
--- a/src/openvpn/ssl_backend.h
+++ b/src/openvpn/ssl_backend.h
@@ -568,4 +568,7 @@ void get_highest_preference_tls_cipher(char *buf, int size);
  */
 const char *get_ssl_library_version(void);
 
+int tls_ctx_use_pkcs11_engine(struct tls_root_ctx *ssl_ctx,
+                       const char *uri);
+
 #endif /* SSL_BACKEND_H_ */
diff --git a/src/openvpn/ssl_openssl.c b/src/openvpn/ssl_openssl.c
index 3120c51a..b8a91c59 100644
--- a/src/openvpn/ssl_openssl.c
+++ b/src/openvpn/ssl_openssl.c
@@ -2251,4 +2251,223 @@ 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 */
+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);
+
+    printf("uri = %s\n", uri);
+    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, "Unknown INFO prompt from token: <%s>", UI_get0_output_string(uis));
+            break;
+       case UIT_ERROR:
+            msg(M_INFO, "Unknown ERROR prompt from token: <%s>", UI_get0_output_string(uis));
+            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;
+}
+
+/*
+ * POP an attribute from a pkcs11_uri and return its value.
+ * The URI is of the form:
+ * pkcs11:attr1=value1;attr2=value2;...."
+ * On return the speciifed attribute is removed from the uri.
+ */
+static const char *
+pkcs11_uri_pop(char *uri, const char *attr, struct gc_arena *gc)
+{
+    ASSERT(attr);
+
+    char *start = strstr(uri, attr);
+    char *val;
+    int skip = strlen(attr) + 1; /* attribute name and folloiwng = sign */
+
+    if (!start || start[strlen(attr)] != '=')
+    {
+        return NULL;
+    }
+    const char *end = strchr(start, ';');
+    if (!end)
+    {
+        val = string_alloc(start + skip , gc);
+        *start = '\0';
+        return val;
+    }
+
+    int len = end - (start + skip) + 1;
+    val = gc_malloc(len, false, gc);
+
+    strncpynt(val, start + skip, len);
+
+    /* remove the matched attr=value; part */
+    memmove(start, end + 1, strlen(end+1)+1);
+
+    return val;
+}
+
+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 && module_path)
+    {
+        ENGINE_ctrl_cmd_string(e, "MODULE_PATH", module_path, 0);
+    }
+
+    if (e)
+    {
+        ENGINE_ctrl_cmd(e, "SET_USER_INTERFACE", 0, ui, NULL, 0);
+    }
+
+    return e;
+}
+
+int
+tls_ctx_use_pkcs11_engine(struct tls_root_ctx *tls_ctx, const char *uri)
+{
+    char *tmp_uri;
+    const char *engine_id, *module_path, *cert_id;
+    struct gc_arena gc = gc_new();
+    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);
+
+    tmp_uri = string_alloc(uri, &gc);
+    engine_id = pkcs11_uri_pop(tmp_uri, "engine", &gc);
+    module_path = pkcs11_uri_pop(tmp_uri, "provider", &gc);
+    cert_id = tmp_uri;
+
+    struct
+    {
+        const char *cert_id;
+        X509* cert;
+    } params = {cert_id, NULL};
+
+    ENGINE *e = setup_pkcs11_engine(engine_id, module_path, ui);
+    if (!e || !ENGINE_init(e))
+    {
+        goto cleanup;
+    }
+    ENGINE_ctrl_cmd(e, "SET_CALLBACK_DATA", 0, (void *)uri, NULL, 0);
+
+    msg (D_SHOW_PKCS11, "Loading certificate <%s> using engine", 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", cert_id);
+
+    pkey = ENGINE_load_private_key(e, cert_id, ui, (void *)uri);
+    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);
+    gc_free(&gc);
+    UI_destroy_method(ui);
+
+    return ret;
+}
+
+#endif /* HAVE_OPENSSL_ENGINE */
+
+#endif /* ENABLE_CRYPTO_OPENSSL */
