]> git.ipfire.org Git - thirdparty/gnutls.git/commitdiff
pkcs11: respect Mozilla's time-based distrust upon issuer lookup
authorDaiki Ueno <ueno@gnu.org>
Mon, 13 Mar 2023 07:58:45 +0000 (16:58 +0900)
committerDaiki Ueno <ueno@gnu.org>
Thu, 16 Mar 2023 21:25:49 +0000 (06:25 +0900)
This implements the basic logic needed to support time-based distrust
of CA, according to [1].

1. https://wiki.mozilla.org/CA/Additional_Trust_Changes#Distrust_After

Signed-off-by: Daiki Ueno <ueno@gnu.org>
.gitignore
lib/pkcs11.c
lib/pkcs11_int.h
lib/x509/common.h
lib/x509/time.c
lib/x509/verify.c
tests/Makefile.am
tests/pkcs11/distrust-after.c [new file with mode: 0644]
tests/pkcs11/pkcs11-mock3.c [new file with mode: 0644]

index 95cb3d89aece320b644d291cccaeed4b64fa5384..b474c09f41f37a5c1ee12cb077d974b4ff42f322 100644 (file)
@@ -456,6 +456,7 @@ tests/keylog-func
 tests/ktls_keyupdate
 tests/libpkcs11mock1.la
 tests/libpkcs11mock2.la
+tests/libpkcs11mock3.la
 tests/libutils.la
 tests/long-session-id
 tests/mini
@@ -570,6 +571,7 @@ tests/pkcs11-privkey-fork-reinit
 tests/pkcs11-privkey-raw
 tests/pkcs11-privkey-safenet-always-auth
 tests/pkcs11-token-raw
+tests/pkcs11/distrust-after
 tests/pkcs11/gnutls_pcert_list_import_x509_file
 tests/pkcs11/gnutls_x509_crt_list_import_url
 tests/pkcs11/list-objects
@@ -734,6 +736,7 @@ tests/slow/hash-large
 tests/slow/keygen
 tests/slow/mac-override
 tests/softhsm-*.db/
+tests/softhsm-distrust-after.config
 tests/softhsm-neg-no-key.config
 tests/softhsm-post-handshake-with-cert-pkcs11.config
 tests/spki
index 2fce456212933cb8080c730e289892e638dd0b39..1db41ad9f6665f6ab23417456122956e548e429b 100644 (file)
 
 #define MAX_SLOTS 48
 
+#ifndef CKA_NSS_SERVER_DISTRUST_AFTER
+# define CKA_NSS_SERVER_DISTRUST_AFTER 0xce534373UL
+#endif
+
+#ifndef CKA_NSS_EMAIL_DISTRUST_AFTER
+# define CKA_NSS_EMAIL_DISTRUST_AFTER 0xce534374UL
+#endif
+
 GNUTLS_STATIC_MUTEX(pkcs11_mutex);
 
 struct gnutls_pkcs11_provider_st {
@@ -103,6 +111,12 @@ struct find_pkey_list_st {
        size_t key_ids_size;
 };
 
+enum distrust_purpose {
+       PKCS11_DISTRUST_AFTER_NONE = 0,
+       PKCS11_DISTRUST_AFTER_SERVER = 1,
+       PKCS11_DISTRUST_AFTER_EMAIL = 2
+};
+
 struct find_cert_st {
        gnutls_datum_t dn;
        gnutls_datum_t issuer_dn;
@@ -112,6 +126,8 @@ struct find_cert_st {
        unsigned need_import;
        gnutls_pkcs11_obj_t obj;
        gnutls_x509_crt_t crt;  /* used when compare flag is specified */
+       enum distrust_purpose distrust_purpose;
+       time_t distrust_after;
        unsigned flags;
 };
 
@@ -4074,6 +4090,58 @@ static int get_data_and_attrs(struct pkcs11_session_info *sinfo,
        return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
 }
 
+static enum distrust_purpose distrust_purpose_from_oid(const char *oid)
+{
+       static const struct {
+               const char *oid;
+               enum distrust_purpose purpose;
+       } map[] = {
+               {GNUTLS_KP_TLS_WWW_SERVER, PKCS11_DISTRUST_AFTER_SERVER},
+               {GNUTLS_KP_EMAIL_PROTECTION, PKCS11_DISTRUST_AFTER_EMAIL},
+       };
+       size_t i;
+
+       for (i = 0; i < sizeof(map) / sizeof(map[0]); i++) {
+               if (strcmp(map[i].oid, oid) == 0) {
+                       return map[i].purpose;
+               }
+       }
+
+       return PKCS11_DISTRUST_AFTER_NONE;
+}
+
+static time_t
+get_distrust_after(struct pkcs11_session_info *sinfo,
+                  ck_object_handle_t object, enum distrust_purpose purpose)
+{
+       /* the attribute is in a fixed format: utcTime with seconds */
+       char buf[14];
+       struct ck_attribute a[1];
+
+       switch (purpose) {
+       case PKCS11_DISTRUST_AFTER_SERVER:
+               a[0].type = CKA_NSS_SERVER_DISTRUST_AFTER;
+               break;
+       case PKCS11_DISTRUST_AFTER_EMAIL:
+               a[0].type = CKA_NSS_EMAIL_DISTRUST_AFTER;
+               break;
+       default:
+               gnutls_assert();
+               return (time_t) (-1);
+       }
+
+       a[0].value = buf;
+       a[0].value_len = sizeof(buf) - 1;
+
+       if (pkcs11_get_attribute_value(sinfo->module, sinfo->pks, object, a,
+                                      1) != CKR_OK) {
+               return (time_t) (-1);
+       }
+
+       buf[a[0].value_len] = '\0';
+       return _gnutls_utcTime2gtime(buf);
+}
+
 static int
 find_cert_cb(struct ck_function_list *module, struct pkcs11_session_info *sinfo,
             struct ck_token_info *tinfo, struct ck_info *lib_info, void *input)
@@ -4270,6 +4338,16 @@ find_cert_cb(struct ck_function_list *module, struct pkcs11_session_info *sinfo,
                                }
                        }
 
+                       if ((priv->flags &
+                            GNUTLS_PKCS11_OBJ_FLAG_RETRIEVE_TRUSTED)
+                           && priv->distrust_purpose !=
+                           PKCS11_DISTRUST_AFTER_NONE) {
+                               priv->distrust_after =
+                                   get_distrust_after(sinfo, ctx,
+                                                      priv->distrust_purpose);
+                               continue;
+                       }
+
                        if (priv->need_import != 0) {
                                ret =
                                    pkcs11_obj_import(class, priv->obj,
@@ -4818,3 +4896,106 @@ char *gnutls_pkcs11_obj_flags_get_str(unsigned int flags)
        return NULL;
 
 }
+
+time_t
+_gnutls_pkcs11_get_distrust_after(const char *url, gnutls_x509_crt_t cert,
+                                 const char *purpose, unsigned int flags)
+{
+       int ret;
+       struct find_cert_st priv;
+       uint8_t serial[128];
+       size_t serial_size;
+       struct p11_kit_uri *info = NULL;
+       enum distrust_purpose distrust_purpose;
+
+       distrust_purpose = distrust_purpose_from_oid(purpose);
+       if (distrust_purpose == PKCS11_DISTRUST_AFTER_NONE) {
+               return (time_t) (-1);
+       }
+
+       PKCS11_CHECK_INIT_FLAGS_RET(flags, 0);
+
+       memset(&priv, 0, sizeof(priv));
+
+       if (url == NULL || url[0] == 0) {
+               url = "pkcs11:";
+       }
+
+       ret = pkcs11_url_to_info(url, &info, 0);
+       if (ret < 0) {
+               gnutls_assert();
+               return (time_t) (-1);
+       }
+
+       /* Attempt searching using the issuer DN + serial number */
+       serial_size = sizeof(serial);
+       ret = gnutls_x509_crt_get_serial(cert, serial, &serial_size);
+       if (ret < 0) {
+               gnutls_assert();
+               ret = (time_t) (-1);
+               goto cleanup;
+       }
+
+       ret = _gnutls_x509_ext_gen_number(serial, serial_size, &priv.serial);
+       if (ret < 0) {
+               gnutls_assert();
+               ret = (time_t) (-1);
+               goto cleanup;
+       }
+
+       priv.crt = cert;
+
+       priv.issuer_dn.data = cert->raw_issuer_dn.data;
+       priv.issuer_dn.size = cert->raw_issuer_dn.size;
+
+       /* assume PKCS11_OBJ_FLAG_COMPARE everywhere but DISTRUST info */
+       if (!(flags & GNUTLS_PKCS11_OBJ_FLAG_RETRIEVE_DISTRUSTED)
+           && !(flags & GNUTLS_PKCS11_OBJ_FLAG_COMPARE_KEY)) {
+               flags |= GNUTLS_PKCS11_OBJ_FLAG_COMPARE;
+       }
+
+       priv.flags = flags;
+       priv.distrust_purpose = distrust_purpose;
+
+       ret =
+           _pkcs11_traverse_tokens(find_cert_cb, &priv, info,
+                                   NULL, pkcs11_obj_flags_to_int(flags));
+       if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
+               _gnutls_debug_log
+                   ("get_distrust_after: did not find cert, using issuer DN + serial, using DN only\n");
+               /* attempt searching with the subject DN only */
+               gnutls_assert();
+               if (priv.obj)
+                       gnutls_pkcs11_obj_deinit(priv.obj);
+               gnutls_free(priv.serial.data);
+               memset(&priv, 0, sizeof(priv));
+               priv.crt = cert;
+               priv.flags = flags;
+               priv.distrust_purpose = distrust_purpose;
+
+               priv.dn.data = cert->raw_dn.data;
+               priv.dn.size = cert->raw_dn.size;
+               ret =
+                   _pkcs11_traverse_tokens(find_cert_cb, &priv, info,
+                                           NULL,
+                                           pkcs11_obj_flags_to_int(flags));
+       }
+       if (ret < 0) {
+               gnutls_assert();
+               _gnutls_debug_log
+                   ("get_distrust_after: did not find any cert\n");
+               ret = (time_t) (-1);
+               goto cleanup;
+       }
+
+       ret = priv.distrust_after;
+
+ cleanup:
+       if (priv.obj)
+               gnutls_pkcs11_obj_deinit(priv.obj);
+       if (info)
+               p11_kit_uri_free(info);
+       gnutls_free(priv.serial.data);
+
+       return ret;
+}
index b93a6591f8356aa50d30f1ef0343438a86686925..5a26a5f5ca0dea4a8b4adcb014946c486e158963 100644 (file)
@@ -460,6 +460,10 @@ _gnutls_pkcs11_crt_is_known(const char *url, gnutls_x509_crt_t cert,
                            unsigned int flags,
                            gnutls_x509_crt_t * trusted_cert);
 
+time_t
+_gnutls_pkcs11_get_distrust_after(const char *url, gnutls_x509_crt_t cert,
+                                 const char *purpose, unsigned int flags);
+
 # endif                                /* ENABLE_PKCS11 */
 
 #endif                         /* GNUTLS_LIB_PKCS11_INT_H */
index 46b5bc79267f0074dffa7ca015ea8846b1cc72d9..51f8faab1985aa4936e340e3512090933c6aacd4 100644 (file)
@@ -256,6 +256,7 @@ unsigned _gnutls_check_key_purpose(gnutls_x509_crt_t cert, const char *purpose,
                                   unsigned no_any);
 
 time_t _gnutls_x509_generalTime2gtime(const char *ttime);
+time_t _gnutls_utcTime2gtime(const char *ttime);
 
 int _gnutls_get_extension(asn1_node asn, const char *root,
                          const char *extension_id, int indx,
index 63db6d60a1d48fa498e6adab085b575ed34e2e47..3dc4eaa7511aef25960be718b326594f1bde50e7 100644 (file)
@@ -35,8 +35,6 @@
 #include <common.h>
 #include <c-ctype.h>
 
-time_t _gnutls_utcTime2gtime(const char *ttime);
-
 /* TIME functions
  * Conversions between generalized or UTC time to time_t
  *
index f4384c4aa5a24f371710227720e289a634f5e2d4..52ccedbe314fe2a5731db788c88ff2cbb0a414a4 100644 (file)
@@ -1230,6 +1230,7 @@ _gnutls_pkcs11_verify_crt_status(gnutls_x509_trust_list_t tlist,
        gnutls_x509_crt_t issuer = NULL;
        gnutls_datum_t raw_issuer = { NULL, 0 };
        time_t now = gnutls_time(0);
+       time_t distrust_after;
 
        if (clist_size > 1) {
                /* Check if the last certificate in the path is self signed.
@@ -1376,6 +1377,25 @@ _gnutls_pkcs11_verify_crt_status(gnutls_x509_trust_list_t tlist,
                goto cleanup;
        }
 
+       /* check if the raw issuer is assigned with a time-based
+        * distrust and the certificate is issued after that period
+        */
+       distrust_after =
+           _gnutls_pkcs11_get_distrust_after(url, issuer,
+                                             purpose == NULL ?
+                                             GNUTLS_KP_TLS_WWW_SERVER :
+                                             purpose,
+                                             GNUTLS_PKCS11_OBJ_FLAG_RETRIEVE_TRUSTED);
+       if (distrust_after != (time_t) - 1
+           && distrust_after <
+           gnutls_x509_crt_get_activation_time(certificate_list
+                                               [clist_size - 1])) {
+               gnutls_assert();
+               status |= GNUTLS_CERT_INVALID;
+               status |= GNUTLS_CERT_SIGNER_NOT_FOUND;
+               goto cleanup;
+       }
+
        /* check if the raw issuer is distrusted (it can happen if
         * the issuer is both in the trusted list and the distrusted)
         */
index c263ac7c5cf6ea70fac8fc62ad1ce9531be4ab8b..48c694409f17ef71be6f0bf348acae0df0f48343 100644 (file)
@@ -342,6 +342,11 @@ libpkcs11mock2_la_SOURCES = pkcs11/pkcs11-mock2.c
 libpkcs11mock2_la_LDFLAGS = -shared -rpath $(pkglibdir) -module -no-undefined -avoid-version
 libpkcs11mock2_la_LIBADD =  ../gl/libgnu.la
 
+noinst_LTLIBRARIES += libpkcs11mock3.la
+libpkcs11mock3_la_SOURCES = pkcs11/pkcs11-mock3.c
+libpkcs11mock3_la_LDFLAGS = -shared -rpath $(pkglibdir) -module -no-undefined -avoid-version
+libpkcs11mock3_la_LIBADD =  ../gl/libgnu.la
+
 pkcs11_cert_import_url_exts_SOURCES = pkcs11/pkcs11-cert-import-url-exts.c
 pkcs11_cert_import_url_exts_DEPENDENCIES = libpkcs11mock1.la libutils.la
 
@@ -487,11 +492,13 @@ pathbuf_CPPFLAGS = $(AM_CPPFLAGS) \
 if ENABLE_PKCS11
 if !WINDOWS
 ctests += tls13/post-handshake-with-cert-pkcs11 pkcs11/tls-neg-pkcs11-no-key \
-       global-init-override
+       global-init-override pkcs11/distrust-after
 tls13_post_handshake_with_cert_pkcs11_DEPENDENCIES = libpkcs11mock2.la libutils.la
 tls13_post_handshake_with_cert_pkcs11_LDADD = $(LDADD) $(LIBDL)
 pkcs11_tls_neg_pkcs11_no_key_DEPENDENCIES = libpkcs11mock2.la libutils.la
 pkcs11_tls_neg_pkcs11_no_key_LDADD = $(LDADD) $(LIBDL)
+pkcs11_distrust_after_DEPENDENCIES = libpkcs11mock3.la libutils.la
+pkcs11_distrust_after_LDADD = $(LDADD) $(LIBDL)
 endif
 endif
 
@@ -612,6 +619,7 @@ TESTS_ENVIRONMENT +=                                                \
        CAFILE=$(srcdir)/cert-tests/data/ca-certs.pem           \
        P11MOCKLIB1=$(abs_builddir)/.libs/libpkcs11mock1.so     \
        P11MOCKLIB2=$(abs_builddir)/.libs/libpkcs11mock2.so     \
+       P11MOCKLIB3=$(abs_builddir)/.libs/libpkcs11mock3.so     \
        PKCS12_MANY_CERTS_FILE=$(srcdir)/cert-tests/data/pkcs12_5certs.p12      \
        PKCS12FILE=$(srcdir)/cert-tests/data/client.p12         \
        PKCS12PASSWORD=foobar                                   \
diff --git a/tests/pkcs11/distrust-after.c b/tests/pkcs11/distrust-after.c
new file mode 100644 (file)
index 0000000..05165ba
--- /dev/null
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2023 Red Hat, Inc.
+ *
+ * Author: Daiki Ueno
+ *
+ * This file is part of GnuTLS.
+ *
+ * GnuTLS is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuTLS 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 Lesser General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#if defined(_WIN32)
+
+int main(void)
+{
+       exit(77);
+}
+
+#else
+
+# include <string.h>
+# include <unistd.h>
+# include <gnutls/gnutls.h>
+# include <assert.h>
+
+# include "cert-common.h"
+# include "pkcs11/softhsm.h"
+# include "utils.h"
+
+/* This program tests that CKA_NSS_SERVER_DISTRUST_AFTER is honored
+ * while validating certificate chain.
+ */
+
+static void tls_log_func(int level, const char *str)
+{
+       fprintf(stderr, "server|<%d>| %s", level, str);
+}
+
+# define PIN "1234"
+
+# define CONFIG_NAME "softhsm-distrust-after"
+# define CONFIG CONFIG_NAME".config"
+
+static const unsigned char chain_pem[] =
+    "-----BEGIN CERTIFICATE-----"
+    "MIID5zCCAp+gAwIBAgIUIXzLE8ObVwBGHepbjMWRwW/NpDgwDQYJKoZIhvcNAQEL"
+    "BQAwGTEXMBUGA1UEAxMOR251VExTIHRlc3QgQ0EwIBcNMjMwMzE0MTAwNDAzWhgP"
+    "OTk5OTEyMzEyMzU5NTlaMDcxGzAZBgNVBAoTEkdudVRMUyB0ZXN0IHNlcnZlcjEY"
+    "MBYGA1UEAxMPdGVzdC5nbnV0bHMub3JnMIIBUjANBgkqhkiG9w0BAQEFAAOCAT8A"
+    "MIIBOgKCATEAtGsnmCWvwf8eyrB+9Ni87UOGZ1Rd2rQewpBfgzwCEfwTcoWyiKRl"
+    "QQt2XyO+ip/+eUtzOy7HSzy/FsmXVTUX86FySzDC4CeUEvNWAObOgksRXaQem/r6"
+    "uRsqTRi1uqXmDMeoqKFtqoiE3JYOsmwcNarnx5Q9+dXHwqINS7NuevcIX8UJzRWT"
+    "GveY3ypMZokk7R/QFmOBZaVYO6HNJWKbmYFUCBcY7HwvCKI7KFcynRdHCob7YrFB"
+    "meb73qjqIH7zG+666pohZCmS8q1z5RkFnTdT4hGfGF8iuuKLDQCMni+nhz1Avkqi"
+    "pZIIDC5hwFh8mpnh1qyDOSXPPhvt66NtncvFON7Bx26bNBS+MD6CkB65Spp25O8z"
+    "DEaiMXL2w2EL+KpnifSl5XY3oSmfgHmqdQIDAQABo4GmMIGjMAwGA1UdEwEB/wQC"
+    "MAAwGgYDVR0RBBMwEYIPdGVzdC5nbnV0bHMub3JnMCcGA1UdJQQgMB4GCCsGAQUF"
+    "BwMBBggrBgEFBQcDAwYIKwYBBQUHAwQwDgYDVR0PAQH/BAQDAgWgMB0GA1UdDgQW"
+    "BBRIIzRTCokxOEpa6sq20qbezh0rGDAfBgNVHSMEGDAWgBQedyNtZzEfkQebli/s"
+    "/MhG/ozhAzANBgkqhkiG9w0BAQsFAAOCATEAYbQLlr74D62lPEevV/HWLOMG8taY"
+    "gPld7Z5VApIhsJa913Jya7AOsW+lz48LX3QNTc8Xgj7FVwQeNP1GtBZXCe6U73KB"
+    "Z+qp1rIEwn2cQVmFG+ShxmUA/gxxmWql2BAORNd5ZCVOcZbMh9uwWjhIQN/SImtW"
+    "x3ebFgV5N7GPFbw+5NUITLXoLrD7Bixv3iQS8hWwmAmmPZbHAENRauL6jYSjniru"
+    "SSFYjzJ1trJB6VgpJ2yWfKdcGZmB3osnGshWbayVOaprbH0AWKwOZ/d7sAldjdVw"
+    "ZsaOhA+6NbvpKYZuw6Tdt0+VmUwGC1ATJGpc0dEXRBaFlt/e+gqQ43Mo+YwiMDYq"
+    "LDU5nLC6uTSZLtgQHTqb32xmQ/D/y6NkUTH3f4OcxPGxBRVBHjOTk6MhRA=="
+    "-----END CERTIFICATE-----"
+    "-----BEGIN CERTIFICATE-----"
+    "MIIDjTCCAkWgAwIBAgIUejTcfGbOAc9l4IBW+kpAN6A7Sj4wDQYJKoZIhvcNAQEL"
+    "BQAwGTEXMBUGA1UEAxMOR251VExTIHRlc3QgQ0EwIBcNMjMwMzE0MDk1NzU1WhgP"
+    "OTk5OTEyMzEyMzU5NTlaMBkxFzAVBgNVBAMTDkdudVRMUyB0ZXN0IENBMIIBUjAN"
+    "BgkqhkiG9w0BAQEFAAOCAT8AMIIBOgKCATEAnORCsX1unl//fy2d1054XduIg/3C"
+    "qVBaT3Hca65SEoDwh0KiPtQoOgZLdKY2cobGs/ojYtOjcs0KnlPYdmtjEh6WEhuJ"
+    "U95v4TQdC4OLMiE56eIGq252hZAbHoTL84Q14DxQWGuzQK830iml7fbw2WcIcRQ8"
+    "vFGs8SzfXw63+MI6Fq6iMAQIqP08WzGmRRzL5wvCiPhCVkrPmwbXoABub6AAsYwW"
+    "PJB91M9/lx5gFH5k9/iPfi3s2Kg3F8MOcppqFYjxDSnsfiz6eMh1+bYVIAo367vG"
+    "VYHigXMEZC2FezlwIHaZzpEoFlY3a7LFJ00yrjQ910r8UE+CEMTYzE40D0olCMo7"
+    "FA9RCjeO3bUIoYaIdVTUGWEGHWSeoxGei9Gkm6u+ASj8f+i0jxdD2qXsewIDAQAB"
+    "o2swaTAPBgNVHRMBAf8EBTADAQH/MCcGA1UdJQQgMB4GCCsGAQUFBwMBBggrBgEF"
+    "BQcDAwYIKwYBBQUHAwQwDgYDVR0PAQH/BAQDAgIEMB0GA1UdDgQWBBQedyNtZzEf"
+    "kQebli/s/MhG/ozhAzANBgkqhkiG9w0BAQsFAAOCATEAa37UdOTvdUfRGwjrodhE"
+    "tEnRnfrwfQ61RMK5GY07UAks7CjdeWFDLoQfv9oP9kH122hEGAA683xg/CH5OeN0"
+    "8zrayQKqwcH40SJQDzc748lTgxUIDaf2rrkoF8butpaDaI0fageqjlEvCeZZSuIC"
+    "KCfZK9NPN47DknuerjOTwrWxvXYRepfSo8VVbjRj8R4qsgJsmJZYQfrAg0XrnKf/"
+    "UibNPXRCYABsxH4ZFtivg93LaQ05z4IrPSWGOTDQxNBoEC0DVGfSc8XElP0MkF/K"
+    "BIPsl3Rt2oFNhfViF9Gpzy9Dj1P1kMD6kE7nBDiRBUPNJZBiJSGVTMZTMc2tg42W"
+    "QcUYnUUzOpQWg1tcOZy4s+EuJ0bEWhSkFfSN3ENxsHXNCYYHgeadATcGbzTxD6ib"
+    "eA==" "-----END CERTIFICATE-----";
+
+static const gnutls_datum_t chain = {
+       (unsigned char *)chain_pem, sizeof(chain_pem) - 1
+};
+
+static
+int pin_func(void *userdata, int attempt, const char *url, const char *label,
+            unsigned flags, char *pin, size_t pin_max)
+{
+       if (attempt == 0) {
+               strcpy(pin, PIN);
+               return 0;
+       }
+       return -1;
+}
+
+static void test(const char *provider, const char *purpose, bool succeeds)
+{
+       int ret;
+       gnutls_x509_crt_t *certs;
+       unsigned int count;
+       gnutls_x509_trust_list_t tl;
+       gnutls_typed_vdata_st vdata;
+       unsigned int status;
+
+       gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL);
+
+       success("test with %s for %s\n", provider, purpose);
+
+       if (debug) {
+               gnutls_global_set_log_function(tls_log_func);
+               gnutls_global_set_log_level(4711);
+       }
+
+       /* point to SoftHSM token that libpkcs11mock3.so internally uses */
+       setenv(SOFTHSM_ENV, CONFIG, 1);
+
+       gnutls_pkcs11_set_pin_function(pin_func, NULL);
+
+       ret = gnutls_pkcs11_add_provider(provider, "trusted");
+       if (ret != 0) {
+               fail("gnutls_pkcs11_add_provider: %s\n", gnutls_strerror(ret));
+       }
+
+       /* initialize softhsm token */
+       ret = gnutls_pkcs11_token_init(SOFTHSM_URL, PIN, "test");
+       if (ret < 0) {
+               fail("gnutls_pkcs11_token_init: %s\n", gnutls_strerror(ret));
+       }
+
+       ret =
+           gnutls_pkcs11_token_set_pin(SOFTHSM_URL, NULL, PIN,
+                                       GNUTLS_PIN_USER);
+       if (ret < 0) {
+               fail("gnutls_pkcs11_token_set_pin: %s\n", gnutls_strerror(ret));
+       }
+
+       gnutls_x509_trust_list_init(&tl, 0);
+
+       ret = gnutls_x509_trust_list_add_trust_file(tl, SOFTHSM_URL, NULL,
+                                                   0, 0, 0);
+       if (ret < 0) {
+               fail("gnutls_x509_trust_list_add_trust_file\n");
+       }
+
+       ret = gnutls_x509_crt_list_import2(&certs, &count,
+                                          &chain, GNUTLS_X509_FMT_PEM, 0);
+       if (ret < 0) {
+               fail("gnutls_x509_crt_import: %s\n", gnutls_strerror(ret));
+       }
+
+       assert(count == 2);
+
+       /* Use the ICA (instead of the actual root CA) for simplicity.  */
+       ret = gnutls_pkcs11_copy_x509_crt(SOFTHSM_URL, certs[1], "ca",
+                                         GNUTLS_PKCS11_OBJ_FLAG_MARK_TRUSTED |
+                                         GNUTLS_PKCS11_OBJ_FLAG_MARK_CA |
+                                         GNUTLS_PKCS11_OBJ_FLAG_LOGIN_SO);
+       if (ret < 0) {
+               fail("gnutls_pkcs11_copy_x509_crt: %s\n", gnutls_strerror(ret));
+       }
+
+       vdata.type = GNUTLS_DT_KEY_PURPOSE_OID;
+       vdata.data = (void *)purpose;
+
+       ret = gnutls_x509_trust_list_verify_crt2(tl, certs, 1, &vdata, 1,
+                                                0, &status, NULL);
+       if (ret < 0) {
+               fail("gnutls_x509_trust_list_verify_crt2: %s\n",
+                    gnutls_strerror(ret));
+       }
+
+       if (succeeds) {
+               if (status != 0) {
+                       fail("verify failed\n");
+               }
+       } else if (!(status & GNUTLS_CERT_SIGNER_NOT_FOUND)) {
+               fail("verify succeeded unexpectedly\n");
+       }
+
+       gnutls_x509_trust_list_deinit(tl, 0);
+       while (count--) {
+               gnutls_x509_crt_deinit(certs[count]);
+       }
+       gnutls_free(certs);
+
+       gnutls_pkcs11_deinit();
+}
+
+void doit(void)
+{
+       const char *bin;
+       const char *lib;
+       char buf[128];
+
+       if (gnutls_fips140_mode_enabled())
+               exit(77);
+
+       /* this must be called once in the program */
+       global_init();
+
+       /* we call gnutls_pkcs11_init manually */
+       gnutls_pkcs11_deinit();
+
+       /* check if softhsm module is loadable */
+       lib = softhsm_lib();
+
+       /* initialize SoftHSM token that libpkcs11mock2.so internally uses */
+       bin = softhsm_bin();
+
+       set_softhsm_conf(CONFIG);
+       snprintf(buf, sizeof(buf),
+                "%s --init-token --slot 0 --label test --so-pin " PIN " --pin "
+                PIN, bin);
+       system(buf);
+
+       test(lib, GNUTLS_KP_TLS_WWW_SERVER, true);
+
+       set_softhsm_conf(CONFIG);
+       snprintf(buf, sizeof(buf),
+                "%s --init-token --slot 0 --label test --so-pin " PIN " --pin "
+                PIN, bin);
+       system(buf);
+
+       test(lib, GNUTLS_KP_EMAIL_PROTECTION, true);
+
+       lib = getenv("P11MOCKLIB3");
+       if (lib == NULL) {
+               fail("P11MOCKLIB3 is not set\n");
+       }
+
+       set_softhsm_conf(CONFIG);
+       snprintf(buf, sizeof(buf),
+                "%s --init-token --slot 0 --label test --so-pin " PIN " --pin "
+                PIN, bin);
+       system(buf);
+
+       test(lib, GNUTLS_KP_TLS_WWW_SERVER, false);
+
+       set_softhsm_conf(CONFIG);
+       snprintf(buf, sizeof(buf),
+                "%s --init-token --slot 0 --label test --so-pin " PIN " --pin "
+                PIN, bin);
+       system(buf);
+
+       test(lib, GNUTLS_KP_EMAIL_PROTECTION, true);
+}
+#endif                         /* _WIN32 */
diff --git a/tests/pkcs11/pkcs11-mock3.c b/tests/pkcs11/pkcs11-mock3.c
new file mode 100644 (file)
index 0000000..dffe300
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2023 Red Hat, Inc.
+ *
+ * Author: Daiki Ueno
+ *
+ * This file is part of GnuTLS.
+ *
+ * GnuTLS is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuTLS 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 Lesser General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <dlfcn.h>
+#include <p11-kit/pkcs11.h>
+#include <p11-kit/pkcs11x.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <assert.h>
+
+#include "softhsm.h"
+
+/* This provides a mock PKCS #11 module that delegates all the
+ * operations to SoftHSM except that it returns
+ * CKA_NSS_SERVER_DISTRUST_AFTER upon C_GetAttributeValue.
+ */
+
+static void *dl;
+static CK_C_GetAttributeValue base_C_GetAttributeValue;
+static CK_FUNCTION_LIST override_funcs;
+
+#ifdef __sun
+# pragma fini(mock_deinit)
+# pragma init(mock_init)
+# define _CONSTRUCTOR
+# define _DESTRUCTOR
+#else
+# define _CONSTRUCTOR __attribute__((constructor))
+# define _DESTRUCTOR __attribute__((destructor))
+#endif
+
+/* Should be a date before the activation time of chain[0] in
+ * pkcs11/distrust-after.c: Tue Mar 14 10:04:03 UTC 2023
+ */
+#define DISTRUST_AFTER "230314000000Z"
+
+static CK_RV
+override_C_GetAttributeValue(CK_SESSION_HANDLE hSession,
+                            CK_OBJECT_HANDLE hObject,
+                            CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount)
+{
+       CK_ATTRIBUTE *template;
+       CK_ULONG count = 0, i, offset = ulCount;
+       CK_RV rv;
+
+       template = malloc(ulCount * sizeof(CK_ATTRIBUTE));
+       if (!template) {
+               return CKR_HOST_MEMORY;
+       }
+
+       for (i = 0; i < ulCount; i++) {
+               if (pTemplate[i].type == CKA_NSS_SERVER_DISTRUST_AFTER) {
+                       offset = i;
+               } else {
+                       template[count++] = pTemplate[i];
+               }
+       }
+
+       rv = base_C_GetAttributeValue(hSession, hObject, template, count);
+
+       for (i = 0; i < offset; i++) {
+               pTemplate[i] = template[i];
+       }
+
+       if (offset < ulCount) {
+               if (!pTemplate[offset].pValue) {
+                       pTemplate[offset].ulValueLen =
+                           sizeof(DISTRUST_AFTER) - 1;
+               } else if (pTemplate[offset].ulValueLen <
+                          sizeof(DISTRUST_AFTER) - 1) {
+                       pTemplate[offset].ulValueLen =
+                           CK_UNAVAILABLE_INFORMATION;
+                       rv = CKR_BUFFER_TOO_SMALL;
+               } else {
+                       memcpy(pTemplate[offset].pValue, DISTRUST_AFTER,
+                              sizeof(DISTRUST_AFTER) - 1);
+                       pTemplate[offset].ulValueLen =
+                           sizeof(DISTRUST_AFTER) - 1;
+               }
+       }
+
+       for (i = offset + 1; i < ulCount; i++) {
+               pTemplate[i] = template[i];
+       }
+
+       free(template);
+
+       return rv;
+}
+
+CK_RV C_GetFunctionList(CK_FUNCTION_LIST ** function_list)
+{
+       CK_C_GetFunctionList func;
+       CK_FUNCTION_LIST *funcs;
+
+       assert(dl);
+
+       func = dlsym(dl, "C_GetFunctionList");
+       if (func == NULL) {
+               return CKR_GENERAL_ERROR;
+       }
+
+       func(&funcs);
+
+       base_C_GetAttributeValue = funcs->C_GetAttributeValue;
+
+       memcpy(&override_funcs, funcs, sizeof(CK_FUNCTION_LIST));
+       override_funcs.C_GetAttributeValue = override_C_GetAttributeValue;
+       *function_list = &override_funcs;
+
+       return CKR_OK;
+}
+
+static _CONSTRUCTOR void mock_init(void)
+{
+       const char *lib;
+
+       /* suppress compiler warning */
+       (void)set_softhsm_conf;
+
+       lib = softhsm_lib();
+
+       dl = dlopen(lib, RTLD_NOW);
+       if (dl == NULL)
+               exit(77);
+}
+
+static _DESTRUCTOR void mock_deinit(void)
+{
+       dlclose(dl);
+}