/*************************************************************************** * Copyright (C) 2005 by Christof Donat * * cdonat@gmx.de * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "sslclient.h" #include "logger.h" #include #include #include #include #include #include #include #include using std::string; using std::streamsize; using boost::lexical_cast; using boost::bad_lexical_cast; int SSLDevice::countInstances = 0; SSLDevice::SSLDevice(const string& address, const short int port): m_session(0) { this->init(address.c_str(),port); } SSLDevice::SSLDevice(const char* address, const short int port): m_session(0) { this->init(address,port); } void SSLDevice::init(const char* address, const short int port) { struct sockaddr_in addr; bzero(&addr,sizeof(addr)); addr.sin_family = AF_INET; if( (addr.sin_addr.s_addr = inet_addr(address)) == INADDR_NONE ) { struct hostent* hp = gethostbyname(address); if( hp == 0 ) throw("can't resolve hostname '"+string(address)+"'"); bcopy(hp->h_addr, (char*) &addr.sin_addr, hp->h_length); } addr.sin_port = htons(port); if( (m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0 ) throw(string("can't open socket")); if( ::connect(m_socket, (struct sockaddr*) &addr, sizeof(addr)) < 0 ) { ::close(m_socket); throw("can't connect to '"+string(address)+"' port "+(lexical_cast(port))); } const int cert_type_priority[3] = { GNUTLS_CRT_X509, GNUTLS_CRT_OPENPGP, 0 }; m_session = new gnutls_session; if( countInstances == 0 ) gnutls_global_init(); countInstances++; gnutls_certificate_allocate_credentials(&m_xcred); gnutls_certificate_set_x509_trust_file(m_xcred, "ca.pem", GNUTLS_X509_FMT_PEM); gnutls_init(m_session,GNUTLS_CLIENT); gnutls_set_default_priority(*m_session); gnutls_credentials_set(*m_session, GNUTLS_CRD_CERTIFICATE, m_xcred); gnutls_transport_set_ptr(*m_session, (gnutls_transport_ptr)&m_socket); if( gnutls_handshake(*m_session) < 0 ) throw(string(gnutls_protocol_get_name(gnutls_protocol_get_version(*m_session)))+" Handshake failed"); verify_certificate( *m_session, address); } SSLDevice::SSLDevice(const SSLDevice& other) : m_session(other.m_session), m_socket(other.m_socket), m_xcred(other.m_xcred) { } SSLDevice::~SSLDevice() { } void SSLDevice::closeSocket() { gnutls_bye(*m_session, GNUTLS_SHUT_RDWR); gnutls_certificate_free_credentials(m_xcred); ::close(m_socket); gnutls_deinit(*m_session); delete(m_session); countInstances--; if( countInstances == 0 ) gnutls_global_deinit(); } streamsize SSLDevice::read(char* s, streamsize n) { return (streamsize)gnutls_record_recv(*m_session,(void*)s,(size_t)n); } streamsize SSLDevice::write(const char* s, streamsize n) { /*streamsize i = (streamsize)gnutls_record_send(*m_session,(const void*)s,(size_t)n); Logger::log(Logger::DEBUG,string("sending ")+boost::lexical_cast(i)+" von "+boost::lexical_cast(n)+" bytes: \n---\n"+s+"\n---\n");*/ return (streamsize)gnutls_record_send(*m_session,(const void*)s,(size_t)n); //i; } void SSLDevice::verify_certificate( gnutls_session session, string hostname) { boost::filesystem::path general("/etc/hsadminc.cert"); if( boost::filesystem::exists(general) && !boost::filesystem::is_directory(general) ) gnutls_certificate_set_x509_trust_file (m_xcred, general.native().c_str(), GNUTLS_X509_FMT_PEM); //string privfilename = getenv("HOME"); string priv = getenv("HOME"); boost::filesystem::path p( priv + "/.hsadmin.cert" ); //p /= ".hsadminc.conf"; if( boost::filesystem::exists(p) && !boost::filesystem::is_directory(p) ) gnutls_certificate_set_x509_trust_file (m_xcred, p.native().c_str(), GNUTLS_X509_FMT_PEM); //gnutls_certificate_set_x509_trust_file (m_xcred, const char * cafile, PEM) unsigned int status = 0;//gnutls_certificate_verify_peers(*m_session); /*if( gnutls_certificate_verify_peers2(*m_session, &status) < 0 ) throw(string("Error while verifying certificate"));*/ string tval = "Certificate Verification failed: "; if( status & GNUTLS_CERT_INVALID ) { throw(tval+"The certificate is not trustet"); } if( status & GNUTLS_CERT_SIGNER_NOT_FOUND ) { throw(tval+"The certificate has no known Issuer"); } if( status & GNUTLS_CERT_REVOKED ) { throw(tval+"The certificate has been revoked"); } // the further checks are only valid for X509 certificates if ( gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) return; const gnutls_datum* cert_list; unsigned int cert_list_size; gnutls_x509_crt cert; if ( gnutls_x509_crt_init(&cert) < 0) throw(tval+"error in initialization"); cert_list = gnutls_certificate_get_peers(session, &cert_list_size); if ( cert_list == NULL ) throw(tval+"no certificate found"); bool failed = false; string tReason = ""; for( int i = 0; i < cert_list_size-1; i++ ) { bool failed = false; if ( gnutls_x509_crt_import( cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) throw(tval+"error parsing certificate Number "+lexical_cast(i)); if ( gnutls_x509_crt_get_expiration_time(cert) < time(0) ) { tReason = "The certificate has expired"; failed = true; } if ( gnutls_x509_crt_get_activation_time(cert) > time(0) ) { tReason = "The certificate is not yet activated"; failed = true; } if ( !gnutls_x509_crt_check_hostname(cert,hostname.c_str()) ) { tReason = "The certificate’s owner does not match hostname ’"; tReason += hostname +"'"; failed = true; } gnutls_x509_crt_deinit(cert); if( !failed ) break; } if( failed ) throw(tval+tReason); }