I generate an assertion object using DCAppAttestService and send it to my Cloud Functions server - to verify that in app purchases come from legitimate instances of my app.
Here is my Swift code that, when an in-app purchase has been made:
- Retrieves a one-time challenge (string) from my server
- Using that string, along with client data, to generate a hash
- Use that hash, and the device's
keyID, to callgenerateAssertion() - Send a base64-encoded string of the assertion object and client data object
Swift:
func callSensitiveFunction(name: String, data: [String:String], completion: @escaping (HTTPSCallableResult?, Error?)->()){
var functions = Functions.functions()
var adjustedData: [String:String] = data
guard let keyID = KeychainWrapper.standard.string(forKey: "attestKeyID") else {
print("no attest ID, return")
return
}
let service = DCAppAttestService.shared
// Retrieve the challenge string
functions.httpsCallable("getChallengeString").call(data){ (result, error) in
if let challengeString = result?.data as? String {
// Retrieve the keyID (public key) which was saved at first launch
guard let attestKeyID = KeychainWrapper.standard.string(forKey: "attestKeyID") else { return print("No attestKeyID found") }
// Add challenge string to request
adjustedData["challenge"] = challengeString
// Hash client data (which includes challenge string)
guard let clientData = try? JSONEncoder().encode(adjustedData) else { return }
let clientDataHash = Data(SHA256.hash(data: clientData))
service.generateAssertion(attestKeyID, clientDataHash: clientDataHash) { assertion, err in
guard error == nil else { return print("generateAssertion() error: \(error)") }
let assertionString = assertion?.base64EncodedString() ?? ""
let clientDataString = clientData.base64EncodedString()
adjustedData["assertionString"] = assertionString
adjustedData["clientData"] = clientDataString
functions.httpsCallable(name).call(adjustedData){ (result, error) in
completion(result, error)
print("function \(name) completed")
}
}
}
}
}
However when I receive the assertionString in the backend (Node.js) - I'm not how how to decode it. Using this tutorial here i've gotten this far:
const cbor = require("cbor");
const base64url = require('base64url');
let verifyAppleAnonymousAttestation = (assertionString) => {
let assertionBuffer = base64url.toBuffer(assertionString);
console.log(`assertionBuffer: ${assertionBuffer}`) // assertionBuffer: �isignatureXG0E!���56��r��@��@J�r+X������ڳ%� D��c!
let assertionStruct = cbor.decodeAllSync(assertionBuffer)[0];
console.log(`assertionStruct[0]: ${assertionStruct[0]}`) // assertionStruct: [object Object]
But I'm not sure where to go from here to verify the assertion string. Apple says after the assertion is decoded it looks like this:
{
signature: <Buffer 30 45 02 20 ... >,
authenticatorData: <Buffer 21 c9 9e 00 ... >
}
Any idea how I can extract all of the validating data from the assertionString?