E-MailRelay

gssl_openssl.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2021 Graeme Walker <graeme_walker@users.sourceforge.net>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16// ===
17///
18/// \file gssl_openssl.cpp
19///
20
21#include "gdef.h"
22#include "gssl.h"
23#include "gssl_openssl.h"
24#include "ghashstate.h"
25#include "gformat.h"
26#include "ggettext.h"
27#include "gtest.h"
28#include "gstr.h"
29#include "gpath.h"
30#include "gprocess.h"
31#include "gfile.h"
32#include "groot.h"
33#include "gexception.h"
34#include "glog.h"
35#include <openssl/ssl.h>
36#include <openssl/err.h>
37#include <openssl/rand.h>
38#include <openssl/conf.h>
39#include <openssl/evp.h>
40#include <openssl/hmac.h>
41#include <exception>
42#include <functional>
43#include <vector>
44#include <sstream>
45#include <utility>
46#include <algorithm>
47
48GSsl::OpenSSL::LibraryImp::LibraryImp( G::StringArray & library_config , Library::LogFn log_fn , bool verbose ) :
49 m_log_fn(log_fn) ,
50 m_verbose(verbose) ,
51 m_config(library_config)
52{
53#if OPENSSL_VERSION_NUMBER < 0x10100000L
54 // library initialisation -- not required for OpenSSL v1.1 (August 2016)
55 SSL_load_error_strings() ;
56 SSL_library_init() ;
57 OpenSSL_add_all_digests() ;
58#endif
59
60 // "on systems without /dev/*random devices providing entropy from the kernel the EGD entropy
61 // gathering daemon can be used to collect entropy... OpenSSL automatically queries EGD when
62 // entropy is ... checked via RAND_status() for the first time" (man RAND_egd(3))
63 GDEF_IGNORE_RETURN RAND_status() ;
64
65 // allocate a slot for a pointer from SSL to ProtocolImp
66 m_index = SSL_get_ex_new_index( 0 , nullptr , nullptr , nullptr , nullptr ) ;
67 if( m_index < 0 )
68 {
69 cleanup() ;
70 throw Error( "SSL_get_ex_new_index" ) ;
71 }
72}
73
74GSsl::OpenSSL::LibraryImp::~LibraryImp()
75{
76 cleanup() ;
77}
78
79 void GSsl::OpenSSL::LibraryImp::cleanup()
80{
81#if OPENSSL_VERSION_NUMBER < 0x10100000L
82 ERR_free_strings() ;
83 RAND_cleanup() ;
84 CRYPTO_cleanup_all_ex_data();
85#endif
86}
87
88std::string GSsl::OpenSSL::LibraryImp::sid()
89{
90#if OPENSSL_VERSION_NUMBER < 0x10100000L
91 std::string v = SSLeay_version(SSLEAY_VERSION) ;
92#else
93 std::string v = OpenSSL_version(OPENSSL_VERSION) ;
94#endif
95 return G::Str::unique( G::Str::printable(v) , ' ' ) ;
96}
97
98std::string GSsl::OpenSSL::LibraryImp::id() const
99{
100 return sid() ;
101}
102
103 bool GSsl::OpenSSL::LibraryImp::generateKeyAvailable() const
104{
105 return false ;
106}
107
108std::string GSsl::OpenSSL::LibraryImp::generateKey( const std::string & ) const
109{
110 return std::string() ;
111}
112
113 GSsl::OpenSSL::Config GSsl::OpenSSL::LibraryImp::config() const
114{
115 return m_config ;
116}
117
118std::string GSsl::OpenSSL::LibraryImp::credit( const std::string & prefix , const std::string & eol , const std::string & eot )
119{
120 std::ostringstream ss ;
121 ss
122 << prefix << "This product includes software developed by the OpenSSL Project" << eol
123 << prefix << "for use in the OpenSSL Toolkit (http://www.openssl.org/)" << eol
124 << eot ;
125 return ss.str() ;
126}
127
128 void GSsl::OpenSSL::LibraryImp::addProfile( const std::string & profile_name , bool is_server_profile ,
129 const std::string & key_file , const std::string & cert_file , const std::string & ca_file ,
130 const std::string & default_peer_certificate_name , const std::string & default_peer_host_name ,
131 const std::string & profile_config )
132{
133 std::shared_ptr<ProfileImp> profile_ptr =
134 std::make_shared<ProfileImp>(*this,is_server_profile,key_file,cert_file,ca_file,
135 default_peer_certificate_name,default_peer_host_name,profile_config) ;
136 m_profile_map.insert( Map::value_type(profile_name,profile_ptr) ) ;
137}
138
139 bool GSsl::OpenSSL::LibraryImp::hasProfile( const std::string & profile_name ) const
140{
141 auto p = m_profile_map.find( profile_name ) ;
142 return p != m_profile_map.end() ;
143}
144
145 const GSsl::Profile & GSsl::OpenSSL::LibraryImp::profile( const std::string & profile_name ) const
146{
147 auto p = m_profile_map.find( profile_name ) ;
148 if( p == m_profile_map.end() ) throw Error( "no such profile: [" + profile_name + "]" ) ;
149 return *(*p).second ;
150}
151
152GSsl::Library::LogFn GSsl::OpenSSL::LibraryImp::log() const
153{
154 return m_log_fn ;
155}
156
157 bool GSsl::OpenSSL::LibraryImp::verbose() const
158{
159 return m_verbose ;
160}
161
162 int GSsl::OpenSSL::LibraryImp::index() const
163{
164 return m_index ;
165}
166
167 G::StringArray GSsl::OpenSSL::LibraryImp::digesters( bool need_state ) const
168{
169 G::StringArray result ;
170 if( !need_state )
171 result.push_back( "SHA512" ) ;
172 result.push_back( "SHA256" ) ;
173 result.push_back( "SHA1" ) ;
174 result.push_back( "MD5" ) ;
175 return result ;
176}
177
178 GSsl::Digester GSsl::OpenSSL::LibraryImp::digester( const std::string & hash_type , const std::string & state , bool need_state ) const
179{
180 return Digester( std::make_unique<GSsl::OpenSSL::DigesterImp>(hash_type,state,need_state) ) ;
181}
182
183GSsl::OpenSSL::DigesterImp::DigesterImp( const std::string & hash_type , const std::string & state , bool need_state ) :
184 m_hash_type(Type::Other) ,
185 m_evp_ctx(nullptr)
186{
187 bool have_state = !state.empty() ;
188 if( hash_type == "MD5" && ( have_state || need_state ) )
189 {
190 m_hash_type = Type::Md5 ;
191 MD5_Init( &m_md5 ) ;
192 if( have_state )
193 G::HashState<16,MD5_LONG,MD5_LONG>::decode( state , m_md5.Nh , m_md5.Nl , &m_md5.A , &m_md5.B , &m_md5.C , &m_md5.D ) ;
194 m_block_size = 64U ;
195 m_value_size = 16U ;
196 m_state_size = m_value_size + 4U ;
197 }
198 else if( hash_type == "SHA1" && ( have_state || need_state ) )
199 {
200 m_hash_type = Type::Sha1 ;
201 SHA1_Init( &m_sha1 ) ;
202 if( have_state )
203 G::HashState<20,SHA_LONG,SHA_LONG>::decode( state , m_sha1.Nh , m_sha1.Nl , &m_sha1.h0 , &m_sha1.h1 , &m_sha1.h2 , &m_sha1.h3 , &m_sha1.h4 ) ;
204 m_block_size = 64U ;
205 m_value_size = 20U ;
206 m_state_size = m_value_size + 4U ;
207 }
208 else if( hash_type == "SHA256" && ( have_state || need_state ) )
209 {
210 m_hash_type = Type::Sha256 ;
211 SHA256_Init( &m_sha256 ) ;
212 if( have_state )
213 G::HashState<32,SHA_LONG,unsigned int>::decode( state , m_sha256.Nh , m_sha256.Nl , m_sha256.h ) ;
214 m_block_size = 64U ;
215 m_value_size = 32U ;
216 m_state_size = m_value_size + 4U ;
217 }
218 else
219 {
220 m_hash_type = Type::Other ;
221 m_evp_ctx = EVP_MD_CTX_create() ;
222
223 const EVP_MD * md = EVP_get_digestbyname( hash_type.c_str() ) ;
224 if( md == nullptr )
225 throw Error( "unsupported hash function name: [" + hash_type + "]" ) ;
226
227 m_block_size = static_cast<std::size_t>( EVP_MD_block_size(md) ) ;
228 m_value_size = static_cast<std::size_t>( EVP_MD_size(md) ) ;
229 m_state_size = 0U ; // intermediate state not available
230
231 if( m_state_size == 0U && !state.empty() )
232 throw Error( "hash state resoration not implemented for " + hash_type ) ;
233
234 EVP_DigestInit_ex( m_evp_ctx , md , nullptr ) ;
235 }
236}
237
238GSsl::OpenSSL::DigesterImp::~DigesterImp()
239{
240 if( m_hash_type == Type::Other )
241 EVP_MD_CTX_destroy( m_evp_ctx ) ;
242}
243
244std::size_t GSsl::OpenSSL::DigesterImp::blocksize() const
245{
246 return m_block_size ;
247}
248
249std::size_t GSsl::OpenSSL::DigesterImp::valuesize() const
250{
251 return m_value_size ;
252}
253
254std::size_t GSsl::OpenSSL::DigesterImp::statesize() const
255{
256 return m_state_size ;
257}
258
259std::string GSsl::OpenSSL::DigesterImp::state()
260{
261 if( m_hash_type == Type::Md5 )
262 return G::HashState<16,MD5_LONG,MD5_LONG>::encode( m_md5.Nh , m_md5.Nl , m_md5.A , m_md5.B , m_md5.C , m_md5.D ) ;
263 else if( m_hash_type == Type::Sha1 )
264 return G::HashState<20,SHA_LONG,SHA_LONG>::encode( m_sha1.Nh , m_sha1.Nl , m_sha1.h0 , m_sha1.h1 , m_sha1.h2 , m_sha1.h3 , m_sha1.h4 ) ;
265 else if( m_hash_type == Type::Sha256 )
266 return G::HashState<32,SHA_LONG,SHA_LONG>::encode( m_sha256.Nh , m_sha256.Nl , m_sha256.h ) ;
267 else
268 return std::string() ; // not available
269}
270
271 void GSsl::OpenSSL::DigesterImp::add( const std::string & data )
272{
273 if( m_hash_type == Type::Md5 )
274 MD5_Update( &m_md5 , data.data() , data.size() ) ;
275 else if( m_hash_type == Type::Sha1 )
276 SHA1_Update( &m_sha1 , data.data() , data.size() ) ;
277 else if( m_hash_type == Type::Sha256 )
278 SHA256_Update( &m_sha256 , data.data() , data.size() ) ;
279 else
280 EVP_DigestUpdate( m_evp_ctx , data.data() , data.size() ) ;
281}
282
283std::string GSsl::OpenSSL::DigesterImp::value()
284{
285 std::vector<unsigned char> output ;
286 std::size_t n = 0U ;
287 if( m_hash_type == Type::Md5 )
288 {
289 n = MD5_DIGEST_LENGTH ;
290 output.resize( n ) ;
291 MD5_Final( &output[0] , &m_md5 ) ;
292 }
293 else if( m_hash_type == Type::Sha1 )
294 {
295 n = SHA_DIGEST_LENGTH ;
296 output.resize( n ) ;
297 SHA1_Final( &output[0] , &m_sha1 ) ;
298 }
299 else if( m_hash_type == Type::Sha256 )
300 {
301 n = SHA256_DIGEST_LENGTH ;
302 output.resize( n ) ;
303 SHA256_Final( &output[0] , &m_sha256 ) ;
304 }
305 else
306 {
307 unsigned int output_size = 0 ;
308 output.resize( EVP_MAX_MD_SIZE ) ;
309 EVP_DigestFinal_ex( m_evp_ctx , &output[0] , &output_size ) ;
310 n = static_cast<std::size_t>(output_size) ;
311 }
312 G_ASSERT( n == valuesize() ) ;
313 const char * p = reinterpret_cast<char*>(&output[0]) ;
314 return std::string(p,n) ;
315}
316
317// ==
318
319GSsl::OpenSSL::ProfileImp::ProfileImp( const LibraryImp & library_imp , bool is_server_profile ,
320 const std::string & key_file , const std::string & cert_file , const std::string & ca_path ,
321 const std::string & default_peer_certificate_name , const std::string & default_peer_host_name ,
322 const std::string & profile_config ) :
323 m_library_imp(library_imp) ,
324 m_default_peer_certificate_name(default_peer_certificate_name) ,
325 m_default_peer_host_name(default_peer_host_name) ,
326 m_ssl_ctx(nullptr,std::function<void(SSL_CTX*)>(deleter))
327{
328 using G::format ;
329 using G::gettext ;
330 Config extra_config = m_library_imp.config() ;
331 if( !profile_config.empty() )
332 {
333 G::StringArray profile_config_list = G::Str::splitIntoTokens( profile_config , "," ) ;
334 extra_config = Config( profile_config_list ) ;
335 if( !profile_config_list.empty() )
336 G_WARNING( "GSsl::OpenSSL::ProfileImp::ctor: tls-config: tls " << (is_server_profile?"server":"client")
337 << " profile configuration ignored: [" << G::Str::join(",",profile_config_list) << "]" ) ;
338 }
339
340 if( m_ssl_ctx == nullptr )
341 {
342 Config::Fn version_fn = extra_config.fn( is_server_profile ) ;
343 m_ssl_ctx.reset( SSL_CTX_new( version_fn() ) ) ;
344 if( m_ssl_ctx != nullptr )
345 apply( extra_config ) ;
346 }
347
348 if( m_ssl_ctx == nullptr )
349 throw Error( "SSL_CTX_new" , ERR_get_error() ) ;
350
351 if( !key_file.empty() )
352 {
353 G::Root claim_root ;
354 if( !G::File::exists(key_file) )
355 G_WARNING( "GSsl::Profile: " << format(gettext("cannot open ssl key file: %1%")) % key_file ) ;
356
357 check( SSL_CTX_use_PrivateKey_file(m_ssl_ctx.get(),key_file.c_str(),SSL_FILETYPE_PEM) ,
358 "use_PrivateKey_file" , key_file ) ;
359 }
360
361 if( !cert_file.empty() )
362 {
363 G::Root claim_root ;
364 if( !G::File::exists(cert_file) )
365 G_WARNING( "GSsl::Profile: " << format(gettext("cannot open ssl certificate file: %1%")) % cert_file ) ;
366
367 check( SSL_CTX_use_certificate_chain_file(m_ssl_ctx.get(),cert_file.c_str()) ,
368 "use_certificate_chain_file" , cert_file ) ;
369 }
370
371 if( ca_path.empty() )
372 {
373 // ask for peer certificates but just log them without verifying - we don't
374 // use set_client_CA_list() so we allow the client to not send a certificate
375 SSL_CTX_set_verify( m_ssl_ctx.get() , SSL_VERIFY_PEER , verifyPass ) ;
376 }
377 else if( ca_path == "<none>" )
378 {
379 // dont ask for client certificates (server-side)
380 SSL_CTX_set_verify( m_ssl_ctx.get() , SSL_VERIFY_NONE , nullptr ) ;
381 }
382 else if( ca_path == "<default>" )
383 {
384 // ask for certificates, make sure they verify against the default ca database, and check the name in the certificate (if given)
385 bool no_verify = extra_config.noverify() ;
386 SSL_CTX_set_verify( m_ssl_ctx.get() , no_verify ? SSL_VERIFY_NONE : (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT) , verifyPeerName ) ;
387 check( SSL_CTX_set_default_verify_paths( m_ssl_ctx.get() ) , "set_default_verify_paths" ) ;
388 }
389 else
390 {
391 // ask for certificates, make sure they verify against the given ca database, and check the name in the certificate (if given)
392 bool ca_path_is_dir = G::File::isDirectory( ca_path , std::nothrow ) ;
393 const char * ca_file_p = ca_path_is_dir ? nullptr : ca_path.c_str() ;
394 const char * ca_dir_p = ca_path_is_dir ? ca_path.c_str() : nullptr ;
395 bool no_verify = extra_config.noverify() ;
396 SSL_CTX_set_verify( m_ssl_ctx.get() , no_verify ? SSL_VERIFY_NONE : (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT) , verifyPeerName ) ;
397 check( SSL_CTX_load_verify_locations( m_ssl_ctx.get() , ca_file_p , ca_dir_p ) , "load_verify_locations" , ca_path ) ;
398 }
399
400 SSL_CTX_set_cipher_list( m_ssl_ctx.get() , "DEFAULT" ) ;
401 SSL_CTX_set_session_cache_mode( m_ssl_ctx.get() , SSL_SESS_CACHE_OFF ) ;
402 if( is_server_profile )
403 {
404 static std::string x = "GSsl.OpenSSL." + G::Path(G::Process::exe()).basename() ;
405 SSL_CTX_set_session_id_context( m_ssl_ctx.get() , reinterpret_cast<const unsigned char *>(x.data()) , x.size() ) ;
406 }
407}
408
409GSsl::OpenSSL::ProfileImp::~ProfileImp()
410= default;
411
412 void GSsl::OpenSSL::ProfileImp::deleter( SSL_CTX * p )
413{
414 if( p != nullptr )
415 SSL_CTX_free( p ) ;
416}
417
418std::unique_ptr<GSsl::ProtocolImpBase> GSsl::OpenSSL::ProfileImp::newProtocol( const std::string & peer_certificate_name ,
419 const std::string & peer_host_name ) const
420{
421 return std::make_unique<OpenSSL::ProtocolImp>( *this ,
422 peer_certificate_name.empty()?defaultPeerCertificateName():peer_certificate_name ,
423 peer_host_name.empty()?defaultPeerHostName():peer_host_name ) ; // up-cast
424}
425
426SSL_CTX * GSsl::OpenSSL::ProfileImp::p() const
427{
428 return const_cast<SSL_CTX*>( m_ssl_ctx.get() ) ;
429}
430
431 const GSsl::OpenSSL::LibraryImp & GSsl::OpenSSL::ProfileImp::lib() const
432{
433 return m_library_imp ;
434}
435
436 const std::string & GSsl::OpenSSL::ProfileImp::defaultPeerCertificateName() const
437{
438 return m_default_peer_certificate_name ;
439}
440
441 const std::string & GSsl::OpenSSL::ProfileImp::defaultPeerHostName() const
442{
443 return m_default_peer_host_name ;
444}
445
446 void GSsl::OpenSSL::ProfileImp::check( int rc , const std::string & fnname_tail , const std::string & file )
447{
448 if( rc != 1 )
449 {
450 std::string fnname = "SSL_CTX_" + fnname_tail ;
451 throw Error( fnname , ERR_get_error() , file ) ;
452 }
453}
454
455 void GSsl::OpenSSL::ProfileImp::apply( const Config & config )
456{
457 #if GCONFIG_HAVE_OPENSSL_MIN_MAX
458 if( config.min_() )
459 SSL_CTX_set_min_proto_version( m_ssl_ctx.get() , config.min_() ) ;
460
461 if( config.max_() )
462 SSL_CTX_set_max_proto_version( m_ssl_ctx.get() , config.max_() ) ;
463 #endif
464
465 if( config.reset() != 0L )
466 SSL_CTX_clear_options( m_ssl_ctx.get() , config.reset() ) ;
467
468 if( config.set() != 0L )
469 SSL_CTX_set_options( m_ssl_ctx.get() , config.set() ) ;
470}
471
472 int GSsl::OpenSSL::ProfileImp::verifyPass( int /*ok*/ , X509_STORE_CTX * )
473{
474 return 1 ;
475}
476
477 int GSsl::OpenSSL::ProfileImp::verifyPeerName( int ok , X509_STORE_CTX * ctx )
478{
479 try
480 {
481 if( ok && X509_STORE_CTX_get_error_depth(ctx) == 0 )
482 {
483 SSL * ssl = static_cast<SSL*>( X509_STORE_CTX_get_ex_data( ctx , SSL_get_ex_data_X509_STORE_CTX_idx() ) ) ;
484 if( ssl == nullptr )
485 return 0 ; // never gets here
486
487 OpenSSL::LibraryImp & library = dynamic_cast<OpenSSL::LibraryImp&>( Library::impstance() ) ;
488 OpenSSL::ProtocolImp * protocol = static_cast<OpenSSL::ProtocolImp*>( SSL_get_ex_data(ssl,library.index()) ) ;
489 if( protocol == nullptr )
490 return 0 ; // never gets here
491
492 std::string required_peer_certificate_name = protocol->requiredPeerCertificateName() ;
493 if( !required_peer_certificate_name.empty() )
494 {
495 X509 * cert = X509_STORE_CTX_get_current_cert( ctx ) ;
496 std::string subject = name(X509_get_subject_name(cert)) ;
497 G::StringArray subject_parts = G::Str::splitIntoTokens( subject , "/" ) ;
498 bool found = std::find( subject_parts.begin() , subject_parts.end() , "CN="+required_peer_certificate_name ) != subject_parts.end() ;
499 library.log()( 2 , "certificate-subject=[" + subject + "] required-peer-name=[" + required_peer_certificate_name + "] ok=" + (found?"1":"0") ) ;
500 if( !found )
501 {
502 ok = 0 ;
503 }
504 }
505 }
506 return ok ;
507 }
508 catch(...) // callback from c code
509 {
510 return 0 ;
511 }
512}
513
514std::string GSsl::OpenSSL::ProfileImp::name( X509_NAME * x509_name )
515{
516 if( x509_name == nullptr ) return std::string() ;
517 std::vector<char> buffer( 2048U ) ; // 200 in openssl code
518 X509_NAME_oneline( x509_name , &buffer[0] , buffer.size() ) ;
519 buffer.back() = '\0' ;
520 return G::Str::printable( std::string(&buffer[0]) ) ;
521}
522
523// ==
524
525GSsl::OpenSSL::ProtocolImp::ProtocolImp( const ProfileImp & profile , const std::string & required_peer_certificate_name ,
526 const std::string & target_peer_host_name ) :
527 m_ssl(nullptr,std::function<void(SSL*)>(deleter)) ,
528 m_log_fn(profile.lib().log()) ,
529 m_verbose(profile.lib().verbose()) ,
530 m_fd_set(false) ,
531 m_required_peer_certificate_name(required_peer_certificate_name) ,
532 m_verified(false)
533{
534 m_ssl.reset( SSL_new(profile.p()) ) ;
535 if( m_ssl == nullptr )
536 throw Error( "SSL_new" , ERR_get_error() ) ;
537
538 // TODO feature test for SSL_set_tlsext_host_name() ?
539 if( !target_peer_host_name.empty() )
540 SSL_set_tlsext_host_name( m_ssl.get() , target_peer_host_name.c_str() ) ;
541
542 // store a pointer from SSL to ProtocolImp
543 SSL_set_ex_data( m_ssl.get() , profile.lib().index() , this ) ;
544}
545
546GSsl::OpenSSL::ProtocolImp::~ProtocolImp()
547= default;
548
549 void GSsl::OpenSSL::ProtocolImp::deleter( SSL * p )
550{
551 if( p != nullptr )
552 SSL_free( p ) ;
553}
554
555 void GSsl::OpenSSL::ProtocolImp::clearErrors()
556{
557 // "The current thread's error queue must be empty before [SSL_connect,SSL_accept,SSL_read,SSL_write] "
558 // "is attempted, or SSL_get_error() will not work reliably."
559 Error::clearErrors() ;
560}
561
562 int GSsl::OpenSSL::ProtocolImp::error( const char * op , int rc ) const
563{
564 int e = SSL_get_error( m_ssl.get() , rc ) ;
565 logErrors( op , rc , e , Protocol::str(convert(e)) ) ;
566 return e ;
567}
568
569GSsl::Protocol::Result GSsl::OpenSSL::ProtocolImp::convert( int e )
570{
571 if( e == SSL_ERROR_WANT_READ ) return Protocol::Result::read ;
572 if( e == SSL_ERROR_WANT_WRITE ) return Protocol::Result::write ;
573 return Protocol::Result::error ;
574}
575
576GSsl::Protocol::Result GSsl::OpenSSL::ProtocolImp::connect( G::ReadWrite & io )
577{
578 set( io.fd() ) ;
579 return connect() ;
580}
581
582GSsl::Protocol::Result GSsl::OpenSSL::ProtocolImp::accept( G::ReadWrite & io )
583{
584 set( io.fd() ) ;
585 return accept() ;
586}
587
588 void GSsl::OpenSSL::ProtocolImp::set( int fd )
589{
590 if( !m_fd_set )
591 {
592 int rc = SSL_set_fd( m_ssl.get() , fd ) ;
593 if( rc == 0 )
594 throw Error( "SSL_set_fd" , ERR_get_error() ) ;
595
596 if( G::Test::enabled("log-openssl-bio") ) // log bio activity directly to stderr
597 {
598 BIO_set_callback( SSL_get_rbio(m_ssl.get()) , BIO_debug_callback ) ;
599 BIO_set_callback( SSL_get_wbio(m_ssl.get()) , BIO_debug_callback ) ;
600 }
601
602 m_fd_set = true ;
603 }
604}
605
606GSsl::Protocol::Result GSsl::OpenSSL::ProtocolImp::connect()
607{
608 clearErrors() ;
609 int rc = SSL_connect( m_ssl.get() ) ;
610 if( rc >= 1 )
611 {
612 saveResult() ;
613 return Protocol::Result::ok ;
614 }
615 else if( rc == 0 )
616 {
617 return convert(error("SSL_connect",rc)) ;
618 }
619 else // rc < 0
620 {
621 return convert(error("SSL_connect",rc)) ;
622 }
623}
624
625GSsl::Protocol::Result GSsl::OpenSSL::ProtocolImp::accept()
626{
627 clearErrors() ;
628 int rc = SSL_accept( m_ssl.get() ) ;
629 if( rc >= 1 )
630 {
631 saveResult() ;
632 return Protocol::Result::ok ;
633 }
634 else if( rc == 0 )
635 {
636 return convert(error("SSL_accept",rc)) ;
637 }
638 else // rc < 0
639 {
640 return convert(error("SSL_accept",rc)) ;
641 }
642}
643
644 void GSsl::OpenSSL::ProtocolImp::saveResult()
645{
646 m_peer_certificate = Certificate(SSL_get_peer_certificate(m_ssl.get()),true).str() ;
647 m_peer_certificate_chain = CertificateChain(SSL_get_peer_cert_chain(m_ssl.get())).str() ;
648 m_verified = !m_peer_certificate.empty() && SSL_get_verify_result(m_ssl.get()) == X509_V_OK ;
649}
650
651GSsl::Protocol::Result GSsl::OpenSSL::ProtocolImp::shutdown()
652{
653 int rc = SSL_shutdown( m_ssl.get() ) ;
654 if( rc == 0 || rc == 1 )
655 return Protocol::Result::ok ;
656 else
657 return convert( rc ) ;
658}
659
660GSsl::Protocol::Result GSsl::OpenSSL::ProtocolImp::read( char * buffer , std::size_t buffer_size_in , ssize_t & read_size )
661{
662 read_size = 0 ;
663
664 clearErrors() ;
665 int buffer_size = static_cast<int>(buffer_size_in) ;
666 int rc = SSL_read( m_ssl.get() , buffer , buffer_size ) ;
667 if( rc > 0 )
668 {
669 read_size = static_cast<ssize_t>(rc) ;
670 return SSL_pending(m_ssl.get()) ? Protocol::Result::more : Protocol::Result::ok ;
671 }
672 else if( rc == 0 )
673 {
674 return convert(error("SSL_read",rc)) ;
675 }
676 else // rc < 0
677 {
678 return convert(error("SSL_read",rc)) ;
679 }
680}
681
682GSsl::Protocol::Result GSsl::OpenSSL::ProtocolImp::write( const char * buffer , std::size_t size_in , ssize_t & size_out )
683{
684 size_out = 0 ;
685 clearErrors() ;
686 int size = static_cast<int>(size_in) ;
687 int rc = SSL_write( m_ssl.get() , buffer , size ) ;
688 if( rc > 0 )
689 {
690 size_out = static_cast<ssize_t>(rc) ;
691 return Protocol::Result::ok ;
692 }
693 else if( rc == 0 )
694 {
695 return convert(error("SSL_write",rc)) ;
696 }
697 else // rc < 0
698 {
699 return convert(error("SSL_write",rc)) ;
700 }
701}
702
703std::string GSsl::OpenSSL::ProtocolImp::peerCertificate() const
704{
705 return m_peer_certificate ;
706}
707
708std::string GSsl::OpenSSL::ProtocolImp::peerCertificateChain() const
709{
710 return m_peer_certificate_chain ;
711}
712
713std::string GSsl::OpenSSL::ProtocolImp::cipher() const
714{
715 const SSL_CIPHER * cipher = SSL_get_current_cipher( const_cast<SSL*>(m_ssl.get()) ) ;
716 const char * name = cipher ? SSL_CIPHER_get_name(cipher) : nullptr ;
717 return name ? G::Str::printable(name) : std::string() ;
718}
719
720std::string GSsl::OpenSSL::ProtocolImp::protocol() const
721{
722 #if OPENSSL_VERSION_NUMBER < 0x10100000L
723 int v = SSL_version( m_ssl.get() ) ;
724 #else
725 const SSL_SESSION * session = SSL_get_session( const_cast<SSL*>(m_ssl.get()) ) ;
726 if( session == nullptr ) return std::string() ;
727 int v = SSL_SESSION_get_protocol_version( session ) ;
728 #endif
729 #ifdef TLS1_VERSION
730 if( v == TLS1_VERSION ) return "TLSv1.0" ; // cf. mbedtls
731 #endif
732 #ifdef TLS1_1_VERSION
733 if( v == TLS1_1_VERSION ) return "TLSv1.1" ;
734 #endif
735 #ifdef TLS1_2_VERSION
736 if( v == TLS1_2_VERSION ) return "TLSv1.2" ;
737 #endif
738 #ifdef TLS1_3_VERSION
739 if( v == TLS1_3_VERSION ) return "TLSv1.3" ;
740 #else
741 if( v == 0x304 ) return "TLSv1.3" ; // grr libressl
742 #endif
743 return "#" + G::Str::fromInt(v) ;
744}
745
746 bool GSsl::OpenSSL::ProtocolImp::verified() const
747{
748 return m_verified ;
749}
750
751 void GSsl::OpenSSL::ProtocolImp::logErrors( const std::string & op , int rc , int e , const std::string & strerr ) const
752{
753 if( m_log_fn != nullptr )
754 {
755 if( m_verbose )
756 {
757 std::ostringstream ss ;
758 ss << op << ": rc=" << rc << ": error " << e << " => " << strerr ;
759 (*m_log_fn)( 1 , ss.str() ) ; // 1 => verbose-debug
760 }
761
762 for( int i = 2 ; i < 10000 ; i++ )
763 {
764 unsigned long ee = ERR_get_error() ;
765 if( ee == 0 ) break ;
766 Error eee( op , ee ) ;
767 (*m_log_fn)( 3 , std::string() + eee.what() ) ; // 3 => errors-and-warnings
768 }
769 }
770}
771
772std::string GSsl::OpenSSL::ProtocolImp::requiredPeerCertificateName() const
773{
774 return m_required_peer_certificate_name ;
775}
776
777// ==
778
779GSsl::OpenSSL::Error::Error( const std::string & s ) :
780 std::runtime_error( "tls error: " + s )
781{
782}
783
784GSsl::OpenSSL::Error::Error( const std::string & fnname , unsigned long e ) :
785 std::runtime_error( "tls error: " + fnname + "(): [" + text(e) + "]" )
786{
787 clearErrors() ;
788}
789
790GSsl::OpenSSL::Error::Error( const std::string & fnname , unsigned long e , const std::string & file ) :
791 std::runtime_error( "tls error: " + fnname + "(): [" + text(e) + "]: file=[" + file + "]" )
792{
793 clearErrors() ;
794}
795
796 void GSsl::OpenSSL::Error::clearErrors()
797{
798 for( int i = 0 ; ERR_get_error() && i < 10000 ; i++ )
799 {;}
800}
801
802std::string GSsl::OpenSSL::Error::text( unsigned long e )
803{
804 std::vector<char> v( 300 ) ;
805 ERR_error_string_n( e , &v[0] , v.size() ) ;
806 std::string s( &v[0] , v.size() ) ;
807 return std::string( s.c_str() ) ; // NOLINT copy up to first null character
808}
809
810// ==
811
812GSsl::OpenSSL::CertificateChain::CertificateChain( STACK_OF(X509) * chain )
813{
814 for( int i = 0 ; chain != nullptr && i < sk_X509_num(chain) ; i++ )
815 {
816 void * p = sk_X509_value(chain,i) ; if( p == nullptr ) break ;
817 X509 * x509 = static_cast<X509*>(p) ;
818 m_str.append( Certificate(x509,false).str() ) ;
819 }
820}
821
822std::string GSsl::OpenSSL::CertificateChain::str() const
823{
824 return m_str ;
825}
826
827// ==
828
829GSsl::OpenSSL::Certificate::Certificate( X509 * x509 , bool do_free )
830{
831 if( x509 == nullptr ) return ;
832 BIO * bio = BIO_new( BIO_s_mem() ) ;
833 int rc = PEM_write_bio_X509( bio , x509 ) ;
834 if( !rc ) return ;
835 BUF_MEM * mem = nullptr ;
836 BIO_get_mem_ptr( bio , &mem ) ;
837 std::size_t n = mem ? static_cast<std::size_t>(mem->length) : 0U ;
838 const char * p = mem ? mem->data : nullptr ;
839 std::string data = p&&n ? std::string(p,n) : std::string() ;
840 BIO_free( bio ) ;
841 if( do_free ) X509_free( x509 ) ;
842
843 // sanitise to be strictly printable with embedded newlines
844 std::string result = G::Str::printable( data , '\0' ) ;
845 G::Str::replaceAll( result , std::string(1U,'\0')+"n" , "\n" ) ;
846 G::Str::replaceAll( result , std::string(1U,'\0') , "\\" ) ;
847 m_str = result ;
848}
849
850std::string GSsl::OpenSSL::Certificate::str() const
851{
852 return m_str ;
853}
854
855// ==
856
857GSsl::OpenSSL::Config::Config( G::StringArray & cfg ) :
858 m_min(0) ,
859 m_max(0) ,
860 m_options_set(0L) ,
861 m_options_reset(0L) ,
862 m_noverify(consume(cfg,"noverify"))
863{
864 #if GCONFIG_HAVE_OPENSSL_TLS_METHOD
865 m_server_fn = TLS_server_method ;
866 m_client_fn = TLS_client_method ;
867 #else
868 m_server_fn = SSLv23_server_method ;
869 m_client_fn = SSLv23_client_method ;
870 #endif
871
872 #if GCONFIG_HAVE_OPENSSL_MIN_MAX
873
874 #ifdef SSL3_VERSION
875 if( consume(cfg,"sslv3") ) m_min = SSL3_VERSION ;
876 if( consume(cfg,"-sslv3") ) m_max = SSL3_VERSION ;
877 #endif
878
879 #ifdef TLS1_VERSION
880 if( consume(cfg,"tlsv1.0") ) m_min = TLS1_VERSION ;
881 if( consume(cfg,"-tlsv1.0") ) m_max = TLS1_VERSION ;
882 #endif
883
884 #ifdef TLS1_1_VERSION
885 if( consume(cfg,"tlsv1.1") ) m_min = TLS1_1_VERSION ;
886 if( consume(cfg,"-tlsv1.1") ) m_max = TLS1_1_VERSION ;
887 #endif
888
889 #ifdef TLS1_2_VERSION
890 if( consume(cfg,"tlsv1.2") ) m_min = TLS1_2_VERSION ;
891 if( consume(cfg,"-tlsv1.2") ) m_max = TLS1_2_VERSION ;
892 #endif
893
894 #ifdef TLS1_3_VERSION
895 if( consume(cfg,"tlsv1.3") ) m_min = TLS1_3_VERSION ;
896 if( consume(cfg,"-tlsv1.3") ) m_max = TLS1_3_VERSION ;
897 #endif
898
899 #endif
900
901 #ifdef SSL_OP_ALL
902 if( consume(cfg,"op_all") ) m_options_set |= SSL_OP_ALL ;
903 #endif
904
905 #ifdef SSL_OP_NO_TICKET
906 if( consume(cfg,"op_no_ticket") ) m_options_set |= SSL_OP_NO_TICKET ;
907 #endif
908
909 #ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
910 if( consume(cfg,"op_no_resumption") ) m_options_set |= SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION ;
911 #endif
912
913 #ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
914 if( consume(cfg,"op_server_preference") ) m_options_set |= SSL_OP_CIPHER_SERVER_PREFERENCE ;
915 #endif
916}
917
918 bool GSsl::OpenSSL::Config::consume( G::StringArray & list , const std::string & item )
919{
920 return LibraryImp::consume( list , item ) ;
921}
922
923GSsl::OpenSSL::Config::Fn GSsl::OpenSSL::Config::fn( bool server )
924{
925 return server ? m_server_fn : m_client_fn ;
926}
927
928 long GSsl::OpenSSL::Config::set() const
929{
930 return m_options_set ;
931}
932
933 long GSsl::OpenSSL::Config::reset() const
934{
935 return m_options_reset ;
936}
937
938 int GSsl::OpenSSL::Config::min_() const
939{
940 return m_min ;
941}
942
943 int GSsl::OpenSSL::Config::max_() const
944{
945 return m_max ;
946}
947
948 bool GSsl::OpenSSL::Config::noverify() const
949{
950 return m_noverify ;
951}
GSsl::Digester
A class for objects that can perform a cryptographic hash.
Definition: gssl.h:213
GSsl::LibraryImpBase::consume
static bool consume(G::StringArray &list, const std::string &item)
A convenience function that removes the item from the list and returns true iff is was removed.
Definition: gssl.cpp:257
GSsl::Library::impstance
static LibraryImpBase & impstance()
Returns a reference to the pimple object when enabled().
Definition: gssl.cpp:108
GSsl::OpenSSL::Config
Holds protocol version information, etc.
Definition: gssl_openssl.h:85
GSsl::OpenSSL::LibraryImp
An implementation of the GSsl::LibraryImpBase interface for OpenSSL.
Definition: gssl_openssl.h:184
GSsl::Profile
A base interface for profile classes that work with concrete classes derived from GSsl::LibraryImpBas...
Definition: gssl.h:430
GSsl::Protocol::str
static std::string str(Result result)
Converts a result enumeration into a printable string.
Definition: gssl.cpp:184
G::File::isDirectory
static bool isDirectory(const Path &path, std::nothrow_t)
Returns true if the path exists() and is a directory.
Definition: gfile.cpp:187
G::File::exists
static bool exists(const Path &file)
Returns true if the file (directory, device etc.) exists.
Definition: gfile.cpp:151
G::HashState::encode
static std::string encode(const uint_type *)
Returns the hash state as an N-character string of non-printing characters.
Definition: ghashstate.h:113
G::HashState::decode
static void decode(const std::string &s, uint_type *values_out, size_type &size_out)
Converts an encode()d string back into a hash state of N/4 integers and a data size returned by refer...
Definition: ghashstate.h:169
G::Path
A Path object represents a file system path.
Definition: gpath.h:72
G::Path::basename
std::string basename() const
Returns the rightmost part of the path, ignoring "." parts.
Definition: gpath.cpp:328
G::Process::exe
static std::string exe()
Returns the absolute path of the current executable, independent of the argv array passed to main().
Definition: gprocess_unix.cpp:225
G::ReadWrite
An abstract interface for reading and writing from a non-blocking i/o channel.
Definition: greadwrite.h:50
G::ReadWrite::fd
virtual SOCKET fd() const noexcept=0
Returns the file descriptor.
G::Root
A class which acquires the process's special privileges on construction and releases them on destruct...
Definition: groot.h:52
G::Str::join
static std::string join(const std::string &sep, const StringArray &strings)
Concatenates an array of strings with separators.
Definition: gstr.cpp:1195
G::Str::splitIntoTokens
static void splitIntoTokens(const std::string &in, StringArray &out, string_view ws, char esc='\0')
Splits the string into 'ws'-delimited tokens.
Definition: gstr.cpp:1073
G::Str::unique
static std::string unique(const std::string &s, char c, char r)
Returns a string with repeated 'c' characters replaced by one 'r' character.
Definition: gstr.cpp:1472
G::Str::fromInt
static std::string fromInt(int i)
Converts int 'i' to a string.
Definition: gstr.h:561
G::Str::printable
static std::string printable(const std::string &in, char escape='\\')
Returns a printable representation of the given input string, using chacter code ranges 0x20 to 0x7e ...
Definition: gstr.cpp:885
G::Str::replaceAll
static unsigned int replaceAll(std::string &s, const std::string &from, const std::string &to)
Does a global replace on string 's', replacing all occurrences of sub-string 'from' with 'to'.
Definition: gstr.cpp:287
G::Test::enabled
static bool enabled() noexcept
Returns true if test features are enabled.
Definition: gtest.cpp:79
G::format
A simple version of boost::format for formatting strings in an i18n-friendly way.
Definition: gformat.h:46
gexception.h
gfile.h
gformat.h
ggettext.h
ghashstate.h
glog.h
gpath.h
gprocess.h
groot.h
gssl.h
An interface to an underlying TLS library.
gssl_openssl.h
gstr.h
gtest.h
G::StringArray
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:31
G::gettext
const char * gettext(const char *)
Returns the message translation in the current locale's codeset, eg.
Definition: ggettext.h:69