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