Ver código fonte

2008-11-09 Tatsuhiro Tsujikawa <t-tujikawa@users.sourceforge.net>

	Added the ability to verify peer in SSL/TLS using given CA
	certificates.
	The CA certificates are specified in --ca-certificate option.
	By default, the verification is disabled. Use --check-certificate
	option to enable it.
	* src/HttpRequestCommand.cc
	* src/LibgnutlsTLSContext.cc
	* src/LibgnutlsTLSContext.h
	* src/LibsslTLSContext.cc
	* src/LibsslTLSContext.h
	* src/MultiUrlRequestInfo.cc
	* src/OptionHandlerFactory.cc
	* src/SocketCore.cc
	* src/SocketCore.h
	* src/a2functional.h
	* src/message.h
	* src/option_processing.cc
	* src/prefs.cc
	* src/prefs.h
	* src/usage_text.h
Tatsuhiro Tsujikawa 17 anos atrás
pai
commit
ce4186b4c3

+ 22 - 0
ChangeLog

@@ -1,3 +1,25 @@
+2008-11-09  Tatsuhiro Tsujikawa  <t-tujikawa@users.sourceforge.net>
+
+	Added the ability to verify peer in SSL/TLS using given CA certificates.
+	The CA certificates are specified in --ca-certificate option.
+	By default, the verification is disabled. Use --check-certificate
+	option to enable it.
+	* src/HttpRequestCommand.cc
+	* src/LibgnutlsTLSContext.cc
+	* src/LibgnutlsTLSContext.h
+	* src/LibsslTLSContext.cc
+	* src/LibsslTLSContext.h
+	* src/MultiUrlRequestInfo.cc
+	* src/OptionHandlerFactory.cc
+	* src/SocketCore.cc
+	* src/SocketCore.h
+	* src/a2functional.h
+	* src/message.h
+	* src/option_processing.cc
+	* src/prefs.cc
+	* src/prefs.h
+	* src/usage_text.h
+
 2008-11-08  Tatsuhiro Tsujikawa  <t-tujikawa@users.sourceforge.net>
 
 	Added client certificate authentication for SSL/TLS.

+ 1 - 1
src/HttpRequestCommand.cc

@@ -108,7 +108,7 @@ bool HttpRequestCommand::executeInternal() {
   //socket->setBlockingMode();
   if(req->getProtocol() == Request::PROTO_HTTPS) {
     socket->prepareSecureConnection();
-    if(!socket->initiateSecureConnection()) {
+    if(!socket->initiateSecureConnection(req->getHost())) {
       setReadCheckSocketIf(socket, socket->wantRead());
       setWriteCheckSocketIf(socket, socket->wantWrite());
       e->commands.push_back(this);

+ 25 - 1
src/LibgnutlsTLSContext.cc

@@ -33,6 +33,11 @@
  */
 /* copyright --> */
 #include "LibgnutlsTLSContext.h"
+
+#ifdef HAVE_LIBGNUTLS
+# include <gnutls/x509.h>
+#endif // HAVE_LIBGNUTLS
+
 #include "LogFactory.h"
 #include "Logger.h"
 #include "StringFormat.h"
@@ -40,11 +45,15 @@
 
 namespace aria2 {
 
-TLSContext::TLSContext():_certCred(0), _logger(LogFactory::getInstance())
+TLSContext::TLSContext():_certCred(0),
+			 _peerVerificationEnabled(false),
+			 _logger(LogFactory::getInstance())
 {
   int r = gnutls_certificate_allocate_credentials(&_certCred);
   if(r == GNUTLS_E_SUCCESS) {
     _good = true;
+    gnutls_certificate_set_verify_flags(_certCred,
+					GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
   } else {
     _good =false;
     _logger->error("gnutls_certificate_allocate_credentials() failed."
@@ -106,4 +115,19 @@ gnutls_certificate_credentials_t TLSContext::getCertCred() const
   return _certCred;
 }
 
+void TLSContext::enablePeerVerification()
+{
+  _peerVerificationEnabled = true;
+}
+
+void TLSContext::disablePeerVerification()
+{
+  _peerVerificationEnabled = false;
+}
+
+bool TLSContext::peerVerificationEnabled() const
+{
+  return _peerVerificationEnabled;
+}
+
 } // namespace aria2

+ 8 - 0
src/LibgnutlsTLSContext.h

@@ -53,6 +53,8 @@ private:
 
   bool _good;
 
+  bool _peerVerificationEnabled;
+
   Logger* _logger;
 public:
   TLSContext();
@@ -71,6 +73,12 @@ public:
   bool bad() const;
 
   gnutls_certificate_credentials_t getCertCred() const;
+
+  void enablePeerVerification();
+
+  void disablePeerVerification();
+
+  bool peerVerificationEnabled() const;
 };
 
 } // namespace aria2

+ 18 - 1
src/LibsslTLSContext.cc

@@ -43,7 +43,9 @@
 
 namespace aria2 {
 
-TLSContext::TLSContext():_sslCtx(0), _logger(LogFactory::getInstance())
+TLSContext::TLSContext():_sslCtx(0),
+			 _peerVerificationEnabled(false),
+			 _logger(LogFactory::getInstance())
 {
   _sslCtx = SSL_CTX_new(SSLv23_client_method());
   if(_sslCtx) {
@@ -106,4 +108,19 @@ SSL_CTX* TLSContext::getSSLCtx() const
   return _sslCtx;
 }
 
+void TLSContext::enablePeerVerification()
+{
+  _peerVerificationEnabled = true;
+}
+
+void TLSContext::disablePeerVerification()
+{
+  _peerVerificationEnabled = false;
+}
+
+bool TLSContext::peerVerificationEnabled() const
+{
+  return _peerVerificationEnabled;
+}
+
 } // namespace aria2

+ 8 - 0
src/LibsslTLSContext.h

@@ -53,6 +53,8 @@ private:
 
   bool _good;
 
+  bool _peerVerificationEnabled;
+
   Logger* _logger;
 public:
   TLSContext();
@@ -71,6 +73,12 @@ public:
   bool bad() const;
 
   SSL_CTX* getSSLCtx() const;
+  
+  void enablePeerVerification();
+
+  void disablePeerVerification();
+
+  bool peerVerificationEnabled() const;
 };
 
 } // namespace aria2

+ 6 - 0
src/MultiUrlRequestInfo.cc

@@ -143,6 +143,12 @@ int MultiUrlRequestInfo::execute()
       tlsContext->addClientKeyFile(_option->get(PREF_CERTIFICATE),
 				   _option->get(PREF_PRIVATE_KEY));
     }
+    if(_option->defined(PREF_CA_CERTIFICATE)) {
+      tlsContext->addTrustedCACertFile(_option->get(PREF_CA_CERTIFICATE));
+    }
+    if(_option->getAsBool(PREF_CHECK_CERTIFICATE)) {
+      tlsContext->enablePeerVerification();
+    }
     SocketCore::setTLSContext(tlsContext);
 #endif
 

+ 15 - 0
src/OptionHandlerFactory.cc

@@ -429,6 +429,13 @@ OptionHandlers OptionHandlerFactory::createOptionHandlers()
     handlers.push_back(op);
   }
   // HTTP Specific Options
+  {
+    SharedHandle<OptionHandler> op(new DefaultOptionHandler
+				   (PREF_CA_CERTIFICATE,
+				    TEXT_CA_CERTIFICATE));
+    op->addTag(TAG_HTTP);
+    handlers.push_back(op);
+  }
   {
     SharedHandle<OptionHandler> op(new DefaultOptionHandler
 				   (PREF_CERTIFICATE,
@@ -436,6 +443,14 @@ OptionHandlers OptionHandlerFactory::createOptionHandlers()
     op->addTag(TAG_HTTP);
     handlers.push_back(op);
   }
+  {
+    SharedHandle<OptionHandler> op(new BooleanOptionHandler
+				   (PREF_CHECK_CERTIFICATE,
+				    TEXT_CHECK_CERTIFICATE,
+				    V_FALSE));
+    op->addTag(TAG_HTTP);
+    handlers.push_back(op);
+  }
   {
     SharedHandle<OptionHandler> op(new BooleanOptionHandler
 				   (PREF_ENABLE_HTTP_KEEP_ALIVE,

+ 126 - 3
src/SocketCore.cc

@@ -39,6 +39,10 @@
 #include <cerrno>
 #include <cstring>
 
+#ifdef HAVE_LIBGNUTLS
+# include <gnutls/x509.h>
+#endif // HAVE_LIBGNUTLS
+
 #include "message.h"
 #include "a2netcompat.h"
 #include "DlRetryEx.h"
@@ -46,6 +50,8 @@
 #include "StringFormat.h"
 #include "Util.h"
 #include "LogFactory.h"
+#include "TimeA2.h"
+#include "a2functional.h"
 #ifdef ENABLE_SSL
 # include "TLSContext.h"
 #endif // ENABLE_SSL
@@ -742,7 +748,7 @@ void SocketCore::prepareSecureConnection()
   }
 }
 
-bool SocketCore::initiateSecureConnection()
+bool SocketCore::initiateSecureConnection(const std::string& hostname)
 {
   if(secure == 1) {
     _wantRead = false;
@@ -781,6 +787,49 @@ bool SocketCore::initiateSecureConnection()
 	    (StringFormat(EX_SSL_UNKNOWN_ERROR, ssl_error).str());
       }
     }
+    if(_tlsContext->peerVerificationEnabled()) {
+      // verify peer
+      X509* peerCert = SSL_get_peer_certificate(ssl);
+      if(!peerCert) {
+	throw DlAbortEx(MSG_NO_CERT_FOUND);
+      }
+      auto_delete<X509*> certDeleter(peerCert, X509_free);
+
+      long verifyResult = SSL_get_verify_result(ssl);
+      if(verifyResult != X509_V_OK) {
+	throw DlAbortEx
+	  (StringFormat(MSG_CERT_VERIFICATION_FAILED,
+			X509_verify_cert_error_string(verifyResult)).str());
+      }
+      X509_NAME* name = X509_get_subject_name(peerCert);
+      if(!name) {
+	throw DlAbortEx("Could not get X509 name object from the certificate.");
+      }
+
+      bool hostnameOK = false;
+      int lastpos = -1;
+      while(true) {
+	lastpos = X509_NAME_get_index_by_NID(name, NID_commonName, lastpos);
+	if(lastpos == -1) {
+	  break;
+	}
+	X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, lastpos);
+	unsigned char* out;
+	int outlen = ASN1_STRING_to_UTF8(&out, X509_NAME_ENTRY_get_data(entry));
+	if(outlen < 0) {
+	  continue;
+	}
+	std::string commonName(&out[0], &out[outlen]);
+	OPENSSL_free(out);
+	if(commonName == hostname) {
+	  hostnameOK = true;
+	  break;
+	}
+      }
+      if(!hostnameOK) {
+	throw DlAbortEx(MSG_HOSTNAME_NOT_MATCH);
+      }
+    }
 #endif // HAVE_LIBSSL
 #ifdef HAVE_LIBGNUTLS
     int ret = gnutls_handshake(sslSession);
@@ -790,9 +839,83 @@ bool SocketCore::initiateSecureConnection()
     } else if(ret < 0) {
       throw DlAbortEx
 	(StringFormat(EX_SSL_INIT_FAILURE, gnutls_strerror(ret)).str());
-    } else {
-      peekBuf = new char[peekBufMax];
     }
+
+    if(_tlsContext->peerVerificationEnabled()) {
+      // verify peer
+      unsigned int status;
+      ret = gnutls_certificate_verify_peers2(sslSession, &status);
+      if(ret < 0) {
+	throw DlAbortEx
+	  (StringFormat("gnutls_certificate_verify_peer2() failed. Cause: %s",
+			gnutls_strerror(ret)).str());
+      }
+      if(status) {
+	std::string errors;
+	if(status & GNUTLS_CERT_INVALID) {
+	  errors += " `not signed by known authorities or invalid'";
+	}
+	if(status & GNUTLS_CERT_REVOKED) {
+	  errors += " `revoked by its CA'";
+	}
+	if(status & GNUTLS_CERT_SIGNER_NOT_FOUND) {
+	  errors += " `issuer is not known'";
+	}
+	if(!errors.empty()) {
+	  throw DlAbortEx
+	    (StringFormat(MSG_CERT_VERIFICATION_FAILED, errors.c_str()).str());
+	}
+      }
+      // certificate type: only X509 is allowed.
+      if(gnutls_certificate_type_get(sslSession) != GNUTLS_CRT_X509) {
+	throw DlAbortEx("Certificate type is not X509.");
+      }
+
+      unsigned int peerCertsLength;
+      const gnutls_datum_t* peerCerts = gnutls_certificate_get_peers
+	(sslSession, &peerCertsLength);
+      if(!peerCerts) {
+	throw DlAbortEx(MSG_NO_CERT_FOUND);
+      }
+      Time now;
+      for(unsigned int i = 0; i < peerCertsLength; ++i) {
+	gnutls_x509_crt_t cert;
+	ret = gnutls_x509_crt_init(&cert);
+	if(ret < 0) {
+	  throw DlAbortEx
+	    (StringFormat("gnutls_x509_crt_init() failed. Cause: %s",
+			  gnutls_strerror(ret)).str());
+	}
+	auto_delete<gnutls_x509_crt_t> certDeleter
+	  (cert, gnutls_x509_crt_deinit);
+	ret = gnutls_x509_crt_import(cert, &peerCerts[i], GNUTLS_X509_FMT_DER);
+	if(ret < 0) {
+	  throw DlAbortEx
+	    (StringFormat("gnutls_x509_crt_import() failed. Cause: %s",
+			  gnutls_strerror(ret)).str());
+	}
+	if(i == 0) {
+	  if(!gnutls_x509_crt_check_hostname(cert, hostname.c_str())) {
+	    throw DlAbortEx(MSG_HOSTNAME_NOT_MATCH);
+	  }
+	}
+	time_t activationTime = gnutls_x509_crt_get_activation_time(cert);
+	if(activationTime == -1) {
+	  throw DlAbortEx("Could not get activation time from certificate.");
+	}
+	if(now.getTime() < activationTime) {
+	  throw DlAbortEx("Certificate is not activated yet.");
+	}
+	time_t expirationTime = gnutls_x509_crt_get_expiration_time(cert);
+	if(expirationTime == -1) {
+	  throw DlAbortEx("Could not get expiration time from certificate.");
+	}
+	if(expirationTime < now.getTime()) {
+	  throw DlAbortEx("Certificate has expired.");
+	}
+      }
+    }
+    peekBuf = new char[peekBufMax];
 #endif // HAVE_LIBGNUTLS
     secure = 2;
     return true;

+ 3 - 1
src/SocketCore.h

@@ -291,8 +291,10 @@ public:
    * Makes this socket secure.
    * If the system has not OpenSSL, then this method do nothing.
    * connection must be established  before calling this method.
+   *
+   * If you are going to verify peer's certificate, hostname must be supplied.
    */
-  bool initiateSecureConnection();
+  bool initiateSecureConnection(const std::string& hostname="");
 
   void prepareSecureConnection();
 

+ 14 - 0
src/a2functional.h

@@ -177,6 +177,20 @@ public:
 
 typedef Append<std::string> StringAppend;
 
+template<typename T>
+class auto_delete {
+private:
+  T _obj;
+  void (*_deleter)(T);
+public:
+  auto_delete(T obj, void (*deleter)(T)):_obj(obj), _deleter(deleter) {}
+
+  ~auto_delete()
+  {
+    _deleter(_obj);
+  }
+};
+
 } // namespace aria2
 
 #endif // _D_A2_FUNCTIONAL_H_

+ 4 - 0
src/message.h

@@ -159,6 +159,10 @@
 #define MSG_NETWORK_PROBLEM _("Network problem has occurred. cause:%s")
 #define MSG_LOADING_TRUSTED_CA_CERT_FAILED \
   _("Failed to load trusted CA certificates from %s. Cause: %s")
+#define MSG_CERT_VERIFICATION_FAILED \
+  _("Certificate verification failed. Cause: %s")
+#define MSG_NO_CERT_FOUND _("No certificate found.")
+#define MSG_HOSTNAME_NOT_MATCH _("Hostname not match.")
 
 #define EX_TIME_OUT _("Timeout.")
 #define EX_INVALID_CHUNK_SIZE _("Invalid chunk size.")

+ 8 - 0
src/option_processing.cc

@@ -184,6 +184,8 @@ Option* option_processing(int argc, char* const argv[])
       { PREF_PROXY_METHOD.c_str(), required_argument, &lopt, 230 },
       { PREF_CERTIFICATE.c_str(), required_argument, &lopt, 231 },
       { PREF_PRIVATE_KEY.c_str(), required_argument, &lopt, 232 },
+      { PREF_CA_CERTIFICATE.c_str(), optional_argument, &lopt, 233 },
+      { PREF_CHECK_CERTIFICATE.c_str(), optional_argument, &lopt, 234 },
 #if defined ENABLE_BITTORRENT || defined ENABLE_METALINK
       { PREF_SHOW_FILES.c_str(), no_argument, NULL, 'S' },
       { PREF_SELECT_FILE.c_str(), required_argument, &lopt, 21 },
@@ -458,6 +460,12 @@ Option* option_processing(int argc, char* const argv[])
       case 232:
 	cmdstream << PREF_PRIVATE_KEY << "=" << optarg << "\n";
 	break;
+      case 233:
+	cmdstream << PREF_CA_CERTIFICATE << "=" << optarg << "\n";
+	break;
+      case 234:
+	cmdstream << PREF_CHECK_CERTIFICATE << "=" << toBoolArg(optarg) << "\n";
+	break;
       }
       break;
     }

+ 4 - 0
src/prefs.cc

@@ -184,6 +184,10 @@ const std::string PREF_HEADER("header");
 const std::string PREF_CERTIFICATE("certificate");
 // value: string that your file system recognizes as a file name.
 const std::string PREF_PRIVATE_KEY("private-key");
+// value: string that your file system recognizes as a file name.
+const std::string PREF_CA_CERTIFICATE("ca-certificate");
+// value: true | false
+const std::string PREF_CHECK_CERTIFICATE("check-certificate");
 
 /** 
  * Proxy related preferences

+ 4 - 0
src/prefs.h

@@ -188,6 +188,10 @@ extern const std::string PREF_HEADER;
 extern const std::string PREF_CERTIFICATE;
 // value: string that your file system recognizes as a file name.
 extern const std::string PREF_PRIVATE_KEY;
+// value: string that your file system recognizes as a file name.
+extern const std::string PREF_CA_CERTIFICATE;
+// value: true | false
+extern const std::string PREF_CHECK_CERTIFICATE;
 
 /**;
  * Proxy related preferences

+ 9 - 0
src/usage_text.h

@@ -398,3 +398,12 @@ _(" --certificate=FILE           Use the client certificate in FILE.\n"\
 _(" --private-key=FILE           Use the private key in FILE.\n"\
   "                              The private key must be decrypted and in PEM\n"\
   "                              format. See also --certificate option.")
+#define TEXT_CA_CERTIFICATE \
+_(" --ca-certificate=FILE        Use the certificate authorities in FILE to verify\n"\
+  "                              the peers. The certificate file must be in PEM\n"\
+  "                              format and can contain multiple CA certificates.\n"\
+  "                              Use --check-certificate option to enable\n"\
+  "                              verification.")
+#define TEXT_CHECK_CERTIFICATE \
+_(" --check-certificate[=true|false] Verify the peer using certificates specified\n"\
+  "                              in --ca-certificate option.")