gssl_openssl.cpp
Go to the documentation of this file.
1 //
2 // Copyright (C) 2001-2013 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 // gssl_openssl.cpp
19 //
20 
21 #include "gdef.h"
22 #include "gssl.h"
23 #include "gtest.h"
24 #include "gstr.h"
25 #include "gpath.h"
26 #include "gexception.h"
27 #include "glog.h"
28 #include <openssl/ssl.h>
29 #include <openssl/err.h>
30 #include <openssl/rand.h>
31 #include <exception>
32 #include <vector>
33 #include <cassert>
34 #include <iomanip>
35 #include <fstream>
36 #include <sstream>
37 #include <utility>
38 
39 // debugging...
40 // * network logging
41 // $ sudo tcpdump -s 0 -n -i eth0 -X tcp port 587
42 // * emailrelay smtp proxy to gmail
43 // $ emailrelay --forward-to smtp.gmail.com:587 ...
44 // * openssl smtp client to gmail
45 // $ openssl s_client -tls1 -msg -debug -starttls smtp -crlf -connect smtp.gmail.com:587
46 // * certificate
47 // $ openssl req -x509 -nodes -days 365 -subj "/C=US/ST=Oregon/L=Portland/CN=eight.local"
48 // -newkey rsa:1024 -keyout test.cert -out test.cert
49 // $ cp test.cert /etc/ssl/certs/
50 // $ cd /etc/ssl/certs && ln -s `openssl x509 -noout -hash -in test.cert`.0
51 // * openssl server (without smtp)
52 // $ openssl s_server -accept 10025 -cert /etc/ssl/certs/test.pem -debug -msg -tls1
53 //
54 
55 namespace GSsl
56 {
57  class Context ;
58  class Error ;
59  class Certificate ;
60 }
61 
66 {
67 public:
68  explicit Context( const std::string & pem_file = std::string() , unsigned int flags = 0U ) ;
69  ~Context() ;
70  SSL_CTX * p() const ;
71 
72 private:
73  Context( const Context & ) ;
74  void operator=( const Context & ) ;
75  void init( const std::string & pem_file ) ;
76  static void check( int , const char * ) ;
77 
78 private:
79  SSL_CTX * m_ssl_ctx ;
80 } ;
81 
86 {
87 public:
88  explicit Certificate( X509* ) ;
89  ~Certificate() ;
90  std::string str() const ;
91 
92 private:
93  Certificate( const Certificate & ) ;
94  void operator=( const Certificate & ) ;
95 
96 private:
97  X509 * m_p ;
98 } ;
99 
104 {
105 public:
107  explicit LibraryImp( const std::string & pem_file = std::string(), unsigned int flags = 0U, LogFn log_fn = NULL ) ;
108  ~LibraryImp() ;
109  Context & ctx() const ;
110  std::string pem() const ;
111  LogFn logFn() const ;
112  unsigned int flags() const ;
113 
114 private:
115  LibraryImp( const LibraryImp & ) ;
116  void operator=( const LibraryImp & ) ;
117 
118 private:
119  Context * m_context ;
120  std::string m_pem_file ;
121  unsigned int m_flags ;
122  LogFn m_log_fn ;
123 } ;
124 
129 {
130 public:
135 
136  explicit ProtocolImp( const Context & c , unsigned int flags ) ;
137  ProtocolImp( const Context & c , unsigned int flags , LogFn log ) ;
138  ~ProtocolImp() ;
139  Result connect( int ) ;
140  Result accept( int ) ;
141  Result stop() ;
142  Result read( char * buffer , size_type buffer_size , ssize_type & read_size ) ;
143  Result write( const char * buffer , size_type size_in , ssize_type & size_out ) ;
144  std::pair<std::string,bool> peerCertificate() ;
145 
146 private:
147  ProtocolImp( const ProtocolImp & ) ;
148  void operator=( const ProtocolImp & ) ;
149  int error( const char * , int ) const ;
150  void set( int ) ;
151  Result connect() ;
152  Result accept() ;
153  static Result convert( int ) ;
154  static void clearErrors() ;
155 
156 private:
157  SSL * m_ssl ;
158  unsigned int m_flags ;
159  LogFn m_log_fn ;
160  bool m_fd_set ;
161 } ;
162 
166 class GSsl::Error : public std::exception
167 {
168 public:
169  explicit Error( const std::string & ) ;
170  Error( const std::string & , unsigned long ) ;
171  virtual ~Error() throw() ;
172  virtual const char * what() const throw() ;
173 
174 private:
175  std::string m_what ;
176 } ;
177 
178 //
179 
180 GSsl::LibraryImp::LibraryImp( const std::string & pem_file , unsigned int flags , LogFn log_fn ) :
181  m_context(NULL) ,
182  m_pem_file(pem_file) ,
183  m_flags(flags) ,
184  m_log_fn(log_fn)
185 {
186  SSL_load_error_strings() ;
187  SSL_library_init() ;
188 
189  // we probably don't need extra entropy but make a token effort to
190  // find some - quote: "openssl automatically queries EGD when [...]
191  // the status is checked via RAND_status() for the first time if [a]
192  // socket is located at /var/run/edg-pool ..."
193  //
194  G_IGNORE_RETURN(int) RAND_status() ;
195 
196  m_context = new Context( pem_file , flags ) ;
197 }
198 
200 {
201  delete m_context ;
202  ERR_free_strings() ;
203  RAND_cleanup() ;
204 }
205 
206 unsigned int GSsl::LibraryImp::flags() const
207 {
208  return m_flags ;
209 }
210 
212 {
213  return m_log_fn ;
214 }
215 
217 {
218  return *m_context ;
219 }
220 
221 std::string GSsl::LibraryImp::pem() const
222 {
223  return m_pem_file ;
224 }
225 
226 //
227 
228 GSsl::Library * GSsl::Library::m_this = NULL ;
229 
231  m_imp(NULL)
232 {
233  if( m_this == NULL )
234  m_this = this ;
235  m_imp = new LibraryImp ;
236 }
237 
238 GSsl::Library::Library( bool active , const std::string & pem_file , unsigned int flags , LogFn log_fn ) :
239  m_imp(NULL)
240 {
241  if( m_this == NULL )
242  m_this = this ;
243  if( active )
244  m_imp = new LibraryImp( pem_file , flags , log_fn ) ;
245 }
246 
248 {
249  delete m_imp ;
250  if( m_this == NULL )
251  m_this = NULL ;
252 }
253 
255 {
256  return m_this ;
257 }
258 
259 bool GSsl::Library::enabled( bool for_server ) const
260 {
261  return m_imp != NULL && ( !for_server || !m_imp->pem().empty() ) ;
262 }
263 
264 const GSsl::LibraryImp & GSsl::Library::imp() const
265 {
266  if( m_imp == NULL )
267  throw G::Exception( "internal error: no ssl library instance" ) ;
268  return *m_imp ;
269 }
270 
271 std::string GSsl::Library::credit( const std::string & prefix , const std::string & eol , const std::string & final )
272 {
273  std::ostringstream ss ;
274  ss
275  << prefix << "This product includes software developed by the OpenSSL Project" << eol
276  << prefix << "for use in the OpenSSL Toolkit (http://www.openssl.org/)" << eol
277  << final ;
278  return ss.str() ;
279 }
280 
281 //
282 
283 namespace
284 {
285  int verify_callback_always_pass( int , X509_STORE_CTX * )
286  {
287  return 1 ;
288  }
289 }
290 
291 GSsl::Context::Context( const std::string & pem_file , unsigned int flags )
292 {
293  if( (flags&3U) == 2U )
294  m_ssl_ctx = SSL_CTX_new(SSLv23_method()) ;
295  else if( (flags&3U) == 3U ) {
296  m_ssl_ctx = SSL_CTX_new(SSLv23_method()) ;
297  SSL_CTX_set_options(m_ssl_ctx, SSL_OP_NO_SSLv2) ;
298  } else {
299  m_ssl_ctx = SSL_CTX_new(SSLv23_method()) ;
300  SSL_CTX_set_options(m_ssl_ctx, SSL_OP_NO_SSLv2| SSL_OP_NO_SSLv3) ;
301  }
302 
303  if( m_ssl_ctx == NULL )
304  throw Error( "SSL_CTX_new" , ERR_get_error() ) ;
305 
306  if( flags&4U )
307  {
308  // ask for certificates but dont actually verify them
309  SSL_CTX_set_verify( m_ssl_ctx , SSL_VERIFY_PEER , verify_callback_always_pass ) ;
310  }
311  if( (flags&8U) && !pem_file.empty() )
312  {
313  // ask for certificates and make sure they verify
314  SSL_CTX_set_verify( m_ssl_ctx , SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT , NULL ) ;
315  check( SSL_CTX_load_verify_locations( m_ssl_ctx , NULL ,
316  G::Path(pem_file).dirname().str().c_str() ) , "load_verify_locations" ) ;
317  }
318 
319  init( pem_file ) ;
320 }
321 
323 {
324  SSL_CTX_free( m_ssl_ctx ) ;
325 }
326 
327 SSL_CTX * GSsl::Context::p() const
328 {
329  return m_ssl_ctx ;
330 }
331 
332 void GSsl::Context::init( const std::string & pem_file )
333 {
334  G_DEBUG( "GSsl::Context::init: [" << pem_file << "]" ) ;
335  SSL_CTX_set_quiet_shutdown( m_ssl_ctx , 1 ) ;
336  if( !pem_file.empty() )
337  {
338  check( SSL_CTX_use_certificate_chain_file(m_ssl_ctx,pem_file.c_str()) , "use_certificate_chain_file" ) ;
339  check( SSL_CTX_use_RSAPrivateKey_file(m_ssl_ctx,pem_file.c_str(),SSL_FILETYPE_PEM) , "use_RSAPrivateKey_file" );
340  check( SSL_CTX_set_cipher_list(m_ssl_ctx,"DEFAULT") , "set_cipher_list" ) ;
341  }
342 }
343 
344 void GSsl::Context::check( int rc , const char * op )
345 {
346  if( rc != 1 )
347  throw Error( std::string() + "SSL_CTX_" + op ) ;
348 }
349 
350 //
351 
352 GSsl::Error::Error( const std::string & s ) :
353  m_what(std::string()+"ssl error: "+s)
354 {
355 }
356 
357 GSsl::Error::Error( const std::string & s , unsigned long e ) :
358  m_what(std::string()+"ssl error: "+s)
359 {
360  std::vector<char> v( 300 ) ;
361  ERR_error_string_n( e , &v[0] , v.size() ) ;
362  std::string reason( &v[0] , v.size() ) ;
363  reason = std::string( reason.c_str() ) ; // no nuls
364  m_what.append( std::string() + ": [" + reason + "]" ) ;
365 }
366 
368 {
369 }
370 
371 const char * GSsl::Error::what() const throw ()
372 {
373  return m_what.c_str() ;
374 }
375 
376 //
377 
378 GSsl::Protocol::Protocol( const Library & library ) :
379  m_imp( new ProtocolImp(library.imp().ctx(),library.imp().flags(),library.imp().logFn()) )
380 {
381 }
382 
383 GSsl::Protocol::Protocol( const Library & library , LogFn log_fn ) :
384  m_imp( new ProtocolImp(library.imp().ctx(),library.imp().flags(),log_fn) )
385 {
386 }
387 
389 {
390  delete m_imp ;
391 }
392 
393 std::pair<std::string,bool> GSsl::Protocol::peerCertificate( int )
394 {
395  return m_imp->peerCertificate() ;
396 }
397 
399 {
400  if( result == Result_ok ) return "Result_ok" ;
401  if( result == Result_read ) return "Result_read" ;
402  if( result == Result_write ) return "Result_write" ;
403  if( result == Result_error ) return "Result_error" ;
404  return "Result_undefined" ;
405 }
406 
408 {
409  return m_imp->connect( fd ) ;
410 }
411 
413 {
414  return m_imp->accept( fd ) ;
415 }
416 
418 {
419  return m_imp->stop() ;
420 }
421 
422 GSsl::Protocol::Result GSsl::Protocol::read( char * buffer , size_type buffer_size_in , ssize_type & data_size_out )
423 {
424  return m_imp->read( buffer , buffer_size_in , data_size_out ) ;
425 }
426 
427 GSsl::Protocol::Result GSsl::Protocol::write( const char * buffer , size_type data_size_in ,
428  ssize_type & data_size_out )
429 {
430  return m_imp->write( buffer , data_size_in , data_size_out ) ;
431 }
432 
433 //
434 
435 GSsl::ProtocolImp::ProtocolImp( const Context & c , unsigned int flags ) :
436  m_ssl(NULL) ,
437  m_flags(flags) ,
438  m_log_fn(NULL) ,
439  m_fd_set(false)
440 {
441  m_ssl = SSL_new( c.p() ) ;
442  if( m_ssl == NULL )
443  throw Error( "SSL_new" , ERR_get_error() ) ;
444 }
445 
446 GSsl::ProtocolImp::ProtocolImp( const Context & c , unsigned int flags , LogFn log_fn ) :
447  m_ssl(NULL) ,
448  m_flags(flags) ,
449  m_log_fn(log_fn) ,
450  m_fd_set(false)
451 {
452  m_ssl = SSL_new( c.p() ) ;
453  if( m_ssl == NULL )
454  throw Error( "SSL_new" , ERR_get_error() ) ;
455 }
456 
458 {
459  SSL_free( m_ssl ) ;
460 }
461 
462 void GSsl::ProtocolImp::clearErrors()
463 {
464  for( int i = 0 ; ERR_get_error() && i < 10000 ; i++ )
465  ;
466 }
467 
468 int GSsl::ProtocolImp::error( const char * op , int rc ) const
469 {
470  int e = SSL_get_error( m_ssl , rc ) ;
471 
472  if( m_log_fn != NULL )
473  {
474  std::ostringstream ss ;
475  ss << "ssl error: " << op << ": rc=" << rc << ": error " << e << " => " << Protocol::str(convert(e)) ;
476  (*m_log_fn)( 1 , ss.str() ) ;
477  unsigned long ee = 0 ;
478  for( int i = 2 ; i < 10000 ; i++ )
479  {
480  ee = ERR_get_error() ;
481  if( ee == 0 ) break ;
482  Error eee( op , ee ) ;
483  (*m_log_fn)( 2 , std::string() + eee.what() ) ;
484  }
485  }
486 
487  return e ;
488 }
489 
490 GSsl::Protocol::Result GSsl::ProtocolImp::convert( int e )
491 {
492  if( e == SSL_ERROR_WANT_READ ) return Protocol::Result_read ;
493  if( e == SSL_ERROR_WANT_WRITE ) return Protocol::Result_write ;
494  return Protocol::Result_error ;
495 }
496 
498 {
499  set( fd ) ;
500  return connect() ;
501 }
502 
504 {
505  set( fd ) ;
506  return accept() ;
507 }
508 
509 void GSsl::ProtocolImp::set( int fd )
510 {
511  if( !m_fd_set )
512  {
513  int rc = SSL_set_fd( m_ssl , fd ) ;
514  if( rc == 0 )
515  throw Error( "SSL_set_fd" , ERR_get_error() ) ;
516 
517  if( G::Test::enabled("log-ssl-bio") ) // log bio activity directly to stderr
518  {
519  BIO_set_callback( SSL_get_rbio(m_ssl) , BIO_debug_callback ) ;
520  BIO_set_callback( SSL_get_wbio(m_ssl) , BIO_debug_callback ) ;
521  }
522 
523  m_fd_set = true ;
524  }
525 }
526 
528 {
529  clearErrors() ;
530  int rc = SSL_connect( m_ssl ) ;
531  if( rc >= 1 )
532  {
533  return Protocol::Result_ok ;
534  }
535  else if( rc == 0 )
536  {
537  return convert(error("SSL_connect",rc)) ;
538  }
539  else // rc < 0
540  {
541  return convert(error("SSL_connect",rc)) ;
542  }
543 }
544 
546 {
547  clearErrors() ;
548  int rc = SSL_accept( m_ssl ) ;
549  if( rc >= 1 )
550  {
551  return Protocol::Result_ok ;
552  }
553  else if( rc == 0 )
554  {
555  return convert(error("SSL_accept",rc)) ;
556  }
557  else // rc < 0
558  {
559  return convert(error("SSL_accept",rc)) ;
560  }
561 }
562 
564 {
565  int rc = SSL_shutdown( m_ssl ) ;
566  return rc == 1 ? Protocol::Result_ok : Protocol::Result_error ; // since quiet shutdown
567 }
568 
569 GSsl::Protocol::Result GSsl::ProtocolImp::read( char * buffer , size_type buffer_size_in , ssize_type & read_size )
570 {
571  read_size = 0 ;
572 
573  clearErrors() ;
574  int buffer_size = static_cast<int>(buffer_size_in) ;
575  int rc = SSL_read( m_ssl , buffer , buffer_size ) ;
576  if( rc > 0 )
577  {
578  read_size = static_cast<ssize_type>(rc) ;
579  return SSL_pending(m_ssl) ? Protocol::Result_more : Protocol::Result_ok ;
580  }
581  else if( rc == 0 )
582  {
583  return convert(error("SSL_read",rc)) ;
584  }
585  else // rc < 0
586  {
587  return convert(error("SSL_read",rc)) ;
588  }
589 }
590 
591 GSsl::Protocol::Result GSsl::ProtocolImp::write( const char * buffer , size_type size_in , ssize_type & size_out )
592 {
593  size_out = 0 ;
594 
595  clearErrors() ;
596  int size = static_cast<int>(size_in) ;
597  int rc = SSL_write( m_ssl , buffer , size ) ;
598  if( rc > 0 )
599  {
600  size_out = static_cast<ssize_type>(rc) ;
601  return Protocol::Result_ok ;
602  }
603  else if( rc == 0 )
604  {
605  return convert(error("SSL_write",rc)) ;
606  }
607  else // rc < 0
608  {
609  return convert(error("SSL_write",rc)) ;
610  }
611 }
612 
613 std::pair<std::string,bool> GSsl::ProtocolImp::peerCertificate()
614 {
615  std::pair<std::string,bool> result ;
616  result.first = Certificate(SSL_get_peer_certificate(m_ssl)).str() ;
617  result.second = false ; // since we return a bogus result from our callback
618  return result ;
619 }
620 
621 // ==
622 
624  m_p(p)
625 {
626 }
627 
629 {
630  if( m_p != NULL )
631  X509_free( m_p ) ;
632 }
633 
634 std::string GSsl::Certificate::str() const
635 {
636  if( m_p == NULL ) return std::string() ;
637  BIO * bio = BIO_new( BIO_s_mem() ) ;
638  int rc = PEM_write_bio_X509( bio , m_p ) ;
639  if( !rc ) return std::string() ;
640  BUF_MEM * mem = NULL ;
641  BIO_get_mem_ptr( bio , &mem ) ;
642  size_t n = mem ? static_cast<size_t>(mem->length) : 0U ;
643  const char * p = mem ? mem->data : NULL ;
644  std::string data = p&&n ? std::string(p,n) : std::string() ;
645  BIO_free( bio ) ;
646 
647  // sanitise to be strictly printable with embedded newlines
648  std::string result = G::Str::printable( data , '\0' ) ;
649  G::Str::replaceAll( result , std::string(1U,'\0')+"n" , "\n" ) ;
650  G::Str::replaceAll( result , std::string(1U,'\0') , "\\" ) ;
651  return result ;
652 }
653 
Result write(const char *buffer, size_type size_in, ssize_type &size_out)
Context & ctx() const
static Library * instance()
Returns a pointer to a library object, if any.
static std::string printable(const std::string &in, char escape= '\\')
Returns a printable represention of the given input string.
Definition: gstr.cpp:507
Result accept(int)
Context(const std::string &pem_file=std::string(), unsigned int flags=0U)
Error(const std::string &)
LogFn logFn() const
A private exception class used by ssl classes.
SSL_CTX * p() const
Library::LogFn LogFn
static std::string str(Result result)
Converts a result enumeration into a printable string.
A private pimple class used by GSsl::Library.
~Library()
Destructor. Cleans up the underlying ssl library.
Result connect(int fd)
Starts the protocol actively.
std::pair< std::string, bool > peerCertificate(int format=0)
Returns the peer certificate and a verified flag.
std::pair< std::string, bool > peerCertificate()
Library()
Constructor.
static unsigned int replaceAll(std::string &s, const std::string &from, const std::string &to)
Does a global replace on string 's', replacing all occurences of sub-string 'from' with 'to'...
Definition: gstr.cpp:78
Result stop()
Initiates the protocol shutdown.
size_t size_type
Definition: gssl.h:61
static bool enabled()
Returns true if test features are enabled.
Definition: gtest.cpp:46
void(* LogFn)(int, const std::string &)
Definition: gssl.h:64
A private pimple class used by GSsl::Protocol.
static std::string credit(const std::string &prefix, const std::string &eol, const std::string &final)
Returns a credit string.
Result read(char *buffer, size_type buffer_size, ssize_type &read_size)
Protocol::LogFn LogFn
Definition: gssl.h:150
std::string pem() const
std::string str() const
#define G_DEBUG(expr)
Definition: glog.h:95
virtual const char * what() const
LibraryImp(const std::string &pem_file=std::string(), unsigned int flags=0U, LogFn log_fn=NULL)
Protocol::LogFn LogFn
Protocol(const Library &)
Constructor.
A general-purpose exception class derived from std::exception and containing a std::string.
Definition: gexception.h:44
ssize_t ssize_type
Definition: gssl.h:62
Protocol::size_type size_type
unsigned int flags() const
Result read(char *buffer, size_type buffer_size_in, ssize_type &data_size_out)
Reads user data into the supplied buffer.
A RAII class for initialising the underlying ssl library.
Definition: gssl.h:147
~Protocol()
Destructor.
virtual ~Error()
bool enabled(bool for_serving=false) const
Returns true if this is a real and enabled ssl library.
TLS/SSL transport layer security classes.
Result write(const char *buffer, size_type data_size_in, ssize_type &data_size_out)
Writes user data.
ProtocolImp(const Context &c, unsigned int flags)
An openssl X509 RAII class.
An openssl context wrapper.
Result connect(int)
Protocol::ssize_type ssize_type
A Path object represents a file system path.
Definition: gpath.h:44
Result accept(int fd)
Starts the protocol passively.
Protocol::Result Result