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