https://github.com/zunda-pixel/appattest-swift
App Attest Verifier for Server Side in Swift
https://github.com/zunda-pixel/appattest-swift
apple swift
Last synced: about 1 year ago
JSON representation
App Attest Verifier for Server Side in Swift
- Host: GitHub
- URL: https://github.com/zunda-pixel/appattest-swift
- Owner: zunda-pixel
- License: apache-2.0
- Created: 2024-09-17T12:26:33.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-04-06T10:38:44.000Z (about 1 year ago)
- Last Synced: 2025-04-06T11:23:41.847Z (about 1 year ago)
- Topics: apple, swift
- Language: Swift
- Homepage:
- Size: 44.9 KB
- Stars: 4
- Watchers: 1
- Forks: 1
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# AppAttest
[](https://swiftpackageindex.com/zunda-pixel/appattest-swift)
[](https://swiftpackageindex.com/zunda-pixel/appattest-swift)
## 1. [Server] Generate challenge and return to Client(iOS)
```swift
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
```swift
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(
userId: userId,
sessionId: sessionId,
name: "sample name",
age: 25,
challenge: challenge,
keyId: keyId
)
let bodyData = try JSONEncoder().encode(body)
let assertion = try await service.generateAssertion(
keyId,
clientDataHash: Data(SHA256.hash(data: bodyData))
)
return (attestation, assertion, bodyData)
}
struct Body: Codable {
let sessionId: UUID
let userId: UUID
let name: String
let age: Int
let challenge: Data
let keyId: String
}
```
## 3. [Server] Verify data and Handle Body
```swift
import AppAttest
import Foundation
@main
struct App {
var challenges: [Challenge] = []
func verifyAndHandleBody(
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: body.userId,
sessionId: body.sessionId,
challenge: body.challenge
)
let attestation = try await appAttest.verifyAttestation(
challenge: body.challenge,
keyId: body.keyId,
attestation: attestation
)
try appAttest.verifyAsssertion(
assertion: assertion,
payload: bodyData,
certificate: attestation.statement.credetialCertificate,
counter: attestation.authenticatorData.counter
)
print(body.name)
print(body.age)
}
func verifyChallenge(userId: UUID, sessionId: UUID, challenge: Data) throws {
guard let challenge = challenges.first { $0.userId == userId && $0.sessionId == sessionId && $0.value == challenge } else {
throw AppAttestError.notFoundChallenge
}
guard Date.now <= challenge.expiredAt else {
throw AppAttestError.challengeExpired
}
challenges.removeAll { $0.userId == userId && $0.sessionId == sessionId && $0.value == challenge }
}
}
```