Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions ExampleApp/ExampleApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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";
Expand All @@ -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;
Expand All @@ -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";
Expand Down
2 changes: 1 addition & 1 deletion ExampleApp/ExampleApp/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
8 changes: 7 additions & 1 deletion Sources/SignedShotSDK/APIModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}

Expand Down
33 changes: 24 additions & 9 deletions Sources/SignedShotSDK/SignedShotClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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()
Expand Down Expand Up @@ -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)")

Expand All @@ -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)

Expand Down
6 changes: 4 additions & 2 deletions Tests/SignedShotSDKTests/APIModelsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down