Skip to content

truthly/pg-webauthn

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

59 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ”πŸ˜webauthn

  1. About
  2. Dependencies
  3. Installation
  4. Usage
  5. API
    1. Sign-up functions
      1. webauthn.init_credential()
      2. webauthn.make_credential()
    2. Sign-in functions
      1. webauthn.get_credentials()
      2. webauthn.verify_assertion()

1. About

webauthn is a pure SQL PostgreSQL extension implementing the WebAuthn protocol used by modern browsers for credential creation and assertion using a U2F Token, like those provided by Yubico, or using Built-in sensors, as seen in the Chrome example below.

Verify your identity Touch ID

2. Dependencies

pgcrypto for the digest() and gen_random_bytes() functions.

pguecc for the ECDSA cryptographic ecdsa_verify() function.

3. Installation

Install the webauthn extension with:

$ git clone https://github.com/truthly/pg-webauthn.git
$ cd pg-webauthn
$ make
$ make install
$ make installcheck

Note that the Postgres development tools and a C compiler must be installed (the postgresql-dev or similar package) and the pgcrypto extension must be included in the Postgres distribution (it's generally included by default; if not, the error will mention "could not open extension control file ".../pgcrypto.control").

4. Usage

Use with:

$ psql
# CREATE EXTENSION webauthn CASCADE;
NOTICE:  installing required extension "pguecc"
NOTICE:  installing required extension "pgcrypto"
CREATE EXTENSION;

5. API

The API consists of two sign-up functions and two sign-in functions.

5.1. Sign-up functions

To sign-up, the browser first calls webauthn.init_credential() to get a list of supported crypto algorithms together with a random challenge to be used in the subsequent webauthn.make_credential() call to save the public key credential generated by the browser.

webauthn.init_credential(...) β†’ jsonb

Input Parameter Type Default
challenge bytea
relying_party_name text
relying_party_id text
user_name text
user_id bytea
user_display_name text
timeout interval '5 minutes'
user_verification webauthn.user_verification_requirement 'preferred'

Source code: FUNCTIONS/init_credential.sql

Stores the random challenge and all the other fields to the webauthn.credential_challenges table. Returns a json object compatible with the browser navigator.credentials.create() method, where the only key, publicKey, contains a PublicKeyCredentialCreationOptions object.

The timeout value, if specified, must lie within a reasonable range between 30 seconds to 10 minutes.

SELECT jsonb_pretty(webauthn.init_credential(
  challenge := '\xf1f49abe5e3dcff7a1f522252f4fb574df415dd087aae156114ac9b51fbf4129'::bytea,
  relying_party_name := 'Localhost'::text,
  relying_party_id := 'localhost'::text,
  user_name := 'test'::text,
  user_id := '\xb3368c7317791c5a98b81428cdf3e35012aa71e6090d04930b390049ead7c282064ee24e9dc7219b6d727cc85aad4dcc0f3134f8e62c6c896a48ac08aac3db1b'::bytea,
  user_display_name := 'test'::text,
  timeout := '2 minutes'::interval
));
{
    "publicKey": {
        "rp": {
            "id": "localhost",
            "name": "Localhost"
        },
        "user": {
            "id": "szaMcxd5HFqYuBQozfPjUBKqceYJDQSTCzkASerXwoIGTuJOncchm21yfMharU3MDzE0-OYsbIlqSKwIqsPbGw",
            "name": "test",
            "displayName": "test"
        },
        "timeout": 120000,
        "challenge": "8fSavl49z_eh9SIlL0-1dN9BXdCHquFWEUrJtR-_QSk",
        "attestation": "none",
        "pubKeyCredParams": [
            {
                "alg": -7,
                "type": "public-key"
            }
        ],
        "authenticatorSelection": {
            "userVerification": "preferred",
            "requireResidentKey": false
        }
    }
}

webauthn.make_credential(...) β†’ user_id bytea

Input Parameter Type
credential_id text (base64url)
credential_type webauthn.credential_type
attestation_object text (base64url)
client_data_json text (base64url)
relying_party_id text (valid domain string)

Source code: FUNCTIONS/make_credential.sql

Stores the public key for the credential generated by the browser to the webauthn.credentials table. The challenge can only be used once to prevent replay attacks. If successful, returns the corresponding user_id bytea value given as input to webauthn.init_credential(), or NULL to indicate failure.

SELECT * FROM webauthn.make_credential(
  credential_id := 'ASiVjgqKJgvSawjRv_bjFR6l9uOgpLJ9jaZbGkxytC3vQzq21tlSuPgAnvQF6B0BLK0dujjrqvK3oBktYP8FEdYOZz8LK8PjiyDGXiCrlSYDy58JILDNJIi-n7973HgHhYiDgN_iBCTfX9Y',
  credential_type := 'public-key',
  attestation_object := 'o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjvSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFX9SYD63OAAI1vMYKZIsLJfHwVQMAawEolY4KiiYL0msI0b_24xUepfbjoKSyfY2mWxpMcrQt70M6ttbZUrj4AJ70BegdASytHbo466ryt6AZLWD_BRHWDmc_CyvD44sgxl4gq5UmA8ufCSCwzSSIvp-_e9x4B4WIg4Df4gQk31_WpQECAyYgASFYIFYGLzqrkNKDty3WMhTXQzjWxIXZekODNhjBB8MjZHgpIlgg1wRbPHszTjstSPn7dPAqVDmO0krRy8rWpTjJDAeOFVY',
  client_data_json := 'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiOGZTYXZsNDl6X2VoOVNJbEwwLTFkTjlCWGRDSHF1RldFVXJKdFItX1FTayIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3QiLCJjcm9zc09yaWdpbiI6ZmFsc2V9',
  relying_party_id := 'localhost'
);
                                                              user_id                                                               
------------------------------------------------------------------------------------------------------------------------------------
 \xb3368c7317791c5a98b81428cdf3e35012aa71e6090d04930b390049ead7c282064ee24e9dc7219b6d727cc85aad4dcc0f3134f8e62c6c896a48ac08aac3db1b
(1 row)

5.2. Sign-in functions

To sign-in, the browser first calls webauthn.get_credentials() to get a list of allowed credentials for the given user_name together with a random challenge to be used in the subsequent webauthn.verify_assertion() call to verify the signature generated by the browser.

webauthn.get_credentials(...) β†’ jsonb

Input Parameter Type Default
challenge bytea
relying_party_id text (valid domain string)
user_name text
timeout interval '5 minutes'
user_verification webauthn.user_verification_requirement 'preferred'

Source code: FUNCTIONS/get_credentials.sql

Stores the random challenge to the webauthn.assertion_challenges table and returns a json object with all public keys matching relying_party_id and user_name. Such public keys have previously been created by the webauthn.make_credential() function, stored in the webauthn.credentials table.

The timeout value, if specified, must lie within a reasonable range between 30 seconds to 10 minutes.

The returned json object is compatible with the browser navigator.credentials.get() method, where the only key, publicKey, contains a PublicKeyCredentialRequestOptions object.

SELECT jsonb_pretty(webauthn.get_credentials(
  challenge := '\xa5174d506a1c0a0e9cd9cd65dae1221582b17824cb9b8c91f032f43c1c09cd1f'::bytea,
  relying_party_id := 'localhost',
  user_name := 'test',
  timeout := '2 minutes'::interval
));
{
    "publicKey": {
        "rpId": "localhost",
        "timeout": 120000,
        "challenge": "pRdNUGocCg6c2c1l2uEiFYKxeCTLm4yR8DL0PBwJzR8",
        "allowCredentials": [
            {
                "id": "ASiVjgqKJgvSawjRv_bjFR6l9uOgpLJ9jaZbGkxytC3vQzq21tlSuPgAnvQF6B0BLK0dujjrqvK3oBktYP8FEdYOZz8LK8PjiyDGXiCrlSYDy58JILDNJIi-n7973HgHhYiDgN_iBCTfX9Y",
                "type": "public-key"
            }
        ],
        "userVerification": "preferred"
    }
}

webauthn.verify_assertion(...) β†’ user_id bytea

Input Parameter Type
credential_id text (base64url)
credential_type webauthn.credential_type
authenticator_data text (base64url)
client_data_json text (base64url)
signature text (base64url)
user_handle text (base64url)
relying_party_id text (valid domain string)

Source code: FUNCTIONS/verify_assertion.sql

Verifies the signature is valid for the credential matching client_data_json->>challenge, credential_id, credential_type and relying_party_id.

The user_handle must also match the user_id for the credential, but not if it is NULL or empty string, in which case the check is skipped.

The challenge can only be used once to prevent replay attacks.

If the signatureΒ could be successfully verified, the function stores the verified assertion to the webauthn.assertions table and returns the user_id bytea value for the corresponding credential, or NULL to indicate failure.

SELECT * FROM webauthn.verify_assertion(
  credential_id := 'ASiVjgqKJgvSawjRv_bjFR6l9uOgpLJ9jaZbGkxytC3vQzq21tlSuPgAnvQF6B0BLK0dujjrqvK3oBktYP8FEdYOZz8LK8PjiyDGXiCrlSYDy58JILDNJIi-n7973HgHhYiDgN_iBCTfX9Y',
  credential_type := 'public-key',
  authenticator_data := 'SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFX9SYFg',
  client_data_json := 'eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoicFJkTlVHb2NDZzZjMmMxbDJ1RWlGWUt4ZUNUTG00eVI4REwwUEJ3SnpSOCIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3QiLCJjcm9zc09yaWdpbiI6ZmFsc2V9',
  signature := 'MEUCIBLCsANiAuhOPX2_GkzCPHhYPAL2xL1Ms22xFHiLDHJfAiEA_Ru_HfC51p-PjvU9VVV5lRKk_swZ9vKMJedQyhnsc4w',
  user_handle := 'szaMcxd5HFqYuBQozfPjUBKqceYJDQSTCzkASerXwoIGTuJOncchm21yfMharU3MDzE0-OYsbIlqSKwIqsPbGw',
  relying_party_id := 'localhost'
);
                                                              user_id                                                               
------------------------------------------------------------------------------------------------------------------------------------
 \xb3368c7317791c5a98b81428cdf3e35012aa71e6090d04930b390049ead7c282064ee24e9dc7219b6d727cc85aad4dcc0f3134f8e62c6c896a48ac08aac3db1b
(1 row)

About

πŸ”πŸ˜ PostgreSQL WebAuthn Server

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors