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