Using LDAP directory for FIDO 2.0

2019-10-31 00:00:00 +0000 UTC

This entry is written for LDAPCon 2019 in Sofia.

 Table Of Contents

Authenticators

Abstract

FIDO 2.0 is a next generation framework for the strong authentication. It consists of two specifications: WebAuthn(Web Authentication API) and CTAP(Client To Authenticator Protocol). FIDO2 authenticator uses public key authentication instead of password, so the server stores public key as credential in the repository. I’ll introduce a FIDO2 server that stores credentials to LDAP directory and LDAP schema for FIDO2 that I crafted.

Directory structure

A user may use multiple authenticators, so user entity and credential entity is one-to-many relationship.

For our sniff, the credential that was generated with authenticator will be stored in ou=Credentials directory, and the user entry will be stored in ou=Users.

Figure: directory structure

LDAP schema

I just crafted LDAP schema for storing FIDO2 credentials at the beginning.

The schema file is here: fido2.schema

objectclass ( 1.3.6.1.4.1.34468.2.56.2.1
    NAME 'fido2Credential'
    DESC 'objectClass for FIDO2 Credential'
    SUP top STRUCTURAL
    MUST ( fido2CredentialID $ fido2PublicKey $
           fido2SignCount $ fido2UserID )
    MAY ( fido2RawID $ fido2AAGUID $ fido2CredentialName ))

fido2CredentialID

The fido2CredentialID attribute is used to store credential_id that is a unique identifer mapped to a public key. Note that the credential_id is actually stored on authenticator as a byte string. This attribute is part of DN, so it is encoded with base64url to make easy command line operation. The real credential_id is stored to fido2RawID attribute with binary syntax.

fido2SignCount

The Authenticator has a counter that is incremented for each authentication. The RP is able to detect cloned authenticators by comparing the counter and the fido2SignCount attribute during authentication.

fido2PublicKey

The fido2PublicKey attribute is used to store a public key that is created by authenticator during credential registration. The public key is stored in the COSE Key format that is same as communication with authenticator. I’ll explain the later why I use COSE Key format instead of traditional PKCS#1 DER encoding.

fido2AAGUID

The AAGUID is an identifier that indicates model of Authenticator. The authenticator will only send AAGUID when using attestation, otherwise AAGUID is padded with zero bytes.

fido2UserID

The fido2UserID attribute is used to store user.id also called User Handle in WebAuthn specification. The user.id is easy to be misunderstood in FIDO, so I’ll explain the following sections.

What’s user.id

You probably think that user.id is for mapping a user entry by storing username such as uid and cn. But, It’s wrong.

The user.id aka User Handle is byte string identifer that is to mapped to credential entry and user entry, further WebAuthn spec says:

the Relying Party SHOULD NOT include personally identifying information, e.g., e-mail addresses or usernames, in the user handle.

That is to say, authentication and authorization decisions must not be made on the basis username that may change.

Oh😵, The fido2UserID attribute should be generated as a unique random identider and also be added to user entry, when a user register first credential. And also, the fido2UserID attributes in these entries must be kept consistency.

This is like the foreign key in the RDBMS world. LDAP is not good at that. Does it mean we shouldn’t store FIDO2 credentials in LDAP that has no transaction? I suddenly remembered operational attribute entryUUID that is defined RFC4530. UUID is generally represented as text string, but it is actually 16-byte array. The entryUUID is a permanent ID linked with a user entry, It seems to be suitable for WebAuthn user.id. The fido2UserID attribute is UUID synatax in my schema, but it may be binary syntax.

What’s COSE key

COSE Key format is new public key representation that is defined in RFC8152. It is designed to be smaller than the traditional key format e.g. PKCS#1.

How small is the COSE Key. Let’s make sure that. First, generate a EC P256 key by openssl command. And then convert key to PKCS#1 PEM and DER public key format.

$ openssl ecparam -genkey -name prime256v1 -out priv.pem
$ openssl ec -in priv.pem -outform PEM -pubout -out public.pem
$ openssl ec -in priv.pem -outform DER -pubout -out public.der
$ cat public.pem
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeIasmsKcaulTMa2/gJD1XCCRBev5
GQTWWpr4V5h4JZ8YZsqoy/QyDA3d5Z8cOCq0lj/eZpcb4RaVFS3UcoR+Iw==
-----END PUBLIC KEY-----

$ wc -c public.pem public.der
178 public.pem
 91 public.der

A public key size that PKCS#1 DER encoded is 91 bytes.

Incidentally, Let’s check up JWK format size by using the python script. JWK is printable json object.

$ python pem2jwk.py public.pem
{"crv":"P-256","kid":"zzOM7arPny7lNiH4Z1MXlaYFcr1AKxmrzI__lOodktg","kty":"EC","x":"eIasmsKcaulTMa2_gJD1XCCRBev5GQTWWpr4V5h4JZ8","y":"GGbKqMv0MgwN3eWfHDgqtJY_3maXG-EWlRUt1HKEfiM"}

$ python pem2jwk.py public.pem | wc -c
179

Next, convert the public key to COSE key format by using the python script

$ python pem2cose.py public.pem | xxd
00000000: a501 0203 2620 0121 5820 7886 ac9a c29c  ....& .!X x.....
00000010: 6ae9 5331 adbf 8090 f55c 2091 05eb f919  j.S1.....\ .....
00000020: 04d6 5a9a f857 9878 259f 2258 2018 66ca  ..Z..W.x%."X .f.
00000030: a8cb f432 0c0d dde5 9f1c 382a b496 3fde  ...2......8*..?.
00000040: 6697 1be1 1695 152d d472 847e 23         f......-.r.~#

$ python pem2cose.py public.pem | wc -c
77

Certainly, COSE key encoding is the smallest size than others.

In summary:

formatsize
JWK179 bytes
PKCS#1 PEM178 bytes
PKCS#1 DER91 bytes
COSE Key77 bytes

RP Server demonstration

You can try your self:

$ git clone https://github.com/osstech-jp/fido2-ldap-demo.git
$ cd fido2-ldap-demo
$ docker-compose up --build