summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>2008-11-09 07:36:44 +0000
committerTatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>2008-11-09 07:36:44 +0000
commitce4186b4c3f8d78bcd77fdc93ab1cbe00e2b45e2 (patch)
treedb9fbe036481a27edc419b2c2e2528e1dfe3ed18 /src
parent79d463fae2e1e3cf87b61bb519de1428f1f2b39c (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.cc2
-rw-r--r--src/LibgnutlsTLSContext.cc26
-rw-r--r--src/LibgnutlsTLSContext.h8
-rw-r--r--src/LibsslTLSContext.cc19
-rw-r--r--src/LibsslTLSContext.h8
-rw-r--r--src/MultiUrlRequestInfo.cc6
-rw-r--r--src/OptionHandlerFactory.cc15
-rw-r--r--src/SocketCore.cc129
-rw-r--r--src/SocketCore.h4
-rw-r--r--src/a2functional.h14
-rw-r--r--src/message.h4
-rw-r--r--src/option_processing.cc8
-rw-r--r--src/prefs.cc4
-rw-r--r--src/prefs.h4
-rw-r--r--src/usage_text.h9
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.")