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
pull/1/head
Tatsuhiro Tsujikawa 2008-11-09 07:36:44 +00:00
parent 79d463fae2
commit ce4186b4c3
16 changed files with 275 additions and 7 deletions

View File

@ -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.

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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;

View File

@ -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();

View File

@ -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_

View File

@ -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.")

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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.")