Implement saving of the certificate chain, making the offline status
authorColin Leroy <colin@colino.net>
Wed, 30 Apr 2014 11:06:58 +0000 (13:06 +0200)
committerColin Leroy <colin@colino.net>
Fri, 2 May 2014 07:20:39 +0000 (09:20 +0200)
check correct.

src/common/ssl.c
src/common/ssl_certificate.c
src/common/ssl_certificate.h
src/gtk/sslcertwindow.c
src/ssl_manager.c

index b64ab5efc1cb03a9edcd3654496b68c684fe1193..28ba8967d5a8db1dff32a07c6e583494db7be9ba 100644 (file)
@@ -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;
index 1381aa18585eca0e97f050fccd9457680a6d60a0..b48d4d4b9dad2170495ffbfc3a13423021ac0fe7 100644 (file)
@@ -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);
index fd8822ad77df402ac31424ff752439e3717f0be4..9f4ecf0bad6c91272671e5f892ce797603d04637 100644 (file)
@@ -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);
index b03463f8e9cfd2672ed553ad4e1bbf4cb7f88f98..c6fe7e4005ffedbd855b43f40432e06f74dbc1ac 100644 (file)
@@ -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"));
index 6cc058c87ae4e15dfcc8231d4feb4d3978abb507..6b2f6650078d9e92e3c0c64a727035a83e47092e 100644 (file)
@@ -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);