Created
January 10, 2025 06:30
-
-
Save KunalKumarSwift/2f8bc9f449de85ffbd50998ac5ad47a2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
class APIManager { | |
static let shared = APIManager() | |
private let baseURL = "http://localhost:3000" // Replace with your server's URL | |
/// Fetches a challenge from the server | |
func fetchChallenge(completion: @escaping (Result<(String, String), Error>) -> Void) { | |
guard let url = URL(string: "\(baseURL)/generate-challenge") else { return } | |
URLSession.shared.dataTask(with: url) { data, _, error in | |
if let error = error { | |
completion(.failure(error)) | |
return | |
} | |
guard let data = data else { | |
completion(.failure(NSError(domain: "No data", code: -1, userInfo: nil))) | |
return | |
} | |
do { | |
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: String], | |
let challengeId = json["challengeId"], | |
let challenge = json["challenge"] { | |
completion(.success((challengeId, challenge))) | |
} else { | |
completion(.failure(NSError(domain: "Invalid response", code: -1, userInfo: nil))) | |
} | |
} catch { | |
completion(.failure(error)) | |
} | |
}.resume() | |
} | |
/// Sends the attestation object to the server for verification | |
func verifyAttestation(challengeId: String, keyId: String, attestation: Data, completion: @escaping (Result<String, Error>) -> Void) { | |
guard let url = URL(string: "\(baseURL)/verify-attestation") else { return } | |
var request = URLRequest(url: url) | |
request.httpMethod = "POST" | |
request.setValue("application/json", forHTTPHeaderField: "Content-Type") | |
let body: [String: Any] = [ | |
"challengeId": challengeId, | |
"keyId": keyId, | |
"attestation": attestation.base64EncodedString() | |
] | |
do { | |
request.httpBody = try JSONSerialization.data(withJSONObject: body, options: []) | |
} catch { | |
completion(.failure(error)) | |
return | |
} | |
URLSession.shared.dataTask(with: request) { data, _, error in | |
if let error = error { | |
completion(.failure(error)) | |
return | |
} | |
guard let data = data else { | |
completion(.failure(NSError(domain: "No data", code: -1, userInfo: nil))) | |
return | |
} | |
do { | |
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: String], | |
let message = json["message"] { | |
completion(.success(message)) | |
} else { | |
completion(.failure(NSError(domain: "Invalid response", code: -1, userInfo: nil))) | |
} | |
} catch { | |
completion(.failure(error)) | |
} | |
}.resume() | |
} | |
} | |
// view model: | |
import Foundation | |
import DeviceCheck | |
class ViewModel: ObservableObject { | |
@Published var statusMessage = "" | |
func performAppAttestation() { | |
// Step 1: Fetch Challenge from Server | |
APIManager.shared.fetchChallenge { [weak self] result in | |
switch result { | |
case .success(let (challengeId, challenge)): | |
self?.statusMessage = "Challenge fetched successfully!" | |
// Step 2: Generate Attestation using DCAppAttestService | |
guard DCAppAttestService.shared.isSupported else { | |
self?.statusMessage = "Device does not support App Attest." | |
return | |
} | |
DCAppAttestService.shared.generateKey { keyId, error in | |
if let error = error { | |
DispatchQueue.main.async { | |
self?.statusMessage = "Failed to generate key ID: \(error.localizedDescription)" | |
} | |
return | |
} | |
guard let keyId = keyId else { return } | |
DCAppAttestService.shared.attestKey(keyId, clientDataHash: Data(base64Encoded: challenge)!) { attestationData, error in | |
if let error = error { | |
DispatchQueue.main.async { | |
self?.statusMessage = "Failed to generate attestation object: \(error.localizedDescription)" | |
} | |
return | |
} | |
guard let attestationData = attestationData else { return } | |
// Step 3: Send Attestation to Server for Verification | |
APIManager.shared.verifyAttestation(challengeId: challengeId, keyId: keyId, attestation: attestationData) { result in | |
DispatchQueue.main.async { | |
switch result { | |
case .success(let message): | |
self?.statusMessage = message | |
case .failure(let error): | |
self?.statusMessage = "Verification failed: \(error.localizedDescription)" | |
} | |
} | |
} | |
} | |
} | |
case .failure(let error): | |
DispatchQueue.main.async { | |
self?.statusMessage = "Failed to fetch challenge: \(error.localizedDescription)" | |
} | |
} | |
} | |
} | |
} | |
// content view: | |
import SwiftUI | |
struct ContentView: View { | |
@StateObject private var viewModel = ViewModel() | |
var body: some View { | |
VStack(spacing: 20) { | |
Text("Apple App Attestation Demo") | |
.font(.headline) | |
Button(action: viewModel.performAppAttestation) { | |
Text("Start App Attestation") | |
.padding() | |
.background(Color.blue) | |
.foregroundColor(.white) | |
.cornerRadius(8) | |
} | |
Text(viewModel.statusMessage) | |
.padding() | |
.multilineTextAlignment(.center) | |
} | |
.padding() | |
} | |
} | |
struct ContentView_Previews: PreviewProvider { | |
static var previews: some View { | |
ContentView() | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment