E-MailRelay
gssl_mbedtls.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_mbedtls.cpp
19///
20
21#include "gdef.h"
22#include "gssl.h"
23#include "gssl_mbedtls.h"
24#include "gssl_mbedtls_utils.h"
25#include "ghashstate.h"
26#include "gpath.h"
27#include "gsleep.h"
28#include "groot.h"
29#include "gtest.h"
30#include "gstr.h"
31#include "gfile.h"
32#include "gprocess.h"
33#include "gscope.h"
34#include "gstrmacros.h"
35#include "glog.h"
36#include "gassert.h"
37#include <array>
38#include <sstream>
39#include <fstream>
40#include <vector>
41#include <exception>
42#include <iomanip>
43#include <limits>
44
45// we need access to structure fields that are private in mbedtls v3.0 -- see
46// mbedtls migration guide
47// TODO dont use private mbedtls structure fields
48#if MBEDTLS_VERSION_MAJOR >= 3
49#define GET MBEDTLS_PRIVATE
50#if MBEDTLS_VERSION_MINOR >= 1
51#define GET_RAW(field) field
52#else
53#define GET_RAW MBEDTLS_PRIVATE
54#endif
55#else
56#define GET(field) field
57#define GET_RAW(field) field
58#endif
59
60#ifndef GCONFIG_HAVE_MBEDTLS_HASH_STATE
61#if MBEDTLS_VERSION_MAJOR >= 3
62#define GCONFIG_HAVE_MBEDTLS_HASH_STATE 0
63#else
64#define GCONFIG_HAVE_MBEDTLS_HASH_STATE 1
65#endif
66#endif
67
68GSsl::MbedTls::LibraryImp::LibraryImp( G::StringArray & library_config , Library::LogFn log_fn , bool verbose ) :
69 m_log_fn(log_fn) ,
70 m_config(library_config)
71{
72 mbedtls_debug_set_threshold( verbose ? 3 : 1 ) ; // "Messages that have a level over the threshold value are ignored."
73}
74
75GSsl::MbedTls::LibraryImp::~LibraryImp()
76= default;
77
78const GSsl::MbedTls::Rng & GSsl::MbedTls::LibraryImp::rng() const
79{
80 return m_rng ;
81}
82
83void GSsl::MbedTls::LibraryImp::addProfile( const std::string & profile_name , bool is_server_profile ,
84 const std::string & key_file , const std::string & cert_file , const std::string & ca_file ,
85 const std::string & default_peer_certificate_name , const std::string & default_peer_host_name ,
86 const std::string & profile_config )
87{
88 std::shared_ptr<ProfileImp> profile_ptr =
89 std::make_shared<ProfileImp>(*this,is_server_profile,key_file,cert_file,ca_file,
90 default_peer_certificate_name,default_peer_host_name,profile_config) ;
91 m_profile_map.insert( Map::value_type(profile_name,profile_ptr) ) ;
92}
93
94bool GSsl::MbedTls::LibraryImp::hasProfile( const std::string & profile_name ) const
95{
96 auto p = m_profile_map.find( profile_name ) ;
97 return p != m_profile_map.end() ;
98}
99
100const GSsl::Profile & GSsl::MbedTls::LibraryImp::profile( const std::string & profile_name ) const
101{
102 auto p = m_profile_map.find( profile_name ) ;
103 if( p == m_profile_map.end() ) throw Error( "no such profile: [" + profile_name + "]" ) ;
104 return *(*p).second ;
105}
106
107GSsl::Library::LogFn GSsl::MbedTls::LibraryImp::log() const
108{
109 return m_log_fn ;
110}
111
112std::string GSsl::MbedTls::LibraryImp::version()
113{
114 std::vector<char> buffer( 100U ) ; // "at least 9"
115 G_ASSERT( buffer.size() >= 9U ) ;
116 mbedtls_version_get_string( &buffer[0] ) ;
117 buffer[buffer.size()-1U] = '\0' ;
118 return G::Str::printable( std::string(&buffer[0]) ) ;
119}
120
121std::string GSsl::MbedTls::LibraryImp::sid()
122{
123 std::vector<char> buffer( 100U ) ; // "at least 18"
124 G_ASSERT( buffer.size() >= 18U ) ;
125 mbedtls_version_get_string_full( &buffer[0] ) ;
126 buffer[buffer.size()-1U] = '\0' ;
127 return G::Str::printable( std::string(&buffer[0]) ) ;
128}
129
130std::string GSsl::MbedTls::LibraryImp::id() const
131{
132 return sid() ;
133}
134
135GSsl::MbedTls::Config GSsl::MbedTls::LibraryImp::config() const
136{
137 return m_config ;
138}
139
140std::string GSsl::MbedTls::LibraryImp::credit( const std::string & /*prefix*/ , const std::string & /*eol*/ , const std::string & /*eot*/ )
141{
142 // "Mbed TLS is Copyright The Mbed TLS Contributors"
143 return {} ;
144}
145
147{
148 return { "MD5" , "SHA1" , "SHA256" } ;
149}
150
151GSsl::Digester GSsl::MbedTls::LibraryImp::digester( const std::string & hash_type , const std::string & state , bool need_state ) const
152{
153 return Digester( std::make_unique<DigesterImp>(hash_type,state,need_state) ) ;
154}
155
156// ==
157
158GSsl::MbedTls::Config::Config( G::StringArray & config ) :
159 m_noverify(consume(config,"noverify")),
160 m_clientnoverify(consume(config,"clientnoverify")),
161 m_servernoverify(consume(config,"servernoverify")),
162 m_min(-1),
163 m_max(-1)
164{
165
166#ifdef MBEDTLS_SSL_MINOR_VERSION_0
167 static constexpr int SSL_v3 = MBEDTLS_SSL_MINOR_VERSION_0 ;
168 static_assert( SSL_v3 >= 0 , "" ) ;
169 if( consume(config,"sslv3") ) m_min = SSL_v3 ;
170 if( consume(config,"-sslv3") ) m_max = SSL_v3 ;
171#endif
172
173#ifdef MBEDTLS_SSL_MINOR_VERSION_1
174 static constexpr int TLS_v1_0 = MBEDTLS_SSL_MINOR_VERSION_1 ;
175 static_assert( TLS_v1_0 >= 0 , "" ) ;
176 if( consume(config,"tlsv1.0") ) m_min = TLS_v1_0 ;
177 if( consume(config,"-tlsv1.0") ) m_max = TLS_v1_0 ;
178#endif
179
180#ifdef MBEDTLS_SSL_MINOR_VERSION_2
181 static constexpr int TLS_v1_1 = MBEDTLS_SSL_MINOR_VERSION_2 ;
182 static_assert( TLS_v1_1 >= 0 , "" ) ;
183 if( consume(config,"tlsv1.1") ) m_min = TLS_v1_1 ;
184 if( consume(config,"-tlsv1.1") ) m_max = TLS_v1_1 ;
185#endif
186
187#ifdef MBEDTLS_SSL_MINOR_VERSION_3
188 static constexpr int TLS_v1_2 = MBEDTLS_SSL_MINOR_VERSION_3 ;
189 static_assert( TLS_v1_2 >= 0 , "" ) ;
190 if( consume(config,"tlsv1.2") ) m_min = TLS_v1_2 ;
191 if( consume(config,"-tlsv1.2") ) m_max = TLS_v1_2 ;
192#endif
193
194#ifdef MBEDTLS_SSL_MINOR_VERSION_4
195 static constexpr int TLS_v1_3 = MBEDTLS_SSL_MINOR_VERSION_4 ;
196 static_assert( TLS_v1_3 >= 0 , "" ) ;
197 if( consume(config,"tlsv1.3") ) m_min = TLS_v1_3 ;
198 if( consume(config,"-tlsv1.3") ) m_max = TLS_v1_3 ;
199#endif
200}
201
202int GSsl::MbedTls::Config::min_() const noexcept
203{
204 return m_min ;
205}
206
207int GSsl::MbedTls::Config::max_() const noexcept
208{
209 return m_max ;
210}
211
212bool GSsl::MbedTls::Config::noverify() const noexcept
213{
214 return m_noverify ;
215}
216
217bool GSsl::MbedTls::Config::clientnoverify() const noexcept
218{
219 return m_clientnoverify ;
220}
221
222bool GSsl::MbedTls::Config::servernoverify() const noexcept
223{
224 return m_servernoverify ;
225}
226
227bool GSsl::MbedTls::Config::consume( G::StringArray & list , G::string_view item )
228{
229 return LibraryImp::consume( list , item ) ;
230}
231
232// ==
233
234GSsl::MbedTls::DigesterImp::DigesterImp( const std::string & hash_name , const std::string & state , bool need_state )
235{
236 bool have_state = !state.empty() ;
237 m_state_size = 0U ;
238
239 #if ! GCONFIG_HAVE_MBEDTLS_HASH_STATE
240 if( have_state || need_state )
241 {
242 throw Error( std::string("hash state resoration not implemented for ").append(hash_name) ) ;
243 }
244 #else
245 GDEF_IGNORE_PARAM( need_state ) ;
246 #endif
247
248 if( hash_name == "MD5" )
249 {
250 m_hash_type = Type::Md5 ;
251 m_block_size = 64U ;
252 m_value_size = 16U ;
253
254 mbedtls_md5_init( &m_md5 ) ;
255 #if GCONFIG_HAVE_MBEDTLS_HASH_STATE
256 m_state_size = m_value_size + 4U ;
257 if( have_state )
258 G::HashState<16,uint32_t,uint32_t>::decode( state , m_md5.GET(state) , m_md5.GET(total)[0] ) ;
259 else
260 call( FN_RETv3(mbedtls_md5_starts) , &m_md5 ) ;
261 #else
262 call( FN_RETv3(mbedtls_md5_starts) , &m_md5 ) ;
263 #endif
264 }
265 else if( hash_name == "SHA1" )
266 {
267 m_hash_type = Type::Sha1 ;
268 m_block_size = 64U ;
269 m_value_size = 20U ;
270
271 mbedtls_sha1_init( &m_sha1 ) ;
272 #if GCONFIG_HAVE_MBEDTLS_HASH_STATE
273 m_state_size = m_value_size + 4U ;
274 if( have_state )
275 G::HashState<20,uint32_t,uint32_t>::decode( state , m_sha1.GET(state) , m_sha1.GET(total)[0] ) ;
276 else
277 call( FN_RETv3(mbedtls_sha1_starts) , &m_sha1 ) ;
278 #else
279 call( FN_RETv3(mbedtls_sha1_starts) , &m_sha1 ) ;
280 #endif
281 }
282 else if( hash_name == "SHA256" )
283 {
284 m_hash_type = Type::Sha256 ;
285 m_block_size = 64U ;
286 m_value_size = 32U ;
287
288 mbedtls_sha256_init( &m_sha256 ) ;
289 #if GCONFIG_HAVE_MBEDTLS_HASH_STATE
290 m_state_size = m_value_size + 4U ;
291 if( have_state )
292 G::HashState<32,uint32_t,uint32_t>::decode( state , m_sha256.GET(state) , m_sha256.GET(total)[0] ) ;
293 else
294 call( FN_RETv3(mbedtls_sha256_starts) , &m_sha256 , 0 ) ;
295 #else
296 call( FN_RETv3(mbedtls_sha256_starts) , &m_sha256 , 0 ) ;
297 #endif
298 }
299 else
300 {
301 throw Error( "invalid hash function" ) ;
302 }
303}
304
305GSsl::MbedTls::DigesterImp::~DigesterImp()
306{
307 if( m_hash_type == Type::Md5 )
308 mbedtls_md5_free( &m_md5 ) ;
309 else if( m_hash_type == Type::Sha1 )
310 mbedtls_sha1_free( &m_sha1 ) ;
311 else if( m_hash_type == Type::Sha256 )
312 mbedtls_sha256_free( &m_sha256 ) ;
313}
314
316{
317 if( m_hash_type == Type::Md5 )
318 call( FN_RETv3(mbedtls_md5_update) , &m_md5 , reinterpret_cast<const unsigned char*>(sv.data()) , sv.size() ) ;
319 else if( m_hash_type == Type::Sha1 )
320 call( FN_RETv3(mbedtls_sha1_update) , &m_sha1 , reinterpret_cast<const unsigned char*>(sv.data()) , sv.size() ) ;
321 else if( m_hash_type == Type::Sha256 )
322 call( FN_RETv3(mbedtls_sha256_update) , &m_sha256 , reinterpret_cast<const unsigned char*>(sv.data()) , sv.size() ) ;
323}
324
326{
327 if( m_hash_type == Type::Md5 )
328 {
329 std::array<unsigned char,16> buffer {} ;
330 call( FN_RETv3(mbedtls_md5_finish) , &m_md5 , &buffer[0] ) ;
331 return std::string( reinterpret_cast<const char*>(&buffer[0]) , buffer.size() ) ;
332 }
333 else if( m_hash_type == Type::Sha1 )
334 {
335 std::array<unsigned char,20> buffer {} ;
336 call( FN_RETv3(mbedtls_sha1_finish) , &m_sha1 , &buffer[0] ) ;
337 return std::string( reinterpret_cast<const char*>(&buffer[0]) , buffer.size() ) ;
338 }
339 else if( m_hash_type == Type::Sha256 )
340 {
341 std::array<unsigned char,32> buffer {} ;
342 call( FN_RETv3(mbedtls_sha256_finish) , &m_sha256 , &buffer[0] ) ;
343 return std::string( reinterpret_cast<const char*>(&buffer[0]) , buffer.size() ) ;
344 }
345 else
346 {
347 return std::string() ;
348 }
349}
350
352{
353 #if GCONFIG_HAVE_MBEDTLS_HASH_STATE
354 if( m_hash_type == Type::Md5 )
355 return G::HashState<16,uint32_t,uint32_t>::encode( m_md5.GET(state) , m_md5.GET(total)[0] ) ;
356 else if( m_hash_type == Type::Sha1 )
357 return G::HashState<20,uint32_t,uint32_t>::encode( m_sha1.GET(state) , m_sha1.GET(total)[0] ) ;
358 else if( m_hash_type == Type::Sha256 )
359 return G::HashState<32,uint32_t,uint32_t>::encode( m_sha256.GET(state) , m_sha256.GET(total)[0] ) ;
360 else
361 return {} ;
362 #else
363 return {} ;
364 #endif
365}
366
367std::size_t GSsl::MbedTls::DigesterImp::blocksize() const noexcept
368{
369 return m_block_size ;
370}
371
372std::size_t GSsl::MbedTls::DigesterImp::valuesize() const noexcept
373{
374 return m_value_size ;
375}
376
377std::size_t GSsl::MbedTls::DigesterImp::statesize() const noexcept
378{
379 return m_state_size ;
380}
381
382// ==
383
384GSsl::MbedTls::ProfileImp::ProfileImp( const LibraryImp & library_imp , bool is_server_profile ,
385 const std::string & key_file , const std::string & cert_file , const std::string & ca_path ,
386 const std::string & default_peer_certificate_name , const std::string & default_peer_host_name ,
387 const std::string & profile_config ) :
388 m_library_imp(library_imp) ,
389 m_default_peer_certificate_name(default_peer_certificate_name) ,
390 m_default_peer_host_name(default_peer_host_name) ,
391 m_config{} ,
392 m_authmode(0)
393{
394 mbedtls_ssl_config * cleanup_ptr = nullptr ;
395 G::ScopeExit cleanup( [&](){if(cleanup_ptr) mbedtls_ssl_config_free(cleanup_ptr);} ) ;
396
397 // use library config, or override with profile config
398 Config extra_config = library_imp.config() ;
399 if( !profile_config.empty() )
400 {
401 G::StringArray profile_config_list = G::Str::splitIntoTokens( profile_config , "," ) ;
402 extra_config = Config( profile_config_list ) ;
403 if( !profile_config_list.empty() ) // ie. residue not consumed by Config ctor
404 G_WARNING( "GSsl::MbedTls::ProfileImp::ctor: tls-config: tls " << (is_server_profile?"server":"client")
405 << " profile configuration ignored: [" << G::Str::join(",",profile_config_list) << "]" ) ;
406 }
407
408 // initialise the mbedtls_ssl_config structure
409 {
410 mbedtls_ssl_config_init( &m_config ) ;
411 cleanup_ptr = &m_config ;
412 int rc = mbedtls_ssl_config_defaults( &m_config ,
413 is_server_profile ? MBEDTLS_SSL_IS_SERVER : MBEDTLS_SSL_IS_CLIENT , // see also mbedtls_ssl_conf_endpoint()
414 MBEDTLS_SSL_TRANSPORT_STREAM ,
415 MBEDTLS_SSL_PRESET_DEFAULT ) ;
416 if( rc ) throw Error( "mbedtls_ssl_config_defaults" , rc ) ;
417 }
418
419 // load up the certificate and private key ready for mbedtls_ssl_conf_own_cert()
420 if( !key_file.empty() )
421 m_pk.load( key_file , library_imp.rng() ) ;
422 if( !cert_file.empty() )
423 m_certificate.load( cert_file ) ;
424
425 // identify our certificate/private-key combination
426 if( m_certificate.loaded() )
427 {
428 int rc = mbedtls_ssl_conf_own_cert( &m_config , m_certificate.ptr() , m_pk.ptr() ) ;
429 if( rc != 0 )
430 throw Error( "mbedtls_ssl_conf_own_cert" , rc ) ;
431 }
432
433 // configure verification
434 {
435 if( ca_path.empty() )
436 {
437 // verify if possible, but continue on failure - see mbedtls_ssl_get_verify_result()
438 m_authmode = MBEDTLS_SSL_VERIFY_OPTIONAL ;
439
440 // mbedtls 2.4.2 can incorrectly fail to handshake when OPTIONAL so provide some more tweakability
441 if( is_server_profile && extra_config.servernoverify() )
442 m_authmode = MBEDTLS_SSL_VERIFY_NONE ;
443 else if( !is_server_profile && extra_config.clientnoverify() )
444 m_authmode = MBEDTLS_SSL_VERIFY_NONE ;
445 else if( mbedtls_version_get_number() <= 0x02040200 )
446 G_WARNING_ONCE( "GSsl::MbedTls::LibraryImp::ctor: mbedtls library version " << LibraryImp::version() << " is deprecated" ) ;
447 }
448 else if( ca_path == "<none>" )
449 {
450 // dont verify
451 m_authmode = MBEDTLS_SSL_VERIFY_NONE ;
452 }
453 else if( ca_path == "<default>" )
454 {
455 // verify against the default ca database
456 std::string ca_path_default = "/etc/ssl/certs/ca-certificates.crt" ; // see debian "man update-ca-certificates"
457 m_ca_list.load( ca_path_default ) ;
458 bool no_verify = extra_config.noverify() ;
459 mbedtls_ssl_conf_ca_chain( &m_config , m_ca_list.ptr() , crl() ) ;
460 m_authmode = no_verify ? MBEDTLS_SSL_VERIFY_OPTIONAL : MBEDTLS_SSL_VERIFY_REQUIRED ;
461 }
462 else
463 {
464 // verify against the given ca database
465 m_ca_list.load( ca_path ) ;
466 bool no_verify = extra_config.noverify() ;
467 mbedtls_ssl_conf_ca_chain( &m_config , m_ca_list.ptr() , crl() ) ;
468 m_authmode = no_verify ? MBEDTLS_SSL_VERIFY_OPTIONAL : MBEDTLS_SSL_VERIFY_REQUIRED ;
469 }
470 mbedtls_ssl_conf_authmode( &m_config , m_authmode ) ;
471 }
472
473 // configure protocol version
474 {
475 if( extra_config.min_() >= 0 )
476 mbedtls_ssl_conf_min_version( &m_config , MBEDTLS_SSL_MAJOR_VERSION_3 , extra_config.min_() ) ;
477 if( extra_config.max_() >= 0 )
478 mbedtls_ssl_conf_max_version( &m_config , MBEDTLS_SSL_MAJOR_VERSION_3 , extra_config.max_() ) ;
479 }
480
481 // hooks
482 {
483 mbedtls_ssl_conf_rng( &m_config , mbedtls_ctr_drbg_random , m_library_imp.rng().ptr() ) ;
484 mbedtls_ssl_conf_dbg( &m_config , onDebug , this ) ;
485 }
486
487 // other configuration
488 {
489 mbedtls_ssl_conf_renegotiation( &m_config , MBEDTLS_SSL_RENEGOTIATION_DISABLED ) ;
490 }
491 cleanup.release() ;
492}
493
494GSsl::MbedTls::ProfileImp::~ProfileImp()
495{
496 mbedtls_ssl_config_free( &m_config ) ;
497}
498
499std::unique_ptr<GSsl::ProtocolImpBase> GSsl::MbedTls::ProfileImp::newProtocol( const std::string & peer_certificate_name ,
500 const std::string & peer_host_name ) const
501{
502 return std::make_unique<MbedTls::ProtocolImp>( *this ,
503 peer_certificate_name.empty()?defaultPeerCertificateName():peer_certificate_name ,
504 peer_host_name.empty()?defaultPeerHostName():peer_host_name ) ; // upcast
505}
506
507mbedtls_x509_crl * GSsl::MbedTls::ProfileImp::crl() const
508{
509 // TODO certificate revocation list
510 return nullptr ;
511}
512
513const mbedtls_ssl_config * GSsl::MbedTls::ProfileImp::config() const
514{
515 return &m_config ;
516}
517
518void GSsl::MbedTls::ProfileImp::onDebug( void * This , int level_in , const char * file , int line , const char * message )
519{
520 try
521 {
522 static_cast<ProfileImp*>(This)->doDebug( level_in , file , line , message ) ;
523 }
524 catch(...) // callback from c code
525 {
526 }
527}
528
529void GSsl::MbedTls::ProfileImp::doDebug( int level_in , const char * file , int line , const char * message )
530{
531 // in practice even level 0 messages are too noisy, so discard them all :(
532 level_in = 4 ;
533
534 // map from 'level_in' mbedtls levels to GSsl::Library::LogFn levels...
535 // 4 -> <discarded>
536 // 3 -> logAt(1) (verbose-debug)
537 // 2 -> logAt(1) (verbose-debug)
538 // 1 -> logAt(3) (errors-and-warnings)
539 // 0 -> logAt(3) (errors-and-warnings)
540 // ... with this code doing its own logAt(2) (useful-information)
541 // see also mbedtls_debug_set_threshold() in LibraryImp::ctor()
542 //
543 int level_out = level_in >= 4 ? 0 : ( level_in >= 2 ? 1 : 3 ) ;
544 if( m_library_imp.log() && level_out )
545 {
546 std::ostringstream ss ;
547 G::Path path( file ? std::string(file) : std::string() ) ;
548 ss << path.basename() << "(" << line << "): " << (message?message:"") ;
549 logAt( level_out , ss.str() ) ;
550 }
551}
552
553void GSsl::MbedTls::ProfileImp::logAt( int level_out , std::string s ) const
554{
555 Library::LogFn log_fn = m_library_imp.log() ;
556 if( log_fn )
557 {
559 if( !s.empty() )
560 (*log_fn)( level_out , s ) ;
561 }
562}
563
564const std::string & GSsl::MbedTls::ProfileImp::defaultPeerCertificateName() const
565{
566 return m_default_peer_certificate_name ;
567}
568
569const std::string & GSsl::MbedTls::ProfileImp::defaultPeerHostName() const
570{
571 return m_default_peer_host_name ;
572}
573
574int GSsl::MbedTls::ProfileImp::authmode() const
575{
576 return m_authmode ;
577}
578
579// ==
580
581GSsl::MbedTls::ProtocolImp::ProtocolImp( const ProfileImp & profile , const std::string & required_peer_certificate_name ,
582 const std::string & target_peer_host_name ) :
583 m_profile(profile) ,
584 m_io(nullptr) ,
585 m_ssl(profile.config()) ,
586 m_verified(false)
587{
588 mbedtls_ssl_set_bio( m_ssl.ptr() , this , doSend , doRecv , nullptr/*doRecvTimeout*/ ) ;
589
590 // the mbedtls api uses the same function for the peer-certificate-name validation
591 // and peer-host-name indication -- it also interprets wildcards in the certificate
592 // cname ("www.example.com" matches "CN=*.example.com") so the peer-host-name is
593 // preferred here over the peer-certificate-name
594 //
595 std::string name = target_peer_host_name.empty() ? required_peer_certificate_name : target_peer_host_name ;
596 if( !name.empty() )
597 mbedtls_ssl_set_hostname( m_ssl.ptr() , name.c_str() ) ;
598}
599
600GSsl::MbedTls::ProtocolImp::~ProtocolImp()
601= default;
602
603GSsl::Protocol::Result GSsl::MbedTls::ProtocolImp::read( char * buffer , std::size_t buffer_size_in , ssize_t & data_size_out )
604{
605 int rc = mbedtls_ssl_read( m_ssl.ptr() , reinterpret_cast<unsigned char*>(buffer) , buffer_size_in ) ;
606 data_size_out = rc < 0 ? 0 : rc ;
607 if( rc == 0 ) return Protocol::Result::error ; // disconnected
608 std::size_t available = rc > 0 ? mbedtls_ssl_get_bytes_avail( m_ssl.ptr() ) : 0U ;
609 return convert( "mbedtls_ssl_read" , rc , available > 0U ) ;
610}
611
612GSsl::Protocol::Result GSsl::MbedTls::ProtocolImp::write( const char * buffer_in , std::size_t data_size_in ,
613 ssize_t & data_size_out )
614{
615 const unsigned char * p = reinterpret_cast<const unsigned char *>(buffer_in) ;
616 ssize_t n = static_cast<ssize_t>(data_size_in) ; G_ASSERT( n >= 0 ) ;
617 for(;;)
618 {
619 int rc = mbedtls_ssl_write( m_ssl.ptr() , p , n ) ;
620 if( rc >= n )
621 {
622 data_size_out = static_cast<ssize_t>(data_size_in) ;
623 return Protocol::Result::ok ;
624 }
625 else if( rc >= 0 )
626 {
627 n -= rc ;
628 p += rc ;
629 }
630 else
631 {
632 return convert( "mbedtls_ssl_write" , rc ) ;
633 }
634 }
635}
636
637GSsl::Protocol::Result GSsl::MbedTls::ProtocolImp::shutdown()
638{
639 int rc = mbedtls_ssl_close_notify( m_ssl.ptr() ) ;
640 return convert( "mbedtls_ssl_close_notify" , rc ) ;
641}
642
643#ifndef G_LIB_SMALL
644int GSsl::MbedTls::ProtocolImp::doRecvTimeout( void * This , unsigned char * p , std::size_t n , uint32_t /*timeout_ms*/ )
645{
646 // with event-driven i/o the timeout is probably not useful since
647 // higher layers will time out eventually
648 return doRecv( This , p , n ) ;
649}
650#endif
651
652int GSsl::MbedTls::ProtocolImp::doRecv( void * This , unsigned char * p , std::size_t n )
653{
654 G::ReadWrite * io = static_cast<ProtocolImp*>(This)->m_io ;
655 G_ASSERT( io != nullptr ) ;
656 ssize_t rc = io->read( reinterpret_cast<char*>(p) , n ) ;
657 if( rc < 0 && io->eWouldBlock() ) return MBEDTLS_ERR_SSL_WANT_READ ;
658 if( rc < 0 ) return MBEDTLS_ERR_NET_RECV_FAILED ; // or CONN_RESET for EPIPE or ECONNRESET
659 return static_cast<int>(rc) ;
660}
661
662int GSsl::MbedTls::ProtocolImp::doSend( void * This , const unsigned char * p , std::size_t n )
663{
664 G::ReadWrite * io = static_cast<ProtocolImp*>(This)->m_io ;
665 G_ASSERT( io != nullptr ) ;
666 ssize_t rc = io->write( reinterpret_cast<const char*>(p) , n ) ;
667 if( rc < 0 && io->eWouldBlock() ) return MBEDTLS_ERR_SSL_WANT_WRITE ;
668 if( rc < 0 ) return MBEDTLS_ERR_NET_SEND_FAILED ; // or CONN_RESET for EPIPE or ECONNRESET
669 return static_cast<int>(rc) ;
670}
671
672GSsl::Protocol::Result GSsl::MbedTls::ProtocolImp::convert( const char * fnname , int rc , bool more )
673{
674 // ok , read , write , error , more
675 if( rc == MBEDTLS_ERR_SSL_WANT_READ ) return Protocol::Result::read ;
676 if( rc == MBEDTLS_ERR_SSL_WANT_WRITE ) return Protocol::Result::write ;
677 if( rc == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY ) return Protocol::Result::error ;
678 if( rc < 0 ) // throw on error -- moot
679 {
680 throw Error( fnname , rc , verifyResultString(rc) ) ;
681 }
682 return more ? Protocol::Result::more : Protocol::Result::ok ;
683}
684
685std::string GSsl::MbedTls::ProtocolImp::verifyResultString( int rc )
686{
687 if( rc == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED )
688 {
689 auto verify_result = mbedtls_ssl_get_verify_result( m_ssl.ptr() ) ; // MBEDTLS_X509_BADCERT_...
690 std::vector<char> buffer( 1024U ) ;
691 mbedtls_x509_crt_verify_info( &buffer[0] , buffer.size() , "" , verify_result ) ;
692 buffer.back() = '\0' ;
693 return G::Str::printable( std::string(&buffer[0]) ) ;
694 }
695 else
696 {
697 return std::string() ;
698 }
699}
700
701GSsl::Protocol::Result GSsl::MbedTls::ProtocolImp::connect( G::ReadWrite & io )
702{
703 m_io = &io ;
704 return handshake() ;
705}
706
707GSsl::Protocol::Result GSsl::MbedTls::ProtocolImp::accept( G::ReadWrite & io )
708{
709 m_io = &io ;
710 return handshake() ;
711}
712
713GSsl::Protocol::Result GSsl::MbedTls::ProtocolImp::handshake()
714{
715 int rc = mbedtls_ssl_handshake( m_ssl.ptr() ) ;
716 Result result = convert( "mbedtls_ssl_handshake" , rc ) ;
717 if( result == Protocol::Result::ok )
718 {
719 const char * vstr = "" ;
720 if( m_profile.authmode() == MBEDTLS_SSL_VERIFY_NONE )
721 {
722 m_verified = false ;
723 vstr = "peer certificate not verified" ;
724 }
725 else if( m_profile.authmode() == MBEDTLS_SSL_VERIFY_OPTIONAL )
726 {
727 auto v = mbedtls_ssl_get_verify_result( m_ssl.ptr() ) ;
728 m_verified = v == 0 ;
729 // TODO see also mbedtls_x509_crt_verify_info() and X509_CRT_ERROR_INFO
730 vstr = v == 0 ? "peer certificate verified" : "peer certificate failed to verify" ;
731 if( v & MBEDTLS_X509_BADCERT_EXPIRED ) vstr = "peer certificate has expired" ;
732 if( v & MBEDTLS_X509_BADCERT_REVOKED ) vstr = "peer certificate has been revoked" ;
733 if( v & MBEDTLS_X509_BADCERT_NOT_TRUSTED ) vstr = "peer certificate not signed by a trusted ca" ;
734 if( v & MBEDTLS_X509_BADCERT_MISSING ) vstr = "peer certificate missing" ;
735 if( v & MBEDTLS_X509_BADCERT_SKIP_VERIFY ) vstr = "peer certificate verification was skipped" ;
736 }
737 else // MBEDTLS_SSL_VERIFY_REQUIRED
738 {
739 m_verified = true ;
740 vstr = "peer certificate verified" ;
741 }
742
743 m_peer_certificate = getPeerCertificate() ;
744 m_peer_certificate_chain = m_peer_certificate ; // not implemented
745
746 m_profile.logAt( 2 , std::string("certificate verification: [") + vstr + "]" ) ;
747 }
748 return result ;
749}
750
751std::string GSsl::MbedTls::ProtocolImp::protocol() const
752{
753 const char * p = mbedtls_ssl_get_version( m_ssl.ptr() ) ;
754 return G::Str::printable(p?std::string(p):std::string()) ;
755}
756
757std::string GSsl::MbedTls::ProtocolImp::cipher() const
758{
759 const char * p = mbedtls_ssl_get_ciphersuite( m_ssl.ptr() ) ;
760 return G::Str::printable(p?std::string(p):std::string()) ;
761}
762
763#ifndef G_LIB_SMALL
764const GSsl::Profile & GSsl::MbedTls::ProtocolImp::profile() const
765{
766 return m_profile ;
767}
768#endif
769
770std::string GSsl::MbedTls::ProtocolImp::getPeerCertificate()
771{
772 std::string result ;
773 const mbedtls_x509_crt * certificate = mbedtls_ssl_get_peer_cert( m_ssl.ptr() ) ;
774 if( certificate != nullptr )
775 {
776 const char * head = "-----BEGIN CERTIFICATE-----\n" ;
777 const char * tail = "-----END CERTIFICATE-----\n" ;
778
779 const unsigned char * raw_p = certificate->GET_RAW(raw).GET_RAW(p) ;
780 std::size_t raw_n = certificate->GET_RAW(raw).GET_RAW(len) ;
781
782 // get the required buffer size
783 std::size_t n = 0U ;
784 unsigned char c = '\0' ;
785 int rc = mbedtls_pem_write_buffer( head , tail , raw_p , raw_n , &c , 0 , &n ) ;
786 if( n == 0U || rc != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL )
787 throw Error( "certificate error" ) ;
788 n = n + n ; // old polarssl bug required this
789
790 // write it into the correctly sized buffer
791 std::vector<unsigned char> buffer( n ) ;
792 rc = mbedtls_pem_write_buffer( head , tail , raw_p , raw_n , &buffer[0] , buffer.size() , &n ) ;
793 if( n == 0 || rc != 0 )
794 throw Error( "certificate error" ) ;
795
796 result = std::string( reinterpret_cast<const char *>(&buffer[0]) , n-1U ) ;
797 if( std::string(result.c_str()) != result || result.find(tail) == std::string::npos ) // NOLINT readability-redundant-string-cstr
798 throw Error( "certificate error" ) ;
799 }
800 return result ;
801}
802
804{
805 return m_peer_certificate ;
806}
807
809{
810 return m_peer_certificate_chain ;
811}
812
814{
815 return m_verified ;
816}
817
818// ==
819
820GSsl::MbedTls::Context::Context( const mbedtls_ssl_config * config_p ) :
821 x{}
822{
823 mbedtls_ssl_init( &x ) ;
824
825 int rc = mbedtls_ssl_setup( &x , config_p ) ;
826 if( rc ) throw Error( "mbedtls_ssl_setup" , rc ) ;
827}
828
829GSsl::MbedTls::Context::~Context()
830{
831 mbedtls_ssl_free( &x ) ;
832}
833
834mbedtls_ssl_context * GSsl::MbedTls::Context::ptr()
835{
836 return &x ;
837}
838
839mbedtls_ssl_context * GSsl::MbedTls::Context::ptr() const
840{
841 return const_cast<mbedtls_ssl_context*>(&x) ;
842}
843
844// ==
845
846GSsl::MbedTls::Rng::Rng() :
847 x{} ,
848 entropy{}
849{
850 // quote: "in the default Mbed TLS the entropy collector tries to use what
851 // the platform you run can provide -- for Linux and UNIX-like systems this
852 // is /dev/urandom -- for windows this is CryptGenRandom of the CryptoAPI
853 // -- these are considered strong entropy sources -- when you run Mbed TLS
854 // on a different platform, such as an embedded platform, you have to add
855 // platform-specific or application-specific entropy sources"
856 mbedtls_entropy_init( &entropy ) ;
857
858 mbedtls_ctr_drbg_init( &x ) ;
859
860 static constexpr std::array<unsigned char,33> extra { "sdflkjsdlkjsdfkljxmvnxcvmxmncvxy" } ;
861 int rc = mbedtls_ctr_drbg_seed( &x , mbedtls_entropy_func , &entropy , &extra[0] , extra.size()-1U ) ;
862 if( rc != 0 )
863 {
864 mbedtls_entropy_free( &entropy ) ;
865 throw Error( "mbedtls_ctr_drbg_init" , rc ) ;
866 }
867}
868
869GSsl::MbedTls::Rng::~Rng()
870{
871 mbedtls_ctr_drbg_free( &x ) ;
872 mbedtls_entropy_free( &entropy ) ;
873}
874
875#ifndef G_LIB_SMALL
876mbedtls_ctr_drbg_context * GSsl::MbedTls::Rng::ptr()
877{
878 return &x ;
879}
880#endif
881
882mbedtls_ctr_drbg_context * GSsl::MbedTls::Rng::ptr() const
883{
884 return const_cast<mbedtls_ctr_drbg_context*>(&x) ;
885}
886
887// ==
888
889std::size_t GSsl::MbedTls::SecureFile::fileSize( std::filebuf & fp )
890{
891 std::streamoff pos = fp.pubseekoff( 0 , std::ios_base::end , std::ios_base::in ) ;
892 if( pos < 0 || static_cast<std::make_unsigned<std::streamoff>::type>(pos) >= std::numeric_limits<std::size_t>::max() ) // ">=" sic
893 return 0U ;
894 return static_cast<std::size_t>( pos ) ;
895}
896
897bool GSsl::MbedTls::SecureFile::fileRead( std::filebuf & fp , char * p , std::size_t n )
898{
899 if( p == nullptr || n == 0U )
900 return false ;
901 fp.pubseekpos( 0 , std::ios_base::in ) ;
902 auto rc = fp.sgetn( p , n ) ; // NOLINT narrowing
903 return rc > 0 && static_cast<std::size_t>(rc) == n ;
904}
905
906void GSsl::MbedTls::SecureFile::scrub( char * p_in , std::size_t n ) noexcept
907{
908 // see also SecureZeroMemory(), memset_s(3), explicit_bzero(BSD) and mbedtls_zeroize()
909 volatile char * p = p_in ;
910 while( n-- )
911 *p++ = 0 ;
912}
913
914void GSsl::MbedTls::SecureFile::clear( std::vector<char> & buffer ) noexcept
915{
916 static_assert( noexcept(buffer.empty()) , "" ) ;
917 static_assert( noexcept(buffer.clear()) , "" ) ;
918 if( !buffer.empty() )
919 {
920 scrub( &buffer[0] , buffer.size() ) ;
921 buffer.clear() ;
922 }
923}
924
925GSsl::MbedTls::SecureFile::SecureFile( const std::string & path , bool with_counted_nul )
926{
927 G::ScopeExit clearer( [&](){ clear(m_buffer); } ) ;
928 std::filebuf f ;
929 {
930 G::Root claim_root ;
931 if( G::File::open( f , path , G::File::InOut::In ) == nullptr )
932 return ;
933 }
934
935 std::size_t n = fileSize( f ) ;
936 if( n == 0U )
937 return ;
938
939 m_buffer.reserve( n+1U ) ;
940 m_buffer.resize( n ) ;
941 bool ok = fileRead( f , &m_buffer[0] , n ) ;
942 if( !ok )
943 return ;
944
945 if( with_counted_nul )
946 m_buffer.push_back( '\0' ) ;
947
948 clearer.release() ;
949}
950
951GSsl::MbedTls::SecureFile::~SecureFile()
952{
953 clear( m_buffer ) ;
954}
955
956const char * GSsl::MbedTls::SecureFile::p() const
957{
958 static char c = '\0' ;
959 return m_buffer.empty() ? &c : &m_buffer[0] ;
960}
961
962#ifndef G_LIB_SMALL
963const unsigned char * GSsl::MbedTls::SecureFile::pu() const
964{
965 return reinterpret_cast<const unsigned char*>( p() ) ;
966}
967#endif
968
969unsigned char * GSsl::MbedTls::SecureFile::pu()
970{
971 return const_cast<unsigned char*>( reinterpret_cast<const unsigned char*>(p()) ) ;
972}
973
974std::size_t GSsl::MbedTls::SecureFile::size() const
975{
976 return m_buffer.size() ;
977}
978
979bool GSsl::MbedTls::SecureFile::empty() const
980{
981 return m_buffer.empty() ;
982}
983
984// ==
985
986GSsl::MbedTls::Key::Key() :
987 x{}
988{
989 mbedtls_pk_init( &x ) ;
990}
991
992GSsl::MbedTls::Key::~Key()
993{
994 mbedtls_pk_free( &x ) ;
995}
996
997void GSsl::MbedTls::Key::load( const std::string & pem_file , const Rng & rng )
998{
999 SecureFile file( pem_file , true ) ;
1000 if( file.empty() )
1001 throw Error( "cannot load private key from " + pem_file ) ;
1002
1003 int rc = call_fn( mbedtls_pk_parse_key , &x , file.pu() , file.size() ,
1004 nullptr , 0 , mbedtls_ctr_drbg_random , rng.ptr() ) ;
1005
1006 if( rc < 0 ) // negative error code
1007 throw Error( "mbedtls_pk_parse_key" , rc ) ;
1008 else if( rc > 0 ) // positive number of failed parts
1009 {;} // no-op because the file can contain non-private-key parts
1010}
1011
1012mbedtls_pk_context * GSsl::MbedTls::Key::ptr()
1013{
1014 return &x ;
1015}
1016
1017#ifndef G_LIB_SMALL
1018mbedtls_pk_context * GSsl::MbedTls::Key::ptr() const
1019{
1020 return const_cast<mbedtls_pk_context*>( &x ) ;
1021}
1022#endif
1023
1024// ==
1025
1026GSsl::MbedTls::Certificate::Certificate() :
1027 x{}
1028{
1029 mbedtls_x509_crt_init( &x ) ;
1030}
1031
1032GSsl::MbedTls::Certificate::~Certificate()
1033{
1034 mbedtls_x509_crt_free( &x ) ;
1035}
1036
1037void GSsl::MbedTls::Certificate::load( const std::string & path )
1038{
1039 SecureFile file( path , true ) ;
1040 if( file.empty() )
1041 throw Error( "cannot load certificates from " + path ) ;
1042
1043 int rc = mbedtls_x509_crt_parse( &x , file.pu() , file.size() ) ;
1044 if( rc < 0 ) // negative error code
1045 throw Error( "mbedtls_x509_crt_parse" , rc ) ;
1046 else if( rc > 0 ) // positive number of failed parts
1047 throw Error( "mbedtls_x509_crt_parse" ) ;
1048
1049 m_loaded = true ;
1050}
1051
1052bool GSsl::MbedTls::Certificate::loaded() const
1053{
1054 return m_loaded ;
1055}
1056
1057mbedtls_x509_crt * GSsl::MbedTls::Certificate::ptr()
1058{
1059 return loaded() ? &x : nullptr ;
1060}
1061
1062#ifndef G_LIB_SMALL
1063mbedtls_x509_crt * GSsl::MbedTls::Certificate::ptr() const
1064{
1065 if( loaded() )
1066 return const_cast<mbedtls_x509_crt*>( &x ) ;
1067 else
1068 return nullptr ;
1069}
1070#endif
1071
1072// ==
1073
1074GSsl::MbedTls::Error::Error( const std::string & s ) :
1075 std::runtime_error("tls error: "+s)
1076{
1077}
1078
1079GSsl::MbedTls::Error::Error( const std::string & fnname , int rc , const std::string & more ) :
1080 std::runtime_error(format(fnname,rc,more))
1081{
1082}
1083
1084std::string GSsl::MbedTls::Error::format( const std::string & fnname , int rc , const std::string & more )
1085{
1086 std::vector<char> buffer( 200U ) ;
1087 buffer[0] = '\0' ;
1088 mbedtls_strerror( rc , &buffer[0] , buffer.size() ) ;
1089 buffer.back() = '\0' ;
1090
1091 std::ostringstream ss ;
1092 ss << "tls error: " << fnname << "(): mbedtls [" << G::Str::printable(std::string(&buffer[0])) << "]" ;
1093 if( !more.empty() )
1094 ss << " [" << more << "]" ;
1095
1096 return ss.str() ;
1097}
1098
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_mbedtls.h:187
Holds a mbedtls_ctr_drbg_context structure.
Definition: gssl_mbedtls.h:78
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 void open(std::ofstream &, const Path &)
Calls open() on the given output file stream.
Definition: gfile_unix.cpp:55
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
An abstract interface for reading and writing from a non-blocking i/o channel.
Definition: greadwrite.h:50
virtual ssize_type write(const char *buf, size_type len)=0
Sends data.
virtual ssize_type read(char *buffer, size_type buffer_length)=0
Reads data.
A class which acquires the process's special privileges on construction and releases them on destruct...
Definition: groot.h:52
A class that calls an exit function at the end of its scope.
Definition: gscope.h:46
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 join(string_view sep, const StringArray &strings)
Concatenates an array of strings with separators.
Definition: gstr.cpp:1224
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
static std::string trimmed(const std::string &s, string_view ws)
Returns a trim()med version of s.
Definition: gstr.cpp:343
static string_view ws() noexcept
Returns a string of standard whitespace characters.
Definition: gstr.cpp:1268
A class like c++17's std::string_view.
Definition: gstringview.h:51
An interface to an underlying TLS library.
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstringarray.h:30
STL namespace.