[Libwebsockets] Verifying client certificate

Alexander Bruines alexander.bruines at gmail.com
Wed Aug 5 20:46:28 CEST 2015


On 08/05/2015 07:27 PM, Bruce Perens wrote:
> To make verification of client certificates work:
> 
> Start with my article at
> http://blog.algoram.com/blog/2015/06/19/using-the-arrl-logbook-of-the-world-certificate-to-validate-yourself-to-web-services-as-a-licensed-radio-amateur/
> for some information and a demo.
> 
> Apply my patch at http://algoram.com/tmp/libwebsockets.patch . Take a
> look at my change to ssl.c, where I comment out
> SSL_VERIFY_FAIL_IF_NO_PEER_CERT. Uncomment that if you want the
> program to fail if a certificate is not presented to it.
> 
> In your server, handle
> LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS and load the
> entire certificate chain you intend to verify against. The certificate
> loading elsewhere in libwebsockets doesn't work for client certificate
> verification.
> 
> The code will look like this:
> 
> case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS:
>       {
>         SSL_CTX * ssl_context = (SSL_CTX *)user;
> 
>         if ( SSL_CTX_load_verify_locations(ssl_context, ETC_DIR PROGRAM_NAME \
>          "/certificates/authority/default.pem", 0) != 1 ) {
>           std::cerr << "Loading certification authority certificates
> failed." << std::endl;
>         }
>       }
>       return 0;
> 
> Code that reads out the certificate info will look like this:
> 
> static void
> get_client_info(libwebsocket_context * context, libwebsocket * wsi,
> client_context * client)
> {
>   X509 * certificate = SSL_get_peer_certificate(wsi->ssl);
>   char buffer[256];
>   char rip[256];
> 
>   if ( certificate ) {
>     client->info.certificate_is_valid = SSL_check_private_key(wsi->ssl);
>     X509_NAME *subj = X509_get_subject_name(certificate);
>     X509_NAME_get_text_by_NID(subj, NID_commonName, buffer, sizeof(buffer));
>     client->info.name = strdup(buffer);
>     buffer[0] = '\0';
>     X509_NAME_get_text_by_NID(subj, NID_pkcs9_emailAddress, buffer,
> sizeof(buffer));
>     client->info.email = strdup(buffer);
>     buffer[0] = '\0';
>     X509_NAME_get_text_by_NID(subj, NID_callsign, buffer, sizeof(buffer));
>     client->info.callsign = strdup(buffer);
>     buffer[0] = '\0';
>     rip[0] = '\0';
>   }
>   else {
>     client->info.certificate_is_valid = false;
>   }
>   libwebsockets_get_peer_addresses(context, wsi,
> libwebsocket_get_socket_fd(wsi), buffer, sizeof(buffer), rip,
> sizeof(rip));
>   client->info.hostname = strdup(buffer);
>   client->info.ip_address = strdup(rip);
> }
> 
> Of course you will not have the NID_callsign field, used above, unless
> you are using the ARRL Logbook of the World certificate, which is only
> for radio hams. Leave that code out.
> 
>     Bruce
> 
> On Wed, Aug 5, 2015 at 9:33 AM, Alexander Bruines <andy.green at linaro.org> wrote:
>> On 08/05/2015 04:27 PM, techi eth wrote:
>>> Thanks for answer.
>>>
>>> For quick testing if I have server running on some other machine (.Net Client on Windows) & server is expecting client certificate for verification.
>>>
>>> Is that part is already implemented ?
>>>
>>> On Wed, Aug 5, 2015 at 7:10 PM, Alexander Bruines <andy.green at linaro.org <mailto:andy.green at linaro.org>> wrote:
>>>
>>>     On 08/05/2015 02:25 PM, techi eth wrote:
>>>     > Hi,
>>>     >
>>>     > Please let me know how to enable libwebsockets-test-client & libwebsockets-test-server for verifying client certificate by server during connection.
>>>     >
>>>     > Thanks
>>>     >
>>>     >
>>>     > _______________________________________________
>>>     > Libwebsockets mailing list
>>>     > Libwebsockets at ml.libwebsockets.org <mailto:Libwebsockets at ml.libwebsockets.org>
>>>     > http://ml.libwebsockets.org/mailman/listinfo/libwebsockets
>>>     >
>>>
>>>     Hi,
>>>
>>>     This has not been implemented in the test client/server.
>>>     I'd write a patch to support it but someone seems to be ignoring anything I send his way...
>>>
>>>     If you need an example, you could take a look at my project that uses libwebsockets and CyaSSL.
>>>      http://sourceforge.net/projects/galaxy4linux/
>>>
>>>     Btw, it only works when using CyaSSL/wolfSSL.
>>>     With openSSL libwebsockets crashes immediately when I turn on LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT....
>>>
>>>     Regards, Alex
>>>     _______________________________________________
>>>     Libwebsockets mailing list
>>>     Libwebsockets at ml.libwebsockets.org <mailto:Libwebsockets at ml.libwebsockets.org>
>>>     http://ml.libwebsockets.org/mailman/listinfo/libwebsockets
>>>
>>>
>>>
>>>
>>> _______________________________________________
>>> Libwebsockets mailing list
>>> Libwebsockets at ml.libwebsockets.org
>>> http://ml.libwebsockets.org/mailman/listinfo/libwebsockets
>>>
>>
>> I have no idea about that and I don't know any .net.
>> After turning on LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT in your server, your client application should send a client cert when connecting.
>> Read the API documentation for libwebsockets...
>>
>> _______________________________________________
>> Libwebsockets mailing list
>> Libwebsockets at ml.libwebsockets.org
>> http://ml.libwebsockets.org/mailman/listinfo/libwebsockets
> _______________________________________________
> Libwebsockets mailing list
> Libwebsockets at ml.libwebsockets.org
> http://ml.libwebsockets.org/mailman/listinfo/libwebsockets
> 


Hi Bruce,

Thanks for your reply.
What version of libwebsockets are you using? I cloned a new copy from git://git.libwebsockets.org/libwebsockets and your patch does not apply cleanly.
(SSL_VERIFY_FAIL_IF_NO_PEER_CERT is already uncommented in ssl.c)

>From where in your code are you calling your get_client_info() function?
I have already tried a similar approach but SSL_get_peer_certificate(wsi->ssl) does not work (reliably) outside of LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION (also wsi->ssl is a private data member of struct libwebsocket)!
My problem with this is that LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION does not have access to 'context', 'wsi' or 'user'.

In other words, I can get the info I need from the client certificate but I have no place to store it or associate it with the websockets client.


I am using the following code in the http-callback function to get verification of client certificates working (using wolfSSL v3.6.0b and a fresh clone of libwebsockets):

static int websocket_callback_http(
  struct libwebsocket_context *context,
  struct libwebsocket *wsi,
  enum libwebsocket_callback_reasons reason, void *user,
  void *in,
  size_t len
){
  ...
  ...
  // These rely on it that establishing connections to clients are processed sequentially and not in parallel by libwebsockets.
  // (I'm not sure which of the two it is)
  static char last_client_name[ 256 ];    // http protocol: The last client dns name received by LWS_CALLBACK_FILTER_NETWORK_CONNECTION
  static char last_client_ip[ 50 ];      // http protocol: The last client ip address received by LWS_CALLBACK_FILTER_NETWORK_CONNECTION

  switch( reason ){

    // Who is it? Get peer IP address
    case LWS_CALLBACK_FILTER_NETWORK_CONNECTION:
      {
        last_client_name[0] = last_client_ip[0] = '\0';
        libwebsockets_get_peer_addresses( context, wsi,  (int)(long)in, 
          last_client_name, sizeof last_client_name,
          last_client_ip, sizeof last_client_ip
        );
      }
      break;

    //
    // This callback loads the SSL certificates needed for verifying client certificates
    //
    // user = SSL_CTX*
    //
    case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS:
#if ! HAVE_NO_SSL
      {
        char errbuf[160];
        SSL_CTX *ctx = (SSL_CTX*) user;

        // Load the CA bundle
        n = wolfSSL_CTX_load_verify_locations( ctx, fn_ca_cert, NULL );
        if( n != 1 ){
          n = wolfSSL_ERR_get_error();
          SYSLOG( LOG_ERR, "SSL: problem loading CA certificate bundle: %s", wolfSSL_ERR_error_string( n, errbuf ) );
          SYSLOG( LOG_ERR, "Did you remember to setup the SSL certificates with opengalaxy-ca?" );
          SYSLOG( LOG_ERR, "(To disable SSL start opengalaxy with the --disable-ssl option)");
          return 1;
        }

        // Make sure our crl certificate exists
        FILE *fp = fopen( fn_crl_cert, "rt" );
        if( fp ){
          fclose( fp );

          // Load the CRL and activate CRL checking
          n = wolfSSL_CTX_LoadCRL( ctx, path_crl_cert, SSL_FILETYPE_PEM, 0 );
          if( n != 1 ){
            SYSLOG( LOG_ERR, "SSL: problem loading CRL directory: %s", wolfSSL_ERR_error_string( n, errbuf ) );
            SYSLOG( LOG_ERR, "Did you remember to setup the SSL certificates with opengalaxy-ca?" );
            SYSLOG( LOG_ERR, "(To disable SSL start opengalaxy with the --disable-ssl option)");
            return 1;
          }

          n = wolfSSL_CTX_EnableCRL( ctx, 0 );
          if( n != 1 ){
            SYSLOG( LOG_ERR, "SSL: problem enabling CRL: %s", wolfSSL_ERR_error_string( n, errbuf ) );
            SYSLOG( LOG_ERR, "Did you remember to setup the SSL certificates with opengalaxy-ca?" );
            SYSLOG( LOG_ERR, "(To disable SSL start opengalaxy with the --disable-ssl option)");
            return 1;
          }
        }
        else {
          SYSLOG( LOG_ERR, "SSL: problem loading CRL: %s", fn_crl_cert );
          SYSLOG( LOG_ERR, "Did you remember to setup the SSL certificates with opengalaxy-ca?" );
          SYSLOG( LOG_ERR, "(To disable SSL start opengalaxy with the --disable-ssl option)");
          return 1;
        }
      }
#endif
      break;

    // 
    // This callback decides whether or not to allow a client to connect (for all
    // protocols) and is called as:
    // int verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx);
    //
    // context = NULL !!!
    // wsi     = NULL !!!
    // in      = SSL* 
    // user    = X509_STORE_CTX*
    // len     = preverify_ok
    // 
    // Read https://www.openssl.org/docs/ssl/SSL_CTX_set_verify.html to understand what
    // these parameters mean and how to use them.
    //
    // Libwebsocket return conventions apply as usual, ie. return nonzero to block
    // the connection.
    //
    // TODO: We have to store the cert so we can extract data from it for use in the other
    //       protocol callbacks, but how?
    //
    case LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION:
      {
#if HAVE_NO_SSL
        n = 0; // Allow the connection
#else
        n = 1; // Assume the peer certificate is invalid OR could not be authenticated

        // Get the peer X509 certificate
        WOLFSSL_X509 * x509 = wolfSSL_get_peer_certificate( (WOLFSSL*)in );
        if( x509 ){

          // Who is it? Get the clients name from the certificate common name (CN)
          char *peer = wolfSSL_X509_NAME_oneline( wolfSSL_X509_get_subject_name( x509 ), NULL, 0 );
          if( peer ){
            // Loop over all Common Names and use the last one (there should only be one)
            // to identify the clients (user) name
            char *saveptr, *tok = strtok_r( peer, "/", &saveptr);
            while( tok ){
              if( tok[0] == 'C' && tok[1] == 'N' && tok[2] == '=' ){
                SYSLOG( LOG_INFO, "SSL: Login request from: %s @ %s (%s)", &tok[3], last_client_name, last_client_ip );
              }
              tok = strtok_r( NULL, "/", &saveptr );
            }
            free( peer );
          }

          // Test the state of 'preverify_ok'
          if( !len ){
            // The peer certificate failed to be verified, print the error.
            char errbuf[256];
            int err = wolfSSL_X509_STORE_CTX_get_error( (WOLFSSL_X509_STORE_CTX*)user );
            int depth = wolfSSL_X509_STORE_CTX_get_error_depth( (WOLFSSL_X509_STORE_CTX*)user );
            wolfSSL_ERR_error_string_n( err, errbuf, sizeof( errbuf ) );
            SYSLOG( LOG_INFO, "SSL: Certificate error: %s (%d), depth: %d", errbuf, err, depth );
          }
          else {
            // The peer certificate was verified, now check the certificate again to make sure.
            WOLFSSL_CERT_MANAGER* cm = wolfSSL_CertManagerNew();
            if( cm != NULL ){
              if( wolfSSL_CertManagerLoadCA( cm, fn_ca_cert, NULL ) == SSL_SUCCESS ){
                if( wolfSSL_CertManagerSetCRL_Cb( cm, MissingCRL ) == SSL_SUCCESS ){
                  if( wolfSSL_CertManagerLoadCRL( cm, path_crl_cert, SSL_FILETYPE_PEM, 0 ) == SSL_SUCCESS ){
                    if( wolfSSL_CertManagerEnableCRL( cm, WOLFSSL_CRL_CHECKALL ) == SSL_SUCCESS ){
                      const unsigned char *der;
                      int der_length, verify_ok;
                      der = wolfSSL_X509_get_der( x509, &der_length );
                      verify_ok = wolfSSL_CertManagerVerifyBuffer( cm, der, der_length, SSL_FILETYPE_ASN1 );
                      if( verify_ok == SSL_SUCCESS ){
                        n = 0; // Allow the connection to continue.
                      }
                      else {
                        char errbuf[256];
                        wolfSSL_ERR_error_string_n( verify_ok, errbuf, sizeof( errbuf ) );
                        SYSLOG( LOG_ERR, "SSL: Certificate error: %s (%d)", errbuf, verify_ok );
                      }
                    }
                    else SYSLOG( LOG_ERR, "SSL: Could not enable CRL checking!" );
                  }
                  else SYSLOG( LOG_ERR, "SSL: Could not load CRL from path!" );
                }
                else SYSLOG( LOG_ERR, "SSL: Could not set cbMissingCRL!" );
              }
              else SYSLOG( LOG_ERR, "SSL: Could not load the CA certificate!" );
              wolfSSL_CertManagerFree( cm );
            }
            else SYSLOG( LOG_ERR, "SSL: Could not create a certificate manager!" );
          }
        } // ends if( x509 )
        if( n ){
          // final result (n) is nonzero, the certificate is invalid: block the connection
          SYSLOG( LOG_INFO, "SSL: Failed to verify peer certificate, blocking connection!" );
        }
        else {
          // final result (n) is zero, the certificate is valid: allow the connection
          SYSLOG( LOG_INFO, "SSL: Successfully verified peer certificate!" );
          // TODO: Implement a 2nd authentication factor.
        }
#endif
      }
      return n;

  ...
  ...
  ...

  return 0;
}

Regards, Alex




More information about the Libwebsockets mailing list