How to write a basic ASPE server
Posted on 2023-09-14
This is the second in a series of posts related to the new Ariadne Signature Profiles:
- How to create an Ariadne Signature Profile
- How to write a basic ASPE server (you are here!)
- How to write a basic ASPE client
This post is intended for developers who want to create their own tools compatible with Keyoxide.
This post is also available in the Keyoxide docs.
What is ASPE?
We explored in the previous post how to create an Ariadne Signature Profile. This guide made use of the Keyoxide ASP tool to create the actual profile but the profile will eventually be stored on a server that is compatible with the Ariadne Signature Profile Exchange (ASPE) protocol.
ASPE is defined in this specification. This is still a work in progress, so feel free to share ideas or join the discussion if you are into specs!
The ASP tool sends profiles to a server hosted by Keyoxide that runs aspe-server-rs, a FOSS server written in Rust. It's still new but it works and follows the spec.
In this guide, I will go through the different steps that an ASPE server must perform and invite you to write your own if you feel inclined to! It's not that difficult, and choice only improves ecosystems such as these.
What does an ASPE server do?
A public ASPE server will:
- receive and verify ASP profiles that people upload,
- store ASP profiles,
- send the ASP profiles upon requests.
ASP profiles are encoded, stored and distributed as so-called profile JWSs, which in turn are sent to the ASPE server as part of so-called request JWSs.
Of course, an ASPE server could also be private, meaning people will not be able to upload their profiles to it — or only a list of pre-authorized people/profiles may do so — but the profiles that the server does store should still be publicly accessible.
Implementing an ASPE server
Now, let's try and implement an ASPE server! This will be a simple overview of the different required steps. For more implementation details, refer to the ASP specification.
I have already written a minimal ASPE server written in Rust, creatively named aspe-server-rs. I will be basing this guide on that repository, so feel free to inspect the code for more context.
Receiving ASPs
Let's begin the guide by looking at how ASPE servers receive ASPs.
An ASPE server has an endpoint dedicated to receiving ASPs:
POST ENDPOINT = '/.well-known/aspe/post'
This endpoint must use HTTPS and accept POST requests. POST requests must have a so-called request JWS as body (see the Request JWS section).
Upon receiving a POST request, the server must validate the request JWS by checking the different values it contains as well as validating the signature. These checks are all described in the Request JWS section.
The server must also check the validity of the profile JWS contained within the request JWS.
Luckily, there are many libraries and tools to help with this validation process, including jose (JS) and josekit (Rust).
If the request JWS is valid, the profile JWS it contains must be stored in a way the server implementation sees fit. See the Storing ASPs section. The request JWS must be discarded.
Request JWS
A request JWS is a JSON Web Signature (RFC7515) that is sent over HTTPS to transmit a so-called profile JWS (see the Profile JWS section).
To validate a request JWS, make sure that:
- the
typ
header is set toJWT
, - the
alg
header is set to eitherEdDSA
orES256
, - the
jwk
header is set to a valid JWK key, - the
kid
header is set to the JWK Key's fingerprint as obtained in Computing the fingerprint, - the
http://ariadne.id/type
property is set torequest
, - the
http://ariadne.id/action
property is set tocreate
, - the
http://ariadne.id/profile_jws
property is set to a valid Profile JWS, - the
iat
property is within a reasonable amount of time of the time of processing.
A reasonable amount of time is subjective and requires deliberate considation from the developer. A shorter amount of time helps protect profiles against replay attacks but punishes people living in regions with poor connectivity and flaky internet. The spec recommends accepting iat
values within an hour of the moment that the server receives the request, both before and after.
Profile JWS
A profile JWS is a JSON Web Signature that contains all the information of an ASP profile.
To validate a profile JWS, make sure that:
- the
typ
header is set toJWT
, - the
alg
,jwk
andkid
headers are set to the same values as in the request JWS, - the
http://ariadne.id/version
property is set to0
(as of this writing), - the
http://ariadne.id/type
property is set toprofile
, - the
http://ariadne.id/name
property is set, - the
http://ariadne.id/claims
property is set, - if the
exp
property is set, its value is not in the past.
Computing the fingerprint
To obtain the fingerprint associated with the ASP profile, one must create a specific JSON object based on the JWK-encoded public key, serialize it, hash it, truncate it and encode it. All the details for this process can be found in section 2.2 of the spec.
Storing ASPs
The server may store ASPs in any way it sees fit, as long as it does so in a queryable manner. After all, the sole purpose of storing ASPs is to subsequently retrieve and distribute them in response to a request.
ASPs can only be identified and requested by their URI:
URI = 'aspe:' domain ':' fingerprint
The domain is the one used to access the ASPE server.
The key's fingerprint is obtained as described in Computing the fingerprint. The fingerprint needs to be taken into consideration when implementing a storage layer for the ASPE server.
A simple Key-Value Database could suffice, where the fingerprint is the key and the profile JWS the value.
Distributing ASPs
The endpoint used to request ASPs from the server looks as follows:
GET ENDPOINT = '/.well-known/aspe/id/' fingerprint
Requests made to this endpoint must be GET requests.
If an ASP profile with the requested fingerprint was indeed stored by the ASP server and the profile is not expired (see Profile JWS), it should be sent as the response to the request; if not, the server should return a 404 NOT FOUND.
Allowing servers to update and delete ASPs
Updating and deleting ASPs are basically variations on the process of uploading ASPs!
When the ASPE server receives a request JWS with the http://ariadne.id/action
property set to update
, the server should check whether it indeed already has stored an ASP with the fingerprint of the incoming ASP and whether the keys perfectly match. If all checks are positive, the server can overwrite the existing ASP with the incoming ASP.
Likewise, if the request JWS has the http://ariadne.id/action
property set to delete
and the server has found an existing ASP with a matching fingerprint, it should remove said ASP from storage.
Conclusion
And that's roughly all your ASPE server needs to do! Of course, a lot of details are missing from this blog post and actualy implementations should refer to the ASP spec for all the information.
Hopefully, this guide will enable more developers to create their own tools and enrich the ASP ecosystem! Any ASP profile uploaded to such an ASPE server can be read and displayed by other tools such as keyoxide.org and the Keyoxide mobile app.
If you enjoy the ASP profiles, you can help us by supporting the Keyoxide project on OpenCollective.