summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Griffis <pgriffis@igalia.com>2021-09-01 15:22:34 -0500
committerMarge Bot <marge-bot@gnome.org>2022-08-15 19:12:25 +0000
commit683b9b3733e92e677c601910ba9593aacaa78341 (patch)
tree110725be8c50170e3589cae311aed8db1044b454
parent28114e7b34e90d1272880b7381079b3fc54c4a13 (diff)
downloadglib-networking-683b9b3733e92e677c601910ba9593aacaa78341.tar.gz
Add support for PKCS #12 encrypted files
Part-of: <https://gitlab.gnome.org/GNOME/glib-networking/-/merge_requests/184>
-rw-r--r--tls/gnutls/gtlscertificate-gnutls.c151
-rw-r--r--tls/openssl/gtlscertificate-openssl.c136
-rw-r--r--tls/tests/certificate.c118
-rw-r--r--tls/tests/files/client-and-key-fullchain.pem77
-rw-r--r--tls/tests/files/client-and-key-password-enckey.p12bin0 -> 2794 bytes
-rw-r--r--tls/tests/files/client-and-key-password.p12bin0 -> 2575 bytes
-rw-r--r--tls/tests/files/client-and-key.p12bin0 -> 2455 bytes
-rwxr-xr-xtls/tests/files/create-files.sh16
8 files changed, 498 insertions, 0 deletions
diff --git a/tls/gnutls/gtlscertificate-gnutls.c b/tls/gnutls/gtlscertificate-gnutls.c
index 11f5c5c..1b2f4a9 100644
--- a/tls/gnutls/gtlscertificate-gnutls.c
+++ b/tls/gnutls/gtlscertificate-gnutls.c
@@ -25,6 +25,7 @@
#include "config.h"
#include <gnutls/gnutls.h>
+#include <gnutls/pkcs12.h>
#include <gnutls/x509.h>
#include <string.h>
@@ -48,6 +49,8 @@ enum
PROP_ISSUER_NAME,
PROP_DNS_NAMES,
PROP_IP_ADDRESSES,
+ PROP_PKCS12_DATA,
+ PROP_PASSWORD,
};
struct _GTlsCertificateGnutls
@@ -62,6 +65,9 @@ struct _GTlsCertificateGnutls
GTlsCertificateGnutls *issuer;
+ GByteArray *pkcs12_data;
+ char *password;
+
GError *construct_error;
guint have_cert : 1;
@@ -69,6 +75,7 @@ struct _GTlsCertificateGnutls
};
static void g_tls_certificate_gnutls_initable_iface_init (GInitableIface *iface);
+static GTlsCertificateGnutls *g_tls_certificate_gnutls_new_take_x509 (gnutls_x509_crt_t cert);
G_DEFINE_TYPE_WITH_CODE (GTlsCertificateGnutls, g_tls_certificate_gnutls, G_TYPE_TLS_CERTIFICATE,
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
@@ -85,6 +92,9 @@ g_tls_certificate_gnutls_finalize (GObject *object)
g_clear_pointer (&gnutls->pkcs11_uri, g_free);
g_clear_pointer (&gnutls->private_key_pkcs11_uri, g_free);
+ g_clear_pointer (&gnutls->pkcs12_data, g_byte_array_unref);
+ g_clear_pointer (&gnutls->password, g_free);
+
g_clear_object (&gnutls->issuer);
g_clear_error (&gnutls->construct_error);
@@ -192,6 +202,106 @@ err:
}
static void
+maybe_import_pkcs12 (GTlsCertificateGnutls *gnutls)
+{
+ gnutls_pkcs12_t p12 = NULL;
+ gnutls_x509_privkey_t x509_key = NULL;
+ gnutls_x509_crt_t *chain = NULL;
+ guint chain_len;
+ int status;
+ gnutls_datum_t p12_data;
+ GTlsError error_code = G_TLS_ERROR_BAD_CERTIFICATE;
+ GTlsCertificateGnutls *previous_cert;
+
+ /* If password is set first. */
+ if (!gnutls->pkcs12_data)
+ return;
+
+ p12_data.data = gnutls->pkcs12_data->data;
+ p12_data.size = gnutls->pkcs12_data->len;
+
+ status = gnutls_pkcs12_init (&p12);
+ if (status != GNUTLS_E_SUCCESS)
+ goto import_failed;
+
+ /* Only support DER, it's the common encoding and what everything including OpenSSL uses. */
+ status = gnutls_pkcs12_import (p12, &p12_data, GNUTLS_X509_FMT_DER, 0);
+ if (status != GNUTLS_E_SUCCESS)
+ goto import_failed;
+
+ if (gnutls->password)
+ {
+ status = gnutls_pkcs12_verify_mac (p12, gnutls->password);
+ if (status != GNUTLS_E_SUCCESS)
+ {
+ error_code = G_TLS_ERROR_BAD_CERTIFICATE_PASSWORD;
+ goto import_failed;
+ }
+ }
+
+ /* Note that this *requires* a cert and key, if we want to make keys optional
+ * we would have to re-implement this parsing ourselves. */
+ status = gnutls_pkcs12_simple_parse (p12,
+ gnutls->password ? gnutls->password : "",
+ &x509_key,
+ &chain, &chain_len,
+ NULL, NULL,
+ NULL,
+ GNUTLS_PKCS12_SP_INCLUDE_SELF_SIGNED);
+ if (status == GNUTLS_E_DECRYPTION_FAILED)
+ error_code = G_TLS_ERROR_BAD_CERTIFICATE_PASSWORD;
+ if (status != GNUTLS_E_SUCCESS)
+ goto import_failed;
+
+ /* Clear a previous error to load without a password. */
+ if (g_error_matches (gnutls->construct_error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE_PASSWORD))
+ g_clear_error (&gnutls->construct_error);
+
+ /* Clear existing initialized empty cert. */
+ gnutls_x509_crt_deinit (gnutls->cert);
+
+ /* First cert is the main one. */
+ gnutls->cert = chain[0];
+ gnutls->have_cert = TRUE;
+ previous_cert = gnutls;
+
+ for (guint i = 1; i < chain_len; i++)
+ {
+ /* GnuTLS already built us a valid chain in order by issuer. See pkcs12.c#make_chain(). */
+ GTlsCertificateGnutls *new_cert = g_tls_certificate_gnutls_new_take_x509 (chain[i]);
+ g_tls_certificate_gnutls_set_issuer (previous_cert, new_cert);
+ previous_cert = new_cert;
+ g_object_unref (new_cert);
+ }
+
+ g_clear_pointer (&chain, gnutls_free);
+
+ /* Convert X509 privkey to abstract privkey. */
+ status = gnutls_privkey_init (&gnutls->key);
+ if (status != GNUTLS_E_SUCCESS)
+ goto import_failed;
+
+ status = gnutls_privkey_import_x509 (gnutls->key, x509_key, GNUTLS_PRIVKEY_IMPORT_COPY);
+ if (status != GNUTLS_E_SUCCESS)
+ goto import_failed;
+
+ g_clear_pointer (&x509_key, gnutls_x509_privkey_deinit);
+ gnutls->have_key = TRUE;
+
+ g_clear_pointer (&p12, gnutls_pkcs12_deinit);
+ return;
+
+import_failed:
+ g_clear_error (&gnutls->construct_error);
+ g_set_error (&gnutls->construct_error, G_TLS_ERROR, error_code,
+ _("Failed to import PKCS #12: %s"), gnutls_strerror (status));
+
+ g_clear_pointer (&p12, gnutls_pkcs12_deinit);
+ g_clear_pointer (&x509_key, gnutls_x509_privkey_deinit);
+ g_clear_pointer (&chain, gnutls_free);
+}
+
+static void
g_tls_certificate_gnutls_get_property (GObject *object,
guint prop_id,
GValue *value,
@@ -209,6 +319,10 @@ g_tls_certificate_gnutls_get_property (GObject *object,
switch (prop_id)
{
+ case PROP_PKCS12_DATA:
+ g_value_set_boxed (value, gnutls->pkcs12_data);
+ break;
+
case PROP_CERTIFICATE:
size = 0;
status = gnutls_x509_crt_export (gnutls->cert,
@@ -343,6 +457,26 @@ g_tls_certificate_gnutls_set_property (GObject *object,
switch (prop_id)
{
+ case PROP_PASSWORD:
+ gnutls->password = g_value_dup_string (value);
+ if (gnutls->password)
+ {
+ g_return_if_fail (gnutls->have_cert == FALSE);
+ g_return_if_fail (gnutls->have_key == FALSE);
+ maybe_import_pkcs12 (gnutls);
+ }
+ break;
+
+ case PROP_PKCS12_DATA:
+ gnutls->pkcs12_data = g_value_dup_boxed (value);
+ if (gnutls->pkcs12_data)
+ {
+ g_return_if_fail (gnutls->have_cert == FALSE);
+ g_return_if_fail (gnutls->have_key == FALSE);
+ maybe_import_pkcs12 (gnutls);
+ }
+ break;
+
case PROP_CERTIFICATE:
bytes = g_value_get_boxed (value);
if (!bytes)
@@ -485,6 +619,9 @@ g_tls_certificate_gnutls_initable_init (GInitable *initable,
{
GTlsCertificateGnutls *gnutls = G_TLS_CERTIFICATE_GNUTLS (initable);
+ /* After init we don't need to keep the password around. */
+ g_clear_pointer (&gnutls->password, g_free);
+
if (gnutls->construct_error)
{
g_propagate_error (error, gnutls->construct_error);
@@ -592,6 +729,8 @@ g_tls_certificate_gnutls_class_init (GTlsCertificateGnutlsClass *klass)
g_object_class_override_property (gobject_class, PROP_ISSUER_NAME, "issuer-name");
g_object_class_override_property (gobject_class, PROP_DNS_NAMES, "dns-names");
g_object_class_override_property (gobject_class, PROP_IP_ADDRESSES, "ip-addresses");
+ g_object_class_override_property (gobject_class, PROP_PKCS12_DATA, "pkcs12-data");
+ g_object_class_override_property (gobject_class, PROP_PASSWORD, "password");
}
static void
@@ -614,6 +753,18 @@ g_tls_certificate_gnutls_new (const gnutls_datum_t *datum,
return G_TLS_CERTIFICATE (gnutls);
}
+static GTlsCertificateGnutls *
+g_tls_certificate_gnutls_new_take_x509 (gnutls_x509_crt_t cert)
+{
+ GTlsCertificateGnutls *gnutls;
+
+ gnutls = g_object_new (G_TYPE_TLS_CERTIFICATE_GNUTLS, NULL);
+ gnutls->cert = cert;
+ gnutls->have_cert = TRUE;
+
+ return gnutls;
+}
+
void
g_tls_certificate_gnutls_set_data (GTlsCertificateGnutls *gnutls,
const gnutls_datum_t *datum)
diff --git a/tls/openssl/gtlscertificate-openssl.c b/tls/openssl/gtlscertificate-openssl.c
index 0bc7ea6..d57f5ee 100644
--- a/tls/openssl/gtlscertificate-openssl.c
+++ b/tls/openssl/gtlscertificate-openssl.c
@@ -27,6 +27,7 @@
#include <string.h>
#include "openssl-include.h"
+#include <openssl/pkcs12.h>
#include "gtlscertificate-openssl.h"
#include <glib/gi18n-lib.h>
@@ -38,6 +39,9 @@ struct _GTlsCertificateOpenssl
X509 *cert;
EVP_PKEY *key;
+ GByteArray *pkcs12_data;
+ char *password;
+
GTlsCertificateOpenssl *issuer;
GError *construct_error;
@@ -61,9 +65,12 @@ enum
PROP_ISSUER_NAME,
PROP_DNS_NAMES,
PROP_IP_ADDRESSES,
+ PROP_PKCS12_DATA,
+ PROP_PASSWORD,
};
static void g_tls_certificate_openssl_initable_iface_init (GInitableIface *iface);
+static gboolean is_issuer (GTlsCertificateOpenssl *cert, GTlsCertificateOpenssl *issuer);
G_DEFINE_TYPE_WITH_CODE (GTlsCertificateOpenssl, g_tls_certificate_openssl, G_TYPE_TLS_CERTIFICATE,
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
@@ -79,6 +86,9 @@ g_tls_certificate_openssl_finalize (GObject *object)
if (openssl->key)
EVP_PKEY_free (openssl->key);
+ g_clear_pointer (&openssl->pkcs12_data, g_byte_array_unref);
+ g_clear_pointer (&openssl->password, g_free);
+
g_clear_object (&openssl->issuer);
g_clear_error (&openssl->construct_error);
@@ -206,6 +216,103 @@ out:
}
static void
+maybe_import_pkcs12 (GTlsCertificateOpenssl *openssl)
+{
+ PKCS12 *p12 = NULL;
+ X509 *cert = NULL;
+ STACK_OF(X509) *ca = NULL;
+ EVP_PKEY *key = NULL;
+ BIO *bio = NULL;
+ int status;
+ char error_buffer[256] = { 0 };
+ GTlsError error_code = G_TLS_ERROR_BAD_CERTIFICATE;
+
+ /* If password is set first. */
+ if (!openssl->pkcs12_data)
+ return;
+
+ bio = BIO_new (BIO_s_mem ());
+ status = BIO_write (bio, openssl->pkcs12_data->data, openssl->pkcs12_data->len);
+ if (status <= 0)
+ goto import_failed;
+ g_assert (status == openssl->pkcs12_data->len);
+
+ p12 = d2i_PKCS12_bio (bio, NULL);
+ if (p12 == NULL)
+ goto import_failed;
+
+ status = PKCS12_parse (p12, openssl->password, &key, &cert, &ca);
+ g_clear_pointer (&bio, BIO_free_all);
+
+ if (status != 1)
+ {
+ if (ERR_GET_REASON (ERR_peek_last_error ()) == PKCS12_R_MAC_VERIFY_FAILURE)
+ error_code = G_TLS_ERROR_BAD_CERTIFICATE_PASSWORD;
+ goto import_failed;
+ }
+
+ /* Clear a previous error to load without a password. */
+ if (g_error_matches (openssl->construct_error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE_PASSWORD))
+ g_clear_error (&openssl->construct_error);
+
+ if (cert)
+ {
+ openssl->cert = g_steal_pointer (&cert);
+ openssl->have_cert = TRUE;
+ }
+
+ if (ca)
+ {
+ GTlsCertificateOpenssl *last_cert = openssl;
+
+ for (guint i = 0; i < sk_X509_num (ca); )
+ {
+ GTlsCertificateOpenssl *new_cert;
+ new_cert = G_TLS_CERTIFICATE_OPENSSL (g_tls_certificate_openssl_new_from_x509 (sk_X509_value (ca, i),
+ NULL));
+
+ if (is_issuer (last_cert, new_cert))
+ {
+ g_tls_certificate_openssl_set_issuer (last_cert, new_cert);
+ last_cert = new_cert;
+
+ /* Start the list over to find an issuer of the new cert. */
+ sk_X509_delete (ca, i);
+ i = 0;
+ }
+ else
+ i++;
+
+ g_object_unref (new_cert);
+ }
+
+ sk_X509_pop_free (ca, X509_free);
+ ca = NULL;
+ }
+
+ if (key)
+ {
+ openssl->key = g_steal_pointer (&key);
+ openssl->have_key = TRUE;
+ }
+
+ g_clear_pointer (&p12, PKCS12_free);
+ return;
+
+import_failed:
+ g_clear_error (&openssl->construct_error);
+
+ if (!error_buffer[0])
+ ERR_error_string_n (ERR_get_error (), error_buffer, sizeof (error_buffer));
+
+ g_set_error (&openssl->construct_error, G_TLS_ERROR, error_code,
+ _("Failed to import PKCS #12: %s"), error_buffer);
+
+ g_clear_pointer (&p12, PKCS12_free);
+ g_clear_pointer (&bio, BIO_free_all);
+}
+
+static void
g_tls_certificate_openssl_get_property (GObject *object,
guint prop_id,
GValue *value,
@@ -228,6 +335,10 @@ g_tls_certificate_openssl_get_property (GObject *object,
switch (prop_id)
{
+ case PROP_PKCS12_DATA:
+ g_value_set_boxed (value, openssl->pkcs12_data);
+ break;
+
case PROP_CERTIFICATE:
/* NOTE: we do the two calls to avoid openssl allocating the buffer for us */
size = i2d_X509 (openssl->cert, NULL);
@@ -345,6 +456,26 @@ g_tls_certificate_openssl_set_property (GObject *object,
switch (prop_id)
{
+ case PROP_PASSWORD:
+ openssl->password = g_value_dup_string (value);
+ if (openssl->password)
+ {
+ g_return_if_fail (openssl->have_cert == FALSE);
+ g_return_if_fail (openssl->have_key == FALSE);
+ maybe_import_pkcs12 (openssl);
+ }
+ break;
+
+ case PROP_PKCS12_DATA:
+ openssl->pkcs12_data = g_value_dup_boxed (value);
+ if (openssl->pkcs12_data)
+ {
+ g_return_if_fail (openssl->have_cert == FALSE);
+ g_return_if_fail (openssl->have_key == FALSE);
+ maybe_import_pkcs12 (openssl);
+ }
+ break;
+
case PROP_CERTIFICATE:
bytes = g_value_get_boxed (value);
if (!bytes)
@@ -447,6 +578,9 @@ g_tls_certificate_openssl_initable_init (GInitable *initable,
{
GTlsCertificateOpenssl *openssl = G_TLS_CERTIFICATE_OPENSSL (initable);
+ /* After init we don't need to keep the password around. */
+ g_clear_pointer (&openssl->password, g_free);
+
if (openssl->construct_error)
{
g_propagate_error (error, openssl->construct_error);
@@ -544,6 +678,8 @@ g_tls_certificate_openssl_class_init (GTlsCertificateOpensslClass *klass)
g_object_class_override_property (gobject_class, PROP_ISSUER_NAME, "issuer-name");
g_object_class_override_property (gobject_class, PROP_DNS_NAMES, "dns-names");
g_object_class_override_property (gobject_class, PROP_IP_ADDRESSES, "ip-addresses");
+ g_object_class_override_property (gobject_class, PROP_PKCS12_DATA, "pkcs12-data");
+ g_object_class_override_property (gobject_class, PROP_PASSWORD, "password");
}
static void
diff --git a/tls/tests/certificate.c b/tls/tests/certificate.c
index 01c3c1a..e820ba1 100644
--- a/tls/tests/certificate.c
+++ b/tls/tests/certificate.c
@@ -793,6 +793,120 @@ test_certificate_ip_addresses (void)
g_object_unref (cert);
}
+static GByteArray *
+load_bytes_for_test_file (const char *filename)
+{
+ GFile *file = g_file_new_for_path (tls_test_file_path (filename));
+ GBytes *bytes = g_file_load_bytes (file, NULL, NULL, NULL);
+
+ g_assert_nonnull (bytes);
+ g_object_unref (file);
+ return g_bytes_unref_to_array (bytes);
+}
+
+static void
+assert_cert_contains_cert_and_key (GTlsCertificate *certificate)
+{
+ char *cert_pem, *key_pem;
+
+ g_object_get (certificate,
+ "certificate-pem", &cert_pem,
+ "private-key-pem", &key_pem,
+ NULL);
+
+ g_assert_nonnull (cert_pem);
+ g_assert_nonnull (key_pem);
+
+ g_free (cert_pem);
+ g_free (key_pem);
+}
+
+static void
+assert_equals_original_cert (GTlsCertificate *cert)
+{
+ GTlsCertificate *original_cert = g_tls_certificate_new_from_file (tls_test_file_path ("client-and-key.pem"), NULL);
+ g_assert_nonnull (original_cert);
+ g_assert_true (g_tls_certificate_is_same (original_cert, cert));
+ g_object_unref (original_cert);
+}
+
+static void
+test_certificate_pkcs12_basic (void)
+{
+ GTlsCertificate *cert;
+ GByteArray *pkcs12_data;
+ GError *error = NULL;
+
+ pkcs12_data = load_bytes_for_test_file ("client-and-key.p12");
+ cert = g_tls_certificate_new_from_pkcs12 (pkcs12_data->data, pkcs12_data->len, NULL, &error);
+
+ g_assert_no_error (error);
+ g_assert_nonnull (cert);
+ assert_cert_contains_cert_and_key (cert);
+ assert_equals_original_cert (cert);
+
+ g_byte_array_unref (pkcs12_data);
+ g_object_unref (cert);
+}
+
+static void
+test_certificate_pkcs12_password (void)
+{
+ GTlsCertificate *cert;
+ GByteArray *pkcs12_data;
+ GError *error = NULL;
+
+ pkcs12_data = load_bytes_for_test_file ("client-and-key-password.p12");
+
+ /* Without a password it fails. */
+ cert = g_tls_certificate_new_from_pkcs12 (pkcs12_data->data, pkcs12_data->len, NULL, &error);
+ g_assert_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE_PASSWORD);
+ g_clear_error (&error);
+
+ /* With the wrong password it fails. */
+ cert = g_tls_certificate_new_from_pkcs12 (pkcs12_data->data, pkcs12_data->len, "oajfo", &error);
+ g_assert_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE_PASSWORD);
+ g_clear_error (&error);
+
+ /* With the correct password it succeeds. */
+ cert = g_tls_certificate_new_from_pkcs12 (pkcs12_data->data, pkcs12_data->len, "1234", &error);
+ g_assert_no_error (error);
+ g_assert_nonnull (cert);
+ assert_cert_contains_cert_and_key (cert);
+ assert_equals_original_cert (cert);
+ g_object_unref (cert);
+ g_byte_array_unref (pkcs12_data);
+}
+
+static void
+test_certificate_pkcs12_encrypted (void)
+{
+ GTlsCertificate *cert;
+ GByteArray *pkcs12_enc_data;
+ GError *error = NULL;
+
+ pkcs12_enc_data = load_bytes_for_test_file ("client-and-key-password-enckey.p12");
+
+ /* Without a password it fails. */
+ cert = g_tls_certificate_new_from_pkcs12 (pkcs12_enc_data->data, pkcs12_enc_data->len, NULL, &error);
+ g_assert_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE_PASSWORD);
+ g_clear_error (&error);
+
+ /* With the wrong password it fails. */
+ cert = g_tls_certificate_new_from_pkcs12 (pkcs12_enc_data->data, pkcs12_enc_data->len, "oajfo", &error);
+ g_assert_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE_PASSWORD);
+ g_clear_error (&error);
+
+ /* With the correct password it succeeds. */
+ cert = g_tls_certificate_new_from_pkcs12 (pkcs12_enc_data->data, pkcs12_enc_data->len, "1234", &error);
+ g_assert_no_error (error);
+ g_assert_nonnull (cert);
+ assert_cert_contains_cert_and_key (cert);
+ assert_equals_original_cert (cert);
+ g_object_unref (cert);
+ g_byte_array_unref (pkcs12_enc_data);
+}
+
int
main (int argc,
char *argv[])
@@ -862,5 +976,9 @@ main (int argc,
g_test_add_func ("/tls/" BACKEND "/certificate/dns-names", test_certificate_dns_names);
g_test_add_func ("/tls/" BACKEND "/certificate/ip-addresses", test_certificate_ip_addresses);
+ g_test_add_func ("/tls/" BACKEND "/certificate/pkcs12/basic", test_certificate_pkcs12_basic);
+ g_test_add_func ("/tls/" BACKEND "/certificate/pkcs12/password", test_certificate_pkcs12_password);
+ g_test_add_func ("/tls/" BACKEND "/certificate/pkcs12/encrypted", test_certificate_pkcs12_encrypted);
+
return g_test_run();
}
diff --git a/tls/tests/files/client-and-key-fullchain.pem b/tls/tests/files/client-and-key-fullchain.pem
new file mode 100644
index 0000000..591d2c0
--- /dev/null
+++ b/tls/tests/files/client-and-key-fullchain.pem
@@ -0,0 +1,77 @@
+-----BEGIN CERTIFICATE-----
+MIIE4zCCA8ugAwIBAgIUQL/y3nsri2som8J5/D+Hm8ljMwcwDQYJKoZIhvcNAQEL
+BQAwgYYxEzARBgoJkiaJk/IsZAEZFgNDT00xFzAVBgoJkiaJk/IsZAEZFgdFWEFN
+UExFMR4wHAYDVQQLDBVDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxFzAVBgNVBAMMDmNh
+LmV4YW1wbGUuY29tMR0wGwYJKoZIhvcNAQkBFg5jYUBleGFtcGxlLmNvbTAgFw0y
+MTA3MzExODEzMTBaGA8yMDUxMDcyNDE4MTMxMFowgYYxEzARBgoJkiaJk/IsZAEZ
+FgNDT00xFzAVBgoJkiaJk/IsZAEZFgdFWEFNUExFMR4wHAYDVQQLDBVDZXJ0aWZp
+Y2F0ZSBBdXRob3JpdHkxFzAVBgNVBAMMDmNhLmV4YW1wbGUuY29tMR0wGwYJKoZI
+hvcNAQkBFg5jYUBleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBALk97CGeG3O95ZKKTbZ25xkZdeKkXm1fQZRwjK1FwC+XMG1ehGtI40zG
+rb8ayC/olqoMFqz4yUrkb4qpzau8Uml+jZSOx+E3VsLS41lrgMf4k4lgYqghgXpu
++ErDwL2crJR1IQsKSk/xWeltSZuDkZjUSUqK6SfhC7XZ9PAJmb+Km5YqZ2Bnniox
+K1lMC25BFtYyXMje0/+15r/Am0+5ek2HMHmD2nCAaHeXEU4mn30lPGEkq9l7nNpo
+vH9Rei8mCuZtXkeuzpcO7icRTusI8VBAPXvYB5d3GRmcECnV6jBwWZ0MAQ6ib+td
+T/PkjEkR4oncAsyO8cRw25gltPo+DpECAwEAAaOCAUMwggE/MB0GA1UdDgQWBBR9
+MHdtjvb0Yq9WTAKv4wB756MpeTCBxgYDVR0jBIG+MIG7gBR9MHdtjvb0Yq9WTAKv
+4wB756MpeaGBjKSBiTCBhjETMBEGCgmSJomT8ixkARkWA0NPTTEXMBUGCgmSJomT
+8ixkARkWB0VYQU1QTEUxHjAcBgNVBAsMFUNlcnRpZmljYXRlIEF1dGhvcml0eTEX
+MBUGA1UEAwwOY2EuZXhhbXBsZS5jb20xHTAbBgkqhkiG9w0BCQEWDmNhQGV4YW1w
+bGUuY29tghRAv/LeeyuLayibwnn8P4ebyWMzBzAPBgNVHRMBAf8EBTADAQH/MA4G
+A1UdDwEB/wQEAwIBBjAZBgNVHREEEjAQgQ5jYUBleGFtcGxlLmNvbTAZBgNVHRIE
+EjAQgQ5jYUBleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAVn5Zp/2oUFAO
+Ork8QU7IJo9a/NEfBMOJfFQBXyN8l7AKux/wDICDqd+Hmeb3yoPx/Yy+5Z/BoEdY
+H+sG5gAGK8Kd5KsQxfcPW6uQMQ8K+WEmtdeT2OjDkKzkLhMfAXbaSOrGf/95rMk8
+Tq/Cx7csH+M21m8ZvrvJCHFr673d4GvWKN7nfa7xcd2ErvKWLEIiyp607AfD5wHg
+cKm0LtC9RCVABGrS7tvW6DfR2V0QNKk/+OlWAqPXzElJwZPwI4CdGjAVfYkEfE86
+uTWT08HDZM5gD1ohhUPEnNxma1HkMOnBhhxmnA3HfFB7l6BPPAj4xmv/tQv5BtfT
+QSNhrzqDvA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDXTCCAkUCAQQwDQYJKoZIhvcNAQELBQAwgYYxEzARBgoJkiaJk/IsZAEZFgND
+T00xFzAVBgoJkiaJk/IsZAEZFgdFWEFNUExFMR4wHAYDVQQLDBVDZXJ0aWZpY2F0
+ZSBBdXRob3JpdHkxFzAVBgNVBAMMDmNhLmV4YW1wbGUuY29tMR0wGwYJKoZIhvcN
+AQkBFg5jYUBleGFtcGxlLmNvbTAeFw0yMTA3MzExODEzMTBaFw00NjA3MjUxODEz
+MTBaMGIxEzARBgoJkiaJk/IsZAEZFgNDT00xFzAVBgoJkiaJk/IsZAEZFgdFWEFN
+UExFMQ8wDQYDVQQDDAZDbGllbnQxITAfBgkqhkiG9w0BCQEWEmNsaWVudEBleGFt
+cGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOPbuZMMuAYN
+HHfO9oXuDJG2vNcphC4nEoldJrS4Wr7X5RUXlVUHyHiwY2S7qXstZP25m6H9Obl4
+2AysGDYccKSm0/Ne97CGkTuMSoJFfm8EP1W5hn2FyiAmcJJDEA4s9AG7ZNuZtbi7
+LMp5M0J7Qyj/7AKruaKYXfB1CWHAR5B9mAqE11hGZJcUAN4gxV7IVNPYk6bnhUgj
+EH8eiCfs6yUB3CqZ1S7LICvC9jwg2J/+Qkfm0tfHbvtoyAEb7tFVWbZ3GcgUfntN
+ixe+1Um3s8pyB+wVznv65cEjCPvgqMMJVfS9oMV7Lg7TuNqjirzaSSGSVWGuR3Iy
+VmjtfpGk9R8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAZs33954DJg4+NJzt8QKI
+5+aDeBb5FcnwvIbx7QpsSPuJJOuamLX8fo4x7VZut3y5VqyK1QFIE8dWNCotVP5r
+I4xFFYG6CEKkzc/fZ8FTMCYFoGVxUce2PMX/XcZuYWaO+F7Gz8LzW1CbYzbFmAyZ
+ZUl0WOeFbWl/yQmwym5ZFKLogZXL0ZHtwNKHH0LiKOyUssiWbN1cD7BQaiuXcsKS
+JjWsl2j5pi+07WaGZGk+MB8mFl56I9u/43qdaxf1lWicyj6QiwvZf/WVxi5dT3zA
+8IEOS5j2Ahg2xP/ot24R/7+d4eV5m27qfqzB4HT7y1bl0Hi5lXl1O6qzmqJo7Z49
+zg==
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA49u5kwy4Bg0cd872he4Mkba81ymELicSiV0mtLhavtflFReV
+VQfIeLBjZLupey1k/bmbof05uXjYDKwYNhxwpKbT8173sIaRO4xKgkV+bwQ/VbmG
+fYXKICZwkkMQDiz0Abtk25m1uLssynkzQntDKP/sAqu5ophd8HUJYcBHkH2YCoTX
+WEZklxQA3iDFXshU09iTpueFSCMQfx6IJ+zrJQHcKpnVLssgK8L2PCDYn/5CR+bS
+18du+2jIARvu0VVZtncZyBR+e02LF77VSbezynIH7BXOe/rlwSMI++CowwlV9L2g
+xXsuDtO42qOKvNpJIZJVYa5HcjJWaO1+kaT1HwIDAQABAoIBAFr6JStQfb10yLQR
+f57UAFLByr6CBhPDaAROnCC2Jw1h+EampupPdDyz+MuEnlPfOR6RWFGiYMTsOh89
+K2GXk2Y6chAPEAh8gkprZmiDRMsgwAUDUW6W17BkiLftbOCsFDUI1GlWAxeiLG0f
+XVcjDhq1rE8Yr8L64S8cG8Xa1vgaXI6YpPch8YaBXQrBtZZsKiqmD8OUcE/sj1rc
+jrSuwTYmR9wqNymJH1b21cDhCKcU2UAUAhbLWm6k9rhKGEvbcrFcBVe6cRjY10h3
+RahLV8geyNKtundqg4Fajw5i74SxRHqnGrrK99EAYOyehxI0hjAPlrUmUsx5juwX
+DuThPrECgYEA9jTMikAY7TIZOb2R1Dmy4ob3jGV23YoetSb/RyDuIYI1FhOxk8M7
+Lh61zdcnih4q4AZU4f+tSrLM0faVO+Qm/aPsXcJZSw6voOLX7crWA6RoffrNXpcr
+I8QfBqXoDosUEZhu+ZWTOHU3jaUEZojT3HVii3dnDe8lcg2fURZ8CxcCgYEA7OwV
+Jis3geW4JeqimmKYCcb2eCYX/XhK5s0fIY1TUQc2BcjG5qmsfBK5WM4Y/9kgiqRy
+kGb0KSLcf4EGA2UMfARPXMjGBd7O7ZGw97h52OQ9KntwAlk+M88IkGtgiBB8B8/6
+vOM1NlYm9KNdz7yYJyWKhX5CpYWNKDybBBanizkCgYAfFk2ZSzyafo/m1YPbSynG
+JoqNRKZ8lJsJ/pGPFp5axskTL4tlf+oyCZilE/yfGO4K+WGwY4sq+maYQE1ZkZZG
+wnLu58JEkuckJmBjWxAox5KWDZvuzKGa04GjYkFpzK/NBPVGOFetckeAcAydMbum
+g3/c4ke137NXslaic3dYIwKBgQCEJk7JonNsngCwDYczC+l4EqVpcP4SoKVOxX0m
+zth5KseERHBFOsD918fQc+zX0HlpO763MuXfiBVrfUEoZZWoGxNVUpu5rITJWGlY
+U2qLgwtnBcD9Xl/msAU74NjQLDmyddyKvUTyEO3bqL/r3msT8smoGjv8bVjF58Aj
+cKt1aQKBgF8WJ/5JbhEUvhv9+2tkSGXsoGVRZkHVeMr++NCJH4Ee6U2eKeq2DOYA
+e8ex5fKJPcjoaXeguKYaP3lsFVdT+F9bj7+L1RpJea06ovYbTs7wAyzxURLOjbfy
+0SUZG8hhBhdcqz+RVvqnQeYFe8k/c4RVZQeEgtBGChevJtT4ymY3
+-----END RSA PRIVATE KEY-----
diff --git a/tls/tests/files/client-and-key-password-enckey.p12 b/tls/tests/files/client-and-key-password-enckey.p12
new file mode 100644
index 0000000..5100a15
--- /dev/null
+++ b/tls/tests/files/client-and-key-password-enckey.p12
Binary files differ
diff --git a/tls/tests/files/client-and-key-password.p12 b/tls/tests/files/client-and-key-password.p12
new file mode 100644
index 0000000..7d863b3
--- /dev/null
+++ b/tls/tests/files/client-and-key-password.p12
Binary files differ
diff --git a/tls/tests/files/client-and-key.p12 b/tls/tests/files/client-and-key.p12
new file mode 100644
index 0000000..9bccfcf
--- /dev/null
+++ b/tls/tests/files/client-and-key.p12
Binary files differ
diff --git a/tls/tests/files/create-files.sh b/tls/tests/files/create-files.sh
index ca1b842..d350e69 100755
--- a/tls/tests/files/create-files.sh
+++ b/tls/tests/files/create-files.sh
@@ -133,6 +133,10 @@ msg "Concatenating client certificate and private key into a single file"
cat client.pem > client-and-key.pem
cat client-key.pem >> client-and-key.pem
+msg "Concatenating the full client chain into a single file"
+cat ca.pem > client-and-key-fullchain.pem
+cat client-and-key.pem >> client-and-key-fullchain.pem
+
# It is not possible to specify the start and end date using the "x509" tool.
# It would be better to use the "ca" tool. Sorry!
msg "Creating client certificate (past)"
@@ -221,6 +225,18 @@ msg "Updating test expectations"
./update-certificate-test.py server.pem ../certificate.h
#######################################################################
+### Generate PKCS #12 format copies for testing
+#######################################################################
+
+msg "Generating PKCS #12 files"
+# Not encrypted p12 file
+openssl pkcs12 -in client-and-key.pem -export -keypbe NONE -certpbe NONE -nomaciter -out client-and-key.p12 -passout 'pass:' -name "No password"
+# Encrypted key only
+openssl pkcs12 -in client-and-key.pem -export -certpbe NONE -nomaciter -out client-and-key-password.p12 -passout 'pass:1234' -name "With Password"
+# Encrypted p12 file
+openssl pkcs12 -in client-and-key.pem -export -out client-and-key-password-enckey.p12 -passout 'pass:1234' -name "With Password and encrypted privkey"
+
+#######################################################################
### Cleanup
#######################################################################