From: Colin Leroy Date: Wed, 30 Apr 2014 11:06:58 +0000 (+0200) Subject: Implement saving of the certificate chain, making the offline status X-Git-Tag: 3.10.0~96 X-Git-Url: http://git.claws-mail.org/?p=claws.git;a=commitdiff_plain;h=3dc522b3ecbe48cf25f9d315b9ef123b2ebc226b Implement saving of the certificate chain, making the offline status check correct. --- diff --git a/src/common/ssl.c b/src/common/ssl.c index b64ab5efc..28ba8967d 100644 --- a/src/common/ssl.c +++ b/src/common/ssl.c @@ -260,14 +260,56 @@ gboolean ssl_init_socket(SockInfo *sockinfo) return ssl_init_socket_with_method(sockinfo, SSL_METHOD_SSLv23); } +gnutls_x509_crt_t *ssl_get_certificate_chain(gnutls_session_t session, gint *list_len) +{ + const gnutls_datum *raw_cert_list; + gnutls_x509_crt_t *certs = NULL; + gboolean result = TRUE; + + *list_len = -1; + if (!session) + return NULL; + + raw_cert_list = gnutls_certificate_get_peers(session, list_len); + + if (raw_cert_list && gnutls_certificate_type_get(session) == GNUTLS_CRT_X509) { + int i = 0; + + certs = g_malloc(sizeof(gnutls_x509_crt_t) * (*list_len)); + + for(i = 0 ; i < (*list_len) ; i++) { + int r; + + gnutls_x509_crt_init(&certs[i]); + r = gnutls_x509_crt_import(certs[i], &raw_cert_list[i], GNUTLS_X509_FMT_DER); + if (r < 0) { + g_warning("cert get failure: %d %s\n", r, gnutls_strerror(r)); + + result = FALSE; + i--; + break; + } + } + if (!result) { + for (; i >= 0; i--) + gnutls_x509_crt_deinit(certs[i]); + + g_free(certs); + *list_len = -1; + + return NULL; + } + } + + return certs; +} + gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method) { gnutls_session_t session; - int r; - const gnutls_datum_t *raw_cert_list; - unsigned int raw_cert_list_length; - gnutls_x509_crt_t cert = NULL; - guint status; + int r, i; + unsigned int cert_list_length; + gnutls_x509_crt_t *certs = NULL; gnutls_certificate_credentials_t xcred; if (gnutls_certificate_allocate_credentials (&xcred) != 0) @@ -321,28 +363,26 @@ gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method) } /* Get server's certificate (note: beware of dynamic allocation) */ - raw_cert_list = gnutls_certificate_get_peers(session, &raw_cert_list_length); + certs = ssl_get_certificate_chain(session, &cert_list_length); - if (!raw_cert_list - || gnutls_certificate_type_get(session) != GNUTLS_CRT_X509 - || (r = gnutls_x509_crt_init(&cert)) < 0 - || (r = gnutls_x509_crt_import(cert, &raw_cert_list[0], GNUTLS_X509_FMT_DER)) < 0) { - g_warning("cert get failure: %d %s\n", r, gnutls_strerror(r)); + if (!certs) { gnutls_certificate_free_credentials(xcred); gnutls_deinit(session); return FALSE; } - r = gnutls_certificate_verify_peers2(session, &status); - - if (r < 0 || !ssl_certificate_check(cert, status, sockinfo->hostname, sockinfo->port)) { - gnutls_x509_crt_deinit(cert); + if (!ssl_certificate_check_chain(certs, cert_list_length, sockinfo->hostname, sockinfo->port)) { + for (i = 0; i < cert_list_length; i++) + gnutls_x509_crt_deinit(certs[i]); + g_free(certs); gnutls_certificate_free_credentials(xcred); gnutls_deinit(session); return FALSE; } - gnutls_x509_crt_deinit(cert); + for (i = 0; i < cert_list_length; i++) + gnutls_x509_crt_deinit(certs[i]); + g_free(certs); sockinfo->ssl = session; sockinfo->xcred = xcred; diff --git a/src/common/ssl_certificate.c b/src/common/ssl_certificate.c index 1381aa185..b48d4d4b9 100644 --- a/src/common/ssl_certificate.c +++ b/src/common/ssl_certificate.c @@ -65,6 +65,16 @@ static gchar *get_certificate_path(const gchar *host, const gchar *port, const g host, ".", port, ".cert", NULL); } +static gchar *get_certificate_chain_path(const gchar *host, const gchar *port, const gchar *fp) +{ + gchar *tmp = get_certificate_path(host, port, fp); + gchar *result = g_strconcat(tmp, ".chain", NULL); + + g_free(tmp); + + return result; +} + char * readable_fingerprint(unsigned char *src, int len) { int i=0; @@ -393,6 +403,9 @@ void ssl_certificate_delete_from_disk(SSLCertificate *cert) file = get_certificate_path(cert->host, buf, cert->fingerprint); claws_unlink (file); g_free(file); + file = get_certificate_chain_path(cert->host, buf, cert->fingerprint); + claws_unlink (file); + g_free(file); g_free(buf); } @@ -507,13 +520,15 @@ static gboolean ssl_certificate_compare (SSLCertificate *cert_a, SSLCertificate return TRUE; } -static guint check_cert(gnutls_x509_crt_t cert) +static guint check_cert(SSLCertificate *cert) { - gnutls_x509_crt_t *ca_list; - unsigned int max = 512; + gnutls_x509_crt_t *ca_list = NULL; + gnutls_x509_crt_t *chain = NULL; + unsigned int max_ca = 512, max_certs; unsigned int flags = 0; int r, i; unsigned int status; + gchar *chain_file = NULL, *buf = NULL; FILE *fp; if (claws_ssl_get_cert_file()) @@ -521,16 +536,63 @@ static guint check_cert(gnutls_x509_crt_t cert) else return (guint)-1; - if ((r = gnutls_import_X509_list_fp(fp, GNUTLS_X509_FMT_PEM, &ca_list, &max)) < 0) { - debug_print("cert import failed: %s\n", gnutls_strerror(r)); + if ((r = gnutls_import_X509_list_fp(fp, GNUTLS_X509_FMT_PEM, &ca_list, &max_ca)) < 0) { + debug_print("CA import failed: %s\n", gnutls_strerror(r)); fclose(fp); return (guint)-1; } - - r = gnutls_x509_crt_verify(cert, ca_list, max, flags, &status); fclose(fp); + fp = NULL; + + buf = g_strdup_printf("%d", cert->port); + chain_file = get_certificate_chain_path(cert->host, buf, cert->fingerprint); + g_free(buf); + if (is_file_exist(chain_file)) { + unsigned char md[128]; + size_t n; + char *fingerprint; + + fp = g_fopen(chain_file, "r"); + if ((r = gnutls_import_X509_list_fp(fp, GNUTLS_X509_FMT_PEM, &chain, &max_certs)) < 0) { + debug_print("chain import failed: %s\n", gnutls_strerror(r)); + fclose(fp); + g_free(chain_file); + return (guint)-1; + } + g_free(chain_file); + fclose(fp); + fp = NULL; + + gnutls_x509_crt_get_fingerprint(chain[0], GNUTLS_DIG_MD5, md, &n); + fingerprint = readable_fingerprint(md, n); + if (!fingerprint || strcmp(fingerprint, cert->fingerprint)) { + debug_print("Saved chain fingerprint does not match current : %s / %s", + cert->fingerprint, fingerprint); + + return (guint)-1; + } + g_free(fingerprint); + + r = gnutls_x509_crt_list_verify (chain, + max_certs, + ca_list, max_ca, + NULL, 0, + GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT, + &status); + if (r < 0) + debug_print("chain check failed: %s\n", gnutls_strerror(r)); + + for (i = 0; i < max_certs; i++) + gnutls_x509_crt_deinit(chain[i]); + free(chain); - for (i = 0; i < max; i++) + } else { + r = gnutls_x509_crt_verify(cert->x509_cert, ca_list, max_ca, flags, &status); + if (r < 0) + debug_print("cert check failed: %s\n", gnutls_strerror(r)); + } + + for (i = 0; i < max_ca; i++) gnutls_x509_crt_deinit(ca_list[i]); free(ca_list); @@ -541,15 +603,20 @@ static guint check_cert(gnutls_x509_crt_t cert) } -char *ssl_certificate_check_signer (gnutls_x509_crt_t cert, guint status) +char *ssl_certificate_check_signer (SSLCertificate *cert, guint status) { + gnutls_x509_crt_t x509_cert = cert ? cert->x509_cert : NULL; + + if (!cert) + return g_strdup(_("Internal error")); + if (status == (guint)-1) { status = check_cert(cert); if (status == -1) return g_strdup(_("Uncheckable")); } if (status & GNUTLS_CERT_INVALID) { - if (gnutls_x509_crt_check_issuer(cert, cert)) + if (gnutls_x509_crt_check_issuer(x509_cert, x509_cert)) return g_strdup(_("Self-signed certificate")); } if (status & GNUTLS_CERT_REVOKED) @@ -563,6 +630,43 @@ char *ssl_certificate_check_signer (gnutls_x509_crt_t cert, guint status) return NULL; } +static void ssl_certificate_save_chain(gnutls_x509_crt_t *certs, gint len, const gchar *host, gushort port) +{ + gint i; + gchar *file = NULL; + FILE *fp = NULL; + + for (i = 0; i < len; i++) { + size_t n; + unsigned char md[128]; + gnutls_x509_crt_t cert = certs[i]; + gchar *fingerprint; + + if (i == 0) { + gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_MD5, md, &n); + fingerprint = readable_fingerprint(md, n); + gchar *buf = g_strdup_printf("%d", port); + + file = get_certificate_chain_path(host, buf, fingerprint); + + g_free(buf); + + fp = g_fopen(file, "wb"); + if (fp == NULL) { + g_free(file); + debug_print("Can't save certificate !\n"); + return; + } + g_free(file); + } + + gnutls_export_X509_fp(fp, cert, GNUTLS_X509_FMT_PEM); + + } + if (fp) + fclose(fp); +} + gboolean ssl_certificate_check (gnutls_x509_crt_t x509_cert, guint status, const gchar *host, gushort port) { SSLCertificate *current_cert = NULL; @@ -663,9 +767,8 @@ gboolean ssl_certificate_check (gnutls_x509_crt_t x509_cert, guint status, const gboolean ssl_certificate_check_chain(gnutls_x509_crt_t *certs, gint chain_len, const gchar *host, gushort port) { - int ncas = 0, ncrls = 0; + int ncas = 0; gnutls_x509_crt_t *cas = NULL; - gnutls_x509_crl_t *crls = NULL; gboolean result = FALSE; int i; gint status; @@ -697,6 +800,10 @@ gboolean ssl_certificate_check_chain(gnutls_x509_crt_t *certs, gint chain_len, c result = ssl_certificate_check(certs[0], status, host, port); + if (result == TRUE) { + ssl_certificate_save_chain(certs, chain_len, host, port); + } + for (i = 0; i < ncas; i++) gnutls_x509_crt_deinit(cas[i]); free(cas); diff --git a/src/common/ssl_certificate.h b/src/common/ssl_certificate.h index fd8822ad7..9f4ecf0ba 100644 --- a/src/common/ssl_certificate.h +++ b/src/common/ssl_certificate.h @@ -62,7 +62,7 @@ gboolean ssl_certificate_check_chain(gnutls_x509_crt_t *certs, gint chain_len, c void ssl_certificate_destroy(SSLCertificate *cert); void ssl_certificate_delete_from_disk(SSLCertificate *cert); char * readable_fingerprint(unsigned char *src, int len); -char *ssl_certificate_check_signer (gnutls_x509_crt_t cert, guint status); +char *ssl_certificate_check_signer (SSLCertificate *cert, guint status); gnutls_x509_crt_t ssl_certificate_get_x509_from_pem_file(const gchar *file); gnutls_x509_privkey_t ssl_certificate_get_pkey_from_pem_file(const gchar *file); diff --git a/src/gtk/sslcertwindow.c b/src/gtk/sslcertwindow.c index b03463f8e..c6fe7e400 100644 --- a/src/gtk/sslcertwindow.c +++ b/src/gtk/sslcertwindow.c @@ -151,7 +151,7 @@ static GtkWidget *cert_presenter(SSLCertificate *cert) sha1_fingerprint = readable_fingerprint(md, (int)n); /* signature */ - sig_status = ssl_certificate_check_signer(cert->x509_cert, cert->status); + sig_status = ssl_certificate_check_signer(cert, cert->status); if (sig_status==NULL) sig_status = g_strdup(_("Correct")); @@ -343,7 +343,7 @@ static gboolean sslcertwindow_ask_new_cert(SSLCertificate *cert) gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 0); g_free(buf); - sig_status = ssl_certificate_check_signer(cert->x509_cert, cert->status); + sig_status = ssl_certificate_check_signer(cert, cert->status); if (sig_status==NULL) sig_status = g_strdup(_("Correct")); @@ -392,7 +392,7 @@ static gboolean sslcertwindow_ask_expired_cert(SSLCertificate *cert) gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 0); g_free(buf); - sig_status = ssl_certificate_check_signer(cert->x509_cert, cert->status); + sig_status = ssl_certificate_check_signer(cert, cert->status); if (sig_status==NULL) sig_status = g_strdup(_("Correct")); @@ -456,7 +456,7 @@ static gboolean sslcertwindow_ask_changed_cert(SSLCertificate *old_cert, SSLCert gtk_box_pack_start(GTK_BOX(vbox2), label, TRUE, TRUE, 0); g_free(buf); - sig_status = ssl_certificate_check_signer(new_cert->x509_cert, new_cert->status); + sig_status = ssl_certificate_check_signer(new_cert, new_cert->status); if (sig_status==NULL) sig_status = g_strdup(_("Correct")); diff --git a/src/ssl_manager.c b/src/ssl_manager.c index 6cc058c87..6b2f66500 100644 --- a/src/ssl_manager.c +++ b/src/ssl_manager.c @@ -341,7 +341,7 @@ static void ssl_manager_load_certs (void) gchar *server, *port, *fp; SSLCertificate *cert; - if(!strstr(d->d_name, ".cert")) + if(strstr(d->d_name, ".cert") != d->d_name + (strlen(d->d_name) - strlen(".cert"))) continue; server = get_server(d->d_name);