diff options
| author | Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com> | 2013-04-03 02:24:41 +0900 |
|---|---|---|
| committer | Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com> | 2013-04-03 02:24:41 +0900 |
| commit | 8580c98bce8e5b85c1cf7be150d9315cd663db93 (patch) | |
| tree | 5609db1007160261366fe3a2f699bf74f6c606ac /src/LibgnutlsTLSSession.cc | |
| parent | 10a19b138209fde31841a4271582b51940e607d4 (diff) | |
Abstract TLS session implementation
Now TLS session object is abstracted as TLSSession class. Currently,
we have GNUTLS and OpenSSL implementations.
Diffstat (limited to 'src/LibgnutlsTLSSession.cc')
| -rw-r--r-- | src/LibgnutlsTLSSession.cc | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/src/LibgnutlsTLSSession.cc b/src/LibgnutlsTLSSession.cc new file mode 100644 index 00000000..c1d449f3 --- /dev/null +++ b/src/LibgnutlsTLSSession.cc @@ -0,0 +1,266 @@ +/* <!-- copyright */ +/* + * aria2 - The high speed download utility + * + * Copyright (C) 2013 Tatsuhiro Tsujikawa + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you + * do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source + * files in the program, then also delete it here. + */ +/* copyright --> */ +#include "LibgnutlsTLSSession.h" + +#include <gnutls/x509.h> + +#include "TLSContext.h" +#include "util.h" +#include "SocketCore.h" + +namespace aria2 { + +TLSSession::TLSSession(TLSContext* tlsContext) + : sslSession_(0), + tlsContext_(tlsContext), + rv_(0) +{} + +TLSSession::~TLSSession() +{ + if(sslSession_) { + gnutls_deinit(sslSession_); + } +} + +int TLSSession::init(sock_t sockfd) +{ + rv_ = gnutls_init(&sslSession_, + tlsContext_->getSide() == TLS_CLIENT ? + GNUTLS_CLIENT : GNUTLS_SERVER); + if(rv_ != GNUTLS_E_SUCCESS) { + return TLS_ERR_ERROR; + } + // It seems err is not error message, but the argument string + // which causes syntax error. + const char* err; + // For client side, disables TLS1.1 here because there are servers + // that don't understand TLS1.1. TODO Is this still necessary? + rv_ = gnutls_priority_set_direct(sslSession_, + tlsContext_->getSide() == TLS_CLIENT ? + "NORMAL:-VERS-TLS1.1" : + "NORMAL", + &err); + if(rv_ != GNUTLS_E_SUCCESS) { + return TLS_ERR_ERROR; + } + // put the x509 credentials to the current session + rv_ = gnutls_credentials_set(sslSession_, GNUTLS_CRD_CERTIFICATE, + tlsContext_->getCertCred()); + if(rv_ != GNUTLS_E_SUCCESS) { + return TLS_ERR_ERROR; + } + // TODO Consider to use gnutls_transport_set_int() for GNUTLS 3.1.9 + // or later + gnutls_transport_set_ptr(sslSession_, + (gnutls_transport_ptr_t)(ptrdiff_t)sockfd); + return TLS_ERR_OK; +} + +int TLSSession::setSNIHostname(const std::string& hostname) +{ + // TLS extensions: SNI + rv_ = gnutls_server_name_set(sslSession_, GNUTLS_NAME_DNS, + hostname.c_str(), hostname.size()); + if(rv_ != GNUTLS_E_SUCCESS) { + return TLS_ERR_ERROR; + } + return TLS_ERR_OK; +} + +int TLSSession::closeConnection() +{ + rv_ = gnutls_bye(sslSession_, GNUTLS_SHUT_WR); + if(rv_ == GNUTLS_E_SUCCESS) { + return TLS_ERR_OK; + } else if(rv_ == GNUTLS_E_AGAIN) { + return TLS_ERR_WOULDBLOCK; + } else { + return TLS_ERR_ERROR; + } +} + +int TLSSession::checkDirection() +{ + int direction = gnutls_record_get_direction(sslSession_); + return direction == 0 ? TLS_WANT_READ : TLS_WANT_WRITE; +} + +ssize_t TLSSession::writeData(const void* data, size_t len) +{ + while((rv_ = gnutls_record_send(sslSession_, data, len)) == + GNUTLS_E_INTERRUPTED); + if(rv_ >= 0) { + ssize_t ret = rv_; + rv_ = 0; + return ret; + } else if(rv_ == GNUTLS_E_AGAIN) { + return TLS_ERR_WOULDBLOCK; + } else { + return TLS_ERR_ERROR; + } +} + +ssize_t TLSSession::readData(void* data, size_t len) +{ + while((rv_ = gnutls_record_recv(sslSession_, data, len)) == + GNUTLS_E_INTERRUPTED); + if(rv_ >= 0) { + ssize_t ret = rv_; + rv_ = 0; + return ret; + } else if(rv_ == GNUTLS_E_AGAIN) { + return TLS_ERR_WOULDBLOCK; + } else { + return TLS_ERR_ERROR; + } +} + +int TLSSession::tlsConnect(const std::string& hostname, + std::string& handshakeErr) +{ + handshakeErr = ""; + rv_ = gnutls_handshake(sslSession_); + if(rv_ < 0) { + if(rv_ == GNUTLS_E_AGAIN) { + return TLS_ERR_WOULDBLOCK; + } else { + return TLS_ERR_ERROR; + } + } + if(tlsContext_->peerVerificationEnabled()) { + // verify peer + unsigned int status; + rv_ = gnutls_certificate_verify_peers2(sslSession_, &status); + if(rv_ != GNUTLS_E_SUCCESS) { + return TLS_ERR_ERROR; + } + if(status) { + handshakeErr = ""; + if(status & GNUTLS_CERT_INVALID) { + handshakeErr += " `not signed by known authorities or invalid'"; + } + if(status & GNUTLS_CERT_REVOKED) { + handshakeErr += " `revoked by its CA'"; + } + if(status & GNUTLS_CERT_SIGNER_NOT_FOUND) { + handshakeErr += " `issuer is not known'"; + } + // TODO should check GNUTLS_CERT_SIGNER_NOT_CA ? + if(status & GNUTLS_CERT_INSECURE_ALGORITHM) { + handshakeErr += " `insecure algorithm'"; + } + if(status & GNUTLS_CERT_NOT_ACTIVATED) { + handshakeErr += " `not activated yet'"; + } + if(status & GNUTLS_CERT_EXPIRED) { + handshakeErr += " `expired'"; + } + // TODO Add GNUTLS_CERT_SIGNATURE_FAILURE here + if(!handshakeErr.empty()) { + return TLS_ERR_ERROR; + } + } + // certificate type: only X509 is allowed. + if(gnutls_certificate_type_get(sslSession_) != GNUTLS_CRT_X509) { + handshakeErr = "certificate type must be X509"; + return TLS_ERR_ERROR; + } + unsigned int peerCertsLength; + const gnutls_datum_t* peerCerts; + peerCerts = gnutls_certificate_get_peers(sslSession_, &peerCertsLength); + if(!peerCerts || peerCertsLength == 0 ) { + handshakeErr = "certificate not found"; + return TLS_ERR_ERROR; + } + gnutls_x509_crt_t cert; + rv_ = gnutls_x509_crt_init(&cert); + if(rv_ != GNUTLS_E_SUCCESS) { + return TLS_ERR_ERROR; + } + auto_delete<gnutls_x509_crt_t> certDeleter(cert, gnutls_x509_crt_deinit); + rv_ = gnutls_x509_crt_import(cert, &peerCerts[0], GNUTLS_X509_FMT_DER); + if(rv_ != GNUTLS_E_SUCCESS) { + return TLS_ERR_ERROR; + } + std::string commonName; + std::vector<std::string> dnsNames; + std::vector<std::string> ipAddrs; + int ret = 0; + char altName[256]; + size_t altNameLen; + for(int i = 0; !(ret < 0); ++i) { + altNameLen = sizeof(altName); + ret = gnutls_x509_crt_get_subject_alt_name(cert, i, altName, + &altNameLen, 0); + if(ret == GNUTLS_SAN_DNSNAME) { + dnsNames.push_back(std::string(altName, altNameLen)); + } else if(ret == GNUTLS_SAN_IPADDRESS) { + ipAddrs.push_back(std::string(altName, altNameLen)); + } + } + altNameLen = sizeof(altName); + ret = gnutls_x509_crt_get_dn_by_oid(cert, + GNUTLS_OID_X520_COMMON_NAME, 0, 0, + altName, &altNameLen); + if(ret == 0) { + commonName.assign(altName, altNameLen); + } + if(!net::verifyHostname(hostname, dnsNames, ipAddrs, commonName)) { + handshakeErr = "hostname does not match"; + return TLS_ERR_ERROR; + } + } + return TLS_ERR_OK; +} + +int TLSSession::tlsAccept() +{ + rv_ = gnutls_handshake(sslSession_); + if(rv_ == GNUTLS_E_SUCCESS) { + return TLS_ERR_OK; + } else if(rv_ == GNUTLS_E_AGAIN) { + return TLS_ERR_WOULDBLOCK; + } else { + return TLS_ERR_ERROR; + } +} + +std::string TLSSession::getLastErrorString() +{ + return gnutls_strerror(rv_); +} + +} // namespace aria2 |
