Cracking the code: Apple’s DRM Technology (Part-2)
How to Implement FairPlay DRM with “AVAssetResourceLoader” Delegate
1. Prepare DRM-protected HLS Content:
- The HLS media needs to be encoded and encrypted using FairPlay DRM. This process takes place on the license server, where media files are encrypted and content keys are securely stored.
- Content keys are generated and securely stored on the license server, while encrypted .ts segments and .m3u8 playlists are created for streaming.
- The encryption process creates “content keys” for the HLS stream, but these keys are not part of the .m3u8 playlist. Instead, the player retrieves them during playback.
2. License Server Set Up:
The player will request the license server to obtain the decryption key, which is needed to unlock and decode the encrypted media chunks (audio/video).
- In this process, the license server authenticates the user (using session tokens, credentials, etc.) to confirm their access rights and then provides the decryption key.
3. Implement AVAssetResourceLoader in Client Application (tvOS / iOS):
- The “AVAssetResourceLoader” delegate enables custom handling of media data (audio/video) loading. It allows us to intercept content key requests and facilitate the retrieval of decryption keys from the license server.
- Here are the steps to integrate this into the client application (iOS, tvOS application) for playing FairPlay-protected content:
Create an AVURLAsset and assign a resourceLoader delegate to manage key requests:
let contentUrl = URL(string: “www.hls_stream_sample.m3u8”)
let avAsset = AVURLAsset(url: contentUrl!)
asset.resourceLoader.setDelegate(self, queue: DispatchQueue.main)
let playerItem = AVPlayerItem(asset: avAsset)
let player = AVPlayer(playerItem: playerItem)
4. Implement AVAssetResourceLoader in Client Application (tvOS / iOS):
Implement the AVAssetResourceLoaderDelegate protocol to capture key requests. This delegate will be used to manage content key requests as follows.
extension YourViewController: AVAssetResourceLoaderDelegate {
func resourceLoader(_ resourceLoader: AVAssetResourceLoader,
shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {// Handle the request to load a FairPlay license
guard let requestUrl = loadingRequest.request.url else { return false }
// Process the Fairplay License Request
handleFairPlayRequest(loadingRequest: loadingRequest, url: requestUrl)
return true
}
}
5. Handle FairPlay Content Key Request (SPC and CKC exchange):
func handleFairPlayRequest(loadingRequest: AVAssetResourceLoadingRequest, url: URL) {
let contentId = url.absoluteString // Extract Content ID from URL
do {
// Get the App DRM Certificate
let applicationDrmCertificate = fetchApplicationDrmCertificate()
// Generate SPC (Server Playback Context)
let spcData = try loadingRequest.streamingContentKeyRequestData(
for app: applicationDrmCertificate,
contentIdentifier: contentId.data(using: .utf8)!,
options: nil
)
// Send SPC (Server playback Context) to your License Server and receive CKC(Content Key Context)
requestContentKey(fromServer: spcData, for: contentId) { ckcData in
guard let ckc = ckcData else {
loadingRequest.finishLoading(with: NSError(domain: “FairPlay”, code: -1))
return
}
// Provide CKC to AVFoundation to decrypt the content
loadingRequest.dataRequest?.respond(with: ckc)
loadingRequest.finishLoading()
}
} catch {
loadingRequest.finishLoading(with: error)
}
}
6. Send ServerPlayback Content (SPC) to License Server and Retrieve (Content Key Context) CKC:
func requestContentKey(fromServer spcData: Data, for contentId: String, completion: @escaping (Data?) -> Void) {
var request = URLRequest(url: URL(string: “https://your-license-server.com/getckc”)!)
request.httpMethod = “POST”
request.httpBody = spcData
request.setValue(“application/octet-stream”, forHTTPHeaderField: “Content-Type”)
// Optionally add authentication headers
// request.addValue(“Bearer \(authToken)”, forHTTPHeaderField: “Authorization”)
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print(“CKC request error: \(error.localizedDescription)”)
completion(nil)
} else {
completion(data)
}
}.resume()
}
7. Fetch FairPlay DRM Certificate:
func fetchApplicationDrmCertificate() -> Data {
// Load locally (or fetch securely from your backend if required)
guard let certURL = Bundle.main.url(forResource: “fairplay”, withExtension: “cer”),
let certData = try? Data(contentsOf: certURL) else {
fatalError(“Application certificate not found!”)
}
return certData
}
8. Server Validation Process Recap:
- The app sends the Server Playback Context (SPC) to the license server.
- The license server forwards the SPC (Server Playback Context) to Apple’s KSM (Key Server Module).
- The KSM (Key Server Module) verifies and returns a CKC (Content Key Context).
- The CKC is passed back to AVFoundation in the app.
9. AVFoundation Decrypts the Content
- AVFoundation utilizes the CKC to decrypt the content securely, with hardware enforcing protection..
10. Playback Starts
- Content is played via AVPlayer as normal.
Conclusion:
Implementing FairPlay DRM ensures your premium video content is securely streamed on Apple devices, maintaining both performance and protection. The code snippet above demonstrates the essential steps for setting up FairPlay in your Apple application—from handling SPC/CKC exchanges to initialising the AVPlayer with a protected stream.