5

I have a niche website programmed by a volunteer. Like pretty much every website it's secured via TLS, and the main page doesn't let you do much except login via username & password or request an account. Some users recently requested the ability login by instead providing a certificate, but the programmer hasn't worked with certs really so we're wondering how this is typically done. My guesses:

  • Change the TLS server to request a client certificate; the TLS client responds with a Certificate (and associated) handshake message which can contain a blank cert; either way, the TLS connection is made and the TLS server informs the application of the client's authentication status (yes/no)
  • When certificate login is selected, a new TLS connection is attempted, this time with the TLS server requesting client authentication; if the attempt fails the old (unauthenticated client) TLS connection is maintained - the user should be told of the login failure
  • When certificate login is selected, the browser sends its certificate that is encoded in a standard format (like PKCS #7) in a HTTPS message, the server runs some standard tool similar to what's in TLS to authenticate or reject the cert - it doesn't seem like HTTP Authentication headers can handle passing a cert like they can handle supplying username/password

I realize it could be more complicated with the client possibly also supplying CA certs, but for our 1st go assume the server has access to any needed CA certs up to a TA.

7
  • 2
    @security_paranoid: Client certificate authentication is part of the TLS handshake
    – Ja1024
    Commented Dec 3 at 3:18
  • Are you sure that's what the users asked for? While there are many scenarios where file-based client certs are useful, it's usually not really something users "request" as it can be a bit complex and the added value for them is probably quite low. Are you sure they are not instead asking for delegated auth (OIDC, oAuth2, SAML...) or webAuthN (aka passkeys)?
    – jcaron
    Commented Dec 4 at 1:42
  • Also note that you need some way to know you can trust the certificate. This usually means having your own CA, generating private/public key pairs, generating a CSR, sending it to the server, knowing (via other means, e.g. user/password) who the user is, signing the cert, sending it back, and having it installed in the user's system. Alternatively you must rely on certs issued/signed by some other CA you trust. Yet another alternative: allow the user to register a cert in their account (like is often done for ssh public keys). But that's really for advanced users...
    – jcaron
    Commented Dec 4 at 1:47
  • 1
    @jcaron: CBHacking already suggested WebAuthn, but according to the OP's comments, the users specifically requested support for X.509 client certificates.
    – Ja1024
    Commented Dec 4 at 8:13
  • IIRC, the authentication process strongly depends on a server that establishes TLS connections since client verification is embedded into TLS. You didn't mention which server you use. Documentation for that server is the first resource you should consult.
    – beroal
    Commented Dec 4 at 11:13

2 Answers 2

6

The first option is an accurate description of how mTLS with a fallback to password authentication can be implemented, and it's probably the most convenient approach both for you and your users. You make the webserver request a client certificate in all cases, but providing one is optional. If a the user has successfully authenticated with a client certificate, you immediately allow them to access the protected resource. If they haven't provided any certificate, you redirect them to a log-in page where they can enter a username and password.

It's also possible to make the user explicitly choose one of the authentication methods, but then you need a way to distinguish between connections where you want to enforce mTLS and connections where no client certificate has to be presented in the handshake, e.g., by setting up a separate domain. I'm not sure what you mean by “the old (unauthenticated client) TLS connection is maintained”. If you require a client certificate, and the user doesn't provide one, then the server will close the entire connection (e.g., with a TLS 1.3 certificate_required alert). The user then has to make a new request.

I would recommend against the third option which sounds like a misunderstanding of client authentication. It's not enough for the client to merely provide a valid certificate. Certificates aren't secret, so potentially anybody can obtain a certificate from somebody and present it to the server. What the client has to do is prove ownership by, e.g., producing a signature. This is part of the TLS handshake, and you should use this existing mechanism instead of trying to implement your own.

8
  • The first option seems best, but I don't see any mechanism for TLS to notify the application (webserver) "If they haven't provided any certificate" in the TLS 1.3 spec.
    – JMacSD
    Commented Dec 3 at 17:11
  • 1
    @JMacSD: The active user identity is different. Depending on the webserver and configuration, that might be the web application executing under a different OS user, environment variable passed through to CGI, or session context available through in-process load (FastCGI, ASP, etc)
    – Ben Voigt
    Commented Dec 3 at 17:19
  • 1
    @JMacSD: You don’t need a special mechanism for the first option. The webserver already knows when it has received in empty Certificate message from the client and can act upon that, e.g., with a redirect (or pass the information to the web application). For example, nginx sets the $ssl_client_verify variable to either SUCCESS (the client presented a valid certificate), FAILED (the client presented an invalid certificate) or NONE (the client didn’t present any certificate.
    – Ja1024
    Commented Dec 3 at 18:38
  • 2
    @JMacSD: The second option as you describe it makes no sense. Remember that HTTP(S) is stateless: The client makes a request and gets a response. Of course the underlying TLS protocol is connection-oriented, but this is largely outside of your control (unless you write a custom browser, server and HTTP variant). It's not like you could arbitrarily keep connections open, resume or close them as part of your application logic. TLS does have the concept of session resumption, but this is an optimization mechanism handled automatically by the browser and server (if enabled in the configuration).
    – Ja1024
    Commented Dec 3 at 19:05
  • 3
    @JMacSD: What webserver software are you using? Apache? Then you get the same information (SUCCESS/FAILED/NONE) through the SSL_CLIENT_VERIFY environment variable. I would strongly recommend you double-check if the webserver really cannot verify client certificates, because I find that hard to believe. Since the developer seems to lack experience, you should probably not rely on their information alone -- google it or ask the support for the specific webserver or post a question in a sysadmin community like Server Fault.
    – Ja1024
    Commented Dec 4 at 1:13
4

Client certificate (mutual TLS, or mTLS) authentication is only rarely used for user-facing applications, because it complicates the authentication flow by taking credentials at two different layers: the application layer (passwords or similar conventional credentials) or the network layer (client certs are presented when the TLS handshake occurs, before the user sees any UI at all). Additionally, unless you are yourself a certificate authority (or at least operate one internally), there's the question of how you identify trusted client certificates. On the other hand, it's more secure than most credentials, and can be more convenient too.

All web server software worth running any production code on supports client certificates. It is then up to the application developer to retrieve the presented certificate from the user's connection (every server does this a different way), validate the certificate to ensure it's one that you trust (the server typically only verifies that the client holds the private key before completing the handshake, though some will also let you configure a CA cert the client certs need to have been issued from and validate those), locate the corresponding user identity, and set that identity as the authenticated user at the application layer (as if the user had logged in with a conventional credential). This isn't hard, exactly, but it's error-prone, and that's not something you want in authentication code.

For users who don't present a certificate, you want to fall back to standard authentication, of course. Alternatively, you can initially present a landing page that doesn't expect a client cert, and offer the user a choice to authenticate with a cert instead. Which approach is easiest will depend on your server software; some can only be configured for mTLS or not at a site-wide granularity but gracefully handle "the client was requested to supply a cert but didn't"; others treat the lack of an expected client cert as an error so you want to present the user with an option to use mTLS rather than expect it by default.

Note also that logging out from an mTLS session can be difficult to implement. The usual expectation is that if the client can present a client cert once, it can do it arbitrarily many times, so there isn't any reason to enable "log out" as an option other than by the client closing the connection. However, in some cases you might want to support the option of the client reverting to an unauthenticated state and offering the ability to log in (by any method) again. Sometimes the only way to do that is return an Unauthorized error to the client to tell it that the cert is not accepted (anymore).


These days, there are alternative ways to do public key authentication! Consider using FIDO2 authentication, specifically the "webauthn" protocol that all modern browsers implement. "Passkeys" are a general term for webauthn, generally with additional on-client authentication (e.g. requiring the user to authenticate locally to unlock the private key). I somewhat don't like the term "passkey" because it gets used in different ways and one of the more common meanings is "webauthn as sole factor, not even any option to use a second factor", but it's perhaps what your users are most likely to recognize. Some advantages of webauthn over mTLS:

  • Webauthn is explicitly built around the assumption that the user creates a unique public/private key pair for each site. This improves privacy (the server can't tell where else you have accounts) and greatly limits the harm if some public key were to be cracked.
  • Webauthn key generation and enrollment happens automatically within the client, avoiding the relatively involved process (generate key pair, generate CSR, submit CSR to CA, install returned cert from CA) generally involved in obtaining certificates from a CA.
  • Because the user's key (for that site), or a thumbprint of it, is stored on the server, validation is trivial compared to a certificate. No need to check whether the certificate is signed by the right CA, or the chain of trust is valid, or the cert has the right metadata, or the cert's subject exists in the user table; you simply check whether the presented public key is known, and that's all you need. (One can of course do this with certs as well, simply pinning the public key for each user, but it's best to do that when you know the key pair was newly generated).
  • While servers can never prove that clients are taking the expected security measures, the conventions around how to store and access FIDO2 private keys are stronger than for TLS client certificates. Typically, FIDO2 private keys are stored in hardware (either a trusted platform module [TPM]/secure enclave/other hardware security module [HSM] within the client device, or a removable hardware security module like a Yubikey or smart card, and non-exportable), while TLS certificates (and private keys) are often stored just as, or in, files on a file system that can be read and copied by anybody with access to the file. Sometimes a TLS private key isn't even encrypted (and even when it is, the encryption usually is a pretty weak password-based encryption).
  • You avoid the complexity of accepting credentials at two different times and layers (handshake on the network vs. login form in the application); it's just another credential, like a password or access code or whatever, that is presented at login time. As with mTLS, though, you don't have to create UI for the actual authentication process; the browser handles that (asking the user which authenticator to use if they have multiple e.g. a Yubikey and a biometic-based one, authenticating the user if relevant, and actually carrying out the cryptographic operations and so on).
16
  • 1
    The finest granularity it's possible to set an mTLS requirement at is domain-level. The server will either request a client certificate at connection time or it won't, and the user has not requested anything finer-grained than the domain at this time. It's possible of course to only pay attention to the user certificate status when handling requests to specific paths, but there's no option to only request a client certificate for specific paths.
    – James_pic
    Commented Dec 3 at 11:41
  • 3
    @JMacSD To be clear, mTLS literally means "client certs" as you mean them. The "m" stands for "mutual", as in "just as the client validates the server's cert, the server also validates the client's cert". "All web server software... supports client certificates" in the vernacular sense is synonymous with "All web server software... supports mTLS" in the jargon sense. ("Client certs" in the jargon sense refer to the certificates, not the process of using them to verify the client's identity.)
    – ssokolow
    Commented Dec 4 at 0:04
  • 2
    @JMacSD: I'm not sure the developer really knows what they're talking about. The two most popular webservers (nginx and Apache) absolutely do support mTLS, i.e., TLS with built-in client certificate authentication. The application then doesn't have to (and shouldn't) perform any certificate checks itself. Ask the dev what webserver software they're using.
    – Ja1024
    Commented Dec 4 at 1:26
  • 5
    @James_pic: that's false. Through TLS 1.2 the server can initially connect without client cert and then when you GET /restricted/thing initiate renegotiation requiring client cert; Apache httpd for one can do this based on fixed configuration or a per-directory .htaccess file. I'm pretty sure Tomcat (and Wildfly) can similarly require client-auth for particular resources, but haven't looked recently. TLS 1.3 no longer allows renegotiation because $issues but has a specific new capability for post-handshake client auth, see rfc8446 4.6.2. Commented Dec 4 at 2:05
  • 2
    @Ja1024 While the application absolutely shouldn't be doing any parsing of the certificate or similar, there are times when you want it to perform additional validation checks beyond what the server provides. You generally want to be pretty specific about what certs are accepted, beyond just "were they issued by a trusted CA and otherwise valid". Sometimes that works, though, especially if you scope down "trusted CA" to e.g. "only the CA that we use specifically for issuing client certs to users" or whatever. Finally, the application does need to ID the user based on the cert, for authz.
    – CBHacking
    Commented Dec 4 at 7:43

You must log in to answer this question.

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