diff options
author | dx <dx@dxzone.com.ar> | 2017-03-07 00:22:58 -0300 |
---|---|---|
committer | dx <dx@dxzone.com.ar> | 2017-03-07 00:22:58 -0300 |
commit | e7c9dd5fbb26afd42261563a2af301ab392169c4 (patch) | |
tree | f44bc25cec8254735e4180847a5be8b08e849750 | |
parent | ad012674707f9db0202455deba280ec6696e089e (diff) | |
download | pidgin-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.API | 2 | ||||
-rw-r--r-- | libpurple/certificate.c | 27 | ||||
-rw-r--r-- | libpurple/certificate.h | 24 | ||||
-rw-r--r-- | libpurple/plugins/ssl/ssl-gnutls.c | 41 | ||||
-rw-r--r-- | libpurple/plugins/ssl/ssl-nss.c | 1 |
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 = |