* merge patch that enforces stricter https server certificate
authorMichael Vogt <michael.vogt@ubuntu.com>
Fri, 25 Jul 2008 17:57:08 +0000 (19:57 +0200)
committerMichael Vogt <michael.vogt@ubuntu.com>
Fri, 25 Jul 2008 17:57:08 +0000 (19:57 +0200)
  checking (thanks to Arnaud Ebalard, closes: #485960)
* allow per-mirror specific https settings
  (thanks to Arnaud Ebalard, closes: #485965)
* add doc/examples/apt-https-method-example.cof
  (thanks to Arnaud Ebalard, closes: #485964)

debian/changelog
doc/examples/apt-https-method-example.conf [new file with mode: 0644]
methods/https.cc

index 6a7cd3e..3048752 100644 (file)
@@ -1,5 +1,6 @@
 apt (0.7.15) UNRELEASED; urgency=low
 
+  [ Christian Perrier ]
   * Fix typo in cron.daily script. Closes: #486179
 
   [ Program translations ]
@@ -7,6 +8,14 @@ apt (0.7.15) UNRELEASED; urgency=low
   * Traditional Chinese updated. Closes: #488526
   * German corrected. Closes: #490532
 
+  [ Michael Vogt ]
+  * merge patch that enforces stricter https server certificate
+    checking (thanks to Arnaud Ebalard, closes: #485960)
+  * allow per-mirror specific https settings
+    (thanks to Arnaud Ebalard, closes: #485965)
+  * add doc/examples/apt-https-method-example.cof
+    (thanks to Arnaud Ebalard, closes: #485964)
+
  -- Christian Perrier <bubulle@debian.org>  Sat, 14 Jun 2008 07:39:06 +0200
 
 apt (0.7.14) unstable; urgency=low
diff --git a/doc/examples/apt-https-method-example.conf b/doc/examples/apt-https-method-example.conf
new file mode 100644 (file)
index 0000000..0067171
--- /dev/null
@@ -0,0 +1,165 @@
+/* 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.
+    - 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";
+
+// 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::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]::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".
+
+*/
index b0b05a4..e53ba1a 100644 (file)
@@ -108,6 +108,8 @@ bool HttpsMethod::Fetch(FetchItem *Itm)
    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 @@ bool HttpsMethod::Fetch(FetchItem *Itm)
    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)