From 73d752fb1cd01a9092191c9ac52ddb8fa378065f Mon Sep 17 00:00:00 2001
From: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>
Date: Sat, 6 Dec 2014 00:26:43 +0900
Subject: [PATCH] Add --min-tls-version option

The --min-tls-version option specifies minimum SSL/TLS version to
enable. Possible Values: SSLv3, TLSv1, TLSv1.1, TLSv1.2 Default: TLSv1
---
 doc/manual-src/en/aria2c.rst |  6 ++++++
 src/AppleTLSContext.cc       |  4 ++--
 src/AppleTLSContext.h        | 10 +++++++--
 src/AppleTLSSession.cc       | 32 ++++++++++++++++++++++++-----
 src/LibgnutlsTLSContext.cc   |  7 ++++---
 src/LibgnutlsTLSContext.h    |  8 +++++++-
 src/LibgnutlsTLSSession.cc   | 15 +++++++++++++-
 src/LibsslTLSContext.cc      | 25 +++++++++++++++++-----
 src/LibsslTLSContext.h       |  2 +-
 src/MultiUrlRequestInfo.cc   |  8 ++++++--
 src/OptionHandlerFactory.cc  | 11 ++++++++++
 src/TLSContext.h             |  9 +++++++-
 src/WinTLSContext.cc         | 40 ++++++++++++++++++++++++++++--------
 src/WinTLSContext.h          |  2 +-
 src/prefs.cc                 |  6 ++++++
 src/prefs.h                  |  6 ++++++
 src/usage_text.h             |  2 ++
 src/util.cc                  | 17 +++++++++++++++
 src/util.h                   |  6 ++++++
 19 files changed, 183 insertions(+), 33 deletions(-)

diff --git a/doc/manual-src/en/aria2c.rst b/doc/manual-src/en/aria2c.rst
index 0d9c2a81..003f3cef 100644
--- a/doc/manual-src/en/aria2c.rst
+++ b/doc/manual-src/en/aria2c.rst
@@ -1321,6 +1321,12 @@ Advanced Options
   given URIs do not support resume.  See :option:`--always-resume` option.
   Default: ``0``
 
+.. option:: --min-tls-version=<VERSION>
+
+  Specify minimum SSL/TLS version to enable.
+  Possible Values: ``SSLv3``, ``TLSv1``, ``TLSv1.1``, ``TLSv1.2``
+  Default: ``TLSv1``
+
 .. option:: --log-level=<LEVEL>
 
   Set log level to output.
diff --git a/src/AppleTLSContext.cc b/src/AppleTLSContext.cc
index 10e034fd..ae72b5b7 100644
--- a/src/AppleTLSContext.cc
+++ b/src/AppleTLSContext.cc
@@ -195,9 +195,9 @@ bool checkIdentity(const SecIdentityRef id,
 
 namespace aria2 {
 
-TLSContext* TLSContext::make(TLSSessionSide side)
+TLSContext* TLSContext::make(TLSSessionSide side, TLSVersion ver)
 {
-  return new AppleTLSContext(side);
+  return new AppleTLSContext(side, ver);
 }
 
 AppleTLSContext::~AppleTLSContext()
diff --git a/src/AppleTLSContext.h b/src/AppleTLSContext.h
index b1d24154..6c72e5e9 100644
--- a/src/AppleTLSContext.h
+++ b/src/AppleTLSContext.h
@@ -49,8 +49,8 @@ namespace aria2 {
 class AppleTLSContext : public TLSContext
 {
 public:
-  AppleTLSContext(TLSSessionSide side)
-    : side_(side), verifyPeer_(true), credentials_(nullptr)
+  AppleTLSContext(TLSSessionSide side, TLSVersion ver)
+    : side_(side), minTLSVer_(ver), verifyPeer_(true), credentials_(nullptr)
   {}
 
   virtual ~AppleTLSContext();
@@ -89,8 +89,14 @@ public:
 
   SecIdentityRef getCredentials();
 
+  TLSVersion getMinTLSVersion() const
+  {
+    return minTLSVer_;
+  }
+
 private:
   TLSSessionSide side_;
+  TLSVersion minTLSVer_;
   bool verifyPeer_;
   SecIdentityRef credentials_;
 
diff --git a/src/AppleTLSSession.cc b/src/AppleTLSSession.cc
index 00c662c5..0633583f 100644
--- a/src/AppleTLSSession.cc
+++ b/src/AppleTLSSession.cc
@@ -362,14 +362,36 @@ AppleTLSSession::AppleTLSSession(AppleTLSContext* ctx)
     return;
   }
 #if defined(__MAC_10_8)
-  (void)SSLSetProtocolVersionMin(sslCtx_, kSSLProtocol3);
+  switch (ctx->getMinTLSVersion()) {
+  case TLS_PROTO_SSL3:
+    (void)SSLSetProtocolVersionMin(sslCtx_, kSSLProtocol3);
+    break;
+  case TLS_PROTO_TLS10:
+    (void)SSLSetProtocolVersionMin(sslCtx_, kSSLProtocol1);
+    break;
+  case TLS_PROTO_TLS11:
+    (void)SSLSetProtocolVersionMin(sslCtx_, kSSLProtocol11);
+    break;
+  case TLS_PROTO_TLS12:
+    (void)SSLSetProtocolVersionMin(sslCtx_, kSSLProtocol12);
+    break;
+  }
   (void)SSLSetProtocolVersionMax(sslCtx_, kTLSProtocol12);
 #else
   (void)SSLSetProtocolVersionEnabled(sslCtx_, kSSLProtocolAll, false);
-  (void)SSLSetProtocolVersionEnabled(sslCtx_, kSSLProtocol3, true);
-  (void)SSLSetProtocolVersionEnabled(sslCtx_, kTLSProtocol1, true);
-  (void)SSLSetProtocolVersionEnabled(sslCtx_, kTLSProtocol11, true);
-  (void)SSLSetProtocolVersionEnabled(sslCtx_, kTLSProtocol12, true);
+  switch (ctx->getMinTLSVersion()) {
+  case TLS_PROTO_SSL3:
+    (void)SSLSetProtocolVersionEnabled(sslCtx_, kSSLProtocol3, true);
+    // fall through
+  case TLS_PROTO_TLS10:
+    (void)SSLSetProtocolVersionEnabled(sslCtx_, kTLSProtocol1, true);
+    // fall through
+  case TLS_PROTO_TLS11:
+    (void)SSLSetProtocolVersionEnabled(sslCtx_, kTLSProtocol11, true);
+    // fall through
+  case TLS_PROTO_TLS12:
+    (void)SSLSetProtocolVersionEnabled(sslCtx_, kTLSProtocol12, true);
+  }
 #endif
 
   // BEAST
diff --git a/src/LibgnutlsTLSContext.cc b/src/LibgnutlsTLSContext.cc
index e62f0691..df9ef6fb 100644
--- a/src/LibgnutlsTLSContext.cc
+++ b/src/LibgnutlsTLSContext.cc
@@ -49,14 +49,15 @@
 
 namespace aria2 {
 
-TLSContext* TLSContext::make(TLSSessionSide side)
+TLSContext* TLSContext::make(TLSSessionSide side, TLSVersion ver)
 {
-  return new GnuTLSContext(side);
+  return new GnuTLSContext(side, ver);
 }
 
-GnuTLSContext::GnuTLSContext(TLSSessionSide side)
+GnuTLSContext::GnuTLSContext(TLSSessionSide side, TLSVersion ver)
   : certCred_(0),
     side_(side),
+    minTLSVer_(ver),
     verifyPeer_(true)
 {
   int r = gnutls_certificate_allocate_credentials(&certCred_);
diff --git a/src/LibgnutlsTLSContext.h b/src/LibgnutlsTLSContext.h
index ef2b005c..d31aa6d5 100644
--- a/src/LibgnutlsTLSContext.h
+++ b/src/LibgnutlsTLSContext.h
@@ -46,7 +46,7 @@ namespace aria2 {
 
 class GnuTLSContext : public TLSContext {
 public:
-  GnuTLSContext(TLSSessionSide side);
+  GnuTLSContext(TLSSessionSide side, TLSVersion ver);
 
   virtual ~GnuTLSContext();
 
@@ -79,9 +79,15 @@ public:
 
   gnutls_certificate_credentials_t getCertCred() const;
 
+  TLSVersion getMinTLSVersion() const
+  {
+    return minTLSVer_;
+  }
+
 private:
   gnutls_certificate_credentials_t certCred_;
   TLSSessionSide side_;
+  TLSVersion minTLSVer_;
   bool good_;
   bool verifyPeer_;
 };
diff --git a/src/LibgnutlsTLSSession.cc b/src/LibgnutlsTLSSession.cc
index bbb8d100..deda15a3 100644
--- a/src/LibgnutlsTLSSession.cc
+++ b/src/LibgnutlsTLSSession.cc
@@ -107,7 +107,20 @@ int GnuTLSSession::init(sock_t sockfd)
   // It seems err is not error message, but the argument string
   // which causes syntax error.
   const char* err;
-  rv_ = gnutls_priority_set_direct(sslSession_, "SECURE128:-VERS-SSL3.0", &err);
+  std::string pri = "SECURE128";
+  switch(tlsContext_->getMinTLSVersion()) {
+  case TLS_PROTO_TLS12:
+    pri += ":-VERS-TLS1.1";
+    // fall through
+  case TLS_PROTO_TLS11:
+    pri += ":-VERS-TLS1.0";
+    // fall through
+  case TLS_PROTO_TLS10:
+    pri += ":-VERS-SSL3.0";
+  default:
+    break;
+  };
+  rv_ = gnutls_priority_set_direct(sslSession_, pri.c_str(), &err);
   if(rv_ != GNUTLS_E_SUCCESS) {
     return TLS_ERR_ERROR;
   }
diff --git a/src/LibsslTLSContext.cc b/src/LibsslTLSContext.cc
index 3ecac860..70c13a60 100644
--- a/src/LibsslTLSContext.cc
+++ b/src/LibsslTLSContext.cc
@@ -81,12 +81,12 @@ namespace {
 
 namespace aria2 {
 
-TLSContext* TLSContext::make(TLSSessionSide side)
+TLSContext* TLSContext::make(TLSSessionSide side, TLSVersion minVer)
 {
-  return new OpenSSLTLSContext(side);
+  return new OpenSSLTLSContext(side, minVer);
 }
 
-OpenSSLTLSContext::OpenSSLTLSContext(TLSSessionSide side)
+OpenSSLTLSContext::OpenSSLTLSContext(TLSSessionSide side, TLSVersion minVer)
   : sslCtx_(nullptr),
     side_(side),
     verifyPeer_(true)
@@ -100,8 +100,23 @@ OpenSSLTLSContext::OpenSSLTLSContext(TLSSessionSide side)
                      ERR_error_string(ERR_get_error(), nullptr)));
     return;
   }
-  // Disable SSLv2/3 and enable all workarounds for buggy servers
-  SSL_CTX_set_options(sslCtx_, SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3
+
+  long ver_opts = 0;
+  switch(minVer) {
+  case TLS_PROTO_TLS12:
+    ver_opts |= SSL_OP_NO_TLSv1_1;
+    // fall through
+  case TLS_PROTO_TLS11:
+    ver_opts |= SSL_OP_NO_TLSv1;
+    // fall through
+  case TLS_PROTO_TLS10:
+    ver_opts |= SSL_OP_NO_SSLv3;
+  default:
+    break;
+  };
+
+  // Disable SSLv2 and enable all workarounds for buggy servers
+  SSL_CTX_set_options(sslCtx_, SSL_OP_ALL | SSL_OP_NO_SSLv2 | ver_opts
 #ifdef SSL_OP_SINGLE_ECDH_USE
                       | SSL_OP_SINGLE_ECDH_USE
 #endif // SSL_OP_SINGLE_ECDH_USE
diff --git a/src/LibsslTLSContext.h b/src/LibsslTLSContext.h
index 65582366..ea37e6b2 100644
--- a/src/LibsslTLSContext.h
+++ b/src/LibsslTLSContext.h
@@ -48,7 +48,7 @@ namespace aria2 {
 
 class OpenSSLTLSContext : public TLSContext {
 public:
-  OpenSSLTLSContext(TLSSessionSide side);
+  OpenSSLTLSContext(TLSSessionSide side, TLSVersion minVer);
 
   ~OpenSSLTLSContext();
 
diff --git a/src/MultiUrlRequestInfo.cc b/src/MultiUrlRequestInfo.cc
index 7ce24b63..e8239309 100644
--- a/src/MultiUrlRequestInfo.cc
+++ b/src/MultiUrlRequestInfo.cc
@@ -188,7 +188,9 @@ int MultiUrlRequestInfo::prepare()
       }
       // We set server TLS context to the SocketCore before creating
       // DownloadEngine instance.
-      std::shared_ptr<TLSContext> svTlsContext(TLSContext::make(TLS_SERVER));
+      auto minTLSVer = util::toTLSVersion(option_->get(PREF_MIN_TLS_VERSION));
+      std::shared_ptr<TLSContext> svTlsContext
+        (TLSContext::make(TLS_SERVER, minTLSVer));
       if(!svTlsContext->addCredentialFile
          (option_->get(PREF_RPC_CERTIFICATE),
           option_->get(PREF_RPC_PRIVATE_KEY))) {
@@ -245,7 +247,9 @@ int MultiUrlRequestInfo::prepare()
     e_->setAuthConfigFactory(std::move(authConfigFactory));
 
 #ifdef ENABLE_SSL
-    std::shared_ptr<TLSContext> clTlsContext(TLSContext::make(TLS_CLIENT));
+    auto minTLSVer = util::toTLSVersion(option_->get(PREF_MIN_TLS_VERSION));
+    std::shared_ptr<TLSContext> clTlsContext
+      (TLSContext::make(TLS_CLIENT, minTLSVer));
     if(!option_->blank(PREF_CERTIFICATE)) {
       clTlsContext->addCredentialFile(option_->get(PREF_CERTIFICATE),
                                       option_->get(PREF_PRIVATE_KEY));
diff --git a/src/OptionHandlerFactory.cc b/src/OptionHandlerFactory.cc
index dec0624a..f2f4eec9 100644
--- a/src/OptionHandlerFactory.cc
+++ b/src/OptionHandlerFactory.cc
@@ -584,6 +584,17 @@ std::vector<OptionHandler*> OptionHandlerFactory::createOptionHandlers()
     op->setChangeOptionForReserved(true);
     handlers.push_back(op);
   }
+#ifdef ENABLE_SSL
+  {
+    OptionHandler* op(new ParameterOptionHandler
+                      (PREF_MIN_TLS_VERSION,
+                       TEXT_MIN_TLS_VERSION,
+                       A2_V_TLS10,
+                       { A2_V_SSL3, A2_V_TLS10, A2_V_TLS11, A2_V_TLS12 }));
+    op->addTag(TAG_ADVANCED);
+    handlers.push_back(op);
+  }
+#endif // ENABLE_SSL
   {
     OptionHandler* op(new BooleanOptionHandler
                       (PREF_NO_CONF,
diff --git a/src/TLSContext.h b/src/TLSContext.h
index 64223ada..91b20965 100644
--- a/src/TLSContext.h
+++ b/src/TLSContext.h
@@ -46,9 +46,16 @@ enum TLSSessionSide {
   TLS_SERVER
 };
 
+enum TLSVersion {
+  TLS_PROTO_SSL3,
+  TLS_PROTO_TLS10,
+  TLS_PROTO_TLS11,
+  TLS_PROTO_TLS12,
+};
+
 class TLSContext {
 public:
-  static TLSContext* make(TLSSessionSide side);
+  static TLSContext* make(TLSSessionSide side, TLSVersion minVer);
   virtual ~TLSContext() {}
 
   // private key `keyfile' must be decrypted.
diff --git a/src/WinTLSContext.cc b/src/WinTLSContext.cc
index ee67cfd0..d30aea52 100644
--- a/src/WinTLSContext.cc
+++ b/src/WinTLSContext.cc
@@ -63,28 +63,50 @@
 
 namespace aria2 {
 
-WinTLSContext::WinTLSContext(TLSSessionSide side) : side_(side), store_(0)
+WinTLSContext::WinTLSContext(TLSSessionSide side, TLSVersion ver)
+  : side_(side), store_(0)
 {
   memset(&credentials_, 0, sizeof(credentials_));
   credentials_.dwVersion = SCHANNEL_CRED_VERSION;
+  credentials_.grbitEnabledProtocols = 0;
   if (side_ == TLS_CLIENT) {
-    credentials_.grbitEnabledProtocols =
-        SP_PROT_SSL3_CLIENT | SP_PROT_TLS1_CLIENT | SP_PROT_TLS1_1_CLIENT |
-        SP_PROT_TLS1_2_CLIENT;
+    switch (ver) {
+    case TLS_PROTO_SSL3:
+      credentials_.grbitEnabledProtocols |= SP_PROT_SSL3_CLIENT;
+      // fall through
+    case TLS_PROTO_TLS10:
+      credentials_.grbitEnabledProtocols |= SP_PROT_TLS1_CLIENT;
+      // fall through
+    case TLS_PROTO_TLS11:
+      credentials_.grbitEnabledProtocols |= SP_PROT_TLS1_1_CLIENT;
+      // fall through
+    case TLS_PROTO_TLS12:
+      credentials_.grbitEnabledProtocols |= SP_PROT_TLS1_2_CLIENT;
+    }
   }
   else {
-    credentials_.grbitEnabledProtocols =
-        SP_PROT_SSL3_SERVER | SP_PROT_TLS1_SERVER | SP_PROT_TLS1_1_SERVER |
-        SP_PROT_TLS1_2_SERVER;
+    switch (ver) {
+    case TLS_PROTO_SSL3:
+      credentials_.grbitEnabledProtocols |= SP_PROT_SSL3_SERVER;
+      // fall through
+    case TLS_PROTO_TLS10:
+      credentials_.grbitEnabledProtocols |= SP_PROT_TLS1_SERVER;
+      // fall through
+    case TLS_PROTO_TLS11:
+      credentials_.grbitEnabledProtocols |= SP_PROT_TLS1_1_SERVER;
+      // fall through
+    case TLS_PROTO_TLS12:
+      credentials_.grbitEnabledProtocols |= SP_PROT_TLS1_2_SERVER;
+    }
   }
   credentials_.dwMinimumCipherStrength = 128; // bit
 
   setVerifyPeer(side_ == TLS_CLIENT);
 }
 
-TLSContext* TLSContext::make(TLSSessionSide side)
+TLSContext* TLSContext::make(TLSSessionSide side, TLSVersion ver)
 {
-  return new WinTLSContext(side);
+  return new WinTLSContext(side, ver);
 }
 
 WinTLSContext::~WinTLSContext()
diff --git a/src/WinTLSContext.h b/src/WinTLSContext.h
index 7b182c6f..8ca9e3a8 100644
--- a/src/WinTLSContext.h
+++ b/src/WinTLSContext.h
@@ -67,7 +67,7 @@ typedef std::unique_ptr<CredHandle, cred_deleter> CredPtr;
 class WinTLSContext : public TLSContext
 {
 public:
-  WinTLSContext(TLSSessionSide side);
+  WinTLSContext(TLSSessionSide side, TLSVersion ver);
 
   virtual ~WinTLSContext();
 
diff --git a/src/prefs.cc b/src/prefs.cc
index a6de66ef..2332bd57 100644
--- a/src/prefs.cc
+++ b/src/prefs.cc
@@ -169,6 +169,10 @@ const std::string V_ARC4("arc4");
 const std::string V_HTTP("http");
 const std::string V_HTTPS("https");
 const std::string V_FTP("ftp");
+const std::string A2_V_SSL3("SSLv3");
+const std::string A2_V_TLS10("TLSv1");
+const std::string A2_V_TLS11("TLSv1.1");
+const std::string A2_V_TLS12("TLSv1.2");
 
 PrefPtr PREF_VERSION = makePref("version");
 PrefPtr PREF_HELP = makePref("help");
@@ -367,6 +371,8 @@ PrefPtr PREF_DSCP = makePref("dscp");
 PrefPtr PREF_PAUSE_METADATA = makePref("pause-metadata");
 // values: 1*digit
 PrefPtr PREF_RLIMIT_NOFILE = makePref("rlimit-nofile");
+// vlaues: SSLv3 | TLSv1 | TLSv1.1 | TLSv1.2
+PrefPtr PREF_MIN_TLS_VERSION = makePref("min-tls-version");
 
 /**
  * FTP related preferences
diff --git a/src/prefs.h b/src/prefs.h
index 33ab67ac..a4b5bfdf 100644
--- a/src/prefs.h
+++ b/src/prefs.h
@@ -105,6 +105,10 @@ extern const std::string V_ARC4;
 extern const std::string V_HTTP;
 extern const std::string V_HTTPS;
 extern const std::string V_FTP;
+extern const std::string A2_V_SSL3;
+extern const std::string A2_V_TLS10;
+extern const std::string A2_V_TLS11;
+extern const std::string A2_V_TLS12;
 
 extern PrefPtr PREF_VERSION;
 extern PrefPtr PREF_HELP;
@@ -304,6 +308,8 @@ extern PrefPtr PREF_DSCP;
 extern PrefPtr PREF_PAUSE_METADATA;
 // values: 1*digit
 extern PrefPtr PREF_RLIMIT_NOFILE;
+// vlaues: SSLv3 | TLSv1 | TLSv1.1 | TLSv1.2
+extern PrefPtr PREF_MIN_TLS_VERSION;
 
 /**
  * FTP related preferences
diff --git a/src/usage_text.h b/src/usage_text.h
index 2831b48e..1b139b13 100644
--- a/src/usage_text.h
+++ b/src/usage_text.h
@@ -1011,3 +1011,5 @@
   "                              and the next download waiting in queue gets\n" \
   "                              started. But be aware that seeding item is still\n" \
   "                              recognized as active download in RPC method.")
+#define TEXT_MIN_TLS_VERSION                                            \
+  _(" --min-tls-version=VERSION  Specify minimum SSL/TLS version to enable.")
diff --git a/src/util.cc b/src/util.cc
index 0a0f3901..9332ae47 100644
--- a/src/util.cc
+++ b/src/util.cc
@@ -2009,6 +2009,23 @@ bool strless(const char* a, const char* b)
   return strcmp(a, b) < 0;
 }
 
+TLSVersion toTLSVersion(const std::string& ver)
+{
+  if(ver == A2_V_SSL3) {
+    return TLS_PROTO_SSL3;
+  }
+  if(ver == A2_V_TLS10) {
+    return TLS_PROTO_TLS10;
+  }
+  if(ver == A2_V_TLS11) {
+    return TLS_PROTO_TLS11;
+  }
+  if(ver == A2_V_TLS12) {
+    return TLS_PROTO_TLS12;
+  }
+  return TLS_PROTO_TLS10;
+}
+
 } // namespace util
 
 } // namespace aria2
diff --git a/src/util.h b/src/util.h
index d14d8fc9..ff24c279 100644
--- a/src/util.h
+++ b/src/util.h
@@ -64,6 +64,10 @@
 #include "fmt.h"
 #include "prefs.h"
 
+#ifdef ENABLE_SSL
+#include "TLSContext.h"
+#endif // ENABLE_SSL
+
 #ifndef HAVE_SIGACTION
 #  define sigset_t int
 #endif // HAVE_SIGACTION
@@ -880,6 +884,8 @@ bool noProxyDomainMatch(const std::string& hostname, const std::string& domain);
 // Checks hostname matches pattern as described in RFC 6125.
 bool tlsHostnameMatch(const std::string& pattern, const std::string& hostname);
 
+TLSVersion toTLSVersion(const std::string& ver);
+
 } // namespace util
 
 } // namespace aria2