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:
- The client connects over HTTPS (everything is anyway, but it's relevant that hopefully this prevents any MITM attacks stealing tokens)
- The client sends an identifier (username), and some data signed with a private key
- 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
- Is there an off-the-shelf solution which I should use instead?
- Is this approach secure, or is there a flaw?
- Is there any advantage to having the server send some data to the client to sign, rather than using a timestamp?