pidgin/pidgin

b3d0ba7c75f6
Parents 274575856b52
Children 6a674536b8e5
certificate: Use SHA256 fingerprints instead of SHA1

This meant adding a get_fingerprint_sha256 function to the certificate scheme
structs, which meant adding a struct_size member because we ran out of reserved
members there.

The API-facing purple_certificate_get_fingerprint_sha256() has a fallback
parameter to use sha1 if the SSL plugin doesn't implement this function
(probably an outdated installation, or a third party SSL plugin). When using
the function for display purposes, the fallback is disabled and it returns
NULL, but when using it to compare certificates it's better to have at least
the SHA1.

In functions like purple_certificate_display_x509(), some slight changes to
translatable strings would have been required. Since we're in a string freeze
right now, I avoided those by concatenating a language-neutral "SHA256: %s" at
the end of those messages. The SHA1 line used the word "fingerprint" but we
can't reuse that translation. This should be cleaned up after the release.
--- a/ChangeLog.API Thu Feb 23 06:35:24 2017 +0000
+++ b/ChangeLog.API Mon Mar 06 03:32:06 2017 -0300
@@ -5,6 +5,9 @@
Added:
* PURPLE_MESSAGE_REMOTE_SEND in PurpleMessageFlags, to specify
messages like _SEND that were sent from another location.
+ * purple_certificate_get_fingerprint_sha256
+ * PurpleCertificateScheme.get_fingerprint_sha256
+ * PURPLE_CERTIFICATE_SCHEME_HAS_FUNC
version 2.11.0:
libpurple:
--- a/libpurple/certificate.c Thu Feb 23 06:35:24 2017 +0000
+++ b/libpurple/certificate.c Mon Mar 06 03:32:06 2017 -0300
@@ -84,6 +84,29 @@
}
}
+static void
+get_ascii_fingerprints (PurpleCertificate *crt, gchar **sha1, gchar **sha256)
+{
+ GByteArray *sha_bin;
+
+ if (sha1 != NULL) {
+ sha_bin = purple_certificate_get_fingerprint_sha1(crt);
+
+ *sha1 = purple_base16_encode_chunked(sha_bin->data, sha_bin->len);
+
+ g_byte_array_free(sha_bin, TRUE);
+ }
+
+ if (sha256 != NULL) {
+ sha_bin = purple_certificate_get_fingerprint_sha256(crt, FALSE);
+
+ *sha256 = (sha_bin == NULL) ? g_strdup("(null)") :
+ purple_base16_encode_chunked(sha_bin->data, sha_bin->len);
+
+ g_byte_array_free(sha_bin, TRUE);
+ }
+}
+
void
purple_certificate_verify (PurpleCertificateVerifier *verifier,
const gchar *subject_name, GList *cert_chain,
@@ -388,6 +411,30 @@
return fpr;
}
+GByteArray *
+purple_certificate_get_fingerprint_sha256(PurpleCertificate *crt, gboolean sha1_fallback)
+{
+ PurpleCertificateScheme *scheme;
+ GByteArray *fpr = NULL;
+
+ g_return_val_if_fail(crt, NULL);
+ g_return_val_if_fail(crt->scheme, NULL);
+
+ scheme = crt->scheme;
+
+ if (!PURPLE_CERTIFICATE_SCHEME_HAS_FUNC(scheme, get_fingerprint_sha256)) {
+ /* outdated ssl module? fallback to sha1 and print a warning */
+ if (sha1_fallback) {
+ fpr = purple_certificate_get_fingerprint_sha1(crt);
+ }
+ g_return_val_if_reached(fpr);
+ }
+
+ fpr = (scheme->get_fingerprint_sha256)(crt);
+
+ return fpr;
+}
+
gchar *
purple_certificate_get_unique_id(PurpleCertificate *crt)
{
@@ -630,18 +677,13 @@
static void
x509_singleuse_start_verify (PurpleCertificateVerificationRequest *vrq)
{
- gchar *sha_asc;
- GByteArray *sha_bin;
+ gchar *sha1_asc, *sha256_asc;
gchar *cn;
const gchar *cn_match;
- gchar *primary, *secondary;
+ gchar *primary, *secondary, *secondary_extra;
PurpleCertificate *crt = (PurpleCertificate *) vrq->cert_chain->data;
- /* Pull out the SHA1 checksum */
- sha_bin = purple_certificate_get_fingerprint_sha1(crt);
- /* Now decode it for display */
- sha_asc = purple_base16_encode_chunked(sha_bin->data,
- sha_bin->len);
+ get_ascii_fingerprints(crt, &sha1_asc, &sha256_asc);
/* Get the cert Common Name */
cn = purple_certificate_get_subject_name(crt);
@@ -655,14 +697,17 @@
/* Make messages */
primary = g_strdup_printf(_("%s has presented the following certificate for just-this-once use:"), vrq->subject_name);
- secondary = g_strdup_printf(_("Common name: %s %s\nFingerprint (SHA1): %s"), cn, cn_match, sha_asc);
+ secondary = g_strdup_printf(_("Common name: %s %s\nFingerprint (SHA1): %s"), cn, cn_match, sha1_asc);
+
+ /* TODO: make this part of the translatable string above */
+ secondary_extra = g_strdup_printf("%s\nSHA256: %s", secondary, sha256_asc);
/* Make a semi-pretty display */
purple_request_accept_cancel(
vrq->cb_data, /* TODO: Find what the handle ought to be */
_("Single-use Certificate Verification"),
primary,
- secondary,
+ secondary_extra,
0, /* Accept by default */
NULL, /* No account */
NULL, /* No other user */
@@ -675,8 +720,9 @@
g_free(cn);
g_free(primary);
g_free(secondary);
- g_free(sha_asc);
- g_byte_array_free(sha_bin, TRUE);
+ g_free(secondary_extra);
+ g_free(sha1_asc);
+ g_free(sha256_asc);
}
static void
@@ -1506,10 +1552,10 @@
return;
}
- /* Now get SHA1 sums for both and compare them */
+ /* Now get SHA256 sums for both and compare them */
/* TODO: This is not an elegant way to compare certs */
- peer_fpr = purple_certificate_get_fingerprint_sha1(peer_crt);
- cached_fpr = purple_certificate_get_fingerprint_sha1(cached_crt);
+ peer_fpr = purple_certificate_get_fingerprint_sha256(peer_crt, TRUE);
+ cached_fpr = purple_certificate_get_fingerprint_sha256(cached_crt, TRUE);
if (!memcmp(peer_fpr->data, cached_fpr->data, peer_fpr->len)) {
purple_debug_info("certificate/x509/tls_cached",
"Peer cert matched cached\n");
@@ -1616,8 +1662,8 @@
if (ca_crt != NULL) {
GByteArray *failing_fpr;
GByteArray *ca_fpr;
- failing_fpr = purple_certificate_get_fingerprint_sha1(failing_crt);
- ca_fpr = purple_certificate_get_fingerprint_sha1(ca_crt);
+ failing_fpr = purple_certificate_get_fingerprint_sha256(failing_crt, TRUE);
+ ca_fpr = purple_certificate_get_fingerprint_sha256(ca_crt, TRUE);
if (byte_arrays_equal(failing_fpr, ca_fpr)) {
purple_debug_info("certificate/x509/tls_cached",
"Full chain verification failed (probably a bad "
@@ -1699,10 +1745,10 @@
* If the fingerprints don't match, we'll fall back to checking the
* signature.
*/
- last_fpr = purple_certificate_get_fingerprint_sha1(end_crt);
+ last_fpr = purple_certificate_get_fingerprint_sha256(end_crt, TRUE);
for (cur = ca_crts; cur; cur = cur->next) {
ca_crt = cur->data;
- ca_fpr = purple_certificate_get_fingerprint_sha1(ca_crt);
+ ca_fpr = purple_certificate_get_fingerprint_sha256(ca_crt, TRUE);
if ( byte_arrays_equal(last_fpr, ca_fpr) ||
purple_certificate_signed_by(end_crt, ca_crt) )
@@ -2152,19 +2198,14 @@
void
purple_certificate_display_x509(PurpleCertificate *crt)
{
- gchar *sha_asc;
- GByteArray *sha_bin;
+ gchar *sha1_asc, *sha256_asc;
gchar *cn, *issuer_id;
time_t activation, expiration;
gchar *activ_str, *expir_str;
- gchar *secondary;
+ gchar *secondary, *secondary_extra;
gboolean self_signed;
- /* Pull out the SHA1 checksum */
- sha_bin = purple_certificate_get_fingerprint_sha1(crt);
- /* Now decode it for display */
- sha_asc = purple_base16_encode_chunked(sha_bin->data,
- sha_bin->len);
+ get_ascii_fingerprints(crt, &sha1_asc, &sha256_asc);
/* Get the cert Common Name */
/* TODO: Will break on CA certs */
@@ -2193,20 +2234,23 @@
"Expiration date: %s\n"),
cn ? cn : "(null)",
self_signed ? _("(self-signed)") : (issuer_id ? issuer_id : "(null)"),
- sha_asc ? sha_asc : "(null)",
+ sha1_asc ? sha1_asc : "(null)",
activ_str ? activ_str : "(null)",
expir_str ? expir_str : "(null)");
+ /* TODO: make this part of the translatable string above */
+ secondary_extra = g_strdup_printf("%sSHA256: %s", secondary, sha256_asc);
+
/* Make a semi-pretty display */
if (self_signed) {
purple_notify_info(NULL, /* TODO: Find what the handle ought to be */
_("Certificate Information"),
"",
- secondary);
+ secondary_extra);
} else {
purple_request_action(NULL, /* TODO: Find what the handle ought to be */
_("Certificate Information"), _("Certificate Information"),
- secondary, 2, NULL, NULL, NULL,
+ secondary_extra, 2, NULL, NULL, NULL,
issuer_id, 2,
_("View Issuer Certificate"), PURPLE_CALLBACK(display_x509_issuer),
_("Close"), PURPLE_CALLBACK(g_free));
@@ -2219,10 +2263,11 @@
g_free(cn);
g_free(issuer_id);
g_free(secondary);
- g_free(sha_asc);
+ g_free(secondary_extra);
+ g_free(sha1_asc);
+ g_free(sha256_asc);
g_free(activ_str);
g_free(expir_str);
- g_byte_array_free(sha_bin, TRUE);
}
void purple_certificate_add_ca_search_path(const char *path)
--- a/libpurple/certificate.h Thu Feb 23 06:35:24 2017 +0000
+++ b/libpurple/certificate.h Mon Mar 06 03:32:06 2017 -0300
@@ -315,9 +315,31 @@
*/
void (* verify_cert)(PurpleCertificateVerificationRequest *vrq, PurpleCertificateInvalidityFlags *flags);
- void (*_purple_reserved3)(void);
+ /**
+ * The size of the PurpleCertificateScheme. This should always be sizeof(PurpleCertificateScheme).
+ * This allows adding more functions to this struct without requiring a major version bump.
+ *
+ * PURPLE_CERTIFICATE_SCHEME_HAS_FUNC() should be used for functions after this point.
+ */
+ unsigned long struct_size;
+
+ /**
+ * Retrieves the certificate public key fingerprint using SHA256
+ *
+ * @param crt Certificate instance
+ * @return Binary representation of SHA256 hash - must be freed using
+ * g_byte_array_free()
+ * @since 2.12.0
+ */
+ GByteArray * (* get_fingerprint_sha256)(PurpleCertificate *crt);
};
+#define PURPLE_CERTIFICATE_SCHEME_HAS_FUNC(obj, member) \
+ (((G_STRUCT_OFFSET(PurpleCertificateScheme, member) < G_STRUCT_OFFSET(PurpleCertificateScheme, struct_size)) \
+ || (G_STRUCT_OFFSET(PurpleCertificateScheme, member) < obj->struct_size)) && \
+ obj->member != NULL)
+
+
/** A set of operations used to provide logic for verifying a Certificate's
* authenticity.
*
@@ -578,12 +600,26 @@
* Retrieves the certificate public key fingerprint using SHA1.
*
* @param crt Certificate instance
- * @return Binary representation of the hash. You are responsible for free()ing
- * this.
+ * @return Binary representation of the hash. You are responsible for freeing
+ * this with g_byte_array_free().
+ * @see purple_base16_encode_chunked()
+ * @see purple_certificate_get_fingerprint_sha256()
+ */
+GByteArray *
+purple_certificate_get_fingerprint_sha1(PurpleCertificate *crt);
+
+/**
+ * Retrieves the certificate public key fingerprint using SHA256.
+ *
+ * @param crt Certificate instance
+ * @param sha1_fallback If true, return SHA1 if the SSL module doesn't
+ * implement SHA256. Otherwise, return NULL.
+ * @return Binary representation of the hash. You are responsible for freeing
+ * this with g_byte_array_free().
* @see purple_base16_encode_chunked()
*/
GByteArray *
-purple_certificate_get_fingerprint_sha1(PurpleCertificate *crt);
+purple_certificate_get_fingerprint_sha256(PurpleCertificate *crt, gboolean sha1_fallback);
/**
* Get a unique identifier for the certificate
--- a/libpurple/plugins/perl/common/Certificate.xs Thu Feb 23 06:35:24 2017 +0000
+++ b/libpurple/plugins/perl/common/Certificate.xs Mon Mar 06 03:32:06 2017 -0300
@@ -219,6 +219,19 @@
OUTPUT:
RETVAL
+SV*
+purple_certificate_get_fingerprint_sha256(crt, sha1_fallback)
+ Purple::Certificate crt
+ gboolean sha1_fallback
+ PREINIT:
+ GByteArray *gba = NULL;
+ CODE:
+ gba = purple_certificate_get_fingerprint_sha256(crt, sha1_fallback);
+ RETVAL = newSVpv((gchar *)gba->data, gba->len);
+ g_byte_array_free(gba, TRUE);
+ OUTPUT:
+ RETVAL
+
void
purple_certificate_verify(verifier, subject_name, cert_chain, cb, cb_data)
Purple::Certificate::Verifier verifier
--- a/libpurple/plugins/ssl/ssl-gnutls.c Thu Feb 23 06:35:24 2017 +0000
+++ b/libpurple/plugins/ssl/ssl-gnutls.c Mon Mar 06 03:32:06 2017 -0300
@@ -1037,9 +1037,9 @@
}
static GByteArray *
-x509_sha1sum(PurpleCertificate *crt)
+x509_shasum(PurpleCertificate *crt, gnutls_digest_algorithm_t algo)
{
- size_t hashlen = 20; /* SHA1 hashes are 20 bytes */
+ size_t hashlen = (algo == GNUTLS_DIG_SHA1) ? 20 : 32;
size_t tmpsz = hashlen; /* Throw-away variable for GnuTLS to stomp on*/
gnutls_x509_crt_t crt_dat;
GByteArray *hash; /**< Final hash container */
@@ -1051,7 +1051,7 @@
/* Extract the fingerprint */
g_return_val_if_fail(
- 0 == gnutls_x509_crt_get_fingerprint(crt_dat, GNUTLS_DIG_SHA,
+ 0 == gnutls_x509_crt_get_fingerprint(crt_dat, algo,
hashbuf, &tmpsz),
NULL);
@@ -1065,6 +1065,18 @@
return hash;
}
+static GByteArray *
+x509_sha1sum(PurpleCertificate *crt)
+{
+ return x509_shasum(crt, GNUTLS_DIG_SHA1);
+}
+
+static GByteArray *
+x509_sha256sum(PurpleCertificate *crt)
+{
+ return x509_shasum(crt, GNUTLS_DIG_SHA256);
+}
+
static gchar *
x509_cert_dn (PurpleCertificate *crt)
{
@@ -1239,8 +1251,8 @@
NULL,
NULL,
- NULL
-
+ sizeof(PurpleCertificateScheme), /* struct_size */
+ x509_sha256sum, /* SHA256 fingerprint */
};
static PurpleSslOps ssl_ops =
--- a/libpurple/plugins/ssl/ssl-nss.c Thu Feb 23 06:35:24 2017 +0000
+++ b/libpurple/plugins/ssl/ssl-nss.c Mon Mar 06 03:32:06 2017 -0300
@@ -881,11 +881,11 @@
}
static GByteArray *
-x509_sha1sum(PurpleCertificate *crt)
+x509_shasum(PurpleCertificate *crt, SECOidTag algo)
{
CERTCertificate *crt_dat;
- size_t hashlen = 20; /* Size of an sha1sum */
- GByteArray *sha1sum;
+ size_t hashlen = (algo == SEC_OID_SHA1) ? 20 : 32;
+ GByteArray *hash;
SECItem *derCert; /* DER representation of the cert */
SECStatus st;
@@ -899,22 +899,34 @@
derCert = &(crt_dat->derCert);
/* Make a hash! */
- sha1sum = g_byte_array_sized_new(hashlen);
+ hash = g_byte_array_sized_new(hashlen);
/* glib leaves the size as 0 by default */
- sha1sum->len = hashlen;
+ hash->len = hashlen;
- st = PK11_HashBuf(SEC_OID_SHA1, sha1sum->data,
+ st = PK11_HashBuf(algo, hash->data,
derCert->data, derCert->len);
/* Check for errors */
if (st != SECSuccess) {
- g_byte_array_free(sha1sum, TRUE);
+ g_byte_array_free(hash, TRUE);
purple_debug_error("nss/x509",
"Error: hashing failed!\n");
return NULL;
}
- return sha1sum;
+ return hash;
+}
+
+static GByteArray *
+x509_sha1sum(PurpleCertificate *crt)
+{
+ return x509_shasum(crt, SEC_OID_SHA1);
+}
+
+static GByteArray *
+x509_sha256sum(PurpleCertificate *crt)
+{
+ return x509_shasum(crt, SEC_OID_SHA256);
}
static gchar *
@@ -1211,7 +1223,8 @@
x509_importcerts_from_file, /* Multiple certificate import function */
x509_register_trusted_tls_cert, /* Register a certificate as trusted for TLS */
x509_verify_cert, /* Verify that the specified cert chain is trusted */
- NULL
+ sizeof(PurpleCertificateScheme), /* struct_size */
+ x509_sha256sum, /* SHA256 fingerprint */
};
static PurpleSslOps ssl_ops =