mirror of https://github.com/aria2/aria2
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.hpull/1/head
parent
79d463fae2
commit
ce4186b4c3
22
ChangeLog
22
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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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.")
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.")
|
||||
|
|
Loading…
Reference in New Issue