FairPlay Streaming

SuperDRM issues FairPlay licences (SPC → CKC) with Apple's FairPlay Streaming Server SDK 26 running as a local process, fronted by a small adapter that speaks SuperDRM's KSM contract. Nothing from Apple's SDK ships in our images or repository: it is downloaded from your Apple developer account, unpacked on the licence host, built there, and stays there. Apple devices always decrypt in the Secure Enclave path, so every FairPlay licence is a hardware licence.

Safari / iOS / tvOS ──SPC──▶ api.<domain> /v1/<tenant>/license/fairplay
                                   │  opens the content key from the vault
                                   ▼
                     ksm-sidecar adapter  127.0.0.1:8781
                                   │  Apple JSON: spc, content-key, content-iv, lease/offline, hdcp-type
                                   ▼
                     Apple fpssdk_server  127.0.0.1:8780   (built from the SDK; credentials/ next to it)
                                   │  libfpscrypto: decrypt SPC, derive keys, build CKC
                                   ▼
                              CKC ──▶ back to the device

#What is proven

LayerProof
Apple's SDK builds and passes its own suite on Linux/arm64its regression tests pass
Adapter ↔ Apple serverall 106 SPC test vectors Apple ships (iOS, macOS Intel and Apple silicon, tvOS, visionOS, watchOS; lease, no-lease, offline; 1024- and 2048-bit; cbcs and cbc-ts) produced CKCs that Apple's own verifier decrypts and matches
Through the SuperDRM APIa tenant with Apple's test key imported; POST /v1/<tenant>/license/fairplay?lt=… with the iOS SPC → 200, a 604-byte CKC, meter row fairplay · hw-main · ok · 9 ms
In productionthe hosted service runs with production credentials (/healthfairplay_ksm ok · production credentials) and serves SuperSecure's Safari and iOS viewers

#The adapter contract

POST /ksm { spc: b64, key: b64(16), iv: b64(16), asset_id, policy }{ ckc: b64, client, ms, mode }

SuperDRM policyApple asset-info
lease_duration_seconds > 0lease-duration (seconds from SPC creation)
persistence: true + rental_duration_seconds, playback_duration_secondsoffline-hls { stream-id, title-id, rental-duration, playback-duration }
hdcp: none / type0 / type1hdcp-type: -1 / 0 / 1
content_type (default hd; audio, sd, hd, uhd)content-type
encryption_scheme (default cbcs; cbc-ts)encryption-scheme

client carries what Apple's KSM learned from the SPC: hu (anonymised device id), security_level (baseline or main), device_class, os_version, hdcp_capable, offline_capable. The api records security = hw-<level> and a hash of hu on the meter row. Only the CKC ever goes to the device.

GET /health on the adapter → { ok, mode: test|production, sdk: "26.0.4" }.

#Production credentials (your own Apple package, for an on-prem install)

Apple issues a certificate bundle and provisioning data against two certificate signing requests.

  1. Generate keys and CSRs on the licence host, in a directory with mode 0600:

    Shell
    openssl req -out csr_1024.csr -new -newkey rsa:1024 -nodes -keyout priv_key_1024.pem \
      -subj "/CN=<your company>/OU=DRM/O=<your company>/C=US"
    openssl req -out csr_2048.csr -new -newkey rsa:2048 -nodes -keyout priv_key_2048.pem \
      -subj "/CN=<your company>/OU=DRM/O=<your company>/C=US"

    If a 1024-bit key was already used with FairPlay, Apple lets you reuse that certificate and submit only the 2048-bit CSR.

  2. developer.apple.com → Certificates, IDs & Profiles → Certificates → +FairPlay Streaming Certificate → select the current SDK version → upload csr_2048.csr under 2048-bit and csr_1024.csr under 1024-bit (or "reuse previous certificate") → Continue → Download fps_bundle.zip.

  3. fps_bundle.zip contains fps_certificate.bin (served to clients, and read by the KSM) and provisioning_data.bin (KSM only). Copy both plus the two .pem files into the server's credentials/ folder and write credentials/certificates.json:

    JSON
    { "certificates": [ {
        "certificate": "fps_certificate.bin",
        "1024-private-key": "priv_key_1024.pem",
        "2048-private-key": "priv_key_2048.pem",
        "provisioning-data": "provisioning_data.bin" } ] }

    Several bundles may coexist in the array; the KSM picks by the SPC header's certificate hash.

  4. Build Apple's server for production (without the test_credentials feature): cargo build --release --bin fpssdk_server, install it next to the adapter, set FPS_CREDENTIALS_MODE=production, restart the ksm service. Then serve the certificate to clients:

    Shell
    superdrm tenant:drm <slug> fairplay --mode shared --public '{"cert_b64":"<base64 of fps_certificate.bin>"}'

Notes from Apple's guide: certificates are not backward-compatible (use with the matching SDK or newer); an SDK 26 certificate enables SPC version 3 and the highest video-key protection; an older certificate makes SDK 26 behave like SDK 5.x.

#Older (SDK 4-style) credentials

certificates.json also accepts {"certificate": "<the .der/.cer>", "1024-private-key": "<its key>", "ask": "<16-byte ASk as 32 hex chars>"} — no provisioning data needed for that generation. Use it if you hold the private key and Application Secret Key from an earlier FairPlay deployment.

#Operations

ServiceRoleListens
fpsApple fpssdk_server (FPS_BIND, FPS_CERT_PATH)127.0.0.1:8780
ksmthe adapter (KSM_TOKEN, FPS_CREDENTIALS_MODE, FPS_CONTENT_TYPE)127.0.0.1:8781
apiSUPERDRM_FPS_KSM_URL=http://127.0.0.1:8781, SUPERDRM_FPS_KSM_TOKEN

Apple's reference server parses Content-Length case-sensitively; the adapter therefore talks to it with an explicitly cased header. Containers: the Compose profile ksm builds ksm-sidecar/Dockerfile, which expects the SDK's Key_Server_Module copied to ksm-sidecar/vendor/ (git-ignored) and the credentials mounted at /fps. In hardened tiers the pair runs inside an enclave; the contract is the same.

#Bridging to a previous vendor

A tenant whose keys only a previous vendor holds can run tenant_drm.fairplay mode=proxy, which relays the SPC to that vendor's FairPlay endpoint until the keys are imported. Once SuperDRM holds a key (issued or imported), --mode shared licenses it with the local KSM — the KSM does not depend on who generated the key.

Updated September 2026