From 285c61cacb8ec0177a0b60224343dac2ec8af6b8 Mon Sep 17 00:00:00 2001
From: Miao Wang <shankerwangmiao@gmail.com>
Date: Sat, 9 Oct 2021 18:13:30 +0800
Subject: [PATCH] Use gnutls_certificate_verify_peers2 to verify server
 certificates

Fixes: #641

Signed-off-by: Miao Wang <shankerwangmiao@gmail.com>
---
 src/lftp_ssl.cc | 207 +++++++++++-------------------------------------
 src/lftp_ssl.h  |   2 -
 2 files changed, 48 insertions(+), 161 deletions(-)

diff --git a/src/lftp_ssl.cc b/src/lftp_ssl.cc
index 968d3fb26..26e91e4b9 100644
--- a/src/lftp_ssl.cc
+++ b/src/lftp_ssl.cc
@@ -338,6 +338,16 @@ void lftp_ssl_gnutls::load_keys()
       if(res<0)
 	 Log::global->Format(0,"gnutls_certificate_set_x509_key_file(%s,%s): %s\n",cert_file,key_file,gnutls_strerror(res));
    }
+   res = gnutls_certificate_set_x509_trust(cred, instance->ca_list, instance->ca_list_size);
+   if(res < 0)
+      Log::global->Format(0, "gnutls_certificate_set_x509_trust: %s\n", gnutls_strerror(res));
+   else
+      Log::global->Format(9, "Loaded %d CAs\n", res);
+   res = gnutls_certificate_set_x509_crl(cred, instance->crl_list, instance->crl_list_size);
+   if(res < 0)
+      Log::global->Format(0, "gnutls_certificate_set_x509_crl: %s\n", gnutls_strerror(res));
+   else
+      Log::global->Format(9, "Loaded %d CRLs\n", res);
    gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, cred);
 }
 void lftp_ssl_gnutls::shutdown()
@@ -358,174 +368,53 @@ lftp_ssl_gnutls::~lftp_ssl_gnutls()
  */
 void lftp_ssl_gnutls::verify_certificate_chain(const gnutls_datum_t *cert_chain,int cert_chain_length)
 {
-   int i;
-   gnutls_x509_crt_t *cert=(gnutls_x509_crt_t*)alloca(cert_chain_length*sizeof(gnutls_x509_crt_t));
-
-   /* Import all the certificates in the chain to
-    * native certificate format.
-    */
-   for (i = 0; i < cert_chain_length; i++)
-   {
-      gnutls_x509_crt_init(&cert[i]);
-      gnutls_x509_crt_import(cert[i],&cert_chain[i],GNUTLS_X509_FMT_DER);
+   int err;
+   unsigned int status;
+
+   gnutls_x509_crt_t leaf_cert;
+   err = gnutls_x509_crt_init(&leaf_cert);
+   if(err < 0){
+     set_cert_error(xstring::format("GnuTLS Error: %s", gnutls_strerror(err)), NULL);
+     goto err_out;
    }
-
-   /* Now verify the certificates against their issuers
-    * in the chain.
-    */
-   for (i = 1; i < cert_chain_length; i++)
-      verify_cert2(cert[i - 1], cert[i]);
-
-    /* Here we must verify the last certificate in the chain against
-     * our trusted CA list.
-     */
-   verify_last_cert(cert[cert_chain_length - 1]);
-
-   /* Check if the name in the first certificate matches our destination!
-    */
-   bool check_hostname = ResMgr::QueryBool("ssl:check-hostname", hostname);
-   if(check_hostname) {
-      if(!gnutls_x509_crt_check_hostname(cert[0], hostname))
-	 set_cert_error(xstring::format("certificate common name doesn't match requested host name %s",quote(hostname)),get_fp(cert[0]));
-   } else {
-      Log::global->Format(0, "WARNING: Certificate verification: hostname checking disabled\n");
+   gnutls_x509_crt_import(leaf_cert, &cert_chain[0], GNUTLS_X509_FMT_DER);
+   if(err < 0){
+     set_cert_error(xstring::format("GnuTLS Error: %s", gnutls_strerror(err)), NULL);
+     goto deinit_cert;
    }
 
-   for (i = 0; i < cert_chain_length; i++)
-      gnutls_x509_crt_deinit(cert[i]);
-}
-
-
-/* Verifies a certificate against an other certificate
- * which is supposed to be it's issuer. Also checks the
- * crl_list if the certificate is revoked.
- */
-void lftp_ssl_gnutls::verify_cert2(gnutls_x509_crt_t crt,gnutls_x509_crt_t issuer)
-{
-   int ret;
-   time_t now = SMTask::now;
-   size_t name_size;
-   char name[256];
-
-   /* Print information about the certificates to
-    * be checked.
-    */
-   name_size = sizeof(name);
-   gnutls_x509_crt_get_dn(crt, name, &name_size);
-
-   Log::global->Format(9, "Certificate: %s\n", name);
-
-   name_size = sizeof(name);
-   gnutls_x509_crt_get_issuer_dn(crt, name, &name_size);
-
-   Log::global->Format(9, " Issued by:        %s\n", name);
-
-   /* Get the DN of the issuer cert.
-    */
-   name_size = sizeof(name);
-   gnutls_x509_crt_get_dn(issuer, name, &name_size);
-
-   Log::global->Format(9, " Checking against: %s\n", name);
-
-   /* Do the actual verification.
-    */
-   unsigned crt_status=0;
-   unsigned issuer_status=0;
-   gnutls_x509_crt_verify(crt, &issuer, 1, 0, &crt_status);
-   if(crt_status&GNUTLS_CERT_SIGNER_NOT_CA)
-   {
-      // recheck the issuer certificate against CA
-      gnutls_x509_crt_verify(issuer, instance->ca_list, instance->ca_list_size, 0, &issuer_status);
-      if(issuer_status==0)
-	 crt_status&=~GNUTLS_CERT_SIGNER_NOT_CA;
-      if(crt_status==GNUTLS_CERT_INVALID)
-	 crt_status=0;
+   err = gnutls_certificate_verify_peers2 (session, &status);
+   if(err < 0){
+     set_cert_error(xstring::format("Cerificate Verification Error: %s", gnutls_strerror(err)), get_fp(leaf_cert));
+     goto deinit_cert;
    }
-   if (crt_status & GNUTLS_CERT_INVALID)
-   {
-      char msg[256];
-      strcpy(msg,"Not trusted");
-      if(crt_status & GNUTLS_CERT_SIGNER_NOT_FOUND)
-	 strcat(msg,": no issuer was found");
-      if(crt_status & GNUTLS_CERT_SIGNER_NOT_CA)
-	 strcat(msg,": issuer is not a CA");
-      set_cert_error(msg,get_fp(crt));
-   }
-   else
-      Log::global->Format(9, "  Trusted\n");
 
-
-    /* Now check the expiration dates.
-     */
-    if (gnutls_x509_crt_get_activation_time(crt) > now)
-	set_cert_error("Not yet activated",get_fp(crt));
-
-    if (gnutls_x509_crt_get_expiration_time(crt) < now)
-	set_cert_error("Expired",get_fp(crt));
-
-    /* Check if the certificate is revoked.
-     */
-    ret = gnutls_x509_crt_check_revocation(crt, instance->crl_list, instance->crl_list_size);
-    if (ret == 1) {		/* revoked */
-	set_cert_error("Revoked",get_fp(crt));
-    }
-}
-
-
-/* Verifies a certificate against the trusted CA list.
- * Also checks the crl_list if the certificate is revoked.
- */
-void lftp_ssl_gnutls::verify_last_cert(gnutls_x509_crt_t crt)
-{
-   unsigned int crt_status;
-   int ret;
-   time_t now = SMTask::now;
-   size_t name_size;
-   char name[256];
-
-   /* Print information about the certificates to
-    * be checked.
-    */
-   name_size = sizeof(name);
-   gnutls_x509_crt_get_dn(crt, name, &name_size);
-
-   Log::global->Format(9, "Certificate: %s\n", name);
-
-   name_size = sizeof(name);
-   gnutls_x509_crt_get_issuer_dn(crt, name, &name_size);
-
-   Log::global->Format(9, " Issued by: %s\n", name);
-
-   /* Do the actual verification.
-    */
-   gnutls_x509_crt_verify(crt, instance->ca_list, instance->ca_list_size, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT, &crt_status);
-
-   if (crt_status & GNUTLS_CERT_INVALID)
-   {
-      char msg[256];
-      strcpy(msg,"Not trusted");
-      if (crt_status & GNUTLS_CERT_SIGNER_NOT_CA)
-	 strcat(msg,": Issuer is not a CA");
-      set_cert_error(msg,get_fp(crt));
+   if(status != 0){
+     gnutls_datum_t reason;
+     err = gnutls_certificate_verification_status_print(status, gnutls_certificate_type_get(session), &reason, 0);
+     if(err < 0){
+       set_cert_error(xstring::format("Cerificate Verification Error: %s", gnutls_strerror(err)), get_fp(leaf_cert));
+       goto deinit_cert;
+     }
+     set_cert_error((const char*)reason.data, get_fp(leaf_cert));
+     gnutls_free(reason.data);
+     goto deinit_cert;
    }
-   else
-      Log::global->Format(9, "  Trusted\n");
 
+   if(ResMgr::QueryBool("ssl:check-hostname", hostname)) {
+      if(!gnutls_x509_crt_check_hostname(leaf_cert, hostname)){
+         set_cert_error(xstring::format("certificate common name doesn't match requested host name %s",quote(hostname)),get_fp(leaf_cert));
+         goto deinit_cert;
+      }
+   } else {
+      Log::global->Format(0, "WARNING: Certificate verification: hostname checking disabled\n");
+   }
 
-   /* Now check the expiration dates.
-    */
-   if(gnutls_x509_crt_get_activation_time(crt) > now)
-      set_cert_error("Not yet activated",get_fp(crt));
-
-   if(gnutls_x509_crt_get_expiration_time(crt) < now)
-      set_cert_error("Expired",get_fp(crt));
+   deinit_cert:
+   gnutls_x509_crt_deinit(leaf_cert);
 
-   /* Check if the certificate is revoked.
-    */
-   ret = gnutls_x509_crt_check_revocation(crt, instance->crl_list, instance->crl_list_size);
-   if (ret == 1) {		/* revoked */
-      set_cert_error("Revoked",get_fp(crt));
-   }
+   err_out:
+   return;
 }
 
 bool lftp_ssl_gnutls::check_fatal(int res)
diff --git a/src/lftp_ssl.h b/src/lftp_ssl.h
index c37b047b4..87b92d4fa 100644
--- a/src/lftp_ssl.h
+++ b/src/lftp_ssl.h
@@ -92,8 +92,6 @@ class lftp_ssl_gnutls : public lftp_ssl_base
    gnutls_session_t session;
    gnutls_certificate_credentials_t cred;
    void verify_certificate_chain(const gnutls_datum_t *cert_chain,int cert_chain_length);
-   void verify_cert2(gnutls_x509_crt_t crt,gnutls_x509_crt_t issuer);
-   void verify_last_cert(gnutls_x509_crt_t crt);
    int do_handshake();
    bool check_fatal(int res);
    static const xstring& get_fp(gnutls_x509_crt_t crt);
