From 96ce3feeb90a5accbd4bc5c53c4dc2b44f53d263 Mon Sep 17 00:00:00 2001 From: Patrick Griffis Date: Fri, 3 Sep 2021 19:13:21 -0500 Subject: [PATCH] gtlscertificate: Add ability to load PKCS #12 encrypted files This depends on the GTlsBackend implementing these properties --- docs/reference/gio/gio-sections-common.txt | 2 + gio/gioenums.h | 5 +- gio/gtlscertificate.c | 205 ++++++++++++++++-- gio/gtlscertificate.h | 10 +- .../cert-tests/key-cert-password-123.p12 | Bin 0 -> 1717 bytes gio/tests/tls-certificate.c | 43 ++++ 6 files changed, 245 insertions(+), 20 deletions(-) create mode 100644 gio/tests/cert-tests/key-cert-password-123.p12 diff --git a/docs/reference/gio/gio-sections-common.txt b/docs/reference/gio/gio-sections-common.txt index 137737260..aef4fcc72 100644 --- a/docs/reference/gio/gio-sections-common.txt +++ b/docs/reference/gio/gio-sections-common.txt @@ -3715,7 +3715,9 @@ g_tls_backend_get_type GTlsCertificate GTlsCertificate g_tls_certificate_new_from_pem +g_tls_certificate_new_from_pkcs12 g_tls_certificate_new_from_file +g_tls_certificate_new_from_file_with_password g_tls_certificate_new_from_files g_tls_certificate_new_from_pkcs11_uris g_tls_certificate_list_new_from_file diff --git a/gio/gioenums.h b/gio/gioenums.h index 4c595803d..ff1f5f48a 100644 --- a/gio/gioenums.h +++ b/gio/gioenums.h @@ -1552,6 +1552,8 @@ typedef enum * @G_TLS_ERROR_INAPPROPRIATE_FALLBACK: The TLS handshake failed * because the client sent the fallback SCSV, indicating a protocol * downgrade attack. Since: 2.60 + * @G_TLS_ERROR_BAD_CERTIFICATE_PASSWORD: The certificate failed + * to load because a password was incorrect. Since: 2.72 * * An error code used with %G_TLS_ERROR in a #GError returned from a * TLS-related routine. @@ -1566,7 +1568,8 @@ typedef enum { G_TLS_ERROR_HANDSHAKE, G_TLS_ERROR_CERTIFICATE_REQUIRED, G_TLS_ERROR_EOF, - G_TLS_ERROR_INAPPROPRIATE_FALLBACK + G_TLS_ERROR_INAPPROPRIATE_FALLBACK, + G_TLS_ERROR_BAD_CERTIFICATE_PASSWORD } GTlsError; /** diff --git a/gio/gtlscertificate.c b/gio/gtlscertificate.c index 8961a429d..9181756e7 100644 --- a/gio/gtlscertificate.c +++ b/gio/gtlscertificate.c @@ -50,7 +50,11 @@ * Since: 2.28 */ -G_DEFINE_ABSTRACT_TYPE (GTlsCertificate, g_tls_certificate, G_TYPE_OBJECT) +struct _GTlsCertificatePrivate { + gboolean pkcs12_properties_not_overridden; +}; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GTlsCertificate, g_tls_certificate, G_TYPE_OBJECT) enum { @@ -69,6 +73,8 @@ enum PROP_ISSUER_NAME, PROP_DNS_NAMES, PROP_IP_ADDRESSES, + PROP_PKCS12_DATA, + PROP_PASSWORD, }; static void @@ -84,11 +90,11 @@ g_tls_certificate_get_property (GObject *object, { switch (prop_id) { + /* Subclasses must override these properties but this allows older backends to not fatally error */ case PROP_PRIVATE_KEY: case PROP_PRIVATE_KEY_PEM: case PROP_PKCS11_URI: case PROP_PRIVATE_KEY_PKCS11_URI: - /* Subclasses must override this property but this allows older backends to not fatally error */ g_value_set_static_string (value, NULL); break; default: @@ -102,11 +108,19 @@ g_tls_certificate_set_property (GObject *object, const GValue *value, GParamSpec *pspec) { + GTlsCertificate *cert = (GTlsCertificate*)object; + GTlsCertificatePrivate *priv = g_tls_certificate_get_instance_private (cert); + switch (prop_id) { case PROP_PKCS11_URI: case PROP_PRIVATE_KEY_PKCS11_URI: - /* Subclasses must override this property but this allows older backends to not fatally error */ + /* Subclasses must override these properties but this allows older backends to not fatally error. */ + break; + case PROP_PKCS12_DATA: + case PROP_PASSWORD: + /* We don't error on setting these properties however we track that they were not overridden. */ + priv->pkcs12_properties_not_overridden = TRUE; break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -121,6 +135,39 @@ g_tls_certificate_class_init (GTlsCertificateClass *class) gobject_class->set_property = g_tls_certificate_set_property; gobject_class->get_property = g_tls_certificate_get_property; + /** + * GTlsCertificate:pkcs12-data: (nullable) + * + * The PKCS #12 formatted data used to construct the object. + * + * See also: g_tls_certificate_new_from_pkcs12() + * + * Since: 2.72 + */ + g_object_class_install_property (gobject_class, PROP_PKCS12_DATA, + g_param_spec_boxed ("pkcs12-data", + P_("PKCS #12 data"), + P_("The PKCS #12 data used for construction"), + G_TYPE_BYTE_ARRAY, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + /** + * GTlsCertificate:password: (nullable) + * + * An optional password used when constructed with GTlsCertificate:pkcs12-data. + * + * Since: 2.72 + */ + g_object_class_install_property (gobject_class, PROP_PASSWORD, + g_param_spec_string ("password", + P_("Password"), + P_("Password used when constructing from bytes"), + NULL, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); /** * GTlsCertificate:certificate: * @@ -684,23 +731,138 @@ g_tls_certificate_new_from_pem (const gchar *data, } /** - * g_tls_certificate_new_from_file: - * @file: (type filename): file containing a PEM-encoded certificate to import + * g_tls_certificate_new_from_pkcs12: + * @data: DER-encoded PKCS #12 format certificate data + * @length: the length of @data + * @password: (nullable): optional password for encrypted certificate data * @error: #GError for error reporting, or %NULL to ignore. * - * Creates a #GTlsCertificate from the PEM-encoded data in @file. The - * returned certificate will be the first certificate found in @file. As - * of GLib 2.44, if @file contains more certificates it will try to load - * a certificate chain. All certificates will be verified in the order - * found (top-level certificate should be the last one in the file) and - * the #GTlsCertificate:issuer property of each certificate will be set - * accordingly if the verification succeeds. If any certificate in the - * chain cannot be verified, the first certificate in the file will - * still be returned. + * Creates a #GTlsCertificate from the data in @data. It must contain + * a certificate and matching private key. + * + * If extra certificates are included they will be verified as a chain + * and the #GTlsCertificate:issuer property will be set. + * All other data will be ignored. + * + * You can pass as single password for all of the data which will be + * used both for the PKCS #12 container as well as encrypted + * private keys. If decryption fails it will error with + * %G_TLS_ERROR_BAD_CERTIFICATE_PASSWORD. + * + * This constructor requires support in the current #GTlsBackend. + * If support is missing it will error with + * %G_IO_ERROR_NOT_SUPPORTED. + * + * Other parsing failures will error with %G_TLS_ERROR_BAD_CERTIFICATE. + * + * Returns: the new certificate, or %NULL if @data is invalid + * + * Since: 2.72 + */ +GTlsCertificate * +g_tls_certificate_new_from_pkcs12 (const guint8 *data, + gsize length, + const gchar *password, + GError **error) +{ + GObject *cert; + GTlsBackend *backend; + GByteArray *bytes; + + g_return_val_if_fail (data != NULL || length == 0, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + backend = g_tls_backend_get_default (); + + bytes = g_byte_array_new (); + g_byte_array_append (bytes, data, length); + + cert = g_initable_new (g_tls_backend_get_certificate_type (backend), + NULL, error, + "pkcs12-data", bytes, + "password", password, + NULL); + + g_byte_array_unref (bytes); + + if (cert) + { + GTlsCertificatePrivate *priv = g_tls_certificate_get_instance_private (G_TLS_CERTIFICATE (cert)); + + if (priv->pkcs12_properties_not_overridden) + { + g_clear_object (&cert); + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("The current TLS backend does not support PKCS #12")); + return NULL; + } + } + + return G_TLS_CERTIFICATE (cert); +} + +/** + * g_tls_certificate_new_from_file_with_password: + * @file: (type filename): file containing a certificate to import + * @password: (not nullable): password for PKCS #12 files + * @error: #GError for error reporting, or %NULL to ignore + * + * Creates a #GTlsCertificate from the data in @file. * * If @file cannot be read or parsed, the function will return %NULL and - * set @error. Otherwise, this behaves like - * g_tls_certificate_new_from_pem(). + * set @error. + * + * Any unknown file types will error with %G_IO_ERROR_NOT_SUPPORTED. + * Currently only `.p12` and `.pfx` files are supported. + * See g_tls_certificate_new_from_pkcs12() for more details. + * + * Returns: the new certificate, or %NULL on error + * + * Since: 2.72 + */ +GTlsCertificate * +g_tls_certificate_new_from_file_with_password (const gchar *file, + const gchar *password, + GError **error) +{ + GTlsCertificate *cert; + gchar *contents; + gsize length; + + g_return_val_if_fail (file != NULL, NULL); + g_return_val_if_fail (password != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + if (!g_str_has_suffix (file, ".p12") && !g_str_has_suffix (file, ".pfx")) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "The file type of \"%s\" is unknown. Only .p12 and .pfx files are supported currently.", file); + return NULL; + } + + if (!g_file_get_contents (file, &contents, &length, error)) + return NULL; + + cert = g_tls_certificate_new_from_pkcs12 ((guint8 *)contents, length, password, error); + + g_free (contents); + return cert; +} + +/** + * g_tls_certificate_new_from_file: + * @file: (type filename): file containing a certificate to import + * @error: #GError for error reporting, or %NULL to ignore + * + * Creates a #GTlsCertificate from the data in @file. + * + * As of 2.72, if the filename ends in `.p12` or `.pfx` the data is loaded by + * g_tls_certificate_new_from_pkcs12() otherwise it is loaded by + * g_tls_certificate_new_from_pem(). See those functions for + * exact details. + * + * If @file cannot be read or parsed, the function will return %NULL and + * set @error. * * Returns: the new certificate, or %NULL on error * @@ -708,16 +870,23 @@ g_tls_certificate_new_from_pem (const gchar *data, */ GTlsCertificate * g_tls_certificate_new_from_file (const gchar *file, - GError **error) + GError **error) { GTlsCertificate *cert; gchar *contents; gsize length; + g_return_val_if_fail (file != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + if (!g_file_get_contents (file, &contents, &length, error)) return NULL; - cert = g_tls_certificate_new_from_pem (contents, length, error); + if (g_str_has_suffix (file, ".p12") || g_str_has_suffix (file, ".pfx")) + cert = g_tls_certificate_new_from_pkcs12 ((guint8 *)contents, length, NULL, error); + else + cert = g_tls_certificate_new_from_pem (contents, length, error); + g_free (contents); return cert; } diff --git a/gio/gtlscertificate.h b/gio/gtlscertificate.h index 3b92b97fc..52e678bcb 100644 --- a/gio/gtlscertificate.h +++ b/gio/gtlscertificate.h @@ -63,7 +63,15 @@ GLIB_AVAILABLE_IN_ALL GTlsCertificate *g_tls_certificate_new_from_pem (const gchar *data, gssize length, GError **error); - +GLIB_AVAILABLE_IN_2_72 +GTlsCertificate *g_tls_certificate_new_from_pkcs12 (const guint8 *data, + gsize length, + const gchar *password, + GError **error); +GLIB_AVAILABLE_IN_2_72 +GTlsCertificate *g_tls_certificate_new_from_file_with_password (const gchar *file, + const gchar *password, + GError **error); GLIB_AVAILABLE_IN_ALL GTlsCertificate *g_tls_certificate_new_from_file (const gchar *file, GError **error); diff --git a/gio/tests/cert-tests/key-cert-password-123.p12 b/gio/tests/cert-tests/key-cert-password-123.p12 new file mode 100644 index 0000000000000000000000000000000000000000..4da265fd65ca9d9aed35323968578c177f01af65 GIT binary patch literal 1717 zcmY+DX;c!38ir*NC39b0znUtk5x?)D&?EgWQe5G_~AN zYsSQM$ic?OvfRJQJ3IuAH zhsoPg1jqqu$dFht84|gNLy=@q$lnp9h73BjhdhA*nZ1SnI{_eJkbVFDupaBW%3k7ygw7D}V^#w|*~ zq5G(kLmM$79Rd#p(3Kq*U{w0qaRbS^p4p2m$8Y**^=~8U8(;UX)l%*BFW8-#@}Jvs zTKJ{a{@2j$vd5pL7MoS?g&3>jmPSmjQy^0T)Kvo)tZ&(ur}?h!e#9-N7fYMdvU)O) zByP^~e^8IMqp}jUgNaM}sBJmKa~0@}?Qqsr?}`YPj<(_x4IPldsef+o|3<)^`W^_k z80hcgA1fIlWocM5CWWnn_;=kx<7)SZ+|~8Y@!N=xUA_DgN;?E+F7O|yhn&QZMeAXA zQEU(DT>C@mNdfoHK)>Rss~7*~4dv=fu%FKrH-8R_9?S`!xs-PK^-xwssj&y^lTfxP z@e$Co9aa>p7BNLF4eLhF3|aiNiNm`1Og20hb&lNXb&b2$IN-Se_pXn|`p`cm5S+2% zH9f|MH1*9O^v^pOmKka8nmEg0E*<4W_w1If{ltqK9~&VJ2HHLF_C0XCDwj|@8Ft;M z@UW$Vx=+uZ}^t}_lbveObpdx4nVuAD^Vp7#ET2uPe@deMfFOsbn=V>1y*d@(HQ^aE6a-(cx`+s__%(t zMX#i|WA?pABN4L`WacH$h&av*J>`~4(hhs^tZ($9)?oZ!t?C<_19_kYX$=j@?$ppRd|G^XW@m>*&6{Nd4UfOyC6WYr<@LP zgG};P#cW5TweBV7e$@*@%C+_7iG5@u)tS@T3hYqjgi+?-m&=1u-hljrmniG)hg z9uxy%_l3;0Q8~pBcJ`uBXuszZQ1U+_)sTTAFc~@yR{H?Jy|7^q-uXY< zKwoWJK#LzKC3|>(wQcX5wB~mnu|Zj1Y}AA$Yr1!v#m@gQ{Brl(f9M`lDO-;HqJL(6 zM|jD4$k4IVVk#G}U2V?Ao66A$mKodS!Ii=!4-Jby*D#MQ3MxdWtrRO zQp8s};z8kOl`<)X%YrEL7_*kQ_c_=nS1b5_DHEyT6ub3g{K?$&L#)9tm-3jpN1Lf8 zfi1b6XUG`fD#F18YBFZ^=`wpY|3e=^A8K_crliFltSef%;JPDdueK@ovn}DYCE7>3 zH4PIP)?!*Dr%qZoq59oCd8=QcMu++ z8N{x#N7j2e_c$d5ZYP}3*NTRtFq1EoX1C|P{*X!pYPm;Q+!c4WpYiDF#fiDUqc3%0fJkED0?_0{B zSZz+84i10o-^Ty(u6mRNouBX%UHW-|F#Q**b>M0W*~c^m7vIOQTqyl@_kJdk%Oj^V zgAf{JxU8eSf7Bb*6;xL>;-^I<9B<9^EZ6w9l9>$!85%Ju$?*@)vS@#gPPb&F@ucSJ zkFVGoFLWkUFDX4P)w|eM<~E67z_YYg>&91@GBIGD<^&pKZVt)QQ30JT5opS`Ga*#A z`IvWQPc);#UjO$D0?p@0YZ%Nd!C**o-7sU>#nJB|kv&{lg)%bmTAej%lb3LD{d9+^ zj3uyJWDhpcB=B91|Fo+DYbR9k8Ct=0$v%D7Oc$yB-&RA_%{0Jja_N5FP-XWkMZ`C! z^NG@3aHO*r(hPYN35CcWQvw0CWM#l=aZQzcs(ul7G(G^%@V^x&dO4^GmTy&i_4LUq J!tiUm{{=w)D|!F` literal 0 HcmV?d00001 diff --git a/gio/tests/tls-certificate.c b/gio/tests/tls-certificate.c index 2995b1028..03d17b80c 100644 --- a/gio/tests/tls-certificate.c +++ b/gio/tests/tls-certificate.c @@ -586,6 +586,45 @@ ip_addresses (void) g_object_unref (cert); } +static void +from_pkcs12 (void) +{ + GTlsCertificate *cert; + GError *error = NULL; + const guint8 data[1] = { 0 }; + + /* This simply fails because our test backend doesn't support this + * property. This reflects using a backend that doesn't support it. + * The real test lives in glib-networking. */ + cert = g_tls_certificate_new_from_pkcs12 (data, 1, NULL, &error); + + g_assert_null (cert); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); + g_error_free (error); +} + +static void +from_pkcs12_file (void) +{ + GTlsCertificate *cert; + GError *error = NULL; + char *path = g_test_build_filename (G_TEST_DIST, "cert-tests", "key-cert-password-123.p12", NULL); + + /* Fails on our test backend, see from_pkcs12() above. */ + cert = g_tls_certificate_new_from_file_with_password (path, "123", &error); + g_assert_null (cert); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); + g_clear_error (&error); + + /* Just for coverage. */ + cert = g_tls_certificate_new_from_file (path, &error); + g_assert_null (cert); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); + g_error_free (error); + + g_free (path); +} + int main (int argc, char *argv[]) @@ -656,6 +695,10 @@ main (int argc, from_pkcs11_uri); g_test_add_func ("/tls-certificate/pkcs11-uri-unsupported", from_unsupported_pkcs11_uri); + g_test_add_func ("/tls-certificate/from_pkcs12", + from_pkcs12); + g_test_add_func ("/tls-certificate/from_pkcs12_file", + from_pkcs12_file); g_test_add_func ("/tls-certificate/not-valid-before", not_valid_before); g_test_add_func ("/tls-certificate/not-valid-after",