Skip to content

zunda-pixel/appattest-swift

Repository files navigation

AppAttest

1. [Server] Generate challenge and return to Client(iOS)

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
}

2. [Client(iOS)] Send Data to Server

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
}

3. [Server] Verify data and Handle Body

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 }
  }
}

About

App Attest Verifier for Server Side in Swift

Topics

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •  

Languages