libhttpserver icon indicating copy to clipboard operation
libhttpserver copied to clipboard

Enable client-certificate authentication

Open etr opened this issue 5 years ago • 3 comments

Is your feature request related to a problem? Please describe. Currently the library doesn't allow an easy use of client certificate authentication (conversely of what libmicrohttpd does).

Describe why the feature or enhancement you are proposing fits the library. It is really just a missing feature as of now. The library already allows basic and digest authentication, this would close the loop.

Describe the solution you'd like Reach feature parity with libmicrohttpd. Should also include support for SNI.

Describe alternatives you've considered Not implementing it. It saves in effort but it still leaves a core authentication feature out of the picture.

etr avatar Jan 19 '19 20:01 etr

I had the same problem using client authentication. You need access to the session, so you need the underlaying_connection of the request. Unfortunately this variable is private. I added a function get_connection to the request, this opens the access to the underlaying libraries. It would be great to have this function in the official release.

tomtom5555 avatar Nov 28 '20 20:11 tomtom5555

I think this might be resolved by: https://github.com/etr/libhttpserver/commit/c5cf5eaa89830ad2aa706a161a647705661bd671

I've used it like this:

if (!req.has_tls_session()) {
  LOG(INFO) << "No TLS";
} else {
  auto tls = req.get_tls_session();
  LOG(INFO) << "Verion: " << gnutls_protocol_get_name(gnutls_protocol_get_version(tls));

  auto type = gnutls_certificate_type_get(tls);
  LOG(INFO) << "CertType: " << gnutls_certificate_type_get_name(type);

  unsigned int cert_count = 0;
  auto* certs = gnutls_certificate_get_peers(tls, &cert_count);
  if (!certs) {
    LOG(INFO) << "No Client Certs";
  } else {
    LOG(INFO) << "CertCount(Client): " << cert_count;

    for (unsigned int cn = 0; cn < cert_count ; cn++) {
      switch (type) {
        default:
          LOG(WARNING) << "Unknown cert type";
          break;

        case GNUTLS_CRT_X509: {
          X509 x509(&certs[0]);

          if(!x509.valid) {
            LOG(ERROR) << "Failed to import client certificate #" << cn;
          } else {
            LOG(INFO) << "Cert " << cn << " DN: " << x509.get_DN();
          }
          break;
        }
      }
    }
  }
}

bcsgh avatar May 06 '23 02:05 bcsgh

This is some code I actually got working and have done some minimal testing on.

The most important part is the gnutls_certificate_verify_peers() call; nothing else checks that the cert is actually trusted.

if (!req.has_tls_session()) return Reject();
const auto tls = req.get_tls_session();

{
  // General validation
  static unsigned char PURPOSE[] = GNUTLS_KP_TLS_WWW_CLIENT;
  static gnutls_typed_vdata_st check[1] = {{GNUTLS_DT_KEY_PURPOSE_OID, PURPOSE, sizeof(PURPOSE)}};

  unsigned int status = 0;
  if (auto ver = gnutls_certificate_verify_peers(tls, check, sizeof(check)/sizeof(*check), &status); ver || status) return Reject();
}

gnutls_datum_t peer;
{
  // Get the client side cert (if present)
  unsigned int peer_list_size = 0;
  auto peer_list = gnutls_certificate_get_peers(tls, &peer_list_size);
  if (peer_list == nullptr || peer_list_size <= 0) Reject();
  peer = peer_list[0];
}

gnutls_x509_crt_t cert;
if ( gnutls_x509_crt_init(&cert) < 0)  Reject();
// Must call gnutls_x509_crt_deinit after here.

if (gnutls_x509_crt_import(ret.get(), cert, GNUTLS_X509_FMT_DER) < 0) {
  gnutls_x509_crt_deinit(cert);
  Reject();
}

std::string CN(64, '\0');
{
  size_t CN_len = CN.size();
  auto r = gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, CN.data(), &CN_len);
  gnutls_x509_crt_deinit(cert);

  if (r) return Reject();
}
return CN;

bcsgh avatar Dec 19 '23 01:12 bcsgh