From patchwork Wed Sep 22 21:12:46 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [Openvpn-devel,1/9] A built-in provider for using external key with OpenSSL 3.0 X-Patchwork-Submitter: Selva Nair X-Patchwork-Id: 1972 Message-Id: <20210922211254.7570-2-selva.nair@gmail.com> To: openvpn-devel@lists.sourceforge.net Date: Wed, 22 Sep 2021 17:12:46 -0400 From: selva.nair@gmail.com List-Id: From: Selva Nair Hooking into callbacks in RSA_METHOD and EVP_PKEY_METHOD structures is deprecated in OpenSSL 3.0. For signing with external keys that are not exportable (tokens, stores, etc.) requires a custom provider interface so that key operations are done under its context. A single provider is enough for handling all external keys we support -- management-external-key, cryptoapicert(CNG) and pkcs11-helper. The series of patches starting with this implement such a provider. To activate the use of the provider, essentially, the SSL_CTX object is created with property string set to to prioritize our provider. In the provider we implement only keymgmt and signature operations. All other operations get directly used from the default provider. However, signature operations include verify using peer's public key as well. In particular, we get called for both DigestVerify and DigestSign operations. For the former we call back OpenSSL, for the latter we compute the digest using OpenSSL and then pass it to the backend for signature. So a lot of glue code is needed and this makes the patches somewhat large even after splitting into many commits. This patch implements only the provider_init function so that it can be loaded, but has no capabilities. The required interfaces are added in following commits. Signed-off-by: Selva Nair --- configure.ac | 11 +++ src/openvpn/Makefile.am | 1 + src/openvpn/xkey_common.h | 42 +++++++++ src/openvpn/xkey_provider.c | 177 ++++++++++++++++++++++++++++++++++++ 4 files changed, 231 insertions(+) create mode 100644 src/openvpn/xkey_common.h create mode 100644 src/openvpn/xkey_provider.c diff --git a/configure.ac b/configure.ac index 7c2ead6a..0390a05e 100644 --- a/configure.ac +++ b/configure.ac @@ -821,6 +821,17 @@ if test "${with_crypto_library}" = "openssl"; then AC_DEFINE([HAVE_OPENSSL_ENGINE], [1], [OpenSSL engine support available]) fi + have_openssl_provider="yes" + AC_CHECK_FUNCS( + [OSSL_PROVIDER_load] + , + , + [have_openssl_provider="no"; break] + ) + if test "${have_openssl_provider}" = "yes"; then + AC_DEFINE([HAVE_XKEY_PROVIDER], [1], [External key loading provider can be used]) + fi + AC_CHECK_FUNC( [EVP_aes_256_gcm], , diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am index 5883c291..432efe73 100644 --- a/src/openvpn/Makefile.am +++ b/src/openvpn/Makefile.am @@ -128,6 +128,7 @@ openvpn_SOURCES = \ tls_crypt.c tls_crypt.h \ tun.c tun.h \ vlan.c vlan.h \ + xkey_provider.c xkey_common.h \ win32.h win32.c \ win32-util.h win32-util.c \ cryptoapi.h cryptoapi.c diff --git a/src/openvpn/xkey_common.h b/src/openvpn/xkey_common.h new file mode 100644 index 00000000..eb31604f --- /dev/null +++ b/src/openvpn/xkey_common.h @@ -0,0 +1,42 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2021 Selva Nair + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef XKEY_PUBLIC_H_ +#define XKEY_PUBLIC_H_ + +#ifdef HAVE_XKEY_PROVIDER + +#include +#include + +/** + * Initialization function for OpenVPN external key provider for OpenSSL + * Follows the signature of OSSL_PROVIDER init + */ +OSSL_provider_init_fn xkey_provider_init; + +#endif /* HAVE_XKEY_PROVIDER */ + +#define XKEY_PROV_PROPS "provider=ovpn.xkey" + +#endif /* XKEY_PUBLIC_H_ */ diff --git a/src/openvpn/xkey_provider.c b/src/openvpn/xkey_provider.c new file mode 100644 index 00000000..9d19d37d --- /dev/null +++ b/src/openvpn/xkey_provider.c @@ -0,0 +1,177 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2021 Selva Nair + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#elif defined(_MSC_VER) +#include "config-msvc.h" +#endif + +#ifdef HAVE_XKEY_PROVIDER + +#include "syshead.h" +#include "error.h" +#include "buffer.h" +#include "xkey_common.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +/* A descriptive name */ +static const char *provname = "OpenVPN External Key Provider"; + +typedef struct +{ + const OSSL_CORE_HANDLE *core; + OSSL_PROVIDER *deflt; /* default provider that we load for delegating some ops */ + OSSL_LIB_CTX *libctx; /* libctx of the core context in which we are running */ +} XKEY_PROVIDER_CTX; + +/* main provider interface */ + +/* provider callbacks we implement */ +static OSSL_FUNC_provider_query_operation_fn query_operation; +static OSSL_FUNC_provider_gettable_params_fn gettable_params; +static OSSL_FUNC_provider_get_params_fn get_params; +static OSSL_FUNC_provider_teardown_fn teardown; + +static const OSSL_ALGORITHM * +query_operation(void *provctx, int op, int *no_store) +{ + dmsg(D_LOW, "In xkey provider query op with op = %d", op); + + *no_store = 0; + + switch (op) + { + case OSSL_OP_SIGNATURE: + return NULL; + + case OSSL_OP_KEYMGMT: + return NULL; + + default: + break; + } + return NULL; +} + +static const OSSL_PARAM * +gettable_params(void *provctx) +{ + dmsg(D_LOW, "In xkey provider gettable_params"); + + static const OSSL_PARAM param_types[] = { + OSSL_PARAM_DEFN(OSSL_PROV_PARAM_NAME, OSSL_PARAM_UTF8_PTR, NULL, 0), + OSSL_PARAM_END + }; + + return param_types; +} +static int +get_params(void *provctx, OSSL_PARAM params[]) +{ + OSSL_PARAM *p; + + dmsg(D_LOW, "In xkey provider get_params"); + + p = OSSL_PARAM_locate(params, OSSL_PROV_PARAM_NAME); + if (p) + { + return (OSSL_PARAM_set_utf8_ptr(p, provname) != 0); + } + + return 0; +} + +static void +teardown(void *provctx) +{ + dmsg(D_LOW, "In xkey provider teardown"); + + XKEY_PROVIDER_CTX *prov = provctx; + if (prov && prov->deflt) + { + OSSL_PROVIDER_unload(prov->deflt); + } + free(prov); +} + +static const OSSL_DISPATCH dispatch_table[] = { + {OSSL_FUNC_PROVIDER_GETTABLE_PARAMS, (void (*)(void))gettable_params}, + {OSSL_FUNC_PROVIDER_GET_PARAMS, (void (*)(void))get_params}, + {OSSL_FUNC_PROVIDER_QUERY_OPERATION, (void (*)(void))query_operation}, + {OSSL_FUNC_PROVIDER_TEARDOWN, (void (*)(void))teardown}, + {0, NULL} +}; + +int +xkey_provider_init(const OSSL_CORE_HANDLE *handle, const OSSL_DISPATCH *in, + const OSSL_DISPATCH **out, void **provctx) +{ + XKEY_PROVIDER_CTX *prov; + OSSL_FUNC_core_get_libctx_fn *c_get_libctx = NULL; + + dmsg(D_LOW, "In xkey provider init"); + + prov = calloc(sizeof(*prov), 1); + if (!prov) + { + msg(M_NONFATAL, "xkey_provider_init: out of memory"); + return 0; + } + + /* get our libctx */ + for (; in->function_id != 0; in++) + { + if (in->function_id == OSSL_FUNC_CORE_GET_LIBCTX) + { + c_get_libctx = OSSL_FUNC_core_get_libctx(in); + } + } + + if (c_get_libctx) + { + prov->libctx = (OSSL_LIB_CTX *)c_get_libctx(handle); + } + prov->core = handle; + + prov->deflt = OSSL_PROVIDER_load(prov->libctx, "default"); + if (!prov->deflt) + { + msg(M_NONFATAL, "xkey_provider_init: default provider could not be loaded"); + } + + *out = dispatch_table; + *provctx = prov; + + return 1; +} + +#endif /* HAVE_XKEY_PROVIDER */ From patchwork Wed Sep 22 21:12:47 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [Openvpn-devel,2/9] Initialize the xkey provider and use it in SSL context X-Patchwork-Submitter: Selva Nair X-Patchwork-Id: 1967 Message-Id: <20210922211254.7570-3-selva.nair@gmail.com> To: openvpn-devel@lists.sourceforge.net Date: Wed, 22 Sep 2021 17:12:47 -0400 From: selva.nair@gmail.com List-Id: From: Selva Nair - The provider is loaded during crypto initialization and unloaded in uninit. The SSL server and client context are created with properties indicating preference for this provider. This could be made conditional on use of external keys, but it can't hurt if loaded and used otherwise too. Useful to get the code exercised at least for a period of testing. As the provider is empty, no functionality gets delegated to it as yet. Verb 4 logs with enable-debug will just show the provider_init and teardown called. Signed-off-by: Selva Nair --- src/openvpn/crypto_openssl.c | 19 +++++++++++++++++++ src/openvpn/openssl_compat.h | 12 ++++++++++++ src/openvpn/ssl_openssl.c | 7 +++++-- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/openvpn/crypto_openssl.c b/src/openvpn/crypto_openssl.c index 419265a5..5d7fa847 100644 --- a/src/openvpn/crypto_openssl.c +++ b/src/openvpn/crypto_openssl.c @@ -42,6 +42,7 @@ #include "crypto.h" #include "crypto_backend.h" #include "openssl_compat.h" +#include "xkey_common.h" #include #include @@ -75,6 +76,8 @@ static bool engine_initialized = false; /* GLOBAL */ static ENGINE *engine_persist = NULL; /* GLOBAL */ +static void *xkey_prov; + /* Try to load an engine in a shareable library */ static ENGINE * try_load_engine(const char *engine) @@ -161,6 +164,15 @@ crypto_init_lib(void) OPENSSL_config(NULL); #endif #endif /* _WIN32 */ + +#ifdef HAVE_XKEY_PROVIDER + if (!xkey_prov) + { + OSSL_PROVIDER_add_builtin(NULL, "ovpn.xkey", xkey_provider_init); + xkey_prov = OSSL_PROVIDER_load(NULL, "ovpn.xkey"); + } +#endif + /* * If you build the OpenSSL library and OpenVPN with * CRYPTO_MDEBUG, you will get a listing of OpenSSL @@ -190,6 +202,13 @@ crypto_uninit_lib(void) engine_initialized = false; } #endif +#ifdef HAVE_XKEY_PROVIDER + if (xkey_prov) + { + OSSL_PROVIDER_unload(xkey_prov); + } +#endif + xkey_prov = NULL; } void diff --git a/src/openvpn/openssl_compat.h b/src/openvpn/openssl_compat.h index ce8e2b36..3dcdde4d 100644 --- a/src/openvpn/openssl_compat.h +++ b/src/openvpn/openssl_compat.h @@ -718,4 +718,16 @@ SSL_CTX_set_max_proto_version(SSL_CTX *ctx, long tls_ver_max) return 1; } #endif /* if OPENSSL_VERSION_NUMBER < 0x10100000L && !defined(ENABLE_CRYPTO_WOLFSSL) */ + +/** Mimics SSL_CTX_new_ex for OpenSSL < 3 */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L +static inline SSL_CTX * +SSL_CTX_new_ex(void *libctx, const char *propq, const SSL_METHOD *method) +{ + (void) libctx; + (void) propq; + return SSL_CTX_new(method); +} +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */ + #endif /* OPENSSL_COMPAT_H_ */ diff --git a/src/openvpn/ssl_openssl.c b/src/openvpn/ssl_openssl.c index 241206fb..61256620 100644 --- a/src/openvpn/ssl_openssl.c +++ b/src/openvpn/ssl_openssl.c @@ -45,6 +45,7 @@ #include "ssl_common.h" #include "base64.h" #include "openssl_compat.h" +#include "xkey_common.h" #ifdef ENABLE_CRYPTOAPI #include "cryptoapi.h" @@ -109,7 +110,8 @@ tls_ctx_server_new(struct tls_root_ctx *ctx) { ASSERT(NULL != ctx); - ctx->ctx = SSL_CTX_new(SSLv23_server_method()); + const char *propq = "?" XKEY_PROV_PROPS; + ctx->ctx = SSL_CTX_new_ex(NULL, propq, SSLv23_server_method()); if (ctx->ctx == NULL) { @@ -127,7 +129,8 @@ tls_ctx_client_new(struct tls_root_ctx *ctx) { ASSERT(NULL != ctx); - ctx->ctx = SSL_CTX_new(SSLv23_client_method()); + const char *propq = "?" XKEY_PROV_PROPS; + ctx->ctx = SSL_CTX_new_ex(NULL, propq, SSLv23_client_method()); if (ctx->ctx == NULL) { From patchwork Wed Sep 22 21:12:48 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [Openvpn-devel,3/9] Implement keymgmt in the xkey provider X-Patchwork-Submitter: Selva Nair X-Patchwork-Id: 1964 Message-Id: <20210922211254.7570-4-selva.nair@gmail.com> To: openvpn-devel@lists.sourceforge.net Date: Wed, 22 Sep 2021 17:12:48 -0400 From: selva.nair@gmail.com List-Id: From: Selva Nair A minimal set of functions for keymgmt are implemented. No support for external key import as yet, only native keys. Support for native keys is required as all public key ops will get delegated to us once SSL_CTX is initialized in our context. This will include digest-verify using peer's public key. To test key import, use the normal --key operation, change use_xkey_provider() to return true. The key will import correctly but signing will fail as there is no signature interface in the provider yet. (use enable-debug for the dmsg() chatter). Sign-verify operations for native keys is in the next commit. Signed-off-by: Selva Nair --- src/openvpn/xkey_provider.c | 353 ++++++++++++++++++++++++++++++++++++ 1 file changed, 353 insertions(+) diff --git a/src/openvpn/xkey_provider.c b/src/openvpn/xkey_provider.c index 9d19d37d..4e5ed130 100644 --- a/src/openvpn/xkey_provider.c +++ b/src/openvpn/xkey_provider.c @@ -43,6 +43,9 @@ #include #include +/* propq set all on all ops we implement */ +static const char *const props = XKEY_PROV_PROPS; + /* A descriptive name */ static const char *provname = "OpenVPN External Key Provider"; @@ -53,6 +56,353 @@ typedef struct OSSL_LIB_CTX *libctx; /* libctx of the core context in which we are running */ } XKEY_PROVIDER_CTX; +typedef enum +{ + ORIGIN_NONAME = 0, + OPENSSL_NATIVE, /* native key imported in */ + EXTERNAL_KEY +} XKEY_ORIGIN; + +/* + * XKEY_KEYDATA: Our keydata encapsulation: + * --------------------------------------- + * We keep an opaque handle provided by the backend for the loaded + * key. It's passed back to the backend for any operation on private + * keys --- in practice, sign() op only. + * + * We also keep the public key in the form of a native OpenSSL EVP_PKEY. + * This allows us to do all public ops by calling ops in the default provider. + */ +typedef struct +{ + /* opaque handle dependent on KEY_ORIGIN -- could be NULL */ + void *handle; + /* associated public key as an openvpn native key */ + EVP_PKEY *pubkey; + /* origin of key -- native or external */ + XKEY_ORIGIN origin; + int refcount; /* reference count */ +} XKEY_KEYDATA; + +#define KEYTYPE(key) ((key)->pubkey ? EVP_PKEY_get_id((key)->pubkey) : 0) +#define KEYSIZE(key) ((key)->pubkey ? EVP_PKEY_get_size((key)->pubkey) : 0) + +/* keymgmt provider */ + +/* keymgmt callbacks we implement */ +static OSSL_FUNC_keymgmt_new_fn keymgmt_new; +static OSSL_FUNC_keymgmt_free_fn keymgmt_free; +static OSSL_FUNC_keymgmt_load_fn keymgmt_load; +static OSSL_FUNC_keymgmt_has_fn keymgmt_has; +static OSSL_FUNC_keymgmt_match_fn keymgmt_match; +static OSSL_FUNC_keymgmt_import_fn rsa_keymgmt_import; +static OSSL_FUNC_keymgmt_import_fn ec_keymgmt_import; +static OSSL_FUNC_keymgmt_import_types_fn keymgmt_types; +static OSSL_FUNC_keymgmt_get_params_fn keymgmt_get_params; +static OSSL_FUNC_keymgmt_gettable_params_fn keymgmt_gettable; +static OSSL_FUNC_keymgmt_query_operation_name_fn rsa_keymgmt_name; +static OSSL_FUNC_keymgmt_query_operation_name_fn ec_keymgmt_name; + +static XKEY_KEYDATA * +keydata_new() +{ + dmsg(D_LOW, "In keydata_new"); + + XKEY_KEYDATA *key = calloc(sizeof(*key), 1); + if (!key) + { + msg(M_NONFATAL, "xkey_keydata_new: out of memory"); + } + + return key; +} + +static void +keydata_free(XKEY_KEYDATA *key) +{ + dmsg(D_LOW, "In keydata_free"); + + if (!key || --key->refcount <= 0) /* free when refcount goes to zero */ + { + return; + } + if (key->pubkey) + { + EVP_PKEY_free(key->pubkey); + } + free(key); +} + +static void * +keymgmt_new(void *provctx) +{ + dmsg(D_LOW, "In keymgmt_new"); + + XKEY_KEYDATA *key = keydata_new(); + return key; +} + +static void * +keymgmt_load(const void *reference, size_t reference_sz) +{ + dmsg(D_LOW, "In keymgmt_load"); + + return NULL; +} + +/** + * Key import function + * When key operations like sign/verify are done in our context + * the key gets imported into us. + * + * For native keys we get called with standard OpenSSL params + * appropriate for the key. We just use it to create a native + * EVP_PKEY from params and assign to keydata->handle. + * + * Import of external keys -- to be implemented + */ +static int +keymgmt_import(void *keydata, int selection, const OSSL_PARAM params[], const char *name) +{ + dmsg(D_LOW, "In keymgmt_import"); + + XKEY_KEYDATA *key = keydata; + + /* Our private key is immutable -- we import only if keydata is empty */ + + if (key->handle || key->pubkey + || !(selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY)) + { + msg(M_WARN, "Error: keymgmt_import: keydata not empty or selection not allowed"); + return 0; + } + + /* create a native public key and assign it to key->pubkey */ + EVP_PKEY *pkey = NULL; + + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, name, NULL); + if (!ctx + || (EVP_PKEY_fromdata_init(ctx) != 1) + || (EVP_PKEY_fromdata(ctx, &pkey, selection, (OSSL_PARAM*) params) !=1)) + { + msg(M_WARN, "Error: keymgmt_import failed for key type <%s>", name); + if (pkey) + { + EVP_PKEY_free(pkey); + } + return 0; + } + + key->pubkey = pkey; + key->origin = OPENSSL_NATIVE; + if (selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) + { + /* just use the same key as handle -- do not up-ref */ + key->handle = pkey; + } + return 1; +} + +static int +rsa_keymgmt_import(void *keydata, int selection, const OSSL_PARAM params[]) +{ + dmsg(D_LOW, "In rsa_keymgmt_import"); + return keymgmt_import(keydata, selection, params, "RSA"); +} + +static int +ec_keymgmt_import(void *keydata, int selection, const OSSL_PARAM params[]) +{ + dmsg(D_LOW, "In ec_keymgmt_import"); + return keymgmt_import(keydata, selection, params, "EC"); +} + +/* This function has to exist for key import to work + * though we do not support import of individual params + * like n or e. We simply return an empty list here for + * both rsa and ec, which works. + */ +static const OSSL_PARAM * +keymgmt_import_types(int selection) +{ + dmsg(D_LOW, "In keymgmt import types"); + + static const OSSL_PARAM key_types[] = { + OSSL_PARAM_END + }; + + if (selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) + { + return key_types; + } + return NULL; +} + +/* We do not support any key export */ + +static void +keymgmt_free(void *keydata) +{ + dmsg(D_LOW, "In keymgmt_free"); + keydata_free(keydata); +} + +static int +keymgmt_has(const void *keydata, int selection) +{ + dmsg(D_LOW, "In keymgmt_has with selection = %d", selection); + + const XKEY_KEYDATA *key = keydata; + int ok = (key != NULL); + + if (selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) + { + ok = ok && key->pubkey; + } + if (selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) + { + ok = ok && key->handle; + } + + return ok; +} + +static int +keymgmt_match(const void *keydata1, const void *keydata2, int selection) +{ + const XKEY_KEYDATA *key1 = keydata1; + const XKEY_KEYDATA *key2 = keydata2; + + dmsg(D_LOW, "In keymgmt_match"); + int ret = 1; + + if (selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY + && key1->pubkey && key2->pubkey) + { + ret = ret && EVP_PKEY_eq(key1->pubkey, key2->pubkey); + } + + if (selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) + { + ret = ret && (key1->origin == key2->origin); /* origins must match */ + if (key1->origin == OPENSSL_NATIVE) + { + ret = ret && EVP_PKEY_eq(key1->pubkey, key2->pubkey); + } + /* private key is opaque. We can only check identity of the handle */ + else + { + ret = ret && (key1->handle == key2->handle); + } + } + + return ret; +} + +/* A minimal set of key params that we can return */ +static const OSSL_PARAM * +keymgmt_gettable_params(void *provctx) +{ + dmsg(D_LOW, "In keymgmt_gettable"); + static OSSL_PARAM gettable[] = { + OSSL_PARAM_int(OSSL_PKEY_PARAM_BITS, NULL), + OSSL_PARAM_int(OSSL_PKEY_PARAM_SECURITY_BITS, NULL), + OSSL_PARAM_int(OSSL_PKEY_PARAM_MAX_SIZE, NULL), + OSSL_PARAM_END + }; + return gettable; +} + +static int +keymgmt_get_params(void *keydata, OSSL_PARAM *params) +{ + dmsg(D_LOW, "In keymgmt_get_params"); + + XKEY_KEYDATA *key = keydata; + if (!key || !key->pubkey) + { + return 0; + } + + return EVP_PKEY_get_params(key->pubkey, params); +} + +/** + * If the key is an encapsulated native key, we just call + * EVP_PKEY_set_params in the default context. Only those params + * supported by the default provider would work in that case. + */ +static int +keymgmt_set_params(void *keydata, const OSSL_PARAM *params) +{ + XKEY_KEYDATA *key = keydata; + ASSERT(key); + + if (key->origin != OPENSSL_NATIVE) + { + return 0; /* to be implemented */ + } + else if (key->handle == NULL) /* once handle is set our key is immutable */ + { + /* pubkey is always native -- just delegate */ + return EVP_PKEY_set_params(key->pubkey, (OSSL_PARAM *)params); + } + else + { + msg(M_WARN, "xkey keymgmt_set_params: key is immutable"); + } + return 1; +} + +static const char * +rsa_keymgmt_name(int id) +{ + dmsg(D_LOW, "In rsa_keymgmt_name"); + return "RSA"; +} + +static const char * +ec_keymgmt_name(int id) +{ + dmsg(D_LOW, "In ec_keymgmt_name"); + return "EC"; +} + +static const OSSL_DISPATCH rsa_keymgmt_functions[] = { + {OSSL_FUNC_KEYMGMT_NEW, (void (*)(void))keymgmt_new}, + {OSSL_FUNC_KEYMGMT_FREE, (void (*)(void))keymgmt_free}, + {OSSL_FUNC_KEYMGMT_LOAD, (void (*)(void))keymgmt_load}, + {OSSL_FUNC_KEYMGMT_HAS, (void (*)(void))keymgmt_has}, + {OSSL_FUNC_KEYMGMT_MATCH, (void (*)(void))keymgmt_match}, + {OSSL_FUNC_KEYMGMT_IMPORT, (void (*)(void))rsa_keymgmt_import}, + {OSSL_FUNC_KEYMGMT_IMPORT_TYPES, (void (*)(void))keymgmt_import_types}, + {OSSL_FUNC_KEYMGMT_GETTABLE_PARAMS, (void (*) (void)) keymgmt_gettable_params}, + {OSSL_FUNC_KEYMGMT_GET_PARAMS, (void (*) (void)) keymgmt_get_params}, + {OSSL_FUNC_KEYMGMT_QUERY_OPERATION_NAME, (void (*)(void))rsa_keymgmt_name}, + {0, NULL } +}; + +static const OSSL_DISPATCH ec_keymgmt_functions[] = { + {OSSL_FUNC_KEYMGMT_NEW, (void (*)(void))keymgmt_new}, + {OSSL_FUNC_KEYMGMT_FREE, (void (*)(void))keymgmt_free}, + {OSSL_FUNC_KEYMGMT_LOAD, (void (*)(void))keymgmt_load}, + {OSSL_FUNC_KEYMGMT_HAS, (void (*)(void))keymgmt_has}, + {OSSL_FUNC_KEYMGMT_MATCH, (void (*)(void))keymgmt_match}, + {OSSL_FUNC_KEYMGMT_IMPORT, (void (*)(void))ec_keymgmt_import}, + {OSSL_FUNC_KEYMGMT_IMPORT_TYPES, (void (*)(void))keymgmt_import_types}, + {OSSL_FUNC_KEYMGMT_GETTABLE_PARAMS, (void (*) (void)) keymgmt_gettable_params}, + {OSSL_FUNC_KEYMGMT_GET_PARAMS, (void (*) (void)) keymgmt_get_params}, + {OSSL_FUNC_KEYMGMT_QUERY_OPERATION_NAME, (void (*)(void))ec_keymgmt_name}, + {0, NULL } +}; + +const OSSL_ALGORITHM keymgmts[] = { + {"RSA:rsaEncryption", props, rsa_keymgmt_functions}, + {"RSA-PSS:RSASSA-PSS", props, rsa_keymgmt_functions}, + {"EC:id-ecPublicKey", props, ec_keymgmt_functions}, + {NULL, NULL, NULL} +}; + /* main provider interface */ /* provider callbacks we implement */ @@ -74,6 +424,9 @@ query_operation(void *provctx, int op, int *no_store) return NULL; case OSSL_OP_KEYMGMT: + return keymgmts; + + case OSSL_OP_STORE: return NULL; default: From patchwork Wed Sep 22 21:12:49 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [Openvpn-devel,4/9] Implement provider interface for signature operations X-Patchwork-Submitter: Selva Nair X-Patchwork-Id: 1970 Message-Id: <20210922211254.7570-5-selva.nair@gmail.com> To: openvpn-devel@lists.sourceforge.net Date: Wed, 22 Sep 2021 17:12:49 -0400 From: selva.nair@gmail.com List-Id: From: Selva Nair - DigestVerify and Sign operations for native keys are implemented. DigestVerify ops for native keys are needed because operations on peer's public key will get delegated to us. Sign operations on native keys are also implemented which now allows us to enable the provider in SSL_CTX with private key in a file (for testing). To test use --key and --cert in the config. Provider function calls are logged at verb = 4 if configured with --enable-debug. As external keys are currently supported by hooks into RSA or EVP_PKEY interface, it will continue work bypassing the provider. Even in those cases ops with peer's public key are done by our provider as those are initialized by OpenSSL using the provider interface. Subsequent commits will move all these into the provider framework. Signed-off-by: Selva Nair --- src/openvpn/xkey_common.h | 36 +++ src/openvpn/xkey_provider.c | 486 +++++++++++++++++++++++++++++++++++- 2 files changed, 521 insertions(+), 1 deletion(-) diff --git a/src/openvpn/xkey_common.h b/src/openvpn/xkey_common.h index eb31604f..0cb76db1 100644 --- a/src/openvpn/xkey_common.h +++ b/src/openvpn/xkey_common.h @@ -39,4 +39,40 @@ OSSL_provider_init_fn xkey_provider_init; #define XKEY_PROV_PROPS "provider=ovpn.xkey" +/** + * Stuct to encapsulate signature algorithm parameters to pass + * to sign operation. + */ +typedef struct { + const char *padmode; /* "pkcs1", "pss" or "none" */ + const char *mdname; /* "SHA256" or "SHA2-256" etc. */ + const char *saltlen; /* "digest", "auto" or "max" */ + const char *keytype; /* "EC" or "RSA" */ +} XKEY_SIGALG; + +/** + * Callback for sign operation -- must be implemented for each backend and + * is used in xkey_signature_sign(), or set when loading the key. + * (custom key loading not yet implemented). + * + * @param handle opaque key handle provided by the backend -- could be null + * or unused for management interface. + * @param sig On return caller should fill this with the signature + * @param siglen On entry *siglen has max size of sig and on return must be + * set to the actual size of the signature + * @param tbs buffer to sign + * @param tbslen size of data in tbs buffer + * @sigalg contains the signature algorithm parameters + * + * @returns 1 on success, 0 on error. + * + * The data in tbs is just the digest with no DigestInfo header added. This is + * unlike the deprecated RSA_sign callback which provides encoded digest. + * For RSA_PKCS1 signatures, the external signing function must encode the digest + * before signing. The digest algorithm used is passed in the sigalg structure. + */ +typedef int (XKEY_EXTERNAL_SIGN_fn)(void *handle, unsigned char *sig, size_t *siglen, + const unsigned char *tbs, size_t tbslen, + XKEY_SIGALG sigalg); + #endif /* XKEY_PUBLIC_H_ */ diff --git a/src/openvpn/xkey_provider.c b/src/openvpn/xkey_provider.c index 4e5ed130..88906ef4 100644 --- a/src/openvpn/xkey_provider.c +++ b/src/openvpn/xkey_provider.c @@ -87,6 +87,14 @@ typedef struct #define KEYTYPE(key) ((key)->pubkey ? EVP_PKEY_get_id((key)->pubkey) : 0) #define KEYSIZE(key) ((key)->pubkey ? EVP_PKEY_get_size((key)->pubkey) : 0) +/** + * Helper sign function for native keys -- this is not + * an external key, but we use the same function signature + * for consistency. Implemented using OpenSSL calls. + */ +XKEY_EXTERNAL_SIGN_fn xkey_native_sign; + + /* keymgmt provider */ /* keymgmt callbacks we implement */ @@ -403,6 +411,482 @@ const OSSL_ALGORITHM keymgmts[] = { {NULL, NULL, NULL} }; + +/* signature provider */ + +/* signature provider callbacks we provide */ +static OSSL_FUNC_signature_newctx_fn signature_newctx; +static OSSL_FUNC_signature_freectx_fn signature_freectx; +static OSSL_FUNC_signature_sign_init_fn signature_sign_init; +static OSSL_FUNC_signature_sign_fn signature_sign; +static OSSL_FUNC_signature_digest_verify_init_fn signature_digest_verify_init; +static OSSL_FUNC_signature_digest_verify_fn signature_digest_verify; +static OSSL_FUNC_signature_digest_sign_init_fn signature_digest_sign_init; +static OSSL_FUNC_signature_digest_sign_fn signature_digest_sign; +static OSSL_FUNC_signature_set_ctx_params_fn signature_set_ctx_params; +static OSSL_FUNC_signature_settable_ctx_params_fn signature_settable_ctx_params; +static OSSL_FUNC_signature_get_ctx_params_fn signature_get_ctx_params; +static OSSL_FUNC_signature_gettable_ctx_params_fn signature_gettable_ctx_params; + +typedef struct +{ + XKEY_PROVIDER_CTX *prov; + XKEY_KEYDATA *keydata; + EVP_MD_CTX *mdctx; /* Used for digest-verify ops */ + EVP_PKEY_CTX *ectx; /* Used for digest-verify ops */ + XKEY_SIGALG sigalg; +} XKEY_SIGNATURE_CTX; + +static const XKEY_SIGALG default_sigalg = { .mdname="MD5-SHA1", .saltlen="digest", + .padmode="pkcs1", .keytype = "RSA"}; + +const struct { + int id; + const char *name; +} digest_names[] = {{NID_md5_sha1, "MD5-SHA1"}, {NID_sha1, "SHA1"}, + {NID_sha224, "SHA224",}, {NID_sha256, "SHA256"}, {NID_sha384, "SHA384"}, + {NID_sha224, "SHA2-224"}, {NID_sha256, "SHA2-256"}, {NID_sha384, "SHA2-384"}, + {NID_sha512, "SHA512"}, {NID_sha512, "SHA2-512"}, {NID_md5_sha1, ""}, + {0, NULL}}; + +/* return a string literal for digest name */ +static const char * +xkey_mdname(const char *s) +{ + int i = 0; + while(digest_names[i].name && strcasecmp(digest_names[i].name, s)) + { + i++; + } + return (digest_names[i].id != 0) ? OBJ_nid2sn(digest_names[i].id) : "MD5-SHA1"; +} + +static void * +signature_newctx(void *provctx, const char *props) +{ + dmsg(D_LOW, "In xkey signature_newctx"); + + XKEY_SIGNATURE_CTX *sctx = calloc(sizeof(*sctx), 1); + if (!sctx) + { + msg(M_NONFATAL, "xkey_signature_newctx: out of memory"); + return NULL; + } + + sctx->prov = provctx; + sctx->sigalg = default_sigalg; + + return sctx; +} + +static void +signature_freectx(void *ctx) +{ + dmsg(D_LOW, "In xkey signature_freectx"); + + XKEY_SIGNATURE_CTX *sctx = ctx; + + if (sctx->mdctx) + { + EVP_MD_CTX_free(sctx->mdctx); + /* sctx->ectx is owned by mdctx, do not free */ + } + + free(sctx); +} + +static const OSSL_PARAM * +signature_settable_ctx_params(void *ctx, void *provctx) +{ + dmsg(D_LOW, "In xkey signature_settable_ctx_params"); + + static OSSL_PARAM settable[] = { + OSSL_PARAM_utf8_string(OSSL_SIGNATURE_PARAM_PAD_MODE, NULL, 0), + OSSL_PARAM_utf8_string(OSSL_SIGNATURE_PARAM_DIGEST, NULL, 0), + OSSL_PARAM_utf8_string(OSSL_SIGNATURE_PARAM_PSS_SALTLEN, NULL, 0), + OSSL_PARAM_END + }; + + return settable; +} + +static int +signature_set_ctx_params(void *ctx, const OSSL_PARAM params[]) +{ + dmsg(D_LOW, "In signature_set_ctx_params"); + + XKEY_SIGNATURE_CTX *sctx = ctx; + const OSSL_PARAM *p; + + if (sctx->ectx) + { + EVP_PKEY_CTX_set_params(sctx->ectx, params); + } + + if (params == NULL) + { + return 1; /* not an error */ + } + p = OSSL_PARAM_locate_const(params, OSSL_SIGNATURE_PARAM_PAD_MODE); + if (p && p->data_type == OSSL_PARAM_UTF8_STRING) + { + if (!strcmp(p->data, "pss")) + { + sctx->sigalg.padmode = "pss"; + } + else if (!strcmp(p->data, "pkcs1")) + { + sctx->sigalg.padmode = "pkcs1"; + } + else if (!strcmp(p->data, "none")) + { + sctx->sigalg.padmode = "none"; + } + else + { + msg(D_LOW, "xkey signature_ctx: padmode <%s>, treating as ", + (char *)p->data); + sctx->sigalg.padmode = "none"; + } + dmsg(D_LOW, "xkey_sign_parameters: setting padmode to %s", sctx->sigalg.padmode); + } + else if (p && p->data_type == OSSL_PARAM_INTEGER) + { + int padmode; + if (OSSL_PARAM_get_int(p, &padmode)) + { + if (padmode == RSA_PKCS1_PSS_PADDING) + { + sctx->sigalg.padmode = "pss"; + } + else if (padmode == RSA_PKCS1_PADDING) + { + sctx->sigalg.padmode = "pkcs1"; + } + else + { + sctx->sigalg.padmode = "none"; + } + } + dmsg(D_LOW, "xkey_sign_parameters: setting padmode to <%s>", sctx->sigalg.padmode); + } + else if (p) + { + msg(M_WARN, "xkey_signature_params: unknown padmode ignored"); + } + + p = OSSL_PARAM_locate_const(params, OSSL_SIGNATURE_PARAM_DIGEST); + if (p && p->data_type == OSSL_PARAM_UTF8_STRING) + { + sctx->sigalg.mdname = xkey_mdname(p->data); + msg(D_LOW, "xkey_sign_parameters: setting hashalg to %s", sctx->sigalg.mdname); + } + else if (p) + { + msg(M_WARN, "xkey_signature_params: unknown digest type ignored"); + } + + p = OSSL_PARAM_locate_const(params, OSSL_SIGNATURE_PARAM_PSS_SALTLEN); + if (p && p->data_type == OSSL_PARAM_UTF8_STRING) + { + if (!strcmp((char *)p->data, "digest")) + { + sctx->sigalg.saltlen = "digest"; + } + else if (!strcmp(p->data, "max")) + { + sctx->sigalg.saltlen = "max"; + } + else if (!strcmp(p->data, "auto")) + { + sctx->sigalg.saltlen = "auto"; + } + else + { + msg(M_WARN, "xkey_signature_params: unknown saltlen <%s>", + (char *)p->data); + sctx->sigalg.saltlen = "digest"; /* most common ? */ + } + msg(D_LOW, "xkey_sign_parameters: setting saltlen to %s", sctx->sigalg.saltlen); + } + else if (p) + { + msg(M_WARN, "xkey_signature_params: unknown saltlen ignored"); + } + + return 1; +} + +static const OSSL_PARAM * +signature_gettable_ctx_params(void *ctx, void *provctx) +{ + dmsg(D_LOW,"In xkey signature_gettable_ctx_params"); + + static OSSL_PARAM gettable[] = { + OSSL_PARAM_END + }; + + return gettable; +} + +static int +signature_get_ctx_params(void *ctx, OSSL_PARAM params[]) +{ + dmsg(D_LOW, "In signature_get_ctx_params -- not implemented!!"); + return 0; +} + +static int +signature_sign_init(void *ctx, void *provkey, const OSSL_PARAM params[]) +{ + dmsg(D_LOW, "In xkey sign_init"); + + XKEY_SIGNATURE_CTX *sctx = ctx; + + sctx->keydata = provkey; + sctx->keydata->refcount++; + sctx->sigalg.keytype = KEYTYPE(sctx->keydata) == EVP_PKEY_RSA ? "RSA" : "EC"; + + signature_set_ctx_params(sctx, params); + + return 1; +} + +static int +signature_sign(void *ctx, unsigned char *sig, size_t *siglen, size_t sigsize, + const unsigned char *tbs, size_t tbslen) +{ + dmsg(D_LOW, "In xkey signature_sign with siglen = %zu\n", *siglen); + + XKEY_SIGNATURE_CTX *sctx = ctx; + ASSERT(sctx); + ASSERT(sctx->keydata); + + if (!sig) + { + *siglen = KEYSIZE(sctx->keydata); + return 1; + } + + if (sctx->keydata->origin == OPENSSL_NATIVE) + { + return xkey_native_sign(sctx->keydata->handle, sig, siglen, tbs, tbslen, sctx->sigalg); + } + else + { + /* external key handling not yet implemented */ + return 0; + } +} + +/* Digest verify ops are simply delegated to the default provider using pubkey */ +static int +signature_digest_init_helper(void *ctx, const char *mdname, void *provkey) +{ + dmsg(D_LOW, "In xkey digest_init_helper with mdname = <%s>", mdname); + + XKEY_SIGNATURE_CTX *sctx = ctx; + + ASSERT(sctx); + ASSERT(provkey); + + if (!sctx->mdctx) { + sctx->mdctx = EVP_MD_CTX_new(); + } + if (!sctx->mdctx) { + msg(M_WARN, "xkey_signature_digest_init: EVP_MD_CTX_new failed"); + return 0; + } + + EVP_MD_CTX_init(sctx->mdctx); + sctx->keydata = provkey; /* used by digest_sign */ + sctx->keydata->refcount++; + + return 1; +} + +static int +signature_digest_verify_init(void *ctx, const char *mdname, void *provkey, + const OSSL_PARAM params[]) +{ + dmsg(D_LOW, "In xkey digest_verify init with mdname <%s>", mdname); + + XKEY_SIGNATURE_CTX *sctx = ctx; + ASSERT(sctx); + ASSERT(sctx->prov); + + + int ret = signature_digest_init_helper(ctx, mdname, provkey); + if (ret) + { + EVP_PKEY *pubkey = ((XKEY_KEYDATA*)provkey)->pubkey; + ret = EVP_DigestVerifyInit_ex(sctx->mdctx, &sctx->ectx, mdname, + sctx->prov->libctx, NULL, pubkey, params); + } + return ret; +} + +static int +signature_digest_verify(void *ctx, const unsigned char *sig, size_t siglen, + const unsigned char *tbs, size_t tbslen) +{ + dmsg(D_LOW, "In xkey digest_verify"); + + XKEY_SIGNATURE_CTX *sctx = ctx; + + if (!sctx || !sctx->mdctx) + { + return 0; + } + return EVP_DigestVerify(sctx->mdctx, sig, siglen, tbs, tbslen); +} + +static int +signature_digest_sign_init(void *ctx, const char *mdname, + void *provkey, const OSSL_PARAM params[]) +{ + dmsg(D_LOW, "In xkey digest_sign_init with mdname = %s>", mdname); + + XKEY_SIGNATURE_CTX *sctx = ctx; + + ASSERT(sctx); + ASSERT(provkey); + ASSERT(sctx->prov); + + sctx->keydata = provkey; /* used by digest_sign */ + sctx->keydata->refcount++; + sctx->sigalg.keytype = KEYTYPE(sctx->keydata) == EVP_PKEY_RSA ? "RSA" : "EC"; + + signature_set_ctx_params(ctx, params); + if (mdname) + { + sctx->sigalg.mdname = xkey_mdname(mdname); /* get a string literal pointer */ + } + else + { + msg(M_WARN, "xkey digest_sign_init: mdname is NULL."); + } + return 1; +} + +static int +signature_digest_sign(void *ctx, unsigned char *sig, size_t *siglen, + size_t sigsize, const unsigned char *tbs, size_t tbslen) +{ + dmsg(D_LOW, "In xkey digest_sign"); + + XKEY_SIGNATURE_CTX *sctx = ctx; + + ASSERT(sctx); + ASSERT(sctx->keydata); + + if (!sig) /* set siglen and return */ + { + *siglen = KEYSIZE(sctx->keydata); + return 1; + } + + /* create digest and pass on to signature_sign() */ + + const char *mdname = sctx->sigalg.mdname; + EVP_MD *md = EVP_MD_fetch(sctx->prov->libctx, mdname, NULL); + if (!md) + { + msg(M_WARN, "WARN: xkey digest_sign_init: MD_fetch failed for <%s>", mdname); + return 0; + } + + /* construct digest using OpenSSL */ + unsigned char buf[EVP_MAX_MD_SIZE]; + unsigned int sz; + if (EVP_Digest(tbs, tbslen, buf, &sz, md, NULL) != 1) + { + msg(M_WARN, "WARN: xkey digest_sign: EVP_Digest failed"); + return 0; + } + + return signature_sign(ctx, sig, siglen, sigsize, buf, sz); +} + +/* Sign digest using native sign function -- will only work for native keys + */ +int +xkey_native_sign(void *handle, unsigned char *sig, size_t *siglen, const unsigned char *tbs, + size_t tbslen, XKEY_SIGALG sigalg) +{ + dmsg(D_LOW, "In xkey_native_sign"); + + EVP_PKEY *pkey = handle; + int ret = 0; + + ASSERT(sig); + ASSERT(pkey); + + const char *saltlen = sigalg.saltlen; + const char *mdname = sigalg.mdname; + const char *padmode = sigalg.padmode; + + dmsg(D_LOW, "In xkey_native_sign with digest <%s> padmode = <%s> saltlen=<%s>", mdname, padmode, saltlen); + + int i = 0; + OSSL_PARAM params[6]; + params[i++] = OSSL_PARAM_construct_utf8_string(OSSL_SIGNATURE_PARAM_DIGEST, (char *)mdname, 0); + if (EVP_PKEY_get_id(pkey) == EVP_PKEY_RSA) + { + params[i++] = OSSL_PARAM_construct_utf8_string(OSSL_SIGNATURE_PARAM_PAD_MODE, (char *)padmode, 0); + if (!strcmp(sigalg.padmode, "pss")) + { + params[i++] = OSSL_PARAM_construct_utf8_string(OSSL_SIGNATURE_PARAM_PSS_SALTLEN, (char *) saltlen, 0); + /* same digest for mgf1 */ + params[i++] = OSSL_PARAM_construct_utf8_string(OSSL_SIGNATURE_PARAM_MGF1_DIGEST, (char *) mdname, 0); + } + } + params[i++] = OSSL_PARAM_construct_end(); + + EVP_PKEY_CTX *ectx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL); + + if (!ectx) + { + msg(M_WARN, "WARN: xkey test_sign: call to EVP_PKEY_CTX_new...failed"); + return 0; + } + + /* params must be set in a separate call after the sign_init */ + if (EVP_PKEY_sign_init_ex(ectx, NULL) != 1) + { + msg(M_WARN, "WARN: xkey test_sign: call to EVP_PKEY_sign_init failed"); + return 0; + } + EVP_PKEY_CTX_set_params(ectx, params); + + ret = EVP_PKEY_sign(ectx, sig, siglen, tbs, tbslen); + EVP_PKEY_CTX_free(ectx); + + return ret; +} + +static const OSSL_DISPATCH signature_functions[] = { + {OSSL_FUNC_SIGNATURE_NEWCTX, (void (*)(void))signature_newctx}, + {OSSL_FUNC_SIGNATURE_FREECTX, (void (*)(void))signature_freectx}, + {OSSL_FUNC_SIGNATURE_SIGN_INIT, (void (*)(void))signature_sign_init}, + {OSSL_FUNC_SIGNATURE_SIGN, (void (*)(void))signature_sign}, + {OSSL_FUNC_SIGNATURE_DIGEST_VERIFY_INIT, (void (*)(void))signature_digest_verify_init}, + {OSSL_FUNC_SIGNATURE_DIGEST_VERIFY, (void (*)(void))signature_digest_verify}, + {OSSL_FUNC_SIGNATURE_DIGEST_SIGN_INIT, (void (*)(void))signature_digest_sign_init}, + {OSSL_FUNC_SIGNATURE_DIGEST_SIGN, (void (*)(void))signature_digest_sign}, + {OSSL_FUNC_SIGNATURE_SET_CTX_PARAMS, (void (*)(void))signature_set_ctx_params}, + {OSSL_FUNC_SIGNATURE_SETTABLE_CTX_PARAMS, (void (*)(void))signature_settable_ctx_params}, + {OSSL_FUNC_SIGNATURE_GET_CTX_PARAMS, (void (*)(void))signature_get_ctx_params}, + {OSSL_FUNC_SIGNATURE_GETTABLE_CTX_PARAMS, (void (*)(void))signature_gettable_ctx_params}, + {0, NULL } +}; + +const OSSL_ALGORITHM signatures[] = { + {"RSA:rsaEncryption", props, signature_functions}, + {"ECDSA", props, signature_functions}, + {NULL, NULL, NULL} +}; + /* main provider interface */ /* provider callbacks we implement */ @@ -421,7 +905,7 @@ query_operation(void *provctx, int op, int *no_store) switch (op) { case OSSL_OP_SIGNATURE: - return NULL; + return signatures; case OSSL_OP_KEYMGMT: return keymgmts; From patchwork Wed Sep 22 21:12:50 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [Openvpn-devel,5/9] Implement import of custom external keys X-Patchwork-Submitter: Selva Nair X-Patchwork-Id: 1969 Message-Id: <20210922211254.7570-6-selva.nair@gmail.com> To: openvpn-devel@lists.sourceforge.net Date: Wed, 22 Sep 2021 17:12:50 -0400 From: selva.nair@gmail.com List-Id: From: Selva Nair Our key object retains info about the external key as an opaque handle to the backend. We also need the public key as an EVP_PKEY *. For native keys we use OpenSSL API to import data into the key. In fact the 'handle' in that case is the OpenSSL EVP_PKEY object itself. For importing custom keys, we need to define custom parameters describing the key using OSSL_PARAM structure. We define 4 required and 1 optional parameters for loading the key: Required params: {.key="origin", .data_type = OSSL_PARAM_UTF8_STRING .data = "foobar", .data_size = 0 } Note: data_size = 0 refer to NUL terminated string in OpenSSL. This parameter is only used to identify that the key as non-native with an opaque handle. We really do not check the content of the string. Should not be NULL. {.key="handle", .data_type = OSSL_PARAM_OCTET_PTR, .data = &handle, .data_size = sizeof(handle)} {.key="pubkey", .data_type = OSSL_PARAM_OCTET_STRING, .data = &pubkey, .data_size = sizeof(pubkey)} {.key="sign_op", .data_type = OSSL_PARAM_OCTET_PTR, .data = &sign_op_ptr, .data_size = sizeof(sign_op_ptr)} Optional params: {.key="free_op", .data_type = OSSL_PARAM_OCTET_PTR, .data = &free_op_ptr, .data_size = sizeof(free_op_ptr)} The 'handle' is opaque to us and is retained. The caller should not free it. We will free it when no longer required by calling 'free_op()', if provided. The 'handle' should not be null as that indicates missing private key. The 'pubkey' must be an 'EVP_PKEY *' variable, and is duplicated by us. The caller may free it when not required. The 'sign_op' and 'free_op' function pointers should be of type 'XKEY_EXTERNAL_SIGN_fn' and 'XKEY_PRIVKEY_FREE_fn' defined in xkey_common.h For example, for management-external-key, we really do not need any 'handle'. Pass anything that will live long and won't dereference to NULL. We do not use it for any other purpose. Pointer to a const string would be a good choice. In this case, the 'free_op' must be NULL or some harmless function. NULL is the safest choice. The way of passing pointers via 'OSSL_PARAM' is somewhat fragile, as everything is cast into 'void *' and relies on abiding to the contract. For 'pubkey' we could pass it as a der encoded octet string, for example, and instead of function pointers we can just hard code the callbacks into our implementation. After all this is a private built-in provider. However, no external data is involved, so the only possible bad actor is the developer herself. A helper function to load the management key is in the next commit. Signed-off-by: Selva Nair --- src/openvpn/xkey_common.h | 7 ++ src/openvpn/xkey_provider.c | 166 +++++++++++++++++++++++++++++++++--- 2 files changed, 162 insertions(+), 11 deletions(-) diff --git a/src/openvpn/xkey_common.h b/src/openvpn/xkey_common.h index 0cb76db1..466b2b8d 100644 --- a/src/openvpn/xkey_common.h +++ b/src/openvpn/xkey_common.h @@ -75,4 +75,11 @@ typedef int (XKEY_EXTERNAL_SIGN_fn)(void *handle, unsigned char *sig, size_t *si const unsigned char *tbs, size_t tbslen, XKEY_SIGALG sigalg); +/** + * Signature of private key free function callback used + * to free the opaque private key handle obtained from the + * backend. Not required for management-external-key. + */ +typedef void (XKEY_PRIVKEY_FREE_fn)(void *handle); + #endif /* XKEY_PUBLIC_H_ */ diff --git a/src/openvpn/xkey_provider.c b/src/openvpn/xkey_provider.c index 88906ef4..3ff01634 100644 --- a/src/openvpn/xkey_provider.c +++ b/src/openvpn/xkey_provider.c @@ -72,6 +72,13 @@ typedef enum * * We also keep the public key in the form of a native OpenSSL EVP_PKEY. * This allows us to do all public ops by calling ops in the default provider. + * Both these are references retained by us and freed when the key is + * destroyed. As the pubkey is native, we free it using EVP_PKEY_free(). + * To free the handle we call the backend if a free function + * has been set for that key origin. It could be set when the key is + * created/imported. + * For native keys no need to free handle as its same as the pubkey + * which we always free. */ typedef struct { @@ -81,6 +88,10 @@ typedef struct EVP_PKEY *pubkey; /* origin of key -- native or external */ XKEY_ORIGIN origin; + /* sign function in backend to call */ + XKEY_EXTERNAL_SIGN_fn *sign; + /* keydata handle free function of backend */ + XKEY_PRIVKEY_FREE_fn *free; int refcount; /* reference count */ } XKEY_KEYDATA; @@ -111,6 +122,9 @@ static OSSL_FUNC_keymgmt_gettable_params_fn keymgmt_gettable; static OSSL_FUNC_keymgmt_query_operation_name_fn rsa_keymgmt_name; static OSSL_FUNC_keymgmt_query_operation_name_fn ec_keymgmt_name; +static int +keymgmt_import_helper(XKEY_KEYDATA *key, const OSSL_PARAM params[]); + static XKEY_KEYDATA * keydata_new() { @@ -134,6 +148,11 @@ keydata_free(XKEY_KEYDATA *key) { return; } + if (key->free && key->handle) + { + key->free(key->handle); + key->handle = NULL; + } if (key->pubkey) { EVP_PKEY_free(key->pubkey); @@ -167,7 +186,27 @@ keymgmt_load(const void *reference, size_t reference_sz) * appropriate for the key. We just use it to create a native * EVP_PKEY from params and assign to keydata->handle. * - * Import of external keys -- to be implemented + * For non-native keys the params[] array should include a custom + * value with name "origin". + * + * Other required parameters in the params array are: + * + * pubkey - pointer to native public key as a OCTET_STRING + * the public key is duplicated on receipt + * handle - reference to opaque handle to private key -- if not required + * pass a dummy value that is not zero. type = OCTET_PTR + * The reference is retained -- caller must _not_ free it. + * sign_op - function pointer for sign operation. type = OCTET_PTR + * Must be a reference to XKEY_EXTERNAL_SIGN_fn + * origin - A custom string to indicate the external key origin. UTF8_STRING + * The value doesn't really matter, but must be present. + * + * Optional params + * free_op - Called as free(handle) when the key is deleted. If the + * handle should not be freed, do not include. type = OCTET_PTR + * Must be a reference to XKEY_PRIVKEY_FREE_fn + * + * See xkey_load_management_key for an example use. */ static int keymgmt_import(void *keydata, int selection, const OSSL_PARAM params[], const char *name) @@ -185,6 +224,15 @@ keymgmt_import(void *keydata, int selection, const OSSL_PARAM params[], const ch return 0; } + /* if params contain a custom origin, call our import helper */ + + const OSSL_PARAM *p = OSSL_PARAM_locate_const(params, "origin"); + if (p && p->data_type == OSSL_PARAM_UTF8_STRING) + { + key->origin = EXTERNAL_KEY; + return keymgmt_import_helper(key, params); + } + /* create a native public key and assign it to key->pubkey */ EVP_PKEY *pkey = NULL; @@ -203,6 +251,7 @@ keymgmt_import(void *keydata, int selection, const OSSL_PARAM params[], const ch key->pubkey = pkey; key->origin = OPENSSL_NATIVE; + key->sign = xkey_native_sign; if (selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) { /* just use the same key as handle -- do not up-ref */ @@ -335,10 +384,94 @@ keymgmt_get_params(void *keydata, OSSL_PARAM *params) return EVP_PKEY_get_params(key->pubkey, params); } +/* Helper used by keymgmt_import and keymgmt_set_params + * for our keys. Not to be used for OpenSSL native keys. + */ +static int +keymgmt_import_helper(XKEY_KEYDATA *key, const OSSL_PARAM *params) +{ + dmsg(D_LOW, "In keymgmt_import_helper"); + + const OSSL_PARAM *p; + EVP_PKEY *pkey = NULL; + + ASSERT(key); + /* calling this with native keys is a coding error */ + ASSERT(key->origin != OPENSSL_NATIVE); + + if (params == NULL) + { + return 1; /* not an error */ + } + + /* our keys are immutable, we do not allow resetting parameters */ + if (key->pubkey) + { + return 0; + } + + /* only check params we understand and ignore the rest */ + + p = OSSL_PARAM_locate_const(params, "pubkey"); /*setting pubkey on our keydata */ + if (p && p->data_type == OSSL_PARAM_OCTET_STRING + && p->data_size == sizeof(pkey)) + { + pkey = *(EVP_PKEY **)p->data; + ASSERT(pkey); + + int id = EVP_PKEY_get_id(pkey); + if (id != EVP_PKEY_RSA && id != EVP_PKEY_EC) + { + msg(M_WARN, "Error: xkey keymgmt_import: unknown key type (%d)", id); + return 0; + } + + key->pubkey = EVP_PKEY_dup(pkey); + if (key->pubkey == NULL) + { + msg(M_NONFATAL, "Error: xkey keymgmt_import: duplicating pubkey failed."); + return 0; + } + } + + p = OSSL_PARAM_locate_const(params, "handle"); /*setting privkey */ + if (p && p->data_type == OSSL_PARAM_OCTET_PTR + && p->data_size == sizeof(key->handle)) + { + key->handle = *(void **)p->data; + /* caller should keep the reference alive until we call free */ + ASSERT(key->handle); /* fix your params array */ + } + + p = OSSL_PARAM_locate_const(params, "sign_op"); /*setting sign_op */ + if (p && p->data_type == OSSL_PARAM_OCTET_PTR + && p->data_size == sizeof(key->sign)) + { + key->sign = *(void **)p->data; + ASSERT(key->sign); /* fix your params array */ + } + + /* optional parameters */ + p = OSSL_PARAM_locate_const(params, "free_op"); /*setting free_op */ + if (p && p->data_type == OSSL_PARAM_OCTET_PTR + && p->data_size == sizeof(key->free)) + { + key->free = *(void **)p->data; + } + + return 1; +} + /** + * Set params on a key. + * * If the key is an encapsulated native key, we just call * EVP_PKEY_set_params in the default context. Only those params * supported by the default provider would work in that case. + * + * We treat our key object as immutable, so this works only with an + * empty key. Supported params for external keys are the + * same as those listed in the description of keymgmt_import. */ static int keymgmt_set_params(void *keydata, const OSSL_PARAM *params) @@ -348,7 +481,7 @@ keymgmt_set_params(void *keydata, const OSSL_PARAM *params) if (key->origin != OPENSSL_NATIVE) { - return 0; /* to be implemented */ + return keymgmt_import_helper(key, params); } else if (key->handle == NULL) /* once handle is set our key is immutable */ { @@ -652,6 +785,22 @@ signature_sign_init(void *ctx, void *provkey, const OSSL_PARAM params[]) return 1; } +/* Sign digest using backend sign function */ +static int +xkey_sign_dispatch(XKEY_SIGNATURE_CTX *sctx, unsigned char *sig, size_t *siglen, const unsigned char *tbs, + size_t tbslen) +{ + XKEY_EXTERNAL_SIGN_fn *sign = sctx->keydata->sign; + + if (!sign) + { + /* should not happen */ + msg(M_FATAL, "Signing function for this key is not defined."); + return 0; + } + return sign(sctx->keydata->handle, sig, siglen, tbs, tbslen, sctx->sigalg); +} + static int signature_sign(void *ctx, unsigned char *sig, size_t *siglen, size_t sigsize, const unsigned char *tbs, size_t tbslen) @@ -668,15 +817,10 @@ signature_sign(void *ctx, unsigned char *sig, size_t *siglen, size_t sigsize, return 1; } - if (sctx->keydata->origin == OPENSSL_NATIVE) - { - return xkey_native_sign(sctx->keydata->handle, sig, siglen, tbs, tbslen, sctx->sigalg); - } - else - { - /* external key handling not yet implemented */ - return 0; - } + /* we have sign_op set on natve keys as well + * so no need for the if/else here any more. + */ + return xkey_sign_dispatch(sctx, sig, siglen, tbs, tbslen); } /* Digest verify ops are simply delegated to the default provider using pubkey */ From patchwork Wed Sep 22 21:12:51 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [Openvpn-devel,6/9] A helper function to load key for management-external-key X-Patchwork-Submitter: Selva Nair X-Patchwork-Id: 1966 Message-Id: <20210922211254.7570-7-selva.nair@gmail.com> To: openvpn-devel@lists.sourceforge.net Date: Wed, 22 Sep 2021 17:12:51 -0400 From: selva.nair@gmail.com List-Id: From: Selva Nair - A wrapper around the keymgmt import of xkey provider - When the provider is available, use this to set SSL_CTX_use_PrivateKey for management-external-key sign_op is not implemented yet. This will error out while signing with --management-external-key. The next commit fixes that. Signed-off-by: Selva Nair --- src/openvpn/Makefile.am | 1 + src/openvpn/ssl_openssl.c | 10 ++++ src/openvpn/xkey_common.h | 11 +++++ src/openvpn/xkey_helper.c | 96 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+) create mode 100644 src/openvpn/xkey_helper.c diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am index 432efe73..0331298b 100644 --- a/src/openvpn/Makefile.am +++ b/src/openvpn/Makefile.am @@ -129,6 +129,7 @@ openvpn_SOURCES = \ tun.c tun.h \ vlan.c vlan.h \ xkey_provider.c xkey_common.h \ + xkey_helper.c \ win32.h win32.c \ win32-util.h win32-util.c \ cryptoapi.h cryptoapi.c diff --git a/src/openvpn/ssl_openssl.c b/src/openvpn/ssl_openssl.c index 61256620..b9453653 100644 --- a/src/openvpn/ssl_openssl.c +++ b/src/openvpn/ssl_openssl.c @@ -1450,6 +1450,14 @@ tls_ctx_use_management_external_key(struct tls_root_ctx *ctx) EVP_PKEY *pkey = X509_get0_pubkey(cert); ASSERT(pkey); /* NULL before SSL_CTX_use_certificate() is called */ +#ifdef HAVE_XKEY_PROVIDER + EVP_PKEY *privkey = xkey_load_management_key(NULL, pkey); + if (!privkey + || !SSL_CTX_use_PrivateKey(ctx->ctx, privkey)) + { + goto cleanup; + } +#else if (EVP_PKEY_id(pkey) == EVP_PKEY_RSA) { if (!tls_ctx_use_external_rsa_key(ctx, pkey)) @@ -1478,6 +1486,8 @@ tls_ctx_use_management_external_key(struct tls_root_ctx *ctx) } #endif /* OPENSSL_VERSION_NUMBER > 1.1.0 dev && !defined(OPENSSL_NO_EC) */ +#endif /* HAVE_XKEY_PROVIDER */ + ret = 0; cleanup: if (ret) diff --git a/src/openvpn/xkey_common.h b/src/openvpn/xkey_common.h index 466b2b8d..751f18a0 100644 --- a/src/openvpn/xkey_common.h +++ b/src/openvpn/xkey_common.h @@ -82,4 +82,15 @@ typedef int (XKEY_EXTERNAL_SIGN_fn)(void *handle, unsigned char *sig, size_t *si */ typedef void (XKEY_PRIVKEY_FREE_fn)(void *handle); +/** + * Generate an encapsulated EVP_PKEY for management-external-key + * + * @param libctx library context in which xkey provider has been loaded + * @param pubkey corresponding pubkey in the default provider's context + * + * @returns a new EVP_PKEY in the provider's keymgmt context. + * The pubkey is up-refd if retained -- the caller can free it after return + */ +EVP_PKEY *xkey_load_management_key(OSSL_LIB_CTX *libctx, EVP_PKEY *pubkey); + #endif /* XKEY_PUBLIC_H_ */ diff --git a/src/openvpn/xkey_helper.c b/src/openvpn/xkey_helper.c new file mode 100644 index 00000000..aa9f23b8 --- /dev/null +++ b/src/openvpn/xkey_helper.c @@ -0,0 +1,96 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2021 Selva Nair + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#elif defined(_MSC_VER) +#include "config-msvc.h" +#endif + +#ifdef HAVE_XKEY_PROVIDER + +#include "syshead.h" +#include "error.h" +#include "buffer.h" +#include "xkey_common.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *const props = XKEY_PROV_PROPS; + +XKEY_EXTERNAL_SIGN_fn xkey_management_sign; + +/** + * Load external key for signing via management interface. + * The public key must be passed in by the caller as we may not + * be able to get it from the management. + * Returns an EVP_PKEY object attached to xkey provider. + * Caller must free it when no longer needed. + */ +EVP_PKEY * +xkey_load_management_key(OSSL_LIB_CTX *libctx, EVP_PKEY *pubkey) +{ + EVP_PKEY *pkey = NULL; + ASSERT(pubkey); + + /* Management interface doesnt require any handle to be + * stored in the key. We use a dummy pointer as we do need a + * non-NULL value to indicate private key is avaialble. + */ + void *dummy = & "dummy"; + + const char *origin = "management"; + XKEY_EXTERNAL_SIGN_fn *sign_op = xkey_management_sign; + + /* UTF8 string pointers in here are only read from, so cast is safe */ + OSSL_PARAM params[] = { + {"origin", OSSL_PARAM_UTF8_STRING, (char *) origin, 0, 0}, + {"pubkey", OSSL_PARAM_OCTET_STRING, &pubkey, sizeof(pubkey), 0}, + {"handle", OSSL_PARAM_OCTET_PTR, &dummy, sizeof(dummy), 0}, + {"sign_op", OSSL_PARAM_OCTET_PTR, (void **) &sign_op, sizeof(sign_op), 0}, + {NULL, 0, NULL, 0, 0}}; + + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_pkey(libctx, pubkey, props); + EVP_PKEY_fromdata_init(ctx); + EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params); + + return pkey; +} + +/* not yet implemented */ +int +xkey_management_sign(void *unused, unsigned char *sig, size_t *siglen, + const unsigned char *tbs, size_t tbslen, XKEY_SIGALG alg) +{ + msg(M_FATAL, "FATAL ERROR: A sign callback for this key is not implemented."); + return 0; +} + +#endif /* HAVE_XKEY_PROVIDER */ From patchwork Wed Sep 22 21:12:52 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [Openvpn-devel,7/9] Enable signing via provider for management-external-key X-Patchwork-Submitter: Selva Nair X-Patchwork-Id: 1965 Message-Id: <20210922211254.7570-8-selva.nair@gmail.com> To: openvpn-devel@lists.sourceforge.net Date: Wed, 22 Sep 2021 17:12:52 -0400 From: selva.nair@gmail.com List-Id: From: Selva Nair - Add a function to set as sign_op during key import. The function passes the signature request to management interface, and returns the result to the provider. Signed-off-by: Selva Nair --- src/openvpn/xkey_common.h | 4 +++ src/openvpn/xkey_helper.c | 68 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/src/openvpn/xkey_common.h b/src/openvpn/xkey_common.h index 751f18a0..6a00c382 100644 --- a/src/openvpn/xkey_common.h +++ b/src/openvpn/xkey_common.h @@ -35,6 +35,10 @@ */ OSSL_provider_init_fn xkey_provider_init; +#else + +#define OSSL_LIB_CTX void + #endif /* HAVE_XKEY_PROVIDER */ #define XKEY_PROV_PROPS "provider=ovpn.xkey" diff --git a/src/openvpn/xkey_helper.c b/src/openvpn/xkey_helper.c index aa9f23b8..c9e8d218 100644 --- a/src/openvpn/xkey_helper.c +++ b/src/openvpn/xkey_helper.c @@ -33,6 +33,8 @@ #include "error.h" #include "buffer.h" #include "xkey_common.h" +#include "manage.h" +#include "base64.h" #include #include @@ -84,13 +86,73 @@ xkey_load_management_key(OSSL_LIB_CTX *libctx, EVP_PKEY *pubkey) return pkey; } -/* not yet implemented */ +/** + * Signature callback for xkey_provider with management-external-key + * + * @param handle Unused -- may be null + * @param sig On successful return signature is in sig. + * @param siglen On entry *siglen has length of buffer sig, + * on successful return size of signature + * @param tbs hash to be signed + * @param tbslen len of data in dgst + * @param sigalg extra signature parameters + * + * @return signature length or -1 on error. + * For PKCS1 signature, the passed in hash is pure messaged digest + * not encoded with digest info even for the TLS 1.1 MD5_SHA hashes. + */ int xkey_management_sign(void *unused, unsigned char *sig, size_t *siglen, const unsigned char *tbs, size_t tbslen, XKEY_SIGALG alg) { - msg(M_FATAL, "FATAL ERROR: A sign callback for this key is not implemented."); - return 0; + (void) unused; + char alg_str[64]; + + if (!strcmp(alg.keytype, "EC")) + { + strncpynt(alg_str, "ECDSA", sizeof(alg_str)); + } + /* else assume RSA key */ + else if (!strcmp(alg.padmode, "pkcs1")) + { + strncpynt(alg_str, "RSA_PKCS1_PADDING", sizeof(alg_str)); + } + else if (!strcmp(alg.padmode, "none")) + { + strncpynt(alg_str, "RSA_NO_PADDING", sizeof(alg_str)); + } + else if (!strcmp(alg.padmode, "pss")) + { + openvpn_snprintf(alg_str, sizeof(alg_str), "%s,hashalg=%s,saltlen=%s", + "RSA_PKCS1_PSS_PADDING", alg.mdname,alg.saltlen); + } + else { + msg(M_NONFATAL, "Unsupported RSA padding mode in signature request<%s>", + alg.padmode); + return 0; + } + dmsg(D_LOW, "xkey management_sign: requesting sig with algorithm <%s>", alg_str); + + char *in_b64 = NULL; + char *out_b64 = NULL; + int len = -1; + + int bencret = openvpn_base64_encode(tbs, (int) tbslen, &in_b64); + + if (management && bencret > 0) + { + out_b64 = management_query_pk_sig(management, in_b64, alg_str); + } + if (out_b64) + { + len = openvpn_base64_decode(out_b64, sig, (int) *siglen); + } + free(in_b64); + free(out_b64); + + *siglen = (len > 0) ? len : 0; + + return (*siglen > 0); } #endif /* HAVE_XKEY_PROVIDER */ From patchwork Wed Sep 22 21:12:53 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [Openvpn-devel,8/9] Add a function to encode digests with PKCS1 DigestInfo wrapper X-Patchwork-Submitter: Selva Nair X-Patchwork-Id: 1971 Message-Id: <20210922211254.7570-9-selva.nair@gmail.com> To: openvpn-devel@lists.sourceforge.net Date: Wed, 22 Sep 2021 17:12:53 -0400 From: selva.nair@gmail.com List-Id: From: Selva Nair The EVP_PKEY interface as well as provider provides the raw digest to the sign() function. In case of RSA_PKCS1, our management interface expects expects an encoded hash, which has the DigestInfo header added as per PKCSv1.5 specs, unless the hash algorithm is legacy MD5_SHA1. Fix this by - add a function to perform the pkcs1 encoding before passing the data to sign to the management interface. The implementation is not pretty, but should work. (Unfortunately OpenSSL does not expose a function for this). Note: 1. cryptoki interface used by pkcs11-helper also requires this to be done before calling the Sign op. This will come handy there too. 2. We have a similar function in ssl_mbedtls.c but its not prettier, and require porting. Signed-off-by: Selva Nair --- src/openvpn/xkey_common.h | 20 ++++++ src/openvpn/xkey_helper.c | 127 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+) diff --git a/src/openvpn/xkey_common.h b/src/openvpn/xkey_common.h index 6a00c382..e92d4868 100644 --- a/src/openvpn/xkey_common.h +++ b/src/openvpn/xkey_common.h @@ -97,4 +97,24 @@ typedef void (XKEY_PRIVKEY_FREE_fn)(void *handle); */ EVP_PKEY *xkey_load_management_key(OSSL_LIB_CTX *libctx, EVP_PKEY *pubkey); +/** + * Add PKCS1 DigestInfo to tbs and return the result in *enc. + * + * @param enc pointer to output buffer + * @param enc_len capacity in bytes of output buffer + * @param mdname name of the hash algorithm (SHA256, SHA1 etc.) + * @param tbs pointer to digest to be encoded + * @param tbslen length of data in bytes + * + * @return false on error, true on success + * + * On return enc_len is set to actual size of the result. + * enc is NULL or enc_len is not enough to store the result, it is set + * to the required size and false is returned. + * + */ +bool +encode_pkcs1(unsigned char **enc, size_t *enc_len, const char *mdname, + const unsigned char *tbs, size_t tbslen); + #endif /* XKEY_PUBLIC_H_ */ diff --git a/src/openvpn/xkey_helper.c b/src/openvpn/xkey_helper.c index c9e8d218..4d41631b 100644 --- a/src/openvpn/xkey_helper.c +++ b/src/openvpn/xkey_helper.c @@ -115,6 +115,17 @@ xkey_management_sign(void *unused, unsigned char *sig, size_t *siglen, /* else assume RSA key */ else if (!strcmp(alg.padmode, "pkcs1")) { + /* management interface expects a pkcs1 encoded digest -- add it */ + unsigned char enc[EVP_MAX_MD_SIZE + 32]; /* 32 bytes enough for digest inf structure */ + size_t enc_len = sizeof(enc); + + if (!encode_pkcs1((unsigned char **)&enc, &enc_len, alg.mdname, tbs, tbslen)) + { + return 0; + } + tbs = enc; + tbslen = enc_len; + strncpynt(alg_str, "RSA_PKCS1_PADDING", sizeof(alg_str)); } else if (!strcmp(alg.padmode, "none")) @@ -155,4 +166,120 @@ xkey_management_sign(void *unused, unsigned char *sig, size_t *siglen, return (*siglen > 0); } +/** + * Add PKCS1 DigestInfo to tbs and return the result in *enc. + * + * @param enc pointer to output buffer + * @param enc_len capacity in bytes of output buffer + * @param mdname name of the hash algorithm (SHA256, SHA1 etc.) + * @param tbs pointer to digest to be encoded + * @param tbslen length of data in bytes + * + * @return false on error, true on success + * + * On return enc_len is set to actual size of the result. + * enc is NULL or enc_len is not enough to store the result, it is set + * to the required size and false is returned. + * + */ +bool +encode_pkcs1(unsigned char **enc, size_t *enc_len, const char *mdname, + const unsigned char *tbs, size_t tbslen) +{ + bool ret = false; + unsigned char *ptr; + int out_len = 0; + int tmp_len; + X509_ALGOR *algor = NULL; + ASN1_STRING *digest = NULL; + + ASSERT(enc_len != NULL); + ASSERT(tbs != NULL); + + int nid = OBJ_sn2nid(mdname); + if(nid == NID_undef) + { + msg(M_WARN, "Error: encode_pkcs11: invalid digest name <%s>", mdname); + return false; + } + + if (nid == NID_md5_sha1) /* no encoding needed -- just copy */ + { + out_len = (int) tbslen; + if (enc && (*enc_len >= out_len)) + { + memcpy(*enc, tbs, out_len); + } + *enc_len = tbslen; + ret = 1; + goto cleanup; + } + + if((algor = X509_ALGOR_new()) == NULL + || X509_ALGOR_set0(algor, OBJ_nid2obj(nid), V_ASN1_NULL, NULL) <= 0 + || (digest = ASN1_STRING_type_new(V_ASN1_OCTET_STRING)) == NULL + || ASN1_STRING_set(digest, tbs, tbslen) <= 0) + { + msg(M_WARN, "Error: encode_pkcs11: failed to create ASN1 strings"); + goto cleanup; + } + + if(algor->algorithm == NULL || OBJ_length(algor->algorithm) == 0) + { + msg(M_WARN, "Error: encode_pkcs11: invalide digest type(nid = %d)", nid); + goto cleanup; + } + + /* We want DER encoding of X509_SIG = {algor, digest} which could be + * computed as i2d_X509_SIG(), but, unfortunately, the X509_SIG struct + * is opaque and has no constructor. Hence we combine the two elements + * into a sequence ourselves -- not pretty + */ + + /* find required size for the buffer */ + if((tmp_len = i2d_X509_ALGOR(algor, NULL)) < 0) + { + goto cleanup; + } + out_len = tmp_len; + + if((tmp_len = i2d_ASN1_OCTET_STRING(digest, NULL)) < 0) + { + goto cleanup; + } + out_len += tmp_len + 2 ; /* extra 2 bytes for sequence header added below */ + + if ((out_len > (int) *enc_len) || !enc) + { + *enc_len = out_len; + goto cleanup; + } + + ptr = *enc; + *ptr++ = V_ASN1_SEQUENCE | V_ASN1_CONSTRUCTED; + *ptr++ = out_len - 2; + + /* compute and append the DER of algor and digest to ptr */ + i2d_X509_ALGOR(algor, &ptr); /* this advances ptr */ + i2d_ASN1_OCTET_STRING(digest, &ptr); + + *enc_len = out_len; /* assignment safe as out_len is > 0 at this point */ + ret = 1; + + dmsg(D_LOW, "encode_pkcs1: digest length = %d encoded length = %d", + (int) tbslen, out_len); + +cleanup: + if(digest) + { + ASN1_STRING_free(digest); + } + if(algor) + { + X509_ALGOR_free(algor); + } + + return ret; +} + #endif /* HAVE_XKEY_PROVIDER */ From patchwork Wed Sep 22 21:12:54 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [Openvpn-devel,9/9] Allow management client to announce pss padding support X-Patchwork-Submitter: Selva Nair X-Patchwork-Id: 1963 Message-Id: <20210922211254.7570-10-selva.nair@gmail.com> To: openvpn-devel@lists.sourceforge.net Date: Wed, 22 Sep 2021 17:12:54 -0400 From: selva.nair@gmail.com List-Id: From: Selva Nair pk-sig request from management can currently indicate support for 'nopadding' or 'pkcs1i' signatures. Add 'pss' as an option to indicate that PSS signing requests are accepted. To match, extend the algorithm string in PK_SIGN request to include the following format: - RSA_PKCS1_PSS_PADDING,hashlag=name,saltlen=[max|digest|auto] Here 'name' is the short common name of the hash algorithm. E.g., SHA1, SHA256 etc. Signed-off-by: Selva Nair --- doc/man-sections/management-options.rst | 8 +++++++- doc/management-notes.txt | 15 +++++++++++---- src/openvpn/manage.h | 1 + src/openvpn/options.c | 7 ++++++- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/doc/man-sections/management-options.rst b/doc/man-sections/management-options.rst index de0d47e7..b173a1ea 100644 --- a/doc/man-sections/management-options.rst +++ b/doc/man-sections/management-options.rst @@ -90,9 +90,15 @@ server and client mode operations. management-external-key management-external-key nopadding management-external-key pkcs1 + management-external-key pss + + or any combination like: + :: + management-external-key nopadding pkcs1 + management-external-key pkcs1 pss - The optional parameters :code:`nopadding` and :code:`pkcs1` signal + The optional parameters :code:`nopadding` :code:`pkcs1` and :code:`pss` signal support for different padding algorithms. See :code:`doc/mangement-notes.txt` for a complete description of this feature. diff --git a/doc/management-notes.txt b/doc/management-notes.txt index 84e3d04b..27163239 100644 --- a/doc/management-notes.txt +++ b/doc/management-notes.txt @@ -1019,10 +1019,17 @@ can be indicated in the signing request only if the client version is > 2" The currently defined padding algorithms are: - - RSA_PKCS1_PADDING - PKCS1 padding and RSA signature - - RSA_NO_PADDING - No padding may be added for the signature - - ECDSA - EC signature. - + - RSA_PKCS1_PADDING - PKCS1 padding and RSA signature + - RSA_NO_PADDING - No padding may be added for the signature + - ECDSA - EC signature. + - RSA_PKCS1_PSS_PADDING,params - RSA signature with PSS padding + + params for PSS are specified as 'digest=name,saltlen=[max|digest|auto]'. + The digest names are short common names such as SHA256, SHA224, etc. + In the case of PKCS1, when the hash algorithm is not the legacy MD5-SHA1, + the digest is encoded with DigestInfo header before presening to the + management. This is identical to CKM_RSA_PKCS in cryptoki as well as + what RSA_sign() provides. COMMAND -- certificate (OpenVPN 2.4 or higher) ---------------------------------------------- diff --git a/src/openvpn/manage.h b/src/openvpn/manage.h index 04dc98d1..5ed27c0c 100644 --- a/src/openvpn/manage.h +++ b/src/openvpn/manage.h @@ -339,6 +339,7 @@ struct management *management_init(void); #define MF_QUERY_REMOTE (1<<13) #define MF_QUERY_PROXY (1<<14) #define MF_EXTERNAL_CERT (1<<15) +#define MF_EXTERNAL_KEY_PSSPAD (1<<16) bool management_open(struct management *man, const char *addr, diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 26305a90..6e71563f 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -2213,7 +2213,8 @@ options_postprocess_verify_ce(const struct options *options, #if defined(ENABLE_MANAGEMENT) if ((tls_version_max() >= TLS_VER_1_3) && (options->management_flags & MF_EXTERNAL_KEY) - && !(options->management_flags & (MF_EXTERNAL_KEY_NOPADDING)) + && !(options->management_flags & (MF_EXTERNAL_KEY_NOPADDING) + || options->management_flags & (MF_EXTERNAL_KEY_PSSPAD)) ) { msg(M_ERR, "management-external-key with OpenSSL 1.1.1 requires " @@ -5511,6 +5512,10 @@ add_option(struct options *options, { options->management_flags |= MF_EXTERNAL_KEY_PKCS1PAD; } + else if (streq(p[j], "pss")) + { + options->management_flags |= MF_EXTERNAL_KEY_PSSPAD; + } else { msg(msglevel, "Unknown management-external-key flag: %s", p[j]);