[glib-networking/pgriffis/gtlscertificate-password] Add support for PKCS #12 encrypted files




commit a6434ed42c153ad967b9a6ff9b2e04a3bf377d1d
Author: Patrick Griffis <pgriffis igalia com>
Date:   Wed Sep 1 15:22:34 2021 -0500

    Add support for PKCS #12 encrypted files

 tls/gnutls/gtlscertificate-gnutls.c                | 111 +++++++++++++++++++++
 tls/tests/certificate.c                            |  98 ++++++++++++++++++
 tls/tests/files/client-and-key-password-enckey.p12 | Bin 0 -> 2644 bytes
 tls/tests/files/client-and-key-password.p12        | Bin 0 -> 2552 bytes
 tls/tests/files/client-and-key.p12                 | Bin 0 -> 2548 bytes
 tls/tests/files/create-files.sh                    |   9 ++
 6 files changed, 218 insertions(+)
---
diff --git a/tls/gnutls/gtlscertificate-gnutls.c b/tls/gnutls/gtlscertificate-gnutls.c
index 11f5c5c7..da143ce5 100644
--- a/tls/gnutls/gtlscertificate-gnutls.c
+++ b/tls/gnutls/gtlscertificate-gnutls.c
@@ -26,6 +26,7 @@
 
 #include <gnutls/gnutls.h>
 #include <gnutls/x509.h>
+#include <gnutls/pkcs12.h>
 #include <string.h>
 
 #include "gtlscertificate-gnutls.h"
@@ -48,6 +49,8 @@ enum
   PROP_ISSUER_NAME,
   PROP_DNS_NAMES,
   PROP_IP_ADDRESSES,
+  PROP_PKCS12_BYTES,
+  PROP_PASSWORD,
 };
 
 struct _GTlsCertificateGnutls
@@ -61,8 +64,11 @@ struct _GTlsCertificateGnutls
   gchar *private_key_pkcs11_uri;
 
   GTlsCertificateGnutls *issuer;
+  GBytes *pkcs12_bytes;
+  char *password;
 
   GError *construct_error;
+  gboolean construct_needs_pkcs12_password;
 
   guint have_cert : 1;
   guint have_key  : 1;
@@ -191,6 +197,95 @@ err:
     gnutls_x509_privkey_deinit (x509_privkey);
 }
 
+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;
+
+  /* If password is set first. */
+  if (!gnutls->pkcs12_bytes)
+    return;
+
+  p12_data.data = (guint8*)g_bytes_get_data (gnutls->pkcs12_bytes, NULL);
+  p12_data.size = g_bytes_get_size (gnutls->pkcs12_bytes);
+
+  status = gnutls_pkcs12_init (&p12);
+  if (status != GNUTLS_E_SUCCESS)
+    goto import_failed;
+
+  /* Try both PEM and DER. */
+  status = gnutls_pkcs12_import (p12, &p12_data, GNUTLS_X509_FMT_PEM, 0);
+  if (status != GNUTLS_E_SUCCESS)
+    {
+      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)
+        goto import_failed;
+    }
+
+  status = gnutls_pkcs12_simple_parse (p12,
+                                       gnutls->password ? gnutls->password : "",
+                                       &x509_key,
+                                       &chain, &chain_len,
+                                       NULL, NULL,
+                                       NULL, 0);
+  if (status == GNUTLS_E_DECRYPTION_FAILED)
+    gnutls->construct_needs_pkcs12_password = TRUE;
+  if (status != GNUTLS_E_SUCCESS)
+    goto import_failed;
+
+  /* Clear previous failure now that we have the password. */
+  if (gnutls->construct_needs_pkcs12_password)
+    g_clear_error (&gnutls->construct_error);
+
+  if (chain)
+    {
+      gnutls->cert = chain[0];
+      gnutls->have_cert = TRUE;
+      // TODO: Rest of chain as issuer
+    }
+
+  if (x509_key)
+    {
+      gnutls_privkey_t key;
+
+      status = gnutls_privkey_init (&key);
+      if (status != GNUTLS_E_SUCCESS)
+        goto import_failed;
+
+      status = gnutls_privkey_import_x509 (key, x509_key, GNUTLS_PRIVKEY_IMPORT_COPY);
+      if (status != GNUTLS_E_SUCCESS)
+        goto import_failed;
+
+      gnutls_x509_privkey_deinit (x509_key);
+
+      gnutls->key = key;
+      gnutls->have_key = TRUE;
+    }
+
+  gnutls_pkcs12_deinit (p12);
+  return;
+
+import_failed:
+  g_clear_error (&gnutls->construct_error);
+  g_set_error (&gnutls->construct_error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+              _("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);
+}
+
 static void
 g_tls_certificate_gnutls_get_property (GObject    *object,
                                        guint       prop_id,
@@ -209,6 +304,10 @@ g_tls_certificate_gnutls_get_property (GObject    *object,
 
   switch (prop_id)
     {
+    case PROP_PKCS12_BYTES:
+      g_value_set_boxed (value, gnutls->pkcs12_bytes);
+      break;
+
     case PROP_CERTIFICATE:
       size = 0;
       status = gnutls_x509_crt_export (gnutls->cert,
@@ -343,6 +442,16 @@ g_tls_certificate_gnutls_set_property (GObject      *object,
 
   switch (prop_id)
     {
+    case PROP_PASSWORD:
+      gnutls->password = g_value_dup_string (value);
+      maybe_import_pkcs12 (gnutls);
+      break;
+
+    case PROP_PKCS12_BYTES:
+      gnutls->pkcs12_bytes = g_value_dup_boxed (value);
+      maybe_import_pkcs12 (gnutls);
+      break;
+
     case PROP_CERTIFICATE:
       bytes = g_value_get_boxed (value);
       if (!bytes)
@@ -592,6 +701,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_BYTES, "pkcs12-bytes");
+  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 c0100d30..5c9b5627 100644
--- a/tls/tests/certificate.c
+++ b/tls/tests/certificate.c
@@ -774,6 +774,101 @@ test_certificate_ip_addresses (void)
   g_object_unref (cert);
 }
 
+static GBytes *
+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 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;
+  GBytes *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, NULL, &error);
+
+  g_assert_no_error (error);
+  g_assert_nonnull (cert);
+  assert_cert_contains_cert_and_key (cert);
+  assert_equals_original_cert (cert);
+
+  g_bytes_unref (pkcs12_data);
+  g_object_unref (cert);
+}
+
+static void
+test_certificate_pkcs12_password (void)
+{
+  GTlsCertificate *cert;
+  GBytes *pkcs12_data, *pkcs12_enc_data;
+  GError *error = NULL;
+
+  pkcs12_data = load_bytes_for_test_file ("client-and-key-password.p12");
+  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_data, NULL, &error);
+  g_assert_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE);
+  g_clear_error (&error);
+
+  /* With the wrong password it fails. */
+  cert = g_tls_certificate_new_from_pkcs12 (pkcs12_data, "oajfo", &error);
+  g_assert_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE);
+  g_clear_error (&error);
+
+  /* With the correct password it succeeds. */
+  cert = g_tls_certificate_new_from_pkcs12 (pkcs12_data, "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);
+
+  /* With the same password for the key it succeeds. */
+  cert = g_tls_certificate_new_from_pkcs12 (pkcs12_enc_data, "1234", &error);
+  g_assert_no_error (error);
+  g_assert_nonnull (cert);
+  assert_cert_contains_cert_and_key (cert);
+  assert_equals_original_cert (cert);
+
+  g_bytes_unref (pkcs12_data);
+  g_object_unref (cert);
+}
+
 int
 main (int   argc,
       char *argv[])
@@ -843,5 +938,8 @@ 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);
+
   return g_test_run();
 }
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 00000000..feb8c9aa
Binary files /dev/null and b/tls/tests/files/client-and-key-password-enckey.p12 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 00000000..a11df4f4
Binary files /dev/null and b/tls/tests/files/client-and-key-password.p12 differ
diff --git a/tls/tests/files/client-and-key.p12 b/tls/tests/files/client-and-key.p12
new file mode 100644
index 00000000..ba4ec961
Binary files /dev/null and b/tls/tests/files/client-and-key.p12 differ
diff --git a/tls/tests/files/create-files.sh b/tls/tests/files/create-files.sh
index b699e7c8..fd8601c2 100755
--- a/tls/tests/files/create-files.sh
+++ b/tls/tests/files/create-files.sh
@@ -212,6 +212,15 @@ msg "Updating test expectations"
 ./update-test-database.py ca.pem ../file-database.h
 ./update-certificate-test.py server.pem ../certificate.h
 
+#######################################################################
+### Generate PKCS #12 format copies for testing
+#######################################################################
+
+msg "Generating PKCS #12 files"
+openssl pkcs12 -in client-and-key.pem -export -out client-and-key.p12 -nodes --passout pass: -name "No 
password"
+openssl pkcs12 -in client-and-key.pem -export -out client-and-key-password.p12 -nodes --passout pass:1234 
-name "With Password"
+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
 #######################################################################


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]