dodo  0.0.1
A C++ library to create containerized Linux services
tlscontext.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of the dodo library (https://github.com/jmspit/dodo).
3  * Copyright (c) 2019 Jan-Marten Spit.
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, version 3.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * 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 /**
19  * @file tlscontext.cpp
20  * Implements the dodo::network::TLSContext class.
21  */
22 
23 #include <cstring>
24 #include <iostream>
25 #include <openssl/pkcs12.h>
26 #include <openssl/ssl.h>
27 #include <openssl/x509_vfy.h>
28 #include <openssl/x509v3.h>
29 
30 #include "common/datacrypt.hpp"
31 #include "common/exception.hpp"
32 #include "common/util.hpp"
33 #include "network/tlscontext.hpp"
34 #include "network/x509cert.hpp"
35 
36 namespace dodo::network {
37 
39  SSL_load_error_strings();
40  SSL_library_init();
41  OpenSSL_add_all_algorithms();
42  }
43 
45  ERR_free_strings();
46  EVP_cleanup();
47  }
48 
50  const TLSVersion& tlsversion,
51  bool enableSNI,
52  bool allowSANWildcards ) {
53  construct( peerverficiation, tlsversion, enableSNI, allowSANWildcards );
54 
55  }
56 
57  TLSContext::TLSContext( const YAML::Node &yaml ) {
58  PeerVerification pv = peerVerficiationFromString( common::YAML_read_key<std::string>( yaml, "peer-verification" ) );
59  TLSVersion tv = tlsVersionFromString( common::YAML_read_key<std::string>( yaml, "tls-version" ) );
60  bool enable_sni = common::YAML_read_key<bool>( yaml, "enable-sni" );
61  bool allow_wildcards = common::YAML_read_key<bool>( yaml, "allow-san-wildcards" );
62  construct( pv, tv, enable_sni, allow_wildcards );
63  if ( yaml["pem"] ) {
64  std::string priv = common::YAML_read_key<std::string>( yaml["pem"], "private" );
65  std::string pub = common::YAML_read_key<std::string>( yaml["pem"], "public" );
66  std::string pass = common::YAML_read_key<std::string>( yaml["pem"], "passphrase" );
67  common::Bytes bytes;
68  common::DataCrypt::decrypt( "key", pass, bytes );
69  passphrase_ = bytes.asString();
70  loadPEMIdentity( pub, priv, passphrase_ );
71  } else if ( yaml["pkcs12"] ) {
72  } else throw_Exception( "need either pem or pcks12 section");
73  }
74 
75  void TLSContext::construct( const PeerVerification& peerverficiation,
76  const TLSVersion& tlsversion,
77  bool enableSNI,
78  bool allowSANWildcards ) {
79  tlsversion_ = tlsversion;
80  peerverficiation_ = peerverficiation;
81  enable_sni_ = enableSNI;
82  allow_san_wildcards_ = allowSANWildcards;
83  passphrase_ = "";
84  tlsctx_ = nullptr;
85  long rc = 0;
86  tlsctx_ = SSL_CTX_new( TLS_method() );
87  if ( !tlsctx_ ) throw_ExceptionObject( "SSL_CTX_new failed"
88  << common::Puts::endl() << common::getSSLErrors( '\n' ), this );
89  switch( tlsversion_ ) {
90  case TLSVersion::tls1_1 :
91  rc = SSL_CTX_set_min_proto_version( tlsctx_, TLS1_1_VERSION );
92  break;
93  case TLSVersion::tls1_2 :
94  rc = SSL_CTX_set_min_proto_version( tlsctx_, TLS1_2_VERSION );
95  break;
96  case TLSVersion::tls1_3 :
97  rc = SSL_CTX_set_min_proto_version( tlsctx_, TLS1_3_VERSION );
98  break;
99  default:
100  rc = SSL_CTX_set_min_proto_version( tlsctx_, TLS1_1_VERSION );
101  break;
102  }
103  if ( rc == 0 ) throw_ExceptionObject( "SSL_CTX_set_min_proto_version failed"
104  << common::Puts::endl() << common::getSSLErrors( '\n' ), this );
105 
106  SSL_CTX_set_default_passwd_cb( tlsctx_, pem_passwd_cb );
107  SSL_CTX_set_default_passwd_cb_userdata( tlsctx_, this );
108 
109  if ( peerverficiation == PeerVerification::pvVerifyNone ) {
110  SSL_CTX_set_verify( tlsctx_, SSL_VERIFY_NONE, nullptr );
111  } else {
112  SSL_CTX_set_verify( tlsctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr );
113  }
114 
115  rc = SSL_CTX_set_default_verify_paths(tlsctx_);
116  if ( rc == 0 ) throw_ExceptionObject( "SSL_CTX_set_default_verify_paths failed"
117  << common::Puts::endl() << common::getSSLErrors( '\n' ), this );
118 
119  }
120 
121  TLSContext::~TLSContext() {
122  passphrase_ = "";
123  SSL_CTX_free( tlsctx_ );
124  }
125 
126  void TLSContext::loadPEMIdentity( const std::string& certfile,
127  const std::string& keyfile,
128  const std::string& passphrase ) {
129  passphrase_ = passphrase;
130  if ( SSL_CTX_use_certificate_file( tlsctx_, certfile.c_str(), SSL_FILETYPE_PEM ) != 1 ) {
132  }
133  if ( SSL_CTX_use_PrivateKey_file( tlsctx_, keyfile.c_str(), SSL_FILETYPE_PEM ) != 1 ) {
135  }
136  if ( !SSL_CTX_check_private_key( tlsctx_ ) ) {
138  }
139  passphrase_ = "";
140  }
141 
142  void TLSContext::loadPKCS12( const std::string &p12file,
143  const std::string &p12passphrase ) {
144  passphrase_ = "";
145  PKCS12 *p12 = nullptr;
146  FILE *fp = 0;
147  if ( ( fp = fopen( p12file.c_str(), "rb" ) ) )
148  {
149  p12 = d2i_PKCS12_fp( fp, NULL );
150  if ( p12 ) {
151  EVP_PKEY *pkey = nullptr;
152  X509 *cert = nullptr;
153  STACK_OF(X509) *ca = nullptr;
154  if ( PKCS12_parse( p12, p12passphrase.c_str(), &pkey, &cert, &ca) ) {
155 
156  try {
157 
158  if ( SSL_CTX_use_certificate( tlsctx_, cert ) != 1 )
159  throw_ExceptionObject( "cannot use certificate from '" << p12file << "'"
160  << common::Puts::endl() << common::getSSLErrors( '\n' ), this );
161 
162  if ( SSL_CTX_use_PrivateKey( tlsctx_, pkey ) != 1 || !pkey )
163  throw_ExceptionObject( "cannot use private key from '" << p12file << "'"
164  << common::Puts::endl() << common::getSSLErrors( '\n' ), this );
165 
166  if ( !SSL_CTX_check_private_key( tlsctx_ ) )
167  throw_ExceptionObject( "invalid private key in '" << p12file << "'"
168  << common::Puts::endl() << common::getSSLErrors( '\n' ), this );
169 
170  if ( !SSL_CTX_set0_chain( tlsctx_, ca ) ) {
171 
172  throw_ExceptionObject( "cannot use certificate chain from '" << p12file << "'"
173  << common::Puts::endl() << common::getSSLErrors( '\n' ), this );
174  }
175  if ( cert ) X509_free( cert );
176  if ( pkey ) EVP_PKEY_free( pkey );
177  if ( p12 ) PKCS12_free( p12 );
178  fclose( fp );
179  }
180  catch ( ... ) {
181  if ( cert ) X509_free( cert );
182  if ( pkey ) EVP_PKEY_free( pkey );
183  if ( p12 ) PKCS12_free( p12 );
184  fclose( fp );
185  throw;
186  }
187  } else throw_ExceptionObject( "cannot parse PKCS12 file '" << p12file << "'"
188  << common::Puts::endl() << common::getSSLErrors( '\n' ), this );
189  } else throw_ExceptionObject( "cannot read PKCS12 file '" << p12file << "'"
190  << common::Puts::endl() << common::getSSLErrors( '\n' ), this );
191  } else throw_ExceptionObject( "cannot open'" << p12file << "'"
192  << common::Puts::endl() << common::getSSLErrors( '\n' ), this );
193  }
194 
196  if ( src == "pvVerifyNone" ) return TLSContext::PeerVerification::pvVerifyNone;
197  else if ( src == "pvVerifyPeer" ) return TLSContext::PeerVerification::pvVerifyPeer;
198  else if ( src == "pvVerifyFQDN" ) return TLSContext::PeerVerification::pvVerifyFQDN;
199  else throw_Exception( "invalid TLSContext::PeerVerification '" << src << "'" );
200  }
201 
202  int TLSContext::pem_passwd_cb( char *buf, int size, int rwflag, void *userdata ) {
203  TLSContext* tlsctx = static_cast<TLSContext*>(userdata);
204  if ( size > static_cast<int>( strlen( tlsctx->passphrase_.c_str() ) ) ) {
205  strncpy( buf, tlsctx->passphrase_.c_str(), size );
206  } else buf[0] = 0;
207  buf[size-1] = 0;
208  return static_cast<int>( strlen( tlsctx->passphrase_.c_str() ) );
209  }
210 
211  void TLSContext::setCipherList( const std::string& cipherlist ) {
212  int rc = 0;
213  if ( tlsversion_ == TLSVersion::tls1_3 ) {
214  rc = SSL_CTX_set_ciphersuites( tlsctx_, cipherlist.c_str() );
215  } else {
216  rc = SSL_CTX_set_cipher_list( tlsctx_, cipherlist.c_str() );
217  }
218  if ( rc != 1 ) throw_ExceptionObject( "invalid cipherlist '" <<
219  cipherlist << "'", this );
220  }
221 
222  long TLSContext::setOptions( long options ) {
223  return SSL_CTX_set_options( tlsctx_, options );
224  }
225 
226  void TLSContext::setTrustPaths( const std::string& cafile,
227  const std::string& capath ) {
228  const char *cafile_ptr = nullptr;
229  const char *capath_ptr = nullptr;
230  if ( cafile.length() > 0 ) cafile_ptr = cafile.c_str();
231  if ( capath.length() > 0 ) capath_ptr = capath.c_str();
232  if ( !SSL_CTX_load_verify_locations( tlsctx_, cafile_ptr, capath_ptr ) )
233  throw_ExceptionObject( "SSL_CTX_load_verify_locations failed"
234  << common::Puts::endl() << common::getSSLErrors( '\n' ), this );
235  }
236 
238  if ( src == "1.1" ) return TLSVersion::tls1_1;
239  else if ( src == "1.2" ) return TLSVersion::tls1_2;
240  else if ( src == "1.3" ) return TLSVersion::tls1_3;
241  else throw_Exception( "invalid TLSContext::TLSVersion '" << src << "'" );
242  }
243 
244 }
dodo::network::TLSContext::peerverficiation_
PeerVerification peerverficiation_
The peer verification method used.
Definition: tlscontext.hpp:300
tlscontext.hpp
dodo::network::TLSContext::ShutdownSSL
static void ShutdownSSL()
Shutdown the SSL library.
Definition: tlscontext.cpp:44
dodo::network::TLSContext::TLSVersion::tls1_3
@ tls1_3
TLS 1.3 disables SSLv2, SSLv3, TLS 1.0, TLS 1.1 and TLS 1.2.
dodo::network::TLSContext
TLS security context.
Definition: tlscontext.hpp:50
dodo::network::TLSContext::TLSContext
TLSContext(const PeerVerification &peerverficiation=PeerVerification::pvVerifyFQDN, const TLSVersion &tlsversion=TLSVersion::tlsBest, bool enableSNI=true, bool allowSANWildcards=true)
Construct a TLS context.
Definition: tlscontext.cpp:49
dodo::network::TLSContext::pem_passwd_cb
static int pem_passwd_cb(char *buf, int size, int rwflag, void *userdata)
Password callback, returns the passphrase set in the TLS context by the passphrase argument of loadCe...
Definition: tlscontext.cpp:202
throw_ExceptionObject
#define throw_ExceptionObject(what, thing)
Throws an Exception with DebugContext, passes FILE and LINE to constructor.
Definition: exception.hpp:181
dodo::network::TLSContext::setTrustPaths
void setTrustPaths(const std::string &cafile, const std::string &capath)
Trust all certificates (PEM format) in the specified file and/or directory.
Definition: tlscontext.cpp:226
dodo::network::TLSContext::construct
void construct(const PeerVerification &peerverficiation, const TLSVersion &tlsversion, bool enableSNI, bool allowSANWildcards)
Construct the TLSContext.
Definition: tlscontext.cpp:75
dodo::common::DataCrypt::decrypt
static int decrypt(const std::string &key, const std::string src, Bytes &dest)
Decrypt data with a key.
Definition: datacrypt.cpp:129
dodo::common::Bytes
An array of Octets with size elements.
Definition: bytes.hpp:44
dodo::network::TLSContext::PeerVerification::pvVerifyNone
@ pvVerifyNone
No peer verification - transmission is encrypted, peer is accepted even if peer certificate is invali...
dodo::common::YAML_read_key< bool >
template bool YAML_read_key< bool >(const YAML::Node &, const std::string &)
Instantiate template YAML_read_key for bool.
dodo::network::TLSContext::peerVerficiationFromString
static PeerVerification peerVerficiationFromString(const std::string &src)
Get a PeerVerfication enum from a string.
Definition: tlscontext.cpp:195
dodo::network::TLSContext::allow_san_wildcards_
bool allow_san_wildcards_
Allow SAN names to match agains wildcards (eg foo.domain.org matches *.domain.org).
Definition: tlscontext.hpp:315
dodo::network::TLSContext::TLSVersion::tls1_2
@ tls1_2
TLS 1.2 disables SSLv2, SSLv3, TLS 1.0 and TLS 1.1.
dodo::network::TLSContext::TLSVersion::tls1_1
@ tls1_1
TLS 1.1 disables SSLv2, SSLv3 and TLS 1.0.
dodo::network::TLSContext::PeerVerification
PeerVerification
The TLS peer verification method.
Definition: tlscontext.hpp:80
dodo::network::TLSContext::PeerVerification::pvVerifyPeer
@ pvVerifyPeer
The peer must have a trusted certificate (unless a anonymous cipher is used).
dodo::network::TLSContext::passphrase_
std::string passphrase_
The passphrase to decrypt encrypted private keys (may be empty when the key is not encrypted).
Definition: tlscontext.hpp:305
dodo::common::Puts::endl
Mimics std::endl.
Definition: puts.hpp:52
dodo::network::TLSContext::loadPEMIdentity
void loadPEMIdentity(const std::string &certfile, const std::string &keyfile, const std::string &passphrase)
Load a certificate and the corresponding private key for an identity.
Definition: tlscontext.cpp:126
dodo::network::TLSContext::InitializeSSL
static void InitializeSSL()
Initialize the SSL library.
Definition: tlscontext.cpp:38
dodo::network::TLSContext::tlsversion_
TLSVersion tlsversion_
The TLS version.
Definition: tlscontext.hpp:295
dodo::network::TLSContext::enable_sni_
bool enable_sni_
Enable / disable SNI on TLSSocket objects using this TLSContext.
Definition: tlscontext.hpp:310
dodo::network::TLSContext::TLSVersion
TLSVersion
The TLS version.
Definition: tlscontext.hpp:56
dodo::network::TLSContext::PeerVerification::pvVerifyFQDN
@ pvVerifyFQDN
As pvVerifyPeer, but the remote DNS name must match either the peer cert commonname or match one of t...
throw_Exception
#define throw_Exception(what)
Throws an Exception, passes FILE and LINE to constructor.
Definition: exception.hpp:174
dodo::network
Interface for network communication.
Definition: address.hpp:37
datacrypt.hpp
dodo::common::getSSLErrors
std::string getSSLErrors(char terminator)
Get all OpenSSL errors as a single string, and clear their error state.
Definition: util.cpp:154
dodo::network::TLSContext::setCipherList
void setCipherList(const std::string &cipherlist)
Set a list of ciphers, separated by a colon, the TLSContext will accept.
Definition: tlscontext.cpp:211
dodo::common::Bytes::asString
std::string asString() const
Convert to a std::string.
Definition: bytes.cpp:98
dodo::network::TLSContext::tlsVersionFromString
static TLSVersion tlsVersionFromString(const std::string &src)
Convert the src string to a TLSVersion or throw a common::Exception if that mapping fails.
Definition: tlscontext.cpp:237
dodo::network::TLSContext::tlsctx_
SSL_CTX * tlsctx_
The openssl SSL_CTX.
Definition: tlscontext.hpp:290
exception.hpp
dodo::network::TLSContext::setOptions
long setOptions(long option)
Set SSL options.
Definition: tlscontext.cpp:222
x509cert.hpp
dodo::network::TLSContext::loadPKCS12
void loadPKCS12(const std::string &p12file, const std::string &p12passphrase)
Loads a private key, matching certificate and optional CA certificates (eg a truststore) from a PKCS1...
Definition: tlscontext.cpp:142
util.hpp