1

I'm looking to implement a public-key based login / authentication for a website.

This will not be used for general users – it will be used by both automated scripts and for admin logins.

Before someone suggests it, I don't think I want to use client certificates – my understanding is that:

  • Client Certificates are sent automatically, whereas I'll not want the login to be performed automatically whenever an admin visits the site (they may not wish to login, or may wish to login as another user).

  • Using Client certificates will trigger browsers to prompt all users to provide certificates, which isn't acceptable

  • Client certificates require a config change in Apache/Litespeed. I need a solution which can be implemented entirely at the application level in PHP.

If there's an easy way to use client certificates which negates these issues, I'm interested.

My proposal is:

  1. The client connects over HTTPS (everything is anyway, but it's relevant that hopefully this prevents any MITM attacks stealing tokens)
  2. The client sends an identifier (username), and some data signed with a private key
  3. The server looks up the public key for that username, and verifies the signature for the data is correct. If it is, the request is authenticated.

Obviously the data in question should change, to prevent replay attacks. One option is that the server sends some random data to the user, but this requires an extra step. I'm thinking this can be skipped by using the current time (modulo say 30 seconds) for the data, or a rolling code.

** Assumptions **

I will use existing openssl routines to perform the signing and verification. I'm not rolling my own cryptography!

Storing the private key for the client can be done securely. Storing the public key on the server can be done safely (or at least, this isn't the weak point; if an attacker can change that data in the database, the server is already compromised).

Other Requirements

I don't want to use any 3rd party services such as OpenID; I need it to work just between the client and the server.

It needs to work with both CURL and a web browser. For a web browser, I can generate the signed data separately and enter it into a form, similar to a 2FA code.

In terms of how secure this is, I'm looking for something equivalent to SSH.

Questions

  1. Is there an off-the-shelf solution which I should use instead?
  2. Is this approach secure, or is there a flaw?
  3. Is there any advantage to having the server send some data to the client to sign, rather than using a timestamp?
Dan W
  • 111
  • 3
  • Have you looked into [FIDO2](https://fidoalliance.org/fido2/) and the WebAuthn specification? – zyk May 28 '20 at 18:06
  • @zyk no, thanks, will have a look at WebAuthn. From a quick look, it looks like it’s a mechanism for linking hardware Authenticators to a browser. Has it also got a specific API running over HTTPS? – Dan W May 28 '20 at 18:16
  • Yes. [WebAuthn](https://www.w3.org/TR/webauthn/) is an API framework that allows web applications on traditional web browsers to support passwordless logins and use public-key authentication methods instead. Private keys are stored within the user's authenticator, which can be in the form of a hardware token such as a Yubikey, or the security module within a smartphone (to name a few examples). – zyk May 28 '20 at 18:25
  • See https://www.encryptedsend.com/ for a public-key login solution similar to the one you described. It uses the Web Crypto API to do all of the private-key based cryptography in-browser. – mti2935 May 28 '20 at 21:41
  • @zyk: you should make that an answer – Ángel May 29 '20 at 03:03
  • SAML or OpenID Connect (OIDC), they were designed for authentication. Both will work well for humans (your admin use case). Both are scriptable with OIDC being easier to script but SAML can be scripted too. – identigral May 29 '20 at 19:39

2 Answers2

2

As stated in my earlier comments, and an answer to Question 1, a solution already exists for what you wish to implement, and that is the FIDO2 project by the FIDO Alliance. This is a project that consists of a set of specifications and protocols with the main idea being that of "leveraging common devices to easily authenticate to online services in both mobile and desktop environments" (source) and, thus, essentially get rid of passwords once and for all. Industry leaders such as Google and Microsoft have also started to adopt FIDO2 for their internal as well as consumer-facing services, while all major browsers support it.

FIDO2 includes a web authentication standard by W3C, WebAuthn, which in simplistic terms tells you how to implement an API for accepting public key credentials on your website. Clients are then able to use an authenticator that securely stores the private key and sign the data sent by the server to register and authenticate. This is performed by the Client to Authenticator Protocol (CTAP). The authenticator can be in the form of a security key (such as YubiKey), the TPM on a laptop, or even Android phones running OS version 7.0 or later (source). As such, users can secure their authenticators by adding another local authenticator factor, such as a PIN or Biometrics.

Getting into FIDO2 by reading through the actual specifications may be a bit daunting, so I would suggest to look at the following blog post which explains all of this pretty well, at a high-level of course. I also recommend this video by FIDO Alliance for a dive into the technical details. Something else that may be confusing is the difference between FIDO2 and FIDO, and other standards such as U2F and UAF. The answer to this question explains the differences.

As per Question 2, my recommendation would be to stick with well-research and well-developed solutions rather than coming up with your variation of public key based authentication system. All systems are susceptibles to flaws, but I would trust the wholistic effort going into FIDO2 more than I would trust my own efforts in devising something from scratch. To date, I am not aware of any vulnerabilities with WebAuthn or CTAP.

As per Question 3, @Angel's answer covered it well:

You are allowing replay attacks for 30 seconds. Plus, all the complexity of the clocks of client and server drifting apart. You should have the client sign a server nonce (which can then include a timestamp), rather than trying to "simplify it".

A combination of a nonce and a timestamp is widely implemented in many popular authentication schemes, such as Kerberos to name one.

zyk
  • 399
  • 1
  • 2
  • 11
  • Thanks for turning this into an answer and adding some very helpful links. I'm going to wait to accept it until I've worked through it in detail. – Dan W May 29 '20 at 12:04
  • On closer inspection, this looks to be an excellent solution where a user is present, but very difficult to use for automated systems. There's a few 'token emulators' I've found on the web, but they seem tied to the js/HID interface of U2F/CTAP. I'm at a loss how I would go about using this via CURL for example. – Dan W May 29 '20 at 13:16
1

The traditional way to perform Public-key-based website login is to use certificates.

Please note that

  • Client Certificates are not necessarily sent automatically, it will depend on the browser configuration (although setting them to ask could be annoying).
  • You do not need it to make the user log in. You could ignore the fact that a certificate was provided, unless the user wants to log in, in which case it can check that the connection was performed with a valid certificate for that user, and then approve it.
  • Client certificates will not trigger prompts to all users. Specifically, it will not trigger a prompt for users with no certificates, and it should only ask them if they have a certificate that could be acceptable by the website.
  • Anyway, you could require the certificate just on a subdomain, so if an admin goes to log in, it is redirected through that subdomain so it gets the certification assessment, but there is zero change on the configuration on the normal site.

However, you are right in that

  • Client certificates require a config change in Apache/Litespeed. I need a solution which can be implemented entirely at the application level in PHP.

That's because the certificate is part of the HTTPS negotiation itself. This way, not only you verify the website, but the website is also verifying the client (mutual authentication). This means, it is not possible to perform a man-in-the-middle, since the server won't accept the connection from the intermediate server. On the other hand, with a pure PHP solution a MITM could get your signed token and pass it to the real server, without being noticed.

This is also how SSH works, so you won't be able to reach a level equivalent to logging in with SSH keys at PHP only.

Obviously the data in question should change, to prevent replay attacks. One option is that the server sends some random data to the user, but this requires an extra step. I'm thinking this can be skipped by using the current time (modulo say 30 seconds) for the data.

Is there any advantage to having the server send some data to the client to sign, rather than using a timestamp?

You are allowing replay attacks for 30 seconds. Plus, all the complexity of the clocks of client and server drifting apart. You should have the client sign a server nonce (which can then include a timestamp), rather than trying to "simplify it".

I'm looking for something equivalent to SSH.

What about having your users login through SSH and connect to the website tunneling via that connection?

(note this doesn't require them to have any other access, such as running a shell. The keys can be restricted so that they will only be able to forward a connection. And only to your website)

Ángel
  • 17,578
  • 3
  • 25
  • 60
  • Thanks, though this doesn't solve my problem, this is very helpful in explaining Client Certificates, and the advantage of a server nonce. – Dan W May 29 '20 at 08:34
  • An ssh tunnel is a great solution, but not what I need here – I need something that just runs on HTTPS. – Dan W May 29 '20 at 08:38