0

My client says their API traffic must take the path WAF -> Custom Firewall -> Backend API. Also, mTLS must be terminated after the traffic has gone through the network appliance.

I have created an Envoy proxy that can perform mTLS behind the network appliance. That traffic takes the path GCP External Network Load Balancer -> Network Appliance -> GCP Internal Network Load Balancer -> Envoy (mTLS) -> Backend API. This works as expected when I call the endpoint using more traditional methods (not using a HTTP header).

However, the WAF can only send client certificate information in a HTTP header. I do not believe I can configure Envoy to work that way. I have looked at other tools such as Nginx, Apache, Netty, but that doesn’t look very promising to me at this point either. From what I can tell, those tools expect you to provide the path to the server private key, server public cert, and client certs to trust (typically in PEM format). Those tools use that configuration to perform the SSL/TLS handshake process under the hood.

I have implemented mTLS many times before, but I've never been asked to use client certificate information from a HTTP header to do it. This feels atypical to me, and I'm not even sure this is a secure approach.

I'm wondering:

  • Is using a client cert in a HTTP header a viable and secure approach for implementing mTLS?
  • If yes, do you have any recommendations on a tool/config that can accomplish this?

Thanks in advance for any guidance.

3 Answers 3

2

Sending a mTLS client certificate in the header of an HTTP request would be a bit like putting the cart before the horse.

When using TLS, the HTTP request (including the header) is sent through the TLS connection. In other words, the TLS connection is built first, then the HTTP request is sent through the TLS connection. If using mTLS, the client certificate is involved in building the TLS connection (see https://www.cloudflare.com/learning/access-management/what-is-mutual-tls/ for a good write-up of how this works).

So, sending the mTLS client certificate in the HTTP request would not make any sense, because the mTLS client certificate is needed before the HTTP request is sent - i.e. it is needed when the TLS connection is being built.

2

Authentication with a client certificate consists of multiple steps:

  1. Making sure that the certificate is owned by the client.
    This is done during the mTLS handshake though the client signing some parts of the handshake with the client certificates private key and the server validating this signature. Since the private key should only be accessible to the client this proves ownership.
  2. Making sure that the certificate is valid.
    This means to check expiration, revocation and that the certificate was issued by the expected CA. This is usually done during the handshake but can also be done outside. All what this needs is the client certificate itself, since ownership to it was already proven in step#1.
  3. Making sure that the certificate matches the expected identity.
    Checking the expected identity involves extracting subject or similar and associating it with a specific account. It is typically done by the application which needs the identity, i.e. outside the mTLS handshake.

If the mTLS connection is terminated by the WAF then it would be the WAF which verifies this signature which proves ownership of the client certificate (step#1). Depending on the configuration the WAF might also check the certificates validity (step#2) or it might leave this to some later backend. The WAF cannot create a new mTLS connection with the client certificate to the backend since it has no access to the certificates private key (only the client has). All it can do is to transport the checked certificate outside of TLS, since it has no access to the private key of the client. That's why it is transported in a HTTP header. This choice is not unusual, it is also often done by web servers terminating the mTLS connection and providing the web application behind the server with the client identity in form of the proven client certificate. Even Envoy itself supports this.

Of course, any proxy or server behind the WAF can no longer use mTLS to fully validate the client certificate (i.e. starting with step#1). Instead it must trust the WAF that the clients ownership of the certificate was proven during the mTLS connection with the WAF (trust the WAF to have done step#1 properly). All a proxy, server or web application could do is to extract the certificate from the HTTP header and check if the content matches the expectation, like not expired, signed by the expected CA (step#2 - validation), having the expected subject (step#3 - check identity) etc. This can and needs to be done outside the TLS handshake, i.e. one cannot use the integrated validation for mTLS since the certificate is not transported with mTLS anymore. As long as one can fully trust the WAF that the clients ownership was proven during mTLS with the client certificates private key (step#1), this method is acceptable.

Where exactly the certificate is then validated and the identity information used depends on the exact application. It is not uncommon for a proxy (like a WAF, nginx or Envoy) to extract the certificate and put it into a header and provide it the backend web application for further processing. For this backend application it does not matter much were exactly this certificate got extracted, i.e. if in Envoy, nginx or the WAF. Only the name of the header transporting the client certificate and the details of the encoded information might vary and the application need to adjust for this.

2

Is using a client cert in a HTTP header a viable and secure approach for implementing mTLS?

Eh, kind of? Ideally, mTLS establishes an end-to-end connection, client to server, allowing both peers to authenticate to the other. You gave up on that the moment you terminated TLS before reaching your back-end server, though. Even if your WAF supports establishing an mTLS connection with your back-end to relay the client request over, it'll be using the WAF's client cert, not the actual client's cert.

On the other hand, fundamentally, you have to trust the WAF here. You've given it your TLS private key; it is already fully in position to modify any and all aspects of either the client's request or your response, including forging messages out of nowhere or silently dropping real ones. It's not even accurate to say it could impersonate your servers; it literally is doing that (as far as the client is concerned), and hopefully happens to be forwarding requests and responses with only expected levels of tampering (it wouldn't be much of a good WAF if it didn't at least sometimes tamper with the traffic, but "good WAF" is self-contradictory anyhow).

Back to the point, since you already have to fully trust the WAF, you might as well also trust it when it tells you the details of the client certificate. Or, phrasing that a bit differently, assuming your WAF is trustworthy, you can trust what it tells you about the client cert (and if it's not trustworthy, you have much bigger problems). However, you really do just have to take it on trust; you can validate the client cert itself (assuming that the WAF sends you the full cert including its signature), but you can't verify that the cert was actually sent by this particular client or that this particular client holds the private key for this cert. Those, in particular, are the aspects on which you'll just have to trust the WAF to both perform the validation correctly, and tell it to you truthfully.

If yes, do you have any recommendations on a tool/config that can accomplish this?

That sounds like a question for https://serverfault.com/. At that point where the question isn't "how secure is this?" but just "how do I configure my server to do this?", it's out of scope for this site. However, as a piece of general guidance, you're probably not going to be able to use your back-end's server's usual path for mTLS, because it assumes that it's doing the TLS handshake (as you noted) and also wants to be able to prove that the client holds the private key (which it can't, here, and must trust the WAF to verify). You might be able to re-use part of that feature's code - the part that, having established that the client holds the corresponding private key, actually proceeds to identify or at least authorize the client from the cert (including validation of the cert itself) - but the details are going to be implementation-dependent and it wouldn't actually surprise me if they're not normally exposed. It's generally a bad idea to let people poke around in the TLS authentication code too much, because people will mess it up. But if there's a way, the folks at Server Fault might well know it.

If there isn't a way... you'll basically have to take the client cert, manually reconstruct a certificate object using an X.509 library of your choice, validate it in all the relevant ways (expiry, issuer, usage, and of course signature validity; your WAF will hopefully do these already but it's a good idea to do them yourself especially if you're restricting your client certs to e.g. those from a particular CA), and retrieve the subject information from within it. Then look up that subject, construct a client identity from it (ensuring it's a valid user, if relevant), and tell your webapp framework "here's the client's identity".

1
  • « "good WAF" is self-contradictory anyhow » :)
    – ysdx
    Mar 13 at 10:14

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .