[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

[PATCH] APT https method improvements



Hi,

Attached is a set of simple patches for apt https method developed by
Axel and I. They add client authentication, CRL handling, and ability to
check issuer, and also some other things that are described in detail
below.

Note that this post is more a request for comments and preliminary code
review (for instance w.r.t documentation patch) for the follwing reason:
Because support for CRL handling and issuer check were missing 
in the underlying library (libcurl-gnutls), we submitted [1] a set of
patches to curl developers providing those functionalitie. They were
accepted but came during a freeze window and will only be in 7.18.3 (see
[2]). For that reason, we thought we would request your comments in the
meantime for apt patches.

The patches have been tested (different combinations for options,
revoked CRL, missing CA certificates, bad server name, ...). 

The 3 patches are commented below. They apply in that order on top of
0.7.14 (0.7.13 too).

client_auth_and_additions.patch:

  The patch modifies methods/https.cc and adds support for client
  authentication. It allows user to configure a client certificate and
  associated key (previously, a single knob was available that used an
  undocumented behavior of libcurl-gnutls) to authenticate to a
  mirror. This can be done for all https mirrors or on a per-mirror
  basis. 
  
  The patch also *changes* how apt handles verify-host by setting it to
  true by default and make verify-peer a boolean. The changes to those
  two knobs' default values make the use of SSL/TLS meaningful: 
  
   - server certificate is verified and connection fails if it is
     invalid. This is the expected behavior. User has to explicitly set
     it to false (on a global or per mirror basis) to deactivate the
     check. 
   - identity provided in certificate is checked against server name. If
     they do not match, connection fails by default. Again, this is the
     expected behavior and user has to explicitly set it to false to
     deactivate the check (on a global or per mirror basis).
  
  I decided not to open a ticket against current behavior (not
  documented anyway) and discuss it here before.
  
  The last addition provided by this patch is the ability to force the
  version advertised by the client (SSLv3 or TLSv1). Again, on a global
  or per mirror basis. 
  
  Note that this patch is already usable without the improvements we
  pushed to curl developers (i.e. against current libcurl-gnutls).
  
crl_and_issuer_check_support.patch:
  
  This patch adds support for:
  
   - CRL check: user can pass a list of CRL that will be used to verify
     server certificate. This can be done on a global or per mirror
     basis. If none is provided, no CRL check is done.
   - issuer check: in multi-level PKI, this can be useful to limit the
     servers to a specific branch of the tree. The option allows to do
     that by specifying the issuer certificate. Even if the common
     certificate validation is done (anchors, CRL, ...), the additional
     check is done.
  
  Both can be set on a global or per-mirror basis.
  
apt-transport-https-doc.patch
  
  This provides a sample configuration file that details previous
  functionalities discussed before. We decided to use this format
  because it is simple, and we did not find an existing man page that we
  could improve. Tell us if this should be done differently.
  

Regards,

a+

[1]: http://permalink.gmane.org/gmane.comp.web.curl.library/19221
[2]: http://permalink.gmane.org/gmane.comp.web.curl.library/19244

Index: apt-0.7.13/methods/https.cc
===================================================================
--- apt-0.7.13.orig/methods/https.cc	2008-05-30 12:37:20.866185027 +0200
+++ apt-0.7.13/methods/https.cc	2008-06-02 16:32:03.270155899 +0200
@@ -108,6 +108,8 @@
    struct curl_slist *headers=NULL;  
    char curl_errorstr[CURL_ERROR_SIZE];
    long curl_responsecode;
+   URI Uri = Itm->Uri;
+   string remotehost = Uri.Host;
 
    // TODO:
    //       - http::Pipeline-Depth
@@ -127,23 +129,56 @@
    curl_easy_setopt(curl, CURLOPT_FAILONERROR, true);
    curl_easy_setopt(curl, CURLOPT_FILETIME, true);
 
-   // FIXME: https: offer various options of verification
-   bool peer_verify = _config->FindB("Acquire::https::Verify-Peer", false);
+   // SSL parameters are set by default to the common (non mirror-specific) value
+   // if available (or a default one) and gets overload by mirror-specific ones.
+
+   // File containing the list of trusted CA.
+   string cainfo = _config->Find("Acquire::https::CaInfo","");
+   string knob = "Acquire::https::"+remotehost+"::CaInfo";
+   cainfo = _config->Find(knob.c_str(),cainfo.c_str());
+   if(cainfo != "")
+      curl_easy_setopt(curl, CURLOPT_CAINFO,cainfo.c_str());
+
+   // Check server certificate against previous CA list ...
+   bool peer_verify = _config->FindB("Acquire::https::Verify-Peer",true);
+   knob = "Acquire::https::" + remotehost + "::Verify-Peer";
+   peer_verify = _config->FindB(knob.c_str(), peer_verify);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, peer_verify);
 
-   // sslcert file
+   // ... and hostname against cert CN or subjectAltName
+   int default_verify = 2;
+   bool verify = _config->FindB("Acquire::https::Verify-Host",true);
+   knob = "Acquire::https::"+remotehost+"::Verify-Host";
+   verify = _config->FindB(knob.c_str(),verify);
+   if (!verify)
+      default_verify = 0;
+   curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, verify);
+
+   // For client authentication, certificate file ...
    string pem = _config->Find("Acquire::https::SslCert","");
+   knob = "Acquire::https::"+remotehost+"::SslCert";
+   pem = _config->Find(knob.c_str(),pem.c_str());
    if(pem != "")
       curl_easy_setopt(curl, CURLOPT_SSLCERT, pem.c_str());
-   
-   // CA-Dir
-   string certdir = _config->Find("Acquire::https::CaPath","");
-   if(certdir != "")
-      curl_easy_setopt(curl, CURLOPT_CAPATH, certdir.c_str());
-   
-   // Server-verify 
-   int verify = _config->FindI("Acquire::https::Verify-Host",2);
-   curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, verify);
+
+   // ... and associated key.
+   string key = _config->Find("Acquire::https::SslKey","");
+   knob = "Acquire::https::"+remotehost+"::SslKey";
+   key = _config->Find(knob.c_str(),key.c_str());
+   if(key != "")
+      curl_easy_setopt(curl, CURLOPT_SSLKEY, key.c_str());
+
+   // Allow forcing SSL version to SSLv3 or TLSv1 (SSLv2 is not
+   // supported by GnuTLS).
+   long final_version = CURL_SSLVERSION_DEFAULT;
+   string sslversion = _config->Find("Acquire::https::SslForceVersion","");
+   knob = "Acquire::https::"+remotehost+"::SslForceVersion";
+   sslversion = _config->Find(knob.c_str(),sslversion.c_str());
+   if(sslversion == "TLSv1")
+     final_version = CURL_SSLVERSION_TLSv1;
+   else if(sslversion == "SSLv3")
+     final_version = CURL_SSLVERSION_SSLv3;
+   curl_easy_setopt(curl, CURLOPT_SSLVERSION, final_version);
 
    // cache-control
    if(_config->FindB("Acquire::http::No-Cache",false) == false)
Index: apt-0.7.13/methods/https.cc
===================================================================
--- apt-0.7.13.orig/methods/https.cc	2008-06-02 16:32:03.270155899 +0200
+++ apt-0.7.13/methods/https.cc	2008-06-02 16:32:23.362168477 +0200
@@ -154,6 +154,13 @@
       default_verify = 0;
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, verify);
 
+   // Also enforce issuer of server certificate using its cert
+   string issuercert = _config->Find("Acquire::https::IssuerCert","");
+   knob = "Acquire::https::"+remotehost+"::IssuerCert";
+   issuercert = _config->Find(knob.c_str(),issuercert.c_str());
+   if(issuercert != "")
+      curl_easy_setopt(curl, CURLOPT_ISSUERCERT,issuercert.c_str());
+
    // For client authentication, certificate file ...
    string pem = _config->Find("Acquire::https::SslCert","");
    knob = "Acquire::https::"+remotehost+"::SslCert";
@@ -180,6 +187,13 @@
      final_version = CURL_SSLVERSION_SSLv3;
    curl_easy_setopt(curl, CURLOPT_SSLVERSION, final_version);
 
+   // CRL file
+   string crlfile = _config->Find("Acquire::https::CrlFile","");
+   knob = "Acquire::https::"+remotehost+"::CrlFile";
+   crlfile = _config->Find(knob.c_str(),crlfile.c_str());
+   if(crlfile != "")
+      curl_easy_setopt(curl, CURLOPT_CRLFILE, crlfile.c_str());
+
    // cache-control
    if(_config->FindB("Acquire::http::No-Cache",false) == false)
    {
Index: apt-0.7.13/doc/examples/apt-https-method-example.conf
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ apt-0.7.13/doc/examples/apt-https-method-example.conf	2008-05-30 12:35:14.050157887 +0200
@@ -0,0 +1,186 @@
+/* This file is a sample configuration for apt https method. Configuration
+   parameters found in this example file are expected to be used in main
+   apt.conf file, just like other configuration parameters for different
+   methods (ftp, file, ...).
+
+   This example file starts with a common setup that voluntarily exhibits
+   all available configurations knobs with simple comments. Extended
+   comments on the behavior of the option is provided at the end for
+   better readibility. As a matter of fact, a common configuration file
+   will certainly contain far less elements and benefit of default values
+   for many parameters.
+
+   Because some configuration parameters for apt https method in following
+   examples apply to specific (fictional) repositories, the associated
+   sources.list file is provided here:
+
+   ...
+
+   deb     https://secure.dom1.tld/debian unstable main contrib non-free
+   deb-src https://secure.dom1.tld/debian unstable main contrib non-free
+
+   deb     https://secure.dom2.tld/debian unstable main contrib non-free
+   deb-src https://secure.dom2.tld/debian unstable main contrib non-free
+
+   ...
+
+
+   Some notes on the servers:
+
+    - secure.dom1.tld is freely accessible using https (no client
+      authentication is required).
+    - secure.dom1.tld certificate is part of a multi level PKI, and we
+      want to specifically check the issuer of its certificate. We do
+      not have the constraint for secure.dom2.tld
+    - secure.dom2.tld requires client authentication by certificate
+      to access its content.
+    - The certificate presented by both server have (as expected) a CN that
+      matches their respective DNS names.
+    - We have CRL available for both dom1.tld and dom2.tld PKI, and intend
+      to use them.
+    - It somtimes happens that we had other more generic https available
+      repository to our list. We want the checks to be performed against
+      a common list of anchors (like the one provided by ca-certificates
+      package for instance)
+
+   The sample configuration below basically covers those simpe needs.
+*/
+
+
+// Verify peer certificate and also matching between certificate name
+// and server name as provided in sources.list (default values)
+Acquire::https::Verify-Peer "true";
+Acquire::https::Verify-Host "true";
+
+// Except otherwise specified, use that list of anchors
+Acquire::https::CaInfo     "/etc/ssl/certs/ca-certificates.pem";
+
+// Use a specific anchor and associated CRL. Enforce issuer of
+// server certificate using its cert.
+Acquire::https::secure.dom1.tld::CaInfo     "/etc/apt/certs/ca-dom1-crt.pem";
+Acquire::https::secure.dom1.tld::CrlFile    "/etc/apt/certs/ca-dom1-crl.pem";
+Acquire::https::secure.dom1.tld::IssuerCert "/etc/apt/certs/secure.dom1-issuer-crt.pem";
+
+// Like previous for anchor and CRL, but also provide our
+// certificate and keys for client authentication.
+Acquire::https::secure.dom2.tld::CaInfo  "/etc/apt/certs/ca-dom2-crt.pem";
+Acquire::https::secure.dom2.tld::CrlFile "/etc/apt/certs/ca-dom2-crl.pem";
+Acquire::https::secure.dom2.tld::SslCert "/etc/apt/certs/my-crt.pem";
+Acquire::https::secure.dom2.tld::SslKey  "/etc/apt/certs/my-key.pem";
+
+// No need to downgrade, TLS will be proposed by default. Uncomment
+// to have SSLv3 proposed.
+// Acquire::https::mirror.ipv6.ssi.corp::SslForceVersion "SSLv3";
+
+// No need for more debug if every is fine (default). Uncomment
+// me to get additional information.
+// Debug::Acquire::https "true";
+
+
+/*
+  Options with extended comments:
+
+  Acquire::https[::repo.domain.tld]::CaInfo  "/path/to/ca/certs.pem";
+
+    A string providing the path of a file containing the list of trusted
+    CA certificates used to verify the server certificate. The pointed
+    file is made of the concatenation of the CA certificates (in
+    PEM format) creating the chain used for the verification of the path
+    from the root (self signed one). If the remote server provides the
+    whole chain during the exchange, the file need only contain the root
+    certificate. Otherwise, the whole chain is required.
+
+    If you need to support multiple authorities, the only way is to
+    concatenate everything.
+
+    If None is provided, the default CA bundle used by GnuTLS (apt https
+    method is linked against libcurl-gnutls) is used. At the time of
+    writing, /etc/ssl/certs/ca-certificates.crt.
+
+    If no specific hostname is provided, the file is used by default
+    for all https targets. If a specific mirror is provided, it is
+    used for the https entries in the sources.list file that use that
+    repository (with the same name).
+
+  Acquire::https[::repo.domain.tld]::CrlFile  "/path/to/all/crl.pem";
+
+    Like previous knob but for passing the list of CRL files (in PEM
+    format) to be used to verify revocation status. Again, if the
+    option is defined with no specific mirror (probably makes little
+    sense), this CRL information is used for all defined https entries
+    in sources.list file. In a mirror specific context, it only applies
+    to that mirror.
+
+  Acquire::https[::repo.domain.tld]::IssuerCert "/path/to/issuer/cert.pem";
+
+    Allows to constrain the issuer of the server certificate (for all
+    https mirrors or a specific one) to a specific issuer. If the
+    server certificate has not been issued by this certificate,
+    connection fails.
+
+  Acquire::https[::repo.domain.tld]::Verify-Peer "true";
+
+    When authenticating the server, if the certificate verification fails
+    for some reason (expired, revoked, man in the middle, lack of anchor,
+    ...), the connection fails. This is obviously what you want in all
+    cases and what the default value (true) of this option provides.
+
+    If you know EXACTLY what you are doing, setting this option to "false"
+    allow you to skip peer certificate verification and make the exchange
+    succeed. Again, this option is for debugging or testing purpose only.
+    It removes ALL the security provided by the use of SSL.TLS to secure
+    the HTTP exchanges.
+
+  Acquire::https[::repo.domain.tld]::Verify-Host "true";
+
+    The certificate provided by the server during the TLS/SSL exchange
+    provides the identity of the server which should match the DNS name
+    used to access it. By default, as requested by RFC 2818, the name
+    of the mirror is checked against the identity found in the
+    certificate. This default behavior is safe and should not be
+    changed. If you know that the server you are using has a DNS name
+    which does not match the identity in its certificate, you can
+    [report that issue to its administrator or] set the option to
+    "false", which will prevent the comparison to be done.
+
+    The options can be set globally or on a per-mirror basis. If set
+    globally, the DNS name used is the one found in the sources.list
+    file in the https URI.
+
+  Acquire::https[::repo.domain.tld]::SslCert "/path/to/client/cert.pem";
+  Acquire::https[::repo.domain.tld]::SslKey  "/path/to/client/key.pem";
+
+    These two options provides support for client authentication using
+    certificates. They respectively accept the X.509 client certificate
+    in PEM format and the associated client key in PEM format (non
+    encrypted form).
+
+    The options can be set globally (which rarely makes sense) or on a
+    per-mirror basis.
+
+  Acquire::https[::repo.domain.tld]::SslForceVersion "TLSv1";
+
+    This option can be use to select the version which will be proposed
+    to the server. "SSLv3" and "TLSv1" are supported. SSLv2, which is
+    considered insecure anyway is not supported (by gnutls, which is
+    used by libcurl against which apt https method is linked).
+
+    When the option is set to "SSLv3" to have apt propose SSLv3 (and
+    associated sets of ciphersuites) instead of TLSv1 (the default)
+    when performing the exchange. This prevents the server to select
+    TLSv1 and use associated cipheruites. You should probably not use
+    this option except if you know exactly what you are doing.
+
+    Note that the default setting does not guarantee that the server
+    will not select SSLv3 (for ciphersuites and SSL/TLS version as
+    selectio is always done by the server, in the end). It only means
+    that apt will not advertise TLS support.
+
+  Debug::Acquire::https "true";
+
+    This option can be used to show debug information. Because it is
+    quite verbose, it is mainly useful to debug problems in case of
+    failure to connect to a server for some reason. The default value
+    is "false".
+
+*/

Attachment: pgpR5CyXG4J5r.pgp
Description: PGP signature


Reply to: