Physical Access as a Service: Using the Doordeck API
Physical access has been slow to move to the internet age.
This post provides a hands-on overview of the Doordeck platform and its integration capabilities.
A new paradigm
The humble key has been with us for millenia.
In the last few decades, it has been replaced by magnetic and NFC access cards.
Typical card access system
Access cards work in a pretty simple way.
- The card goes near the reader, prompting a read of its unique identifier.
- The reader relays the identifier to a central controller device (usually a computer), via fixed wiring, where…
- it is checked against the whitelist of card identifiers.
- If the card is in the whitelist, the controller instructs the door’s lock to open.
Though more flexible compared to keys, card-based systems leave a lot to be desired.
Cards can be lost or stolen as easily as keys. Once that happens, there is no way of preventing unwanted access without
manually removing the card id from the whitelist first. Cards do not “recognize” an owner.
In addition, card-based systems are usually closed and installed on-premise. This makes them hard to manage and automate centrally.
If not offered out of the box, extensibility (one-time access, instant revocation, complex automation…) may be impossible.
The Doordeck platform 1 builds on top of the already installed offline access card infrastructure and makes it extensible via an API platform. This becomes possible by combining the capabilities of Android / iPhone platforms with public/private key cryptography.
- An NFC-enabled mobile phone goes near the Doordeck tile (or the camera scans the QR code printed on it).
- The device makes an API call, requesting access. The call’s payload is the tile’s id, signed with the user’s private key. This means “user X wants to open door ABC, associated with tile Y”.
- The server checks the request against a number of possible rules (user allowed for this door, blackout times,…).
- If successful, it instructs the access controller to…
- unlock the door.
In this model, each user is identified by a public/private keypair. Depending on the access mode (direct or as a service), the keypair is generated on the Doordeck server or the mobile device.
Let’s see all of this in action through a hands-on example.
Security as a service
The underlying Doordeck entity model is quite simple.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
+-------+ +--------+
| |* *| |
| User +---------+ Site |
| | | |
+-------+ +--------+
|* 1|
| |
| |
| *|
| +--------+ +-------+
| *| |1 0-1| |
+-------------+ Lock +--------+ Tile |
| | | |
+--------+ +-------+
- A Doordeck User has access to a number of Sites (i.e. buildings).
- Each Site has a number of Locks (doors). The User is explicitly permissioned for each one of them.
- Each Lock can be associated with a Tile. This is effectively an identifier for the door.
For a test of the API we need to do 3 things:
- Create a user account with Doordeck,
- associate the new account with a door (a demo one, for now), and
- unlock it!
We will be calling the staging version of the API.
Creating a user account
This is fairly easy at the registration portal.
After logging in, the web application’s home screen looks rather sparse; we are not yet associated with any locks.
Doordeck user portal screen
Associate with the demo door
First we need to discover our user’s identifier.
Let’s call the login endpoint, replacing the EMAIL
and PASSWORD
with your credentials from the registration portal.
1
2
3
4
5
curl 'https://api.staging.doordeck.com/auth/token/' \
-X POST \
-H 'content-type: application/json' \
--data-binary '{"email":"EMAIL","password":"PASSWORD"}' \
| jq -r '.authToken'
Copy the printed token and paste it in the debugger of jwt.io.
This will show us the internal identifier of the User.
User id in the authentication token
To associate with the demo door, we will use a simple web form, which exists to facilitate testing.
Paste your user id and click Submit.
Associate with demo door
You can verify the association by refreshing your home screen in the web app.
Demo door in the web app
In a real-life scenario, the task of associating a User to a Lock, would be done by an admin, using the relevant lock update API call.
You are all set to…
Unlock your first door
On making a login API call, each user is issued with
- an
authToken
to call the non-sensitive API endpoints, and - a
privateKey
to sign a JWT authentication payload for sensitive operations like unlock.
When sending a signed authentication payload, the Doordeck server imposes a time limit of up to 60 seconds from the time of signing to making the API call. This time constrain serves to prevent replay attacks.
This security feature also makes it hard to test the API by hand; one needs to be typing super-fast.
For this reason I have created the following utility script, which
- takes in your credentials,
- logs in to Doordeck,
- signs a JWT authentication payload, and
- unlocks the demo door.
Click to expand script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/bin/bash
echo "Please provide your Doordeck user credentials"
read -p "Email: " USER
read -s -p "Password: " PWD
DEMO_LOCK_ID="ad8fb900-4def-11e8-9370-170748b9fca8"
echo "Retrieving token for user $USER..."
read AUTH_TOKEN PRIVATE_KEY < \
<(echo \
$(curl 'https://api.staging.doordeck.com/auth/token/' \
-X POST \
-H 'content-type: application/json' \
--data-binary '{"email":"'"$USER"'","password":"'"$PWD"'"}' \
| jq -r '.authToken, .privateKey' \
) \
)
echo $PRIVATE_KEY \
| base64 --decode \
| openssl pkcs8 -nocrypt -inform DER -outform PEM -out privatekey.pem
read USER_ID < \
<(arrTOKEN=(${AUTH_TOKEN//./ }); echo ${arrTOKEN[1]} \
| base64 --decode \
| jq -r '.sub'
)
IAT=$(date +%s)
EXP=$((IAT + 60))
echo "Preparing JWT auth. payload..."
HEADER='{"alg":"RS256","typ":"JWT"}'
BODY='{"iss":"'"$USER_ID"'","sub":"'"$DEMO_LOCK_ID"'","nbf":'"$IAT"',"iat":'"$IAT"',"exp":'"$EXP"',"operation":{"type":"MUTATE_LOCK","locked":false,"duration":5}}'
HEADER_B64=`echo -n $HEADER | base64 | sed 's/+/-/g;s/\//_/g;s/=//g'`
BODY_B64=`echo -n $BODY | base64 | sed 's/+/-/g;s/\//_/g;s/=//g'`
SIGNATURE_B64=`echo -n $HEADER_B64.$BODY_B64 | openssl sha256 -sign privatekey.pem | base64 | sed 's/+/-/g;s/\//_/g;s/=//g'`
JWT=`echo -n $HEADER_B64.$BODY_B64.$SIGNATURE_B64`
echo "Unlocking demo door..."
curl "https://api.staging.doordeck.com/device/$DEMO_LOCK_ID/execute" \
-X POST \
-H "Authorization: Bearer $AUTH_TOKEN" \
-H "Content-type: application/json;charset=UTF-8" \
--data-binary "$JWT"
Save the above script as an executable shell script and open a browser to the demo door’s web page.
Run the script, switch to the demo door’s page and… voila!
Door unlocking sequence
Security as a Platform
Doordeck takes the concept of secure access further by offering an integration platform.
This allows third parties and developers to offer access automation as an add-on service through their existing systems
and applications.
The applications range from tenant experience applications, offered by large property managers, to
centralized industrial facility management to vanilla office management.
The conceptual entity model of the Doordeck platform is slightly different.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
+-------+ +-------+ +-------+ +--------+
| |* 1| |* 1| |* *| |
| App +--------+ App +---------+ Admin +---------+ Site |
| User | | | | | | |
+---+---+ +-------+ +-------+ +--------+
| * 1|
| |
| |
| *|
| +--------+ +-------+
| *| |1 0+1| |
+------------------------------------------------+ Lock +--------+ Tile |
| | | |
+--------+ +-------+
- The Doordeck User (as described in the previous section) is the administrator of…
- the Application, registered with the Doordeck platform.
The Application has a number of… - App Users, which it has previously onboarded and authenticated.
The Application takes care of uniquely identifying App Users to Doordeck (id, e-mail…). - The Doordeck Platform allows these App Users access to Locks, by issuing ephemeral security keys.
Let’s see how that works end-to-end, using the staging API.
Registering the application
The integrator Application needs to identify itself to the Doordeck API. 2
We start by registering the application in the Doordeck portal.
Application identity
The application uniquely identifies itself to the API using a URL.
This is called Auth domain
in the Doordeck portal. Once defined, it will need to be specified verbatim in the iss
field of all JWTs sent to the API.
Adding the application’s external identifier
Application security
We will also need to upload the public key of the application.
The application will sign JWTs with its private key as an authentication step every time it calls the Doordeck API.
The developer portal requires the upload of the public keys in JWK format.
For test purposes, this can be generated manually at https://mkjwk.org/, making sure to only select an algorithm
from the relevant RFC-mandated list (e.g. RS256).
The website can also generate the keys in PEM format. We will needd PEM to automate the local signing of the payloads.
Manually generating a JWK
Alternatively, if you have
- Java 14+ installed and
- the lokey Python tool
you can take a shortcut and generate the keys using the following little script.
The script
- downloads the mkjwk JAR from Github,
- generates a 2048 bit RSA key named
my-key
, - creates files
fullKey.jwk
andpublicKey.jwk
in the current directory, and - creates
privateAppKey.pem
andpublicAppKey.pem
derived from them.
Click to expand script
1
2
3
4
5
6
7
8
9
#!/bin/bash
curl -L -o json-web-key-generator.jar https://github.com/mitreid-connect/json-web-key-generator/releases/download/json-web-key-generator-0.8.2/json-web-key-generator.jar
java -jar json-web-key-generator.jar -t RSA -s 2048 -i my-key -u sig -p -o fullKey.jwk -P publicKey.jwk
cat fullKey.jwk | lokey to pem > privateAppKey.pem
cat publicKey.jwk | lokey to pem > publicAppKey.pem
Paste the public JWK (publicKey.jwk
) in the Doordeck portal. This will be used to verify your application’s API calls.
Adding the integrator application identifier
Save the PEM files because we will need them when we…
Generate an application JWT
As we saw in the previous section, upon user login, we receive a private key with which to sign API call JWTs.
The case of an Application making API calls is not much different.
The Application signs the JWT with its private key and defines the App User details (name, e-mail…) in the JWT’s body.
Since this is a trusted Application, the App User details are also trusted.
The following script assembles, tests and saves such an authentication token using
- the Application’s auth. domain,
- a user e-mail provided by you,
- the
privateAppKey.pem
file generated previously, and - assuming that the signature algorithm is
RS256
and the Application’s key is namedmy-key
.
Save the script somewhere as step1.sh
and run it.
The generated authentication token will be valid for 43,200 sec (i.e. 12 hours).
Click to expand script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/bin/bash
echo "Please provide your Application auth. domain"
read -p "Domain: " DOMAIN
echo "Please provide your Application User's e-mail"
read -p "E-mail: " EMAIL
IAT=$(date +%s)
EXP=$((IAT + 43200))
echo "Preparing the authentication token JWT..."
HEADER='{"alg":"RS256","typ":"JWT","kid":"my-key"}'
BODY='{"iss":"'"$DOMAIN"'","iat":'"$IAT"',"exp":'"$EXP"',"aud":"https://api.doordeck.com","sub":"'"$EMAIL"'","email":"'"$EMAIL"'","email_verified":true,"telephone":"+441234567890","telephone_verified":true,"name":"Name '"$EMAIL"'","locale":"en-gb","zoneinfo":"Europe/London"}'
HEADER_B64=`echo -n $HEADER | base64 | sed 's/+/-/g;s/\//_/g;s/=//g'`
BODY_B64=`echo -n $BODY | base64 | sed 's/+/-/g;s/\//_/g;s/=//g'`
SIGNATURE_B64=`echo -n $HEADER_B64.$BODY_B64 | openssl sha256 -sign privateAppKey.pem | base64 | sed 's/+/-/g;s/\//_/g;s/=//g'`
JWT=`echo -n $HEADER_B64.$BODY_B64.$SIGNATURE_B64`
echo "Validating the application JWT. You should see the user details printed below..."
curl -X GET \
https://api.staging.doordeck.com/platform/auth \
-H "Authorization: Bearer $JWT"
echo -n "$JWT" > auth.token
Authenticating the device
The 3rd party application on the App User’s phone needs to unambiguously and securely identify the user and her phone to Doordeck before unlocking a door.
To achieve this, the Doordeck mobile SDK generates a new Ed25519 keypair, when the user logs in to the device.
This is sent to Doordeck, “registered” against the App User’s “profile” and comes back as a signed certificate.
This proves
- to the SDK that the server responding is indeed the Doordeck API, and
- to the Doordeck API that the device belongs to a previously authorized App User (in subsequent unlock attempts).
To emulate these steps manually, we can utilize the Pynacl Python library, for generating and handling Ed25519 key material.
After installing Pynacl, save the following Python 2.7 script as ed25519.py
in the current working directory; we will be
calling its methods.
Click to expand Python script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import sys
from nacl.signing import SigningKey
from nacl.encoding import Base64Encoder
def generate_keys():
sk = SigningKey.generate()
vk = sk.verify_key
f = open("private_base64.key", "w")
f.write(sk.encode(Base64Encoder()))
f.close()
f = open("public_base64.key", "w")
f.write(vk.encode(Base64Encoder()))
f.close()
def sign_message():
f = open("private_base64.key", "r")
sk_base64 = f.read()
f.close()
message = sys.stdin.read()
sk = SigningKey(sk_base64, Base64Encoder())
sig = sk.sign(message).signature
sys.stdout.write(sig)
Let’s pretend that the App User has opened the 3rd party mobile application.
The SDK generates an Ed25519 keypair and sends the public key to the Doordeck API, along with the current App User’s details.
We have already constructed and saved an authentication token from the previous script, so we will re-use that token.
Click to expand script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/bash
TOKEN=`cat auth.token`
echo "Generating the ephemeral key..."
python -c 'import ed25519; ed25519.generate_keys()'
echo "Submitting the ephemeral key..."
curl -X POST \
"https://api.staging.doordeck.com/auth/certificate" \
-H "Authorization: Bearer $TOKEN" \
-H 'content-type: application/json' \
--data-binary "{\"ephemeralKey\":\"`cat public_base64.key`\"}" > certs.json
USER_ID=$(cat certs.json | jq -r .userId)
echo -n "The userId is: "
echo $USER_ID
echo -n $USER_ID > user.id
CERTS=$(cat certs.json | jq .certificateChain)
echo -n "$CERTS" > cert.chain
This script (let’s save it as step2_1.sh
) does the following
- Generates a new ephemeral key
- Submits it to Doordeck
- Prints back the Doordeck UUID for the particular App User and saves it for the next step, and
- Saves the certificate chain of the ephemeral key (again, for the next step). 3
If the App User’s e-mail is a valid one, then she will receive a confirmation mail like the following
Key sharing e-mail confirmation
Like before, we will need to…
Associate the App User with the demo door
Go to the web form, paste the App User id printed from the script you have just run and click Submit.
Associate with demo door
And now we are again ready to…
Unlock the demo door
The following script (let’s call it step_2_2.sh
) is using all of the artifacts we gathered in our previous steps.
Namely, it loads
- the authentication token, identifying the Application and providing the App User details
- the UserId and Certificate chain, proving that the user device can be positively identified by a known ephemeral Ed25519 keypair
- the private key of the Ed25519 keypair, for signing the unlock request and closing the security “loop”.
Click to expand script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/bin/bash
LOCK_ID="ad8fb900-4def-11e8-9370-170748b9fca8"
echo "Loading token, user id and cert. chain..."
TOKEN=`cat auth.token`
CHAIN=`cat cert.chain`
USER_ID=`cat user.id`
IAT=$(date +%s)
EXP=$((IAT + 60))
echo "Preparing door unlock payload..."
HEADER='{"alg":"EdDSA","typ":"JWT","x5c":'"$CHAIN"'}'
BODY='{"iss":"'"$USER_ID"'","sub":"'"$LOCK_ID"'","nbf":'"$IAT"',"iat":'"$IAT"',"exp":'"$EXP"',"operation":{"type":"MUTATE_LOCK","locked":false,"duration":5}}'
HEADER_B64=`echo -n $HEADER | base64 | sed 's/+/-/g;s/\//_/g;s/=//g'`
BODY_B64=`echo -n $BODY | base64 | sed 's/+/-/g;s/\//_/g;s/=//g'`
SIGNATURE_B64=`echo -n $HEADER_B64.$BODY_B64 | python -c 'import ed25519; ed25519.sign_message()' | base64 | sed 's/+/-/g;s/\//_/g;s/=//g'`
JWT=`echo -n $HEADER_B64.$BODY_B64.$SIGNATURE_B64`
echo "Unlocking demo door..."
curl "https://api.staging.doordeck.com/device/$LOCK_ID/execute" \
-X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-type: application/json;charset=UTF-8" \
--data-binary "$JWT"
Open a browser to the demo door’s web page.
Run the script, switch to the demo door’s page and watch the door being unlocked.
Door unlocking sequence
Parting thoughts
Connecting physical access to the online world is a necessity.
As expected there are various other systems attempting to achieve this. These range from adding mobile access as an afterthought to high capex costs for Bluetooth readers to complete door hardware replacement.
Doordeck occupies a sweet spot in the physical access vertical, as it balances 3 almost competing priorities:
- security,
- extensibility both for integrators and for expansion to other sub-verticals, and
- minimizing capex by re-use of existing physical infrastructure.
As organisations find a new modus operandi post-Covid (socially and financially), the Doordeck platform will provide solutions to many of the new challenges they face.
Footnotes
- Full disclaimer: I am an angel investor in Doordeck.
- This section is a condensed version of the guide found here.
- Calling this script repeatedly for the same App User will trigger the Verify Ephemeral Key with Secondary Authentication flow.
This will be signaled by the API with an HTTP 423 return code. If you are just testing, it might be easier to repeatstep1.sh
with a different user e-mail.