[Openvpn-devel,v3,02/18] Implement KEYMGMT in the xkey provider

Message ID 20211214165928.30676-3-selva.nair@gmail.com
State Accepted
Headers show
Series External key provider for use with OpenSSL 3 | expand

Commit Message

Selva Nair Dec. 14, 2021, 5:59 a.m. UTC
From: Selva Nair <selva.nair@gmail.com>

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 keys may
get imported into us for some operations as well as
for comparison with unexportable external keys that we hold.

Implementation of signature callbacks is in the next commit.

v2 changes: This was commit 3/9 in v1
v3 changes: When OpenSSL native key is imported instead of duplicating
the whole key, use only the public components for public key.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
---
 src/openvpn/xkey_provider.c | 375 +++++++++++++++++++++++++++++++++++-
 1 file changed, 374 insertions(+), 1 deletion(-)

Comments

Arne Schwabe Jan. 19, 2022, 11:15 p.m. UTC | #1
Am 14.12.21 um 17:59 schrieb selva.nair@gmail.com:
> From: Selva Nair <selva.nair@gmail.com>
> 
> 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 keys may
> get imported into us for some operations as well as
> for comparison with unexportable external keys that we hold.
> 
> Implementation of signature callbacks is in the next commit.
> 
> v2 changes: This was commit 3/9 in v1
> v3 changes: When OpenSSL native key is imported instead of duplicating
> the whole key, use only the public components for public key.
> 

Acked-By: Arne Schwabe <arne@rfc2549.org>
Gert Doering Jan. 20, 2022, 2:57 a.m. UTC | #2
Only compile-tested on Linux / OpenSSL 3.0.1 (and briefly glanced over
the code to see what happens).  It breaks...

xkey_provider.c:223:16: error: 'XKEY_KEYDATA' has no member named 'free'
  223 |             key->free = (XKEY_PRIVKEY_FREE_fn *) EVP_PKEY_free;
      |                ^~
xkey_provider.c:223:26: error: 'XKEY_PRIVKEY_FREE_fn' undeclared (first use in this function)
  223 |             key->free = (XKEY_PRIVKEY_FREE_fn *) EVP_PKEY_free;
      |                          ^~~~~~~~~~~~~~~~~~~~

*but* this is fixed in 03/18 - and as the whole patchset is basically
a no-op before all is in (it will break compilation with 3.0.1, so
might annoy someone doing bisect in the future, hitting just that commit
by chance...) I'll still merge & push.

Your patch has been applied to the master branch.

commit 44509116da50b53a5588318fb27b2e4a3da4ab6c
Author: Selva Nair
Date:   Tue Dec 14 11:59:12 2021 -0500

     Implement KEYMGMT in the xkey provider

     Signed-off-by: Selva Nair <selva.nair@gmail.com>
     Acked-by: Arne Schwabe <arne@rfc2549.org>
     Message-Id: <20211214165928.30676-3-selva.nair@gmail.com>
     URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg23438.html
     Signed-off-by: Gert Doering <gert@greenie.muc.de>


--
kind regards,

Gert Doering

Patch

diff --git a/src/openvpn/xkey_provider.c b/src/openvpn/xkey_provider.c
index d47faf0a..a083ec2d 100644
--- a/src/openvpn/xkey_provider.c
+++ b/src/openvpn/xkey_provider.c
@@ -44,6 +44,9 @@ 
 #include <openssl/evp.h>
 #include <openssl/err.h>
 
+/* 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";
 
@@ -59,6 +62,376 @@  typedef struct
               dmsg(f|M_NOPREFIX, __VA_ARGS__);                      \
            } while(0)
 
+typedef enum
+{
+    ORIGIN_UNDEFINED = 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;
+    XKEY_PROVIDER_CTX *prov;
+    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_import_types;
+static OSSL_FUNC_keymgmt_get_params_fn keymgmt_get_params;
+static OSSL_FUNC_keymgmt_gettable_params_fn keymgmt_gettable_params;
+static OSSL_FUNC_keymgmt_set_params_fn keymgmt_set_params;
+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()
+{
+    xkey_dmsg(D_LOW, "entry");
+
+    XKEY_KEYDATA *key = OPENSSL_zalloc(sizeof(*key));
+    if (!key)
+    {
+        msg(M_NONFATAL, "xkey_keydata_new: out of memory");
+    }
+
+    return key;
+}
+
+static void
+keydata_free(XKEY_KEYDATA *key)
+{
+    xkey_dmsg(D_LOW, "entry");
+
+    if (!key || key->refcount-- > 0) /* free when refcount goes to zero */
+    {
+        return;
+    }
+    if (key->pubkey)
+    {
+        EVP_PKEY_free(key->pubkey);
+    }
+    OPENSSL_free(key);
+}
+
+static void *
+keymgmt_new(void *provctx)
+{
+    xkey_dmsg(D_LOW, "entry");
+
+    XKEY_KEYDATA *key = keydata_new();
+    if (key)
+    {
+        key->prov = provctx;
+    }
+
+    return key;
+}
+
+static void *
+keymgmt_load(const void *reference, size_t reference_sz)
+{
+    xkey_dmsg(D_LOW, "entry");
+
+    return NULL;
+}
+
+/**
+ * Key import function
+ * When key operations like sign/verify are done in our context
+ * the key gets imported into us. We will also use import to
+ * load an external key into the provider.
+ *
+ * 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)
+{
+    xkey_dmsg(D_LOW, "entry");
+
+    XKEY_KEYDATA *key = keydata;
+    ASSERT(key);
+
+    /* Our private key is immutable -- we import only if keydata is empty */
+    if (key->handle || key->pubkey)
+    {
+        msg(M_WARN, "Error: keymgmt_import: keydata not empty -- our keys are immutable");
+        return 0;
+    }
+
+    /* create a native public key and assign it to key->pubkey */
+    EVP_PKEY *pkey = NULL;
+    int selection_pub = selection & ~OSSL_KEYMGMT_SELECT_PRIVATE_KEY;
+
+    EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(key->prov->libctx, name, NULL);
+    if (!ctx
+        || (EVP_PKEY_fromdata_init(ctx) != 1)
+        || (EVP_PKEY_fromdata(ctx, &pkey, selection_pub, (OSSL_PARAM*) params) !=1))
+    {
+        msg(M_WARN, "Error: keymgmt_import failed for key type <%s>", name);
+        if (pkey)
+        {
+            EVP_PKEY_free(pkey);
+        }
+        if (ctx)
+        {
+            EVP_PKEY_CTX_free(ctx);
+        }
+        return 0;
+    }
+
+    key->pubkey = pkey;
+    key->origin = OPENSSL_NATIVE;
+    if (selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY)
+    {
+        /* create private key */
+        pkey = NULL;
+        if (EVP_PKEY_fromdata(ctx, &pkey, selection, (OSSL_PARAM*) params) == 1)
+        {
+            key->handle = pkey;
+            key->free = (XKEY_PRIVKEY_FREE_fn *) EVP_PKEY_free;
+        }
+    }
+    EVP_PKEY_CTX_free(ctx);
+
+    xkey_dmsg(D_LOW, "imported native %s key", EVP_PKEY_get0_type_name(pkey));
+    return 1;
+}
+
+static int
+rsa_keymgmt_import(void *keydata, int selection, const OSSL_PARAM params[])
+{
+    xkey_dmsg(D_LOW, "entry");
+
+    return keymgmt_import(keydata, selection, params, "RSA");
+}
+
+static int
+ec_keymgmt_import(void *keydata, int selection, const OSSL_PARAM params[])
+{
+    xkey_dmsg(D_LOW, "entry");
+
+    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)
+{
+    xkey_dmsg(D_LOW, "entry");
+
+    static const OSSL_PARAM key_types[] = { OSSL_PARAM_END };
+
+    if (selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY)
+    {
+       return key_types;
+    }
+    return NULL;
+}
+
+static void
+keymgmt_free(void *keydata)
+{
+    xkey_dmsg(D_LOW, "entry");
+
+    keydata_free(keydata);
+}
+
+static int
+keymgmt_has(const void *keydata, int selection)
+{
+    xkey_dmsg(D_LOW, "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;
+
+    xkey_dmsg(D_LOW, "entry");
+
+    int ret = key1 && key2 && key1->pubkey && key2->pubkey;
+
+    /* our keys always have pubkey -- we only match them */
+
+    if (selection & OSSL_KEYMGMT_SELECT_KEYPAIR)
+    {
+        ret = ret && EVP_PKEY_eq(key1->pubkey, key2->pubkey);
+        xkey_dmsg(D_LOW, "checking key pair match: res = %d", ret);
+    }
+
+    if (selection & OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS)
+    {
+        ret = ret && EVP_PKEY_parameters_eq(key1->pubkey, key2->pubkey);
+        xkey_dmsg(D_LOW, "checking parameter match: res = %d", ret);
+    }
+
+    return ret;
+}
+
+/* A minimal set of key params that we can return */
+static const OSSL_PARAM *
+keymgmt_gettable_params(void *provctx)
+{
+    xkey_dmsg(D_LOW, "entry");
+
+    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)
+{
+    xkey_dmsg(D_LOW, "entry");
+
+    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 this case.
+ */
+static int
+keymgmt_set_params(void *keydata, const OSSL_PARAM *params)
+{
+    XKEY_KEYDATA *key = keydata;
+    ASSERT(key);
+
+    xkey_dmsg(D_LOW, "entry");
+
+    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)
+{
+    xkey_dmsg(D_LOW, "entry");
+
+    return "RSA";
+}
+
+static const char *
+ec_keymgmt_name(int id)
+{
+    xkey_dmsg(D_LOW, "entry");
+
+    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_SET_PARAMS, (void (*) (void)) keymgmt_set_params},
+    {OSSL_FUNC_KEYMGMT_SETTABLE_PARAMS, (void (*) (void)) keymgmt_gettable_params}, /* same as gettable */
+    {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_SET_PARAMS, (void (*) (void)) keymgmt_set_params},
+    {OSSL_FUNC_KEYMGMT_SETTABLE_PARAMS, (void (*) (void)) keymgmt_gettable_params}, /* same as gettable */
+    {OSSL_FUNC_KEYMGMT_QUERY_OPERATION_NAME, (void (*)(void)) ec_keymgmt_name},
+    {0, NULL }
+};
+
+const OSSL_ALGORITHM keymgmts[] = {
+    {"RSA:rsaEncryption", props, rsa_keymgmt_functions, "OpenVPN xkey RSA Key Manager"},
+    {"RSA-PSS:RSASSA-PSS", props, rsa_keymgmt_functions, "OpenVPN xkey RSA-PSS Key Manager"},
+    {"EC:id-ecPublicKey", props, ec_keymgmt_functions, "OpenVPN xkey EC Key Manager"},
+    {NULL, NULL, NULL, NULL}
+};
+
 /* main provider interface */
 
 /* provider callbacks we implement */
@@ -80,7 +453,7 @@  query_operation(void *provctx, int op, int *no_store)
             return NULL;
 
         case OSSL_OP_KEYMGMT:
-            return NULL;
+            return keymgmts;
 
         default:
             xkey_dmsg(D_LOW, "op not supported");