summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordx <dx@dxzone.com.ar>2017-03-07 00:22:58 -0300
committerdx <dx@dxzone.com.ar>2017-03-07 00:22:58 -0300
commite7c9dd5fbb26afd42261563a2af301ab392169c4 (patch)
treef44bc25cec8254735e4180847a5be8b08e849750
parentad012674707f9db0202455deba280ec6696e089e (diff)
downloadpidgin-e7c9dd5fbb26afd42261563a2af301ab392169c4.tar.gz
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.
-rw-r--r--ChangeLog.API2
-rw-r--r--libpurple/certificate.c27
-rw-r--r--libpurple/certificate.h24
-rw-r--r--libpurple/plugins/ssl/ssl-gnutls.c41
-rw-r--r--libpurple/plugins/ssl/ssl-nss.c1
5 files changed, 95 insertions, 0 deletions
diff --git a/ChangeLog.API b/ChangeLog.API
index 2fff1c8182..44d18bc452 100644
--- a/ChangeLog.API
+++ b/ChangeLog.API
@@ -6,7 +6,9 @@ version 2.12.0:
* 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:
diff --git a/libpurple/certificate.c b/libpurple/certificate.c
index 75ab93f6cf..f240b26ca1 100644
--- a/libpurple/certificate.c
+++ b/libpurple/certificate.c
@@ -508,6 +508,24 @@ purple_certificate_get_times(PurpleCertificate *crt, time_t *activation, time_t
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 @@ x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq,
* 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 @@ x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq,
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;
diff --git a/libpurple/certificate.h b/libpurple/certificate.h
index 2ec9dc78fa..08b4bad323 100644
--- a/libpurple/certificate.h
+++ b/libpurple/certificate.h
@@ -332,6 +332,16 @@ struct _PurpleCertificateScheme
* @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 @@ purple_certificate_check_subject_name(PurpleCertificate *crt, const gchar *name)
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);
+
/*@}*/
/*****************************************************************************/
diff --git a/libpurple/plugins/ssl/ssl-gnutls.c b/libpurple/plugins/ssl/ssl-gnutls.c
index 86bdbb5fb8..54a6a23a62 100644
--- a/libpurple/plugins/ssl/ssl-gnutls.c
+++ b/libpurple/plugins/ssl/ssl-gnutls.c
@@ -1232,6 +1232,46 @@ x509_times (PurpleCertificate *crt, time_t *activation, time_t *expiration)
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 @@ static PurpleCertificateScheme x509_gnutls = {
NULL,
sizeof(PurpleCertificateScheme), /* struct_size */
x509_sha256sum, /* SHA256 fingerprint */
+ x509_compare_pubkeys, /* Compare public keys */
};
static PurpleSslOps ssl_ops =
diff --git a/libpurple/plugins/ssl/ssl-nss.c b/libpurple/plugins/ssl/ssl-nss.c
index 8e5fa37cc3..dbae0f660f 100644
--- a/libpurple/plugins/ssl/ssl-nss.c
+++ b/libpurple/plugins/ssl/ssl-nss.c
@@ -1225,6 +1225,7 @@ static PurpleCertificateScheme x509_nss = {
x509_verify_cert, /* Verify that the specified cert chain is trusted */
sizeof(PurpleCertificateScheme), /* struct_size */
x509_sha256sum, /* SHA256 fingerprint */
+ NULL,
};
static PurpleSslOps ssl_ops =