diff options
author | Selva Nair | 2023-03-14 21:35:16 -0400 |
---|---|---|
committer | Gert Doering | 2023-03-16 10:56:01 +0100 |
commit | f970ad99a1a1f30d091853b111e678dbdc3dede9 (patch) | |
tree | cdf0396fd690aefe3eb98f7e0073a5b2ddf17418 | |
parent | 5c2154ca49a591afd8faa8e535a67b149ddbd354 (diff) | |
download | openvpn-f970ad99a1a1f30d091853b111e678dbdc3dede9.zip openvpn-f970ad99a1a1f30d091853b111e678dbdc3dede9.tar.gz |
Add a test for signing with certificates in Windows store
- For each sample certificate/key pair imported into the store,
load the key into xkey-provider and sign a test message.
As the key is "provided", signing will use appropriate
backend (Windows CNG in this case).
The signature is then verified using OpenSSL.
Change-Id: I520b34ba51e8c6d0247a82edc52bde181ab5a717
Signed-off-by: Selva Nair <selva.nair@gmail.com>
Acked-by: Gert Doering <gert@greenie.muc.de>
Message-Id: <20230315013516.1256700-5-selva.nair@gmail.com>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg26416.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>
(cherry picked from commit 0267649a21a2af1b60fbddcb78b0ed642080d6fd)
-rw-r--r-- | tests/unit_tests/openvpn/Makefile.am | 1 | ||||
-rw-r--r-- | tests/unit_tests/openvpn/test_cryptoapi.c | 166 |
2 files changed, 167 insertions, 0 deletions
diff --git a/tests/unit_tests/openvpn/Makefile.am b/tests/unit_tests/openvpn/Makefile.am index 339c7ef..4391a54 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -157,6 +157,7 @@ cryptoapi_testdriver_LDFLAGS = @TEST_LDFLAGS@ \ $(OPTIONAL_CRYPTO_LIBS) -lcrypt32 -lncrypt cryptoapi_testdriver_SOURCES = test_cryptoapi.c mock_msg.c \ $(top_srcdir)/src/openvpn/xkey_helper.c \ + $(top_srcdir)/src/openvpn/xkey_provider.c \ $(top_srcdir)/src/openvpn/buffer.c \ $(top_srcdir)/src/openvpn/base64.c \ $(top_srcdir)/src/openvpn/platform.c \ diff --git a/tests/unit_tests/openvpn/test_cryptoapi.c b/tests/unit_tests/openvpn/test_cryptoapi.c index ccb3207..b07e893 100644 --- a/tests/unit_tests/openvpn/test_cryptoapi.c +++ b/tests/unit_tests/openvpn/test_cryptoapi.c @@ -47,6 +47,7 @@ #include <cryptoapi.c> /* pull-in the whole file to test static functions */ struct management *management; /* global */ +static OSSL_PROVIDER *prov[2]; /* mock a management function that xkey_provider needs */ char * @@ -66,6 +67,11 @@ OSSL_LIB_CTX *tls_libctx; #define _countof(x) sizeof((x))/sizeof(*(x)) #endif +/* A message for signing */ +static const char *test_msg = "Lorem ipsum dolor sit amet, consectetur " + "adipisici elit, sed eiusmod tempor incidunt " + "ut labore et dolore magna aliqua."; + /* test data */ static const uint8_t test_hash[] = { 0x77, 0x38, 0x65, 0x00, 0x1e, 0x96, 0x48, 0xc6, 0x57, 0x0b, 0xae, @@ -336,6 +342,164 @@ test_find_cert_byissuer(void **state) gc_free(&gc); } +static int +setup_cryptoapi_sign(void **state) +{ + (void) state; + /* Initialize providers in a way matching what OpenVPN core does */ + tls_libctx = OSSL_LIB_CTX_new(); + prov[0] = OSSL_PROVIDER_load(tls_libctx, "default"); + OSSL_PROVIDER_add_builtin(tls_libctx, "ovpn.xkey", xkey_provider_init); + prov[1] = OSSL_PROVIDER_load(tls_libctx, "ovpn.xkey"); + + /* set default propq as we do in ssl_openssl.c */ + EVP_set_default_properties(tls_libctx, "?provider!=ovpn.xkey"); + return 0; +} + +static int +teardown_cryptoapi_sign(void **state) +{ + (void) state; + for (size_t i = 0; i < _countof(prov); i++) + { + if (prov[i]) + { + OSSL_PROVIDER_unload(prov[i]); + prov[i] = NULL; + } + } + OSSL_LIB_CTX_free(tls_libctx); + tls_libctx = NULL; + return 0; +} + +/** + * Sign "test_msg" using a private key. The key may be a "provided" key + * in which case its signed by the provider's backend -- cryptoapi in our + * case. Then verify the signature using OpenSSL. + * Returns 1 on success, 0 on error. + */ +static int +digest_sign_verify(EVP_PKEY *privkey, EVP_PKEY *pubkey) +{ + uint8_t *sig = NULL; + size_t siglen = 0; + int ret = 0; + + OSSL_PARAM params[2] = {OSSL_PARAM_END}; + const char *mdname = "SHA256"; + + if (EVP_PKEY_get_id(privkey) == EVP_PKEY_RSA) + { + const char *padmode = "pss"; /* RSA_PSS: for all other params, use defaults */ + params[0] = OSSL_PARAM_construct_utf8_string(OSSL_SIGNATURE_PARAM_PAD_MODE, + (char *)padmode, 0); + params[1] = OSSL_PARAM_construct_end(); + } + else if (EVP_PKEY_get_id(privkey) == EVP_PKEY_EC) + { + params[0] = OSSL_PARAM_construct_end(); + } + else + { + print_error("Unknown key type in digest_sign_verify()"); + return ret; + } + + EVP_PKEY_CTX *pctx = NULL; + EVP_MD_CTX *mctx = EVP_MD_CTX_new(); + + if (!mctx + || EVP_DigestSignInit_ex(mctx, &pctx, mdname, tls_libctx, NULL, privkey, params) <= 0) + { + /* cmocka assert output for these kinds of failures is hardly explanatory, + * print a message and assert in caller. */ + print_error("Failed to initialize EVP_DigestSignInit_ex()\n"); + goto done; + } + + /* sign with sig = NULL to get required siglen */ + if (EVP_DigestSign(mctx, sig, &siglen, (uint8_t *)test_msg, strlen(test_msg)) != 1) + { + print_error("EVP_DigestSign: failed to get required signature size"); + goto done; + } + assert_true(siglen > 0); + + if ((sig = test_calloc(1, siglen)) == NULL) + { + print_error("Out of memory"); + goto done; + } + if (EVP_DigestSign(mctx, sig, &siglen, (uint8_t *)test_msg, strlen(test_msg)) != 1) + { + print_error("EVP_DigestSign: signing failed"); + goto done; + } + + /* + * Now validate the signature using OpenSSL. Just use the public key + * which is a native OpenSSL key. + */ + EVP_MD_CTX_free(mctx); /* this also frees pctx */ + mctx = EVP_MD_CTX_new(); + pctx = NULL; + if (!mctx + || EVP_DigestVerifyInit_ex(mctx, &pctx, mdname, tls_libctx, NULL, pubkey, params) <= 0) + { + print_error("Failed to initialize EVP_DigestVerifyInit_ex()"); + goto done; + } + if (EVP_DigestVerify(mctx, sig, siglen, (uint8_t *)test_msg, strlen(test_msg)) != 1) + { + print_error("EVP_DigestVerify failed"); + goto done; + } + ret = 1; + +done: + if (mctx) + { + EVP_MD_CTX_free(mctx); /* this also frees pctx */ + } + test_free(sig); + return ret; +} + +/* Load sample certificates & keys, sign a test message using + * them and verify the signature. + */ +void +test_cryptoapi_sign(void **state) +{ + (void) state; + char select_string[64]; + X509 *x509 = NULL; + EVP_PKEY *privkey = NULL; + + import_certs(state); /* a no-op if already imported */ + assert_true(certs_loaded); + + for (struct test_cert *c = certs; c->cert; c++) + { + if (c->valid == 0) + { + continue; + } + openvpn_snprintf(select_string, sizeof(select_string), "THUMB:%s", c->hash); + if (Load_CryptoAPI_certificate(select_string, &x509, &privkey) != 1) + { + fail_msg("Load_CryptoAPI_certificate failed: <%s>", c->friendly_name); + return; + } + EVP_PKEY *pubkey = X509_get_pubkey(x509); + assert_int_equal(digest_sign_verify(privkey, pubkey), 1); + X509_free(x509); + EVP_PKEY_free(privkey); + } +} + static void test_parse_hexstring(void **state) { @@ -366,6 +530,8 @@ main(void) cmocka_unit_test(test_find_cert_bythumb), cmocka_unit_test(test_find_cert_byname), cmocka_unit_test(test_find_cert_byissuer), + cmocka_unit_test_setup_teardown(test_cryptoapi_sign, setup_cryptoapi_sign, + teardown_cryptoapi_sign), }; int ret = cmocka_run_group_tests_name("cryptoapi tests", tests, NULL, cleanup); |