How to write a basic ASPE client
Posted on 2023-09-25
This is the third 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
- How to write a basic ASPE client (you are here!)
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 does an ASPE client do?
An Ariadne Signature Profile (ASP) is a type of identity profile that services like Keyoxide can verify but which rely on basic cryptographic standards like EdDSA and ES256 instead of cryptographic tools like OpenPGP.
The ASP Exchange (ASPE) protocol refers to the set of HTTP requests by which people can upload and download ASPs to and from dedicated servers.
An ASPE client will:
- let the user enter profile details and identity claims,
- generate a cryptographic key for each profile,
- generate JWS signatures based on that information,
- upload those signatures to an ASPE server,
- securely store the information and cryptographic key for later editing.
Writing an ASPE client
Now, let's try and write an ASPE client. This will be a simple overview of the different required steps. For more implementation details, refer to the ASP specification.
Here are two open source ASPE clients you can use as basis for your own:
- kx-aspe-web: a browser client,
- kx-aspe-cli: a command line client written in rust.
Creating a new profile
To create a new profile, simply create an object/struct with the following information:
name
(required): name of the profile,claims
(required): array of strings containing the identity claims likedns:keyoxide.org?type=txt
,description
: description of the profile,color
: to be used to theme profile pages.
As always with ASPs, the name of the profile may be real or anonymous. Be sure to remind your users of this fact!
The spec also specifies the avatar_url
and email
properties, but these are optional and for now, no profile verifier clients should use these yet until their behavior is better defined, so ASPE clients should omit them.
Generating a cryptographic key
Every ASP is identified and queried by a so-called fingerprint which is based on the cryptographic key associated with the profile. This means every ASP must have their own unique cryptographic key.
Indeed, the ASP method is based on the principle that cryptographic keys are cheap, single purpose and — in some way — ephemeral.
The spec makes opiniated decisions on which types of cryptographic keys are acceptable for the creation of an ASP. To strike a balance between reduction of code complexity of clients and freedom within the potential constraints of the client's environment, the spec allows two combinations of cryptographic keys and signature algorithm:
- algorithm: "EdDSA", curve: "Ed25519"
- algorithm: "ES256", curve: "P-256"
Verifying clients should always accept both of these, ASPE clients may choose to only generate profiles with a single algorithm. Indeed, at the time of writing, browsers do not widely support Ed25519 keys and are therefore restricted to P-256.
That being said, whenever possible, it is recommended to adopt Ed25519 over P-256. See the Guidance for Choosing an Elliptic Curve Signature Algorithm in 2022 [soatok.blog] for more information.
In the next steps, the client will be generating JSON Web Signatures (JWS) and according to the ASP spec, these JWS must include the cryptographic key in JSON Web Key (JWK) format. Make sure that your cryptographic library of choice is capable of converting keys to the JWK format. kx-aspe-web
uses jose (JS) and kx-aspe-cli
uses josekit (Rust).
Generating a profile JWS
Now that we have profile details and a crytographic key, let's generate cryptographic signatures.
To upload an ASP, the client will have to generate a signature of a signature. Let's start with the "inner" signature, the profile JWS.
It's recommended to use tried and trusted libraries for the generation of cryptographic signatures. kx-aspe-web
uses jose (JS) and kx-aspe-cli
uses josekit (Rust).
The object needed for the profile JWS looks as follows:
- the
typ
header: set toJWT
, - the
alg
header: set to eitherEdDSA
orES256
, - the
jwk
header: set to a valid JWK key, - the
kid
header: set to the key's fingerprint as obtained in Computing the fingerprint, - the
http://ariadne.id/version
property: set to0
, - the
http://ariadne.id/type
property: set toprofile
, - the
http://ariadne.id/name
property: set to profile's name, - the
http://ariadne.id/claims
property: set to the profile's list of identity claims.
If the profile has a description and/or a color, the client may set the http://ariadne.id/color
and the http://ariadne.id/color
(in HEX format) respectively.
The JWS must be serialized using Compact Serialization (see section 3.1 of RFC7515).
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.
Generating a request JWS
The profile JWS generated above is the actual ASP that dedicated servers will host and distribute. To send the profile JWS to a dedicated server, it needs to be encapsulated for transport within a request JWS.
The object needed for the request JWS looks as follows:
- the
typ
header: set toJWT
, - the
alg
header: set to eitherEdDSA
orES256
, - the
jwk
header: set to a valid JWK key, - the
kid
header: set to the key's fingerprint, - the
http://ariadne.id/type
property: set torequest
, - the
http://ariadne.id/action
property: set tocreate
, - the
http://ariadne.id/profile_jws
property: set to the profile JWS generated previously, - the
iat
property: the current UNIX epoch
Evidently, the key used for the request JWS must be the same as the one used for the profile JWS.
Note that the http://ariadne.id/action
property is set to create
. This is only valid for the first upload. Subsequent uploads should use the update
action. To remove the ASP from the server, use the delete
action — omit the http://ariadne.id/profile_jws
property when doing so.
The JWS must also be serialized using Compact Serialization (see section 3.1 of RFC7515).
Uploading a request JWS
Now that we have our profile JWS inside a request JWS, the client needs to upload the latter to a dedicated server. Each public ASPE server has an endpoint dedicated to receiving request JWS:
POST ENDPOINT = '/.well-known/aspe/post'
Use a POST HTTP request with the request JWS as body and set request's Content-Type
header to:
application/asp+jwt; charset=UTF-8
If all went well, the client should receive a 201 CREATED response — or 200 OK for the update
and delete
actions.
The ASP is now published at the following endpoint hosted by the ASPE server:
GET ENDPOINT = '/.well-known/aspe/id/' fingerprint
Securely storing profile data
The client has done its duty of creating a profile and uploading it to a dedicated server. However, people may like to update or delete their profile at a later moment. It's therefore crucial to store both the profile details and the secret cryptographic key.
Profile details
Storing the profile details should not be an issue. kx-aspe-cli
stores it plainly as a TOML file in the OS' user data directory — developers, don't pollute people's $HOME directory, please, thank you.
kx-aspe-web
takes an interesting approach: it doesn't store profile details locally but rather on the ASPE server. When editing the profile, it just fetches the profile from the server and starts from there. This has the benefit of making the profile details available on each and every device without complicated syncing protocols. Drawbacks are that people can't "prepare" an account by creating it first and uploading it later.
Local clients like desktop and mobile clients are strongly recommended to store a local copy of the profile.
Secret cryptographic key
This is perhaps the most delicate step of the process: people need to keep a copy of the secret key in order to keep control over their profile. But if the secret key is handled inappropriately, it becomes trivial for a bad actor to compromise that profile.
Some OS have dedicated key storage system like the Android Keystore or the iOS Keychain but this may not always be available or desired.
Based on the approach used by Minisign, both kx-aspe-web
and kx-aspe-cli
use scrypt to derive a key from a user-provided password and use said key to XOR the secret cryptographic key. The encrypted key resulting from the XOR operation is then either stored on the filesystem (kx-aspe-cli
) or presented to the user to store in a password manager (kx-aspe-web
).
To edit the profile at a later moment, the user must provide their password at the moment of uploading so that the secret key may be decrypted and used to sign the JWSs.
Note that the ASP specification does not favor a particular method for securing cryptographic keys.
Conclusion
Hopefully, this guide will enable more developers to create their own tools and enrich the ASP ecosystem!
If you enjoy the ASP profiles, you can help us by supporting the Keyoxide project on OpenCollective.