This document was drafted for Bitmark Inc. It explores the usage of client side certificates with TLS to authenticate users as owners of files, allowing these users to access their files on the server.

The task at hand is to create a general way to do client authentication for requests in any application using TLS. If possible it should be a general mechanism, so that it will work together with the majority of TLS-enabled applications (HTTP, Bittorrent).

The access control lists of the resources that are governed by the system are stored in the (Bitmark) blockchain. I consider the requests to the blockchain and the resource storage as “out of scope”, because they can be implemented in different various ways and it does not really matter for this scheme. For simplicity’s sake I will model the blockchain as an oracle which we can ask for 1) the owners (public keys) of a file and for 2) the contents of a file.

Requirements

  • In functional operation, client is authenticated to server.
  • In functional operation, client is identified as the owner of the requested file.
  • TLS sessions are used to represent authenticated application sessions.
  • Session tokens must be resistant against replay attacks.
  • Session tokens cannot be used to authenticate for anything that they were not generated for.
  • A file can have any number of owners.

Description of the protocol

We first define some data formats:

-- TBS is based on TBSCertificate, as defined in [RFC 5280]. This definition
-- differs from the original one in the sense that it excludes extensions,
-- because the signature will itself be encapsulated in an extension.
TBS  ::=  SEQUENCE  {
    version         [0]  EXPLICIT Version DEFAULT v1,
    serialNumber         CertificateSerialNumber,
    signature            AlgorithmIdentifier,
    issuer               Name,
    validity             Validity,
    subject              Name,
    subjectPublicKeyInfo SubjectPublicKeyInfo,
    issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
    subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL

-- The OwnershipSignature will contain the actual signatures that prove
-- ownership of a specific file.
OwnershipSignature  ::=  SEQUENCE  {
    publicKey BIT STRING,
    signature BIT STRING
}

In the Go language, these types would be represented by the following structs:

type TBS struct {
	Version            int `asn1:"optional,explicit,default:0,tag:0"`
	SerialNumber       *big.Int
	SignatureAlgorithm pkix.AlgorithmIdentifier
	Issuer             asn1.RawValue
	Validity           validity
	Subject            asn1.RawValue
	PublicKey          asn1.RawValue
	UniqueId           asn1.BitString `asn1:"optional,tag:1"`
	SubjectUniqueId    asn1.BitString `asn1:"optional,tag:2"`
}

type ownershipSignature struct {
	PublicKey asn1.BitString
	Signature asn1.BitString
}

The extension itself is defined by Extension (from RFC 5280, page 16), with extnValue set to the ASN.1 serialised ownershipSignature value.

In the following protocols, numbered lists imply that the order in which the actions are executed is important. Bullet lists imply that the order is free to be chosen by the implementer.

Client side

  1. Generate the data needed for instantiating a TBS struct.
    • version, uniqueId, subjectUniqueId can be left empty.
    • serialNumber should be either the value of a counter or a randomly generated value of at least 128 bits. It is important that a SerialNumber is never used twice.
    • signatureAlgorithm, as described in RFC 5280.
    • issuer and subject contain a distinguished name (Name). In this scheme we (mis)use the CommonName (cn) field to identify the file that is being accessed. Example: Subject: CN = /hello. Note that this identifier can be in any format.
    • validity contains the NotBefore and NotAfter fields of the certificate. Because we are using ephemeral certificates, you should set NotAfter to a time early in the future—e.g. 1 hour or 1 day—depending on how long you expect the session to exist.
    • subjectPublicKeyInfo (as described in the RFC) contains the public key of the certificate.
  2. Use the data generated in 1 to populate a TBS struct.
  3. ASN.1 serialise the struct generated in 2 and sign it with the owner’s Ed25519 key.
  4. Recall the format of ownershipSignature. Populate a struct of this type with the owner’s public (Ed25519) key and the signature generated in 3.
  5. Put the data from 4 in ASN.1 serialised form in a X.509 non-cricital extension. (DOI of the extension is to be requested from IANA.)
  6. Self-sign the tbsCertificate, now containing the extension.
  7. Initiate TLS connection.

Server side

In this protocol we choose to override the default verification algorithm.

Assume certs to be the certificate chain that was presented by the client, with the first certificate as the leaf certificate and the last one as the root certificate of the chain. When anything fails in this protocol, the server must abort the TLS handshake (disconnect). The error to the client must always be 'bad certificate'.

  1. If certs is empty then disconnect.
  2. Throw away all the certificates in certs, except for the leaf certificate, which I will call cert.
  3. Check if the information in the certificate is valid.
    • Check if the certificate has the correct validity for the current time.
    • Check if the signature algorithm is supported and allowed.
    • Check the certificate signature.
    • Check if all the extensions are valid (it can be debated whether this is useful at all, because the certificate is self-signed and thus the user can add any arbitrary extensions).
    • Et cetera.
  4. Check if cert contains the custom extension, if not then disconnect.
  5. Deserialise the extension data into (pub, sig). If the serialised data was bad then disconnect.
  6. Check if pub and sig both have the correct length.
  7. Build a TBS struct (see Client side section) and verify that sig is a correct signature on TBS made by the private key of pub.
  8. Query the file storage whether pub is an owner of the file that is identified by cert.Subject.CommonName.
  9. Accept certificate.
  10. In the application we must always check if the file identifier is the same as the client certificate’s CommonName before serving a file.

Note that it is important to execute step 6 before step 7. If the protocol is implemented the other way around a client has a timing channel with which it can query whether a file exists on the server using somebody else’s public key. By first checking the signature we make sure that we will only query the file storage when the client is authenticated (although not yet identified as the owner).

Critical extension?

The certificate is self signed. The authentication of the certificate comes from the contents of the custom extension and not from a CA. So an attacker can always choose whether the extension is critical or non-cricital. Therefore, we do not gain any security by using a critical extension. So we use a non-cricital extension, but the client MAY also set the Critical field to true if they desire.

Extension objectIdentifier

During development I have used the following objectIdentifier value: 1.3.6.1.4.2.1.4.4.5.12.0.7.5.15.15.13.5.6.8.12.13. Which—to my knowledge—falls outside of any already used range.

The objectIdentifiers are given to companies by IANA. When using this protocol on the internet you must apply to get one for Bitmark. Then you can define bitmark.1 to be the objectIdentifier for this TLS extension. It is recommended that after you get this identifier for Bitmark, that you keep a public record of all the identifiers that are used in the bitmark path.

Limitations by using TLS

TLS is not HTTP, so it is not possible to return status messages to the client that tell anything other than about the TLS handshake. I.e. if a file is not present on a server, we will not be able to return a '404' code. The only thing that we can reply with is 'bad certificate'. I expect that this will make troubleshooting very hard for users.

Security

The securiy of this scheme relies mostly on the secrecy of the Ed25519 master key. When this key is compromised, an attacker can generate any certificate and is able to generate correct signatures.

If the private key of the TLS session gets compromised, the attacker will be able to reuse the TLS certificate. They can also generate any certificate which contains the same data as the (legitimately signed) TBS struct. Therefore it is important that the client uses tight parameters. In particular, I advise any implementer to use a short validity time.

The main problem that I see with the security of this system is that it discards regular checking of client certificates. Verifying PKI certificates is a very elaborate process and implementers often overlook tiny but critical checks which fully compromise the security of the scheme [gotofail, self-signed]. PKI is something that you really don’t want to screw around with, because it is too easy to get it wrong.

Performance

As TLS is optimised to be efficient, this scheme is pretty fast. The main drag on the performance of the system is the generation of the (RSA) key on the client side, because generating large prime numbers is a slow process. On my laptop, the key generation process takes a little less than a second, which is not too long for a session, but is surely too much to compute per request.

Reuse the RSA key pair

This can be mitigated against by caching the RSA private key and reusing this for different certificates. This is actually not really bad for security, as we can expect the RSA private key to remain secret at least during the session of a user, especially if we assume that the security of the Ed25519 master key is ensured.

Use ECDSAWithSHA256

Another way to speed up the certificate generation is to not use RSA keys, but use ECDSA keys instead. When doing this, I would recommend to use the P-256 curve. (Some people don’t like the NIST curves, but these are the only ones supported by TLS 1.2, so we don’t have any other option.)

Discussion & conclusion

First of all, this tweak to X.509 is large enough that it needs a lot of customisation to the application in which it will be implemented. I would argue that it is probably a lot easier to just think of another (possibly home-made) protocol that you can use for this. An example of a simple solution is to just use some one-time-password scheme with HTTP. You will probably need to use a native extension to generate the Ed25519 signatures, but you’d also need this when extending TLS. If everything else fails you could perhaps even use tweetnacl-js. I know that people use crypto libraries implemented in JavaScript, but also this is something that I’d try to stay away from. Web pages and browsers are just not very predictable. You just never know if there is some malvertisement (malware advertisement) running in the same page.

So Sean, If you would ask me whether I would use this myself I think that would be a big fat no. After screwing around with X.509 for a couple of days I found that there are just too much nitty gritty checks that you will get wrong. The system is just too complex. X.509 was made for certificate chains, i.e. for delegating trust to a trusted party (root CA). Adding TLS extensions would be good for adding control to the server. For example, Extended Key Usage is a good idea for restricting the usage of a X.509 certificate. But to me a protocol that requires you to implement all the checks yourself just feels like a recipe for disaster.