iOS
A Swift package over DCAppAttestService, iOS 14+. It holds no Root Herald key and makes no request to Root Herald; enroll is the only call that contacts Apple.
This SDK is implemented but not yet published to its package registry. Until it ships, collect an opaque evidence blob on the device and appraise it server-side with @rootherald/node (or any available server SDK). The API shown below is the planned surface and may change before release.
.package(url: "https://github.com/RootHerald/sdk-ios.git", from: "0.4.0")
// product: "RootHerald". Enable the App Attest capability under Signing & Capabilities.The calls
RootHeraldClient()— persists only the App Attest key id in UserDefaults.collectPosture()→Posture:hasHardwareKeyStore,isEnrolled,appKeysSupported(always false),deviceId. Local, free.enroll(to: challenge, challengeId:)→Datato relay toPOST /api/v1/attest/enroll. One leg, once per install.respond(to: challenge)→Evidence(assertion, keyId);evidence.jsonData()is the blob for verify. Servesidentityandkey; apostureask throws.askUnsupported(.posture).assert(_ message: Data)→ an App Attest assertion, for a backend that received akeyverdict.reset()— forget the key; the next enroll mints a new identity.- Errors:
RootHeraldError.notSupported,.invalidChallenge,.askUnsupported,.notEnrolled,.keyGenerationFailed,.attestationFailed,.assertionFailed.
Example
import RootHerald
let client = RootHeraldClient()
func submit(email: String) async throws {
guard client.collectPosture().hasHardwareKeyStore else { return } // Simulator, or no capability
// Your backend: rh.issueChallenge({ ask: ["identity"] }); relay challengeId + challenge verbatim.
let (challengeId, challenge) = try await api.mintChallenge()
if !client.collectPosture().isEnrolled {
let body = try await client.enroll(to: challenge, challengeId: challengeId)
try await api.relayEnroll(body) // → POST /api/v1/attest/enroll with rh_sk_
}
let evidence = try await client.respond(to: challenge)
try await api.signup(email: email, challengeId: challengeId, evidence: evidence.jsonData())
}const result = await rh.verify(evidence, { // evidence = { iosAttestation: { assertion, keyId } }
challengeId,
policy: "rootherald:builtin:strict-hardware-permissive-mobile", // iOS resolves to device:any
});On iOS the App Attest key is the app key, so a key ask returns no blob and the verdict's key.jwk is its public half. assert(_:) produces a CBOR assertion carrying the enclave's counter, not raw r||s; the server SDKs' verifyKeySignature does not parse it in this release, so iOS-signed requests are verified through Root Herald's verify. App Attest is unavailable on the Simulator.