diff options
| author | Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com> | 2008-11-09 07:36:44 +0000 |
|---|---|---|
| committer | Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com> | 2008-11-09 07:36:44 +0000 |
| commit | ce4186b4c3f8d78bcd77fdc93ab1cbe00e2b45e2 (patch) | |
| tree | db9fbe036481a27edc419b2c2e2528e1dfe3ed18 /src | |
| parent | 79d463fae2e1e3cf87b61bb519de1428f1f2b39c (diff) | |
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
Diffstat (limited to 'src')
| -rw-r--r-- | src/HttpRequestCommand.cc | 2 | ||||
| -rw-r--r-- | src/LibgnutlsTLSContext.cc | 26 | ||||
| -rw-r--r-- | src/LibgnutlsTLSContext.h | 8 | ||||
| -rw-r--r-- | src/LibsslTLSContext.cc | 19 | ||||
| -rw-r--r-- | src/LibsslTLSContext.h | 8 | ||||
| -rw-r--r-- | src/MultiUrlRequestInfo.cc | 6 | ||||
| -rw-r--r-- | src/OptionHandlerFactory.cc | 15 | ||||
| -rw-r--r-- | src/SocketCore.cc | 129 | ||||
| -rw-r--r-- | src/SocketCore.h | 4 | ||||
| -rw-r--r-- | src/a2functional.h | 14 | ||||
| -rw-r--r-- | src/message.h | 4 | ||||
| -rw-r--r-- | src/option_processing.cc | 8 | ||||
| -rw-r--r-- | src/prefs.cc | 4 | ||||
| -rw-r--r-- | src/prefs.h | 4 | ||||
| -rw-r--r-- | src/usage_text.h | 9 |
15 files changed, 253 insertions, 7 deletions
diff --git a/src/HttpRequestCommand.cc b/src/HttpRequestCommand.cc index 9e39ebb2..872cf55d 100644 --- a/src/HttpRequestCommand.cc +++ b/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); diff --git a/src/LibgnutlsTLSContext.cc b/src/LibgnutlsTLSContext.cc index 88a8c79a..3843a620 100644 --- a/src/LibgnutlsTLSContext.cc +++ b/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 diff --git a/src/LibgnutlsTLSContext.h b/src/LibgnutlsTLSContext.h index 5d33438a..043b46f0 100644 --- a/src/LibgnutlsTLSContext.h +++ b/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 diff --git a/src/LibsslTLSContext.cc b/src/LibsslTLSContext.cc index 22e5f08a..17108c32 100644 --- a/src/LibsslTLSContext.cc +++ b/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 diff --git a/src/LibsslTLSContext.h b/src/LibsslTLSContext.h index 8e41bcf0..9f7aff39 100644 --- a/src/LibsslTLSContext.h +++ b/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 diff --git a/src/MultiUrlRequestInfo.cc b/src/MultiUrlRequestInfo.cc index 0524a965..9d666345 100644 --- a/src/MultiUrlRequestInfo.cc +++ b/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 diff --git a/src/OptionHandlerFactory.cc b/src/OptionHandlerFactory.cc index 2f17df92..3acac8a2 100644 --- a/src/OptionHandlerFactory.cc +++ b/src/OptionHandlerFactory.cc @@ -431,6 +431,13 @@ OptionHandlers OptionHandlerFactory::createOptionHandlers() // 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, TEXT_CERTIFICATE)); op->addTag(TAG_HTTP); @@ -438,6 +445,14 @@ OptionHandlers OptionHandlerFactory::createOptionHandlers() } { 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, TEXT_ENABLE_HTTP_KEEP_ALIVE, V_TRUE)); diff --git a/src/SocketCore.cc b/src/SocketCore.cc index aa1bf7e0..e85f86ac 100644 --- a/src/SocketCore.cc +++ b/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; diff --git a/src/SocketCore.h b/src/SocketCore.h index e55c387f..829b1cda 100644 --- a/src/SocketCore.h +++ b/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(); diff --git a/src/a2functional.h b/src/a2functional.h index 15f4acc2..447027d2 100644 --- a/src/a2functional.h +++ b/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_ diff --git a/src/message.h b/src/message.h index a4f51d05..4cb54c99 100644 --- a/src/message.h +++ b/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.") diff --git a/src/option_processing.cc b/src/option_processing.cc index 9e7bd607..4a1c5eca 100644 --- a/src/option_processing.cc +++ b/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; } diff --git a/src/prefs.cc b/src/prefs.cc index dccc0db2..bfdf2f9a 100644 --- a/src/prefs.cc +++ b/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 diff --git a/src/prefs.h b/src/prefs.h index 9d7c863c..f7d21103 100644 --- a/src/prefs.h +++ b/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 diff --git a/src/usage_text.h b/src/usage_text.h index e40cec17..10b1a38b 100644 --- a/src/usage_text.h +++ b/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.") |
