import Foundation
import Crypt
@main
struct App {
// DB or Server session data
var challenges: [Challenge] = []
mutating func generateChallenge(userId: UUID, sessionId: UUID) -> Data {
let challenge = Challenge(
userId: userId,
sessionId: sessionId,
expiredAt: Date.now.addingTimeInterval(5 * 60), // expired after 5 minutes.
value: Data(AES.GCM.Nonce())
)
challenges.append(challenge)
return challenge.value
}
}
struct Challenge {
var userId: UUID
var sessionId: UUID
var expiredAt: Date
var value: Data
}
import Crypto
import DeviceCheck
import Foundation
func sendData(
challenge: Data,
userId: UUID,
sessionId: UUID
) async throws {
let service = DCAppAttestService.shared
let keyId = try await service.generateKey()
let attestation = try await service.attestKey(
keyId,
clientDataHash: Data(SHA256.hash(data: challenge))
)
let body = Body(
name: "sample name",
age: 25
)
let bodyData = try JSONEncoder().encode(body)
let assertion = try await service.generateAssertion(
keyId,
clientDataHash: Data(SHA256.hash(data: bodyData))
)
return (userId, sessionId, challenge, keyId, attestation, assertion, bodyData)
}
struct Body: Codable {
let name: String
let age: Int
}
import AppAttest
import Foundation
@main
actor App {
var challenges: [Challenge] = []
func verifyAndHandleBody(
userId: UUID,
sessionId: UUID,
challenge: Data,
keyId: String,
attestation: Data,
assertion: Data,
bodyData: Data
) async throws {
let teamId = ProcessInfo.processInfo.environment["TEAM_ID"]! // PH3HCZ4AK6
let bundleId = ProcessInfo.processInfo.environment["BUNDLE_ID"]! // com.example.memo
let appAttest = AppAttest(
teamId: teamId,
bundleId: bundleId,
environment: .development
)
let body = try JSONDecoder().decode(Body.self, from: bodyData)
try verifyChallenge(
userId: userId,
sessionId: sessionId,
challengeData: challenge
)
let attestation = try await appAttest.verifyAttestation(
challenge: challenge,
keyId: keyId,
attestation: attestation
)
try appAttest.verifyAssertion(
assertion: assertion,
payload: bodyData,
certificate: attestation.statement.credentialCertificate,
counter: attestation.authenticatorData.counter
)
print(body.name)
print(body.age)
}
func verifyChallenge(userId: UUID, sessionId: UUID, challengeData: Data) throws {
guard let challenge = challenges.first(where: { $0.userId == userId && $0.sessionId == sessionId && $0.value == challengeData }) else {
throw AppAttestError.challengeNotFound
}
guard Date.now <= challenge.expiredAt else {
throw AppAttestError.challengeExpired
}
challenges.removeAll { $0.userId == userId && $0.sessionId == sessionId && $0.value == challengeData }
}
}