<commit_before>/*
 * Copyright (c) 2007-2010 Digital Bazaar, Inc. All rights reserved.
 */
#include "monarch/net/SslSocket.h"

#include "monarch/logging/Logging.h"
#include "monarch/io/PeekInputStream.h"
#include "monarch/net/SocketDefinitions.h"
#include "monarch/net/SocketInputStream.h"
#include "monarch/net/SocketOutputStream.h"
#include "monarch/rt/DynamicObject.h"

#include <openssl/err.h>

using namespace monarch::io;
using namespace monarch::net;
using namespace monarch::rt;

/**
 * Certificate verification callback. Called whenever a handshake is performed
 * to check the certificate's common name against the host or against a
 * provided alternatives list. This method will be called regardless of whether
 * or not peer verification is on.
 *
 * @param preverifyOk 1 if the current certificate passed, 0 if not.
 * @param ctx the X.509 certificate store context for certificate verification.
 *
 * @return 0 to stop certificate chain verification immediately and fail the
 *         current handshake (but the connection will only fail if peer
 *         verification is on), 1 to continue -- if 1 is always returned, then
 *         the SSL connection will not be terminated due to any certificate
 *         verification failures, but any errors that were set will be
 *         available in X509_STORE_CTX.
 */
static int _verifyCallback(int preverifyOk, X509_STORE_CTX *ctx)
{
   // only check common name for peer certificate, which is at depth level 0
   // and only check if the certificate was properly signed/verified
   int depth = X509_STORE_CTX_get_error_depth(ctx);
   if(depth == 0 && preverifyOk)
   {
      // get associated SSL socket
      SSL* ssl = (SSL*)X509_STORE_CTX_get_ex_data(
         ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
      SslSocket* self = static_cast<SslSocket*>(SSL_get_ex_data(ssl, 0));

      // get subject name
      X509* x509 = X509_STORE_CTX_get_current_cert(ctx);
      X509_NAME* name = X509_get_subject_name(x509);

      // find a common name that matches
      bool commonNameFound = false;
      X509_NAME_ENTRY* entry;
      ASN1_STRING* str;
      unsigned char* value;
      int i = -1;
      do
      {
         i = X509_NAME_get_index_by_NID(name, NID_commonName, i);
         if(i != -1)
         {
            entry = X509_NAME_get_entry(name, i);
            str = X509_NAME_ENTRY_get_data(entry);
            if(ASN1_STRING_to_UTF8(&value, str) != -1)
            {
               if(self->verifyCommonName((const char*)value))
               {
                  commonNameFound = true;
               }
               OPENSSL_free(value);
            }
         }
      }
      while(i != -1 && !commonNameFound);

      // if common name not found then certificate verification failure
      if(!commonNameFound)
      {
         // the certificate is signed and valid, but is being used
         // for a common name different from what was requested
         X509_STORE_CTX_set_error(ctx, X509_V_ERR_INVALID_PURPOSE);
         preverifyOk = 0;
      }
   }

   return preverifyOk;
}

SslSocket::SslSocket(
   SslContext* context, TcpSocket* socket, bool client, bool cleanup) :
   SocketWrapper(socket, cleanup),
   mVirtualHost(NULL)
{
   // create ssl object
   mSSL = context->createSSL(socket, client);

   // associate this socket with the SSL instance
   SSL_set_ex_data(mSSL, 0, this);

   // allocate bio pair using default sizes (large enough for SSL records)
   BIO_new_bio_pair(&mSSLBio, 0, &mSocketBio, 0);

   // assign SSL BIO to SSL
   SSL_set_bio(mSSL, mSSLBio, mSSLBio);

   // no ssl session negotiated yet
   mSessionNegotiated = false;

   // create input and output streams
   mInputStream = new PeekInputStream(new SocketInputStream(this), true);
   mOutputStream = new SocketOutputStream(this);
}

SslSocket::~SslSocket()
{
   // free SSL object (implicitly frees SSL BIO)
   SSL_free(mSSL);

   // free Socket BIO
   BIO_free(mSocketBio);

   // destruct input and output streams
   delete mInputStream;
   delete mOutputStream;

   // free verify common names
   for(VerifyCommonNameList::iterator i = mVerifyCommonNames.begin();
       i != mVerifyCommonNames.end(); ++i)
   {
      if(*i != NULL)
      {
         free((char*)*i);
      }
   }

   if(mVirtualHost != NULL)
   {
      free(mVirtualHost);
   }
}

void SslSocket::setSession(SslSession* session)
{
   if(session != NULL && (*session) != NULL && (*session)->session != NULL)
   {
      SSL_set_session(mSSL, (*session)->session);
   }
}

SslSession SslSocket::getSession()
{
   // get SSL_SESSION and increment reference count
   SSL_SESSION* s = SSL_get1_session(mSSL);
   SslSession rval(new SslSessionImpl(s));
   return rval;
}

void SslSocket::addVerifyCommonName(const char* commonName)
{
   // add common name to list
   mVerifyCommonNames.push_back(
      (commonName != NULL) ? strdup(commonName) : NULL);

   // set verify callback (retain verify mode) if adding first common name
   if(mVerifyCommonNames.size() == 1)
   {
      SSL_set_verify(mSSL, mSSL->verify_mode, _verifyCallback);
   }
}

std::vector<const char*>& SslSocket::getVerifyCommonNames()
{
   return mVerifyCommonNames;
}

bool SslSocket::verifyCommonName(const char* commonName)
{
   bool rval = false;

   for(VerifyCommonNameList::iterator i = mVerifyCommonNames.begin();
       !rval && i != mVerifyCommonNames.end(); ++i)
   {
      if(strcmp(*i, commonName) == 0)
      {
         // common name matches
         rval = true;
      }
   }

   // add useful logging output
   if(!rval)
   {
      std::string str;
      for(VerifyCommonNameList::iterator i = mVerifyCommonNames.begin();
          i != mVerifyCommonNames.end(); ++i)
      {
         if(str.length() > 0)
         {
            str.push_back(',');
         }
         str.push_back('\'');
         str.append(*i);
         str.push_back('\'');
      }

      // log error
      MO_CAT_DEBUG(MO_NET_CAT,
         "X.509 certificate verification failure, "
         "no match found for common name '%s', permitted common names: %s",
         commonName, str.c_str());
   }

   return rval;
}
<commit_msg>Changed "permitted" to "acceptable" for clarity.<commit_after>/*
 * Copyright (c) 2007-2010 Digital Bazaar, Inc. All rights reserved.
 */
#include "monarch/net/SslSocket.h"

#include "monarch/logging/Logging.h"
#include "monarch/io/PeekInputStream.h"
#include "monarch/net/SocketDefinitions.h"
#include "monarch/net/SocketInputStream.h"
#include "monarch/net/SocketOutputStream.h"
#include "monarch/rt/DynamicObject.h"

#include <openssl/err.h>

using namespace monarch::io;
using namespace monarch::net;
using namespace monarch::rt;

/**
 * Certificate verification callback. Called whenever a handshake is performed
 * to check the certificate's common name against the host or against a
 * provided alternatives list. This method will be called regardless of whether
 * or not peer verification is on.
 *
 * @param preverifyOk 1 if the current certificate passed, 0 if not.
 * @param ctx the X.509 certificate store context for certificate verification.
 *
 * @return 0 to stop certificate chain verification immediately and fail the
 *         current handshake (but the connection will only fail if peer
 *         verification is on), 1 to continue -- if 1 is always returned, then
 *         the SSL connection will not be terminated due to any certificate
 *         verification failures, but any errors that were set will be
 *         available in X509_STORE_CTX.
 */
static int _verifyCallback(int preverifyOk, X509_STORE_CTX *ctx)
{
   // only check common name for peer certificate, which is at depth level 0
   // and only check if the certificate was properly signed/verified
   int depth = X509_STORE_CTX_get_error_depth(ctx);
   if(depth == 0 && preverifyOk)
   {
      // get associated SSL socket
      SSL* ssl = (SSL*)X509_STORE_CTX_get_ex_data(
         ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
      SslSocket* self = static_cast<SslSocket*>(SSL_get_ex_data(ssl, 0));

      // get subject name
      X509* x509 = X509_STORE_CTX_get_current_cert(ctx);
      X509_NAME* name = X509_get_subject_name(x509);

      // find a common name that matches
      bool commonNameFound = false;
      X509_NAME_ENTRY* entry;
      ASN1_STRING* str;
      unsigned char* value;
      int i = -1;
      do
      {
         i = X509_NAME_get_index_by_NID(name, NID_commonName, i);
         if(i != -1)
         {
            entry = X509_NAME_get_entry(name, i);
            str = X509_NAME_ENTRY_get_data(entry);
            if(ASN1_STRING_to_UTF8(&value, str) != -1)
            {
               if(self->verifyCommonName((const char*)value))
               {
                  commonNameFound = true;
               }
               OPENSSL_free(value);
            }
         }
      }
      while(i != -1 && !commonNameFound);

      // if common name not found then certificate verification failure
      if(!commonNameFound)
      {
         // the certificate is signed and valid, but is being used
         // for a common name different from what was requested
         X509_STORE_CTX_set_error(ctx, X509_V_ERR_INVALID_PURPOSE);
         preverifyOk = 0;
      }
   }

   return preverifyOk;
}

SslSocket::SslSocket(
   SslContext* context, TcpSocket* socket, bool client, bool cleanup) :
   SocketWrapper(socket, cleanup),
   mVirtualHost(NULL)
{
   // create ssl object
   mSSL = context->createSSL(socket, client);

   // associate this socket with the SSL instance
   SSL_set_ex_data(mSSL, 0, this);

   // allocate bio pair using default sizes (large enough for SSL records)
   BIO_new_bio_pair(&mSSLBio, 0, &mSocketBio, 0);

   // assign SSL BIO to SSL
   SSL_set_bio(mSSL, mSSLBio, mSSLBio);

   // no ssl session negotiated yet
   mSessionNegotiated = false;

   // create input and output streams
   mInputStream = new PeekInputStream(new SocketInputStream(this), true);
   mOutputStream = new SocketOutputStream(this);
}

SslSocket::~SslSocket()
{
   // free SSL object (implicitly frees SSL BIO)
   SSL_free(mSSL);

   // free Socket BIO
   BIO_free(mSocketBio);

   // destruct input and output streams
   delete mInputStream;
   delete mOutputStream;

   // free verify common names
   for(VerifyCommonNameList::iterator i = mVerifyCommonNames.begin();
       i != mVerifyCommonNames.end(); ++i)
   {
      if(*i != NULL)
      {
         free((char*)*i);
      }
   }

   if(mVirtualHost != NULL)
   {
      free(mVirtualHost);
   }
}

void SslSocket::setSession(SslSession* session)
{
   if(session != NULL && (*session) != NULL && (*session)->session != NULL)
   {
      SSL_set_session(mSSL, (*session)->session);
   }
}

SslSession SslSocket::getSession()
{
   // get SSL_SESSION and increment reference count
   SSL_SESSION* s = SSL_get1_session(mSSL);
   SslSession rval(new SslSessionImpl(s));
   return rval;
}

void SslSocket::addVerifyCommonName(const char* commonName)
{
   // add common name to list
   mVerifyCommonNames.push_back(
      (commonName != NULL) ? strdup(commonName) : NULL);

   // set verify callback (retain verify mode) if adding first common name
   if(mVerifyCommonNames.size() == 1)
   {
      SSL_set_verify(mSSL, mSSL->verify_mode, _verifyCallback);
   }
}

std::vector<const char*>& SslSocket::getVerifyCommonNames()
{
   return mVerifyCommonNames;
}

bool SslSocket::verifyCommonName(const char* commonName)
{
   bool rval = false;

   for(VerifyCommonNameList::iterator i = mVerifyCommonNames.begin();
       !rval && i != mVerifyCommonNames.end(); ++i)
   {
      if(strcmp(*i, commonName) == 0)
      {
         // common name matches
         rval = true;
      }
   }

   // add useful logging output
   if(!rval)
   {
      std::string str;
      for(VerifyCommonNameList::iterator i = mVerifyCommonNames.begin();
          i != mVerifyCommonNames.end(); ++i)
      {
         if(str.length() > 0)
         {
            str.push_back(',');
         }
         str.push_back('\'');
         str.append(*i);
         str.push_back('\'');
      }

      // log error
      MO_CAT_DEBUG(MO_NET_CAT,
         "X.509 certificate verification failure, "
         "no match found for common name '%s', acceptable common names: %s",
         commonName, str.c_str());
   }

   return rval;
}

{'fraction_non_alphanumeric': 0.1048820179007323, 'fraction_numerical': 0.014320585842148088, 'mean_word_length': 2.7970342910101946, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7518341', 'n_tokens_mistral': 3932, 'n_tokens_neox': 3766, 'n_words': 1041}