pidgin/pidgin

887efbd652d8
certificate: Use public key fingerprint to compare certificates

This fixes an issue with google talk's certificates and gnutls, where the root
certificate in the provided chain is a slightly different version of the one
that is usually present in the certificate stores, but the SubjectPublicKeyInfo
section is the same.

This adds a PurpleCertificateScheme function, compare_pubkeys, and its wrapper
purple_certificate_compare_pubkeys().

This is only implemented for gnutls, since the NSS plugin only uses the NSS
certificate validation code. Even if that path was reachable from a plugin that
doesn't implement this method, it would return FALSE and behave as if this bug
was never fixed.

The gnutls implementation uses the gnutls_x509_crt_get_key_id() function,
which returns a hash of the SubjectPublicKeyInfo section of the certificate.

In gnutls versions older than 3.4.1, this may be a SHA1 hash, but after that
version SHA256 support was added (without much fanfare - the documentation
barely mentions this at all), and we just use the constant for the best known
algo, which for current versions is just SHA256. Older versions ignore that
flag parameter.

The whole comparison is modeled after the private _gnutls_check_if_same_key(),
which checks if both certificates have the same DN ("unique id") and does a
memcmp() of the raw SPKI section. We don't have direct access to the raw SPKI
section but comparing their fingerprints is good enough.
--- a/ChangeLog.API Tue Mar 07 02:38:54 2017 +0000
+++ b/ChangeLog.API Tue Mar 07 00:22:58 2017 -0300
@@ -6,7 +6,9 @@
* PURPLE_MESSAGE_REMOTE_SEND in PurpleMessageFlags, to specify
messages like _SEND that were sent from another location.
* purple_certificate_get_fingerprint_sha256
+ * purple_certificate_compare_pubkeys
* PurpleCertificateScheme.get_fingerprint_sha256
+ * PurpleCertificateScheme.compare_pubkeys
* PURPLE_CERTIFICATE_SCHEME_HAS_FUNC
version 2.11.0:
--- a/libpurple/certificate.c Tue Mar 07 02:38:54 2017 +0000
+++ b/libpurple/certificate.c Tue Mar 07 00:22:58 2017 -0300
@@ -508,6 +508,24 @@
return (scheme->get_times)(crt, activation, expiration);
}
+gboolean
+purple_certificate_compare_pubkeys(PurpleCertificate *crt1, PurpleCertificate *crt2)
+{
+ PurpleCertificateScheme *scheme;
+
+ g_return_val_if_fail(crt1 && crt2, FALSE);
+ g_return_val_if_fail(crt1->scheme && crt2->scheme, FALSE);
+ g_return_val_if_fail(crt1->scheme == crt2->scheme, FALSE);
+
+ scheme = crt1->scheme;
+
+ if (!(PURPLE_CERTIFICATE_SCHEME_HAS_FUNC(scheme, compare_pubkeys))) {
+ return FALSE;
+ }
+
+ return (scheme->compare_pubkeys)(crt1, crt2);
+}
+
gchar *
purple_certificate_pool_mkpath(PurpleCertificatePool *pool, const gchar *id)
{
@@ -1746,11 +1764,17 @@
* signature.
*/
last_fpr = purple_certificate_get_fingerprint_sha256(end_crt, TRUE);
+
+ ca_id = purple_certificate_get_unique_id(end_crt);
+
for (cur = ca_crts; cur; cur = cur->next) {
ca_crt = cur->data;
ca_fpr = purple_certificate_get_fingerprint_sha256(ca_crt, TRUE);
+ ca2_id = purple_certificate_get_unique_id(ca_crt);
if ( byte_arrays_equal(last_fpr, ca_fpr) ||
+ (purple_strequal(ca_id, ca2_id) &&
+ purple_certificate_compare_pubkeys(end_crt, ca_crt)) ||
purple_certificate_signed_by(end_crt, ca_crt) )
{
/* TODO: If signed_by ever returns a reason, maybe mention
@@ -1760,11 +1784,14 @@
user's poor, leaky eyes. */
valid = TRUE;
g_byte_array_free(ca_fpr, TRUE);
+ g_free(ca2_id);
break;
}
g_byte_array_free(ca_fpr, TRUE);
+ g_free(ca2_id);
}
+ g_free(ca_id);
if (valid == FALSE)
flags |= PURPLE_CERTIFICATE_INVALID_CHAIN;
--- a/libpurple/certificate.h Tue Mar 07 02:38:54 2017 +0000
+++ b/libpurple/certificate.h Tue Mar 07 00:22:58 2017 -0300
@@ -332,6 +332,16 @@
* @since 2.12.0
*/
GByteArray * (* get_fingerprint_sha256)(PurpleCertificate *crt);
+
+ /**
+ * Compares the public keys of two certificates
+ *
+ * @param crt1 A certificate instance
+ * @param crt2 Another certificate instance
+ * @return TRUE if both certificates have the same key, otherwise FALSE
+ * @since 2.12.0
+ */
+ gboolean (* compare_pubkeys)(PurpleCertificate *crt1, PurpleCertificate *crt2);
};
#define PURPLE_CERTIFICATE_SCHEME_HAS_FUNC(obj, member) \
@@ -674,6 +684,20 @@
gboolean
purple_certificate_get_times(PurpleCertificate *crt, time_t *activation, time_t *expiration);
+/**
+ * Compares the public keys of two certificates.
+ *
+ * If the SSL backend does not implement this function, it may return FALSE
+ * every time. This is the case with the NSS plugin, which doesn't need it.
+ *
+ * @param crt1 A certificate instance
+ * @param crt2 Another certificate instance
+ * @return TRUE if both certificates have the same key, otherwise FALSE
+ * @since 2.12.0
+ */
+gboolean
+purple_certificate_compare_pubkeys(PurpleCertificate *crt1, PurpleCertificate *crt2);
+
/*@}*/
/*****************************************************************************/
--- a/libpurple/plugins/ssl/ssl-gnutls.c Tue Mar 07 02:38:54 2017 +0000
+++ b/libpurple/plugins/ssl/ssl-gnutls.c Tue Mar 07 00:22:58 2017 -0300
@@ -1232,6 +1232,46 @@
return success;
}
+/* GNUTLS_KEYID_USE_BEST_KNOWN was added in gnutls 3.4.1, but can't ifdef it
+ * because it's an enum member. Older versions will ignore it, which means
+ * using SHA1 instead of SHA256 to compare pubkeys. But hey, not my fault. */
+#if GNUTLS_VERSION_NUMBER < 0x030401
+#define KEYID_FLAG (1<<30)
+#else
+#define KEYID_FLAG GNUTLS_KEYID_USE_BEST_KNOWN
+#endif
+
+static gboolean
+x509_compare_pubkeys (PurpleCertificate *crt1, PurpleCertificate *crt2)
+{
+ gnutls_x509_crt_t crt_dat1, crt_dat2;
+ unsigned char buffer1[64], buffer2[64];
+ size_t size1, size2;
+ size1 = size2 = sizeof(buffer1);
+
+ g_return_val_if_fail(crt1 && crt2, FALSE);
+ g_return_val_if_fail(crt1->scheme == &x509_gnutls, FALSE);
+ g_return_val_if_fail(crt2->scheme == &x509_gnutls, FALSE);
+
+ crt_dat1 = X509_GET_GNUTLS_DATA(crt1);
+
+ if (gnutls_x509_crt_get_key_id(crt_dat1, KEYID_FLAG, buffer1, &size1) != 0) {
+ return FALSE;
+ }
+
+ crt_dat2 = X509_GET_GNUTLS_DATA(crt2);
+
+ if (gnutls_x509_crt_get_key_id(crt_dat2, KEYID_FLAG, buffer2, &size2) != 0) {
+ return FALSE;
+ }
+
+ if (size1 != size2) {
+ return FALSE;
+ }
+
+ return memcmp(buffer1, buffer2, size1) == 0;
+}
+
/* X.509 certificate operations provided by this plugin */
static PurpleCertificateScheme x509_gnutls = {
"x509", /* Scheme name */
@@ -1253,6 +1293,7 @@
NULL,
sizeof(PurpleCertificateScheme), /* struct_size */
x509_sha256sum, /* SHA256 fingerprint */
+ x509_compare_pubkeys, /* Compare public keys */
};
static PurpleSslOps ssl_ops =
--- a/libpurple/plugins/ssl/ssl-nss.c Tue Mar 07 02:38:54 2017 +0000
+++ b/libpurple/plugins/ssl/ssl-nss.c Tue Mar 07 00:22:58 2017 -0300
@@ -1225,6 +1225,7 @@
x509_verify_cert, /* Verify that the specified cert chain is trusted */
sizeof(PurpleCertificateScheme), /* struct_size */
x509_sha256sum, /* SHA256 fingerprint */
+ NULL,
};
static PurpleSslOps ssl_ops =