Since iOS 14, Apple offers a new way to attest the integrity of a device. This is based on the WebAuthn specification. This go module implements the server-side validation of both attestations and assertions that can be obtained using the DCAppAttestService.
Get the module with go get github.com/bas-d/appattest
.
The module offers a packge for attestation and for assertion.
Generate a key pair and attestation in your app as specified in the documentation.
The validate the attestation, create an AuthenticatorAttestationResponse
object, for example:
attestation = `{
"attestationObject": "o2NmbXRv...WUHFMII",
"keyID": "AcP/pnpoNVPIJYZOvmIvWzDvmxkFoQCE4Uu7Nk6WiAA=",
"clientData": "YXR0ZXN0YXRpb24tdGVzdA"
}`
The attestationObject
should be URL-safe base64 encoded (as it is generated by DCAppAttestService
).
The keyID
should be normal base64-encoded (also as it is generated by DCAppAttestService
).
The clientData
should also be URL-safe base64 encoded. This should be encoded by you. It should contain a randomly generated challenge that your obtained from the server before starting the attestation.
After creating the AuthenticatorAttestationResponse
, validate the attestation by calling:
pk, clientDataHash, err := aar.Verify("<TEAMID.reverse.dns.app.id>", isProduction)
if err != nil {
// Handle error
}
You should specify whether this called in a production environment, because Apple uses a different value for attestation objects in a live app and a development build.
The function returns the public key and receipt if the validation succeeds. If not, err is not nil.
The public key and receipt should be saved in your database.
Assuming attestation was succesful and you have the public key stored in your database, create an assertion in your app as specificied in the documentation.
To validate the assertion, create an AuthenticatorAssertionResponse
object, for example:
const assertion = `{
"assertion": "omlzaW...n9wdEAAAAAD",
"clientData": "eyJjaGFsbGVuZ2UiOiJhc3NlcnRpb24tdGVzdCJ9"
}`
The assertion
should be URL-safe base64 encoded (as it is generated by DCAppAttestService
).
The clientData
should be a URL-safe base64 encoded JSON-object and should contain a string with key challenge
, which should have been obtained from the server before starting the assertion in your app. It may contain other data that you want to assert as well.
Verify the assertion by calling:
counter, err = aar.Verify("<your_stored_challenge>", "<TEAMID.reverse.dns.app.id>", initialCounter, pubKey)
if err != nil {
// Handle error
}
You should provide the challenge you shared earlier to your app. It should be a string and is expected to be the same as the challenge in the clientData
also a string. Second argument is your App ID. Furthermore, provide the current counter (prevents replay attacks) and decoded public key (retrieved from your database).
When the assertion succeeds, you get the new counter that you should store in your database.
This is the first time I use Go, so any feedback and suggestions are welcome, also on how to make this module more go-idiomatic. Pull requests are welcome, please create them to dev
branch.
Most of the code has been based on the WebAuthn Library by Duo Labs, so most of the works was aleady done by them!
BSD 3-Clause License. See LICENSE.