diff --git a/ExampleApp/ExampleApp.xcodeproj/project.pbxproj b/ExampleApp/ExampleApp.xcodeproj/project.pbxproj index da6d0e9..d45a648 100644 --- a/ExampleApp/ExampleApp.xcodeproj/project.pbxproj +++ b/ExampleApp/ExampleApp.xcodeproj/project.pbxproj @@ -282,7 +282,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = ExampleApp/ExampleApp.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = CMQ8K8AVJB; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -299,7 +299,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.1; PRODUCT_BUNDLE_IDENTIFIER = io.signedshot.capture; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -319,7 +319,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = ExampleApp/ExampleApp.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = CMQ8K8AVJB; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -336,7 +336,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.1; PRODUCT_BUNDLE_IDENTIFIER = io.signedshot.capture; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; diff --git a/ExampleApp/ExampleApp/ContentView.swift b/ExampleApp/ExampleApp/ContentView.swift index bd9d135..cde9a91 100644 --- a/ExampleApp/ExampleApp/ContentView.swift +++ b/ExampleApp/ExampleApp/ContentView.swift @@ -66,7 +66,7 @@ struct ContentView: View { publisherId: "8f6b5d94-af3b-4f57-be68-e93eedd772fc" )! #endif - client = SignedShotClient(configuration: config) + client = SignedShotClient(configuration: config, enclaveService: enclaveService) integrityService = MediaIntegrityService(enclaveService: enclaveService) } diff --git a/Sources/SignedShotSDK/APIModels.swift b/Sources/SignedShotSDK/APIModels.swift index 8babef7..2cea810 100644 --- a/Sources/SignedShotSDK/APIModels.swift +++ b/Sources/SignedShotSDK/APIModels.swift @@ -7,12 +7,18 @@ public struct DeviceCreateRequest: Codable, Sendable { /// Unique identifier for the device (e.g., hardware ID, app installation ID) public let externalId: String - public init(externalId: String) { + /// Base64-encoded uncompressed EC public key (65 bytes: 0x04 + X + Y) + /// from the device's content-signing key pair (Secure Enclave) + public let publicKey: String + + public init(externalId: String, publicKey: String) { self.externalId = externalId + self.publicKey = publicKey } private enum CodingKeys: String, CodingKey { case externalId = "external_id" + case publicKey = "public_key" } } diff --git a/Sources/SignedShotSDK/SignedShotClient.swift b/Sources/SignedShotSDK/SignedShotClient.swift index 3472ab9..8a52b9e 100644 --- a/Sources/SignedShotSDK/SignedShotClient.swift +++ b/Sources/SignedShotSDK/SignedShotClient.swift @@ -38,6 +38,7 @@ public actor SignedShotClient { private let configuration: SignedShotConfiguration private let session: URLSession private let keychain: KeychainStorage + private let enclaveService: SecureEnclaveService private let decoder: JSONDecoder private let encoder: JSONEncoder @@ -52,9 +53,15 @@ public actor SignedShotClient { /// - Parameters: /// - configuration: Client configuration /// - keychain: Keychain storage (defaults to standard) - public init(configuration: SignedShotConfiguration, keychain: KeychainStorage = KeychainStorage()) { + /// - enclaveService: Secure Enclave service for key management (defaults to standard) + public init( + configuration: SignedShotConfiguration, + keychain: KeychainStorage = KeychainStorage(), + enclaveService: SecureEnclaveService = SecureEnclaveService() + ) { self.configuration = configuration self.keychain = keychain + self.enclaveService = enclaveService self.session = URLSession.shared self.decoder = JSONDecoder() @@ -116,13 +123,10 @@ public actor SignedShotClient { return newId } - private func performRegistration( + private func buildRegistrationRequest( externalId: String, - attestationToken: String? = nil, - isRetry: Bool - ) async throws -> DeviceCreateResponse { - SignedShotLogger.api.info("Registering device with externalId: \(externalId.prefix(8))...") - + attestationToken: String? + ) throws -> URLRequest { let url = configuration.baseURL.appendingPathComponent("devices") SignedShotLogger.api.debug("POST \(url.absoluteString)") @@ -131,14 +135,25 @@ public actor SignedShotClient { request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue(configuration.publisherId, forHTTPHeaderField: "X-Publisher-ID") - // Add attestation token header if provided if let token = attestationToken { request.setValue(token, forHTTPHeaderField: "X-Attestation-Token") SignedShotLogger.api.debug("Including attestation token in request") } - let body = DeviceCreateRequest(externalId: externalId) + let publicKeyBase64 = try enclaveService.getPublicKeyBase64() + let body = DeviceCreateRequest(externalId: externalId, publicKey: publicKeyBase64) request.httpBody = try encoder.encode(body) + return request + } + + private func performRegistration( + externalId: String, + attestationToken: String? = nil, + isRetry: Bool + ) async throws -> DeviceCreateResponse { + SignedShotLogger.api.info("Registering device with externalId: \(externalId.prefix(8))...") + + let request = try buildRegistrationRequest(externalId: externalId, attestationToken: attestationToken) let (data, response) = try await performRequest(request) diff --git a/Tests/SignedShotSDKTests/APIModelsTests.swift b/Tests/SignedShotSDKTests/APIModelsTests.swift index 39e5ebb..21b035e 100644 --- a/Tests/SignedShotSDKTests/APIModelsTests.swift +++ b/Tests/SignedShotSDKTests/APIModelsTests.swift @@ -6,24 +6,26 @@ final class APIModelsTests: XCTestCase { // MARK: - DeviceCreateRequest Tests func testDeviceCreateRequestEncoding() throws { - let request = DeviceCreateRequest(externalId: "test-device-123") + let request = DeviceCreateRequest(externalId: "test-device-123", publicKey: "dGVzdHB1YmxpY2tleQ==") let encoder = JSONEncoder() let data = try encoder.encode(request) let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] XCTAssertEqual(json?["external_id"] as? String, "test-device-123") + XCTAssertEqual(json?["public_key"] as? String, "dGVzdHB1YmxpY2tleQ==") } func testDeviceCreateRequestDecoding() throws { let json = """ - {"external_id": "my-device-id"} + {"external_id": "my-device-id", "public_key": "dGVzdHB1YmxpY2tleQ=="} """ let decoder = JSONDecoder() let request = try decoder.decode(DeviceCreateRequest.self, from: json.data(using: .utf8)!) XCTAssertEqual(request.externalId, "my-device-id") + XCTAssertEqual(request.publicKey, "dGVzdHB1YmxpY2tleQ==") } // MARK: - DeviceCreateResponse Tests