[EME] Correctly report errors when generating key requests from AVContentKeySession.
[WebKit-https.git] / Source / WebCore / platform / graphics / avfoundation / objc / CDMSessionAVContentKeySession.mm
1 /*
2  * Copyright (C) 2015 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "CDMSessionAVContentKeySession.h"
28
29 #if ENABLE(ENCRYPTED_MEDIA_V2) && ENABLE(MEDIA_SOURCE)
30
31 #import "CDM.h"
32 #import "CDMPrivateMediaSourceAVFObjC.h"
33 #import "ExceptionCode.h"
34 #import "FileSystem.h"
35 #import "Logging.h"
36 #import "MediaPlayer.h"
37 #import "SoftLinking.h"
38 #import "SourceBufferPrivateAVFObjC.h"
39 #import "UUID.h"
40 #import "WebCoreNSErrorExtras.h"
41 #import <AVFoundation/AVError.h>
42 #import <CoreMedia/CMBase.h>
43 #import <objc/objc-runtime.h>
44 #import <runtime/TypedArrayInlines.h>
45 #import <wtf/NeverDestroyed.h>
46
47 SOFT_LINK_FRAMEWORK_OPTIONAL(AVFoundation)
48 SOFT_LINK_CLASS(AVFoundation, AVStreamDataParser);
49 SOFT_LINK_CLASS_OPTIONAL(AVFoundation, AVContentKeySession);
50 SOFT_LINK_CONSTANT_MAY_FAIL(AVFoundation, AVContentKeyRequestProtocolVersionsKey, NSString *)
51
52 @interface AVContentKeySession : NSObject
53 - (instancetype)initWithStorageDirectoryAtURL:(NSURL *)storageURL;
54 @property (assign) id delegate;
55 - (void)addStreamDataParser:(AVStreamDataParser *)streamDataParser;
56 - (void)removeStreamDataParser:(AVStreamDataParser *)streamDataParser;
57 @property (readonly) NSArray *streamDataParsers;
58 - (void)expire;
59 @property (readonly) NSData *contentProtectionSessionIdentifier;
60 - (void)processContentKeyRequestInitializationData:(NSData *)initializationData options:(NSDictionary *)options;
61 + (NSArray *)pendingExpiredSessionReportsWithAppIdentifier:(NSData *)appIdentifier storageDirectoryAtURL:(NSURL *)storageURL;
62 + (void)removePendingExpiredSessionReports:(NSArray *)expiredSessionReports withAppIdentifier:(NSData *)appIdentifier storageDirectoryAtURL:(NSURL *)storageURL;
63 @end
64
65 typedef NS_ENUM(NSInteger, AVContentKeyRequestStatus) {
66     AVContentKeySessionStatusNoKey,
67     AVContentKeySessionStatusRequestingKey,
68     AVContentKeySessionStatusKeyPresent,
69     AVContentKeySessionStatusExpired,
70     AVContentKeySessionStatusFailed
71 };
72
73 @interface AVContentKeyRequest : NSObject
74 @property (readonly) AVContentKeyRequestStatus status;
75 @property (readonly) NSError *error;
76 @property (readonly) NSData *initializationData;
77 - (NSData *)contentKeyRequestDataForApp:(NSData *)appIdentifier contentIdentifier:(NSData *)contentIdentifier options:(NSDictionary *)options error:(NSError **)outError;
78 - (void)processContentKeyResponseData:(NSData *)contentKeyResponseData;
79 - (void)processContentKeyResponseError:(NSError *)error;
80 - (void)renewExpiringContentKeyResponseData;
81 @end
82
83 @interface WebCDMSessionAVContentKeySessionDelegate : NSObject {
84     WebCore::CDMSessionAVContentKeySession *m_parent;
85 }
86 - (void)invalidate;
87 @end
88
89 @implementation WebCDMSessionAVContentKeySessionDelegate
90 - (id)initWithParent:(WebCore::CDMSessionAVContentKeySession *)parent
91 {
92     if ((self = [super init]))
93         m_parent = parent;
94     return self;
95 }
96
97
98 - (void)invalidate
99 {
100     m_parent = nullptr;
101 }
102
103 - (void)contentKeySession:(AVContentKeySession *)session didProvideContentKeyRequest:(AVContentKeyRequest *)keyRequest
104 {
105     UNUSED_PARAM(session);
106
107     if (m_parent)
108         m_parent->didProvideContentKeyRequest(keyRequest);
109 }
110
111 - (void)contentKeySessionContentProtectionSessionIdentifierDidChange:(AVContentKeySession *)session
112 {
113     if (!m_parent)
114         return;
115
116     NSData* identifier = [session contentProtectionSessionIdentifier];
117     RetainPtr<NSString> sessionIdentifierString = identifier ? adoptNS([[NSString alloc] initWithData:identifier encoding:NSUTF8StringEncoding]) : nil;
118     m_parent->setSessionId(sessionIdentifierString.get());
119 }
120 @end
121
122 static const NSString *PlaybackSessionIdKey = @"PlaybackSessionID";
123
124 namespace WebCore {
125
126 CDMSessionAVContentKeySession::CDMSessionAVContentKeySession(const Vector<int>& protocolVersions, CDMPrivateMediaSourceAVFObjC& cdm, CDMSessionClient* client)
127     : CDMSessionMediaSourceAVFObjC(cdm, client)
128     , m_contentKeySessionDelegate(adoptNS([[WebCDMSessionAVContentKeySessionDelegate alloc] initWithParent:this]))
129     , m_protocolVersions(protocolVersions)
130     , m_mode(Normal)
131 {
132 }
133
134 CDMSessionAVContentKeySession::~CDMSessionAVContentKeySession()
135 {
136     [m_contentKeySessionDelegate invalidate];
137
138     for (auto& sourceBuffer : m_sourceBuffers)
139         removeParser(sourceBuffer->parser());
140 }
141
142 bool CDMSessionAVContentKeySession::isAvailable()
143 {
144     return getAVContentKeySessionClass();
145 }
146
147 RefPtr<Uint8Array> CDMSessionAVContentKeySession::generateKeyRequest(const String& mimeType, Uint8Array* initData, String& destinationURL, unsigned short& errorCode, uint32_t& systemCode)
148 {
149     UNUSED_PARAM(mimeType);
150     UNUSED_PARAM(destinationURL);
151     ASSERT(initData);
152
153     LOG(Media, "CDMSessionAVContentKeySession::generateKeyRequest(%p)", this);
154
155     errorCode = MediaPlayer::NoError;
156     systemCode = 0;
157
158     m_initData = initData;
159
160     if (equalIgnoringCase(mimeType, "keyrelease")) {
161         m_mode = KeyRelease;
162         return generateKeyReleaseMessage(errorCode, systemCode);
163     }
164
165     if (!m_certificate) {
166         String certificateString(ASCIILiteral("certificate"));
167         RefPtr<Uint8Array> array = Uint8Array::create(certificateString.length());
168         for (unsigned i = 0, length = certificateString.length(); i < length; ++i)
169             array->set(i, certificateString[i]);
170         return array;
171     }
172
173     if (!m_keyRequest) {
174         NSData* nsInitData = [NSData dataWithBytes:m_initData->data() length:m_initData->length()];
175         [contentKeySession() processContentKeyRequestInitializationData:nsInitData options:nil];
176     }
177
178     return nullptr;
179 }
180
181 void CDMSessionAVContentKeySession::releaseKeys()
182 {
183     if (hasContentKeySession()) {
184         m_stopped = true;
185         for (auto& sourceBuffer : m_sourceBuffers)
186             sourceBuffer->flush();
187
188         LOG(Media, "CDMSessionAVContentKeySession::releaseKeys(%p) - expiring stream session", this);
189         [contentKeySession() expire];
190
191         if (!m_certificate)
192             return;
193
194         if (![getAVContentKeySessionClass() respondsToSelector:@selector(pendingExpiredSessionReportsWithAppIdentifier:storageDirectoryAtURL:)])
195             return;
196
197         RetainPtr<NSData> certificateData = adoptNS([[NSData alloc] initWithBytes:m_certificate->data() length:m_certificate->length()]);
198         NSArray* expiredSessions = [getAVContentKeySessionClass() pendingExpiredSessionReportsWithAppIdentifier:certificateData.get() storageDirectoryAtURL:[NSURL fileURLWithPath:storagePath()]];
199         for (NSData* expiredSessionData in expiredSessions) {
200             NSDictionary *expiredSession = [NSPropertyListSerialization propertyListWithData:expiredSessionData options:kCFPropertyListImmutable format:nullptr error:nullptr];
201             NSString *playbackSessionIdValue = (NSString *)[expiredSession objectForKey:PlaybackSessionIdKey];
202             if (![playbackSessionIdValue isKindOfClass:[NSString class]])
203                 continue;
204
205             if (m_sessionId == String(playbackSessionIdValue)) {
206                 LOG(Media, "CDMSessionAVContentKeySession::releaseKeys(%p) - found session, sending expiration message");
207                 m_expiredSession = expiredSessionData;
208                 m_client->sendMessage(Uint8Array::create(static_cast<const uint8_t*>([m_expiredSession bytes]), [m_expiredSession length]).get(), emptyString());
209                 break;
210             }
211         }
212     }
213 }
214
215 static bool isEqual(Uint8Array* data, const char* literal)
216 {
217     ASSERT(data);
218     ASSERT(literal);
219     unsigned length = data->length();
220
221     for (unsigned i = 0; i < length; ++i) {
222         if (!literal[i])
223             return false;
224
225         if (data->item(i) != static_cast<uint8_t>(literal[i]))
226             return false;
227     }
228     return !literal[length];
229 }
230
231 bool CDMSessionAVContentKeySession::update(Uint8Array* key, RefPtr<Uint8Array>& nextMessage, unsigned short& errorCode, uint32_t& systemCode)
232 {
233     UNUSED_PARAM(nextMessage);
234
235     bool shouldGenerateKeyRequest = !m_certificate || isEqual(key, "renew");
236     if (!m_certificate) {
237         LOG(Media, "CDMSessionAVContentKeySession::update(%p) - certificate data", this);
238
239         m_certificate = key;
240     }
241
242     if (isEqual(key, "acknowledged")) {
243         LOG(Media, "CDMSessionAVContentKeySession::update(%p) - acknowleding secure stop message", this);
244
245         if (!m_expiredSession) {
246             errorCode = MediaPlayer::InvalidPlayerState;
247             return false;
248         }
249
250         RetainPtr<NSData> certificateData = adoptNS([[NSData alloc] initWithBytes:m_certificate->data() length:m_certificate->length()]);
251
252         if ([getAVContentKeySessionClass() respondsToSelector:@selector(removePendingExpiredSessionReports:withAppIdentifier:storageDirectoryAtURL:)])
253             [getAVContentKeySessionClass() removePendingExpiredSessionReports:@[m_expiredSession.get()] withAppIdentifier:certificateData.get() storageDirectoryAtURL:[NSURL fileURLWithPath:storagePath()]];
254         m_expiredSession = nullptr;
255         return true;
256     }
257
258     if (m_mode == KeyRelease)
259         return false;
260
261     if (!m_keyRequest) {
262         NSData* nsInitData = [NSData dataWithBytes:m_initData->data() length:m_initData->length()];
263         [contentKeySession() processContentKeyRequestInitializationData:nsInitData options:nil];
264     }
265
266     if (shouldGenerateKeyRequest) {
267         ASSERT(m_keyRequest);
268         RetainPtr<NSData> certificateData = adoptNS([[NSData alloc] initWithBytes:m_certificate->data() length:m_certificate->length()]);
269
270         RetainPtr<NSDictionary> options;
271         if (!m_protocolVersions.isEmpty() && canLoadAVContentKeyRequestProtocolVersionsKey()) {
272             RetainPtr<NSMutableArray> protocolVersionsOption = adoptNS([[NSMutableArray alloc] init]);
273             for (auto& version : m_protocolVersions) {
274                 if (!version)
275                     continue;
276                 [protocolVersionsOption addObject:@(version)];
277             }
278
279             options = @{ getAVContentKeyRequestProtocolVersionsKey(): protocolVersionsOption.get() };
280         }
281
282         errorCode = MediaPlayer::NoError;
283         systemCode = 0;
284         NSError* error = nil;
285         NSData* requestData = [m_keyRequest contentKeyRequestDataForApp:certificateData.get() contentIdentifier:nil options:options.get() error:&error];
286         if (error) {
287             errorCode = MediaPlayerClient::DomainError;
288             systemCode = mediaKeyErrorSystemCode(error);
289             return false;
290         }
291
292         nextMessage = Uint8Array::create(static_cast<const uint8_t*>([requestData bytes]), [requestData length]);
293         return false;
294     }
295
296     LOG(Media, "CDMSessionAVContentKeySession::update(%p) - key data", this);
297     errorCode = MediaPlayer::NoError;
298     systemCode = 0;
299     RetainPtr<NSData> keyData = adoptNS([[NSData alloc] initWithBytes:key->data() length:key->length()]);
300     [m_keyRequest processContentKeyResponseData:keyData.get()];
301
302     return true;
303 }
304
305 void CDMSessionAVContentKeySession::addParser(AVStreamDataParser* parser)
306 {
307     [contentKeySession() addStreamDataParser:parser];
308 }
309
310 void CDMSessionAVContentKeySession::removeParser(AVStreamDataParser* parser)
311 {
312     [contentKeySession() removeStreamDataParser:parser];
313 }
314
315 PassRefPtr<Uint8Array> CDMSessionAVContentKeySession::generateKeyReleaseMessage(unsigned short& errorCode, uint32_t& systemCode)
316 {
317     ASSERT(m_mode == KeyRelease);
318     m_certificate = m_initData;
319     RetainPtr<NSData> certificateData = adoptNS([[NSData alloc] initWithBytes:m_certificate->data() length:m_certificate->length()]);
320
321     if (![getAVContentKeySessionClass() respondsToSelector:@selector(pendingExpiredSessionReportsWithAppIdentifier:storageDirectoryAtURL:)]) {
322         errorCode = MediaPlayer::KeySystemNotSupported;
323         systemCode = '!mor';
324         return nullptr;
325     }
326
327     NSArray* expiredSessions = [getAVContentKeySessionClass() pendingExpiredSessionReportsWithAppIdentifier:certificateData.get() storageDirectoryAtURL:[NSURL fileURLWithPath:storagePath()]];
328     if (![expiredSessions count]) {
329         LOG(Media, "CDMSessionAVContentKeySession::generateKeyReleaseMessage(%p) - no expired sessions found", this);
330
331         errorCode = MediaPlayer::KeySystemNotSupported;
332         systemCode = '!mor';
333         return nullptr;
334     }
335
336     LOG(Media, "CDMSessionAVContentKeySession::generateKeyReleaseMessage(%p) - found %d expired sessions", this, [expiredSessions count]);
337
338     errorCode = 0;
339     systemCode = 0;
340     m_expiredSession = [expiredSessions firstObject];
341     return Uint8Array::create(static_cast<const uint8_t*>([m_expiredSession bytes]), [m_expiredSession length]);
342 }
343
344 void CDMSessionAVContentKeySession::didProvideContentKeyRequest(AVContentKeyRequest *keyRequest)
345 {
346     m_keyRequest = keyRequest;
347 }
348
349 AVContentKeySession* CDMSessionAVContentKeySession::contentKeySession()
350 {
351     if (!m_contentKeySession) {
352
353         String storagePath = this->storagePath();
354         if (storagePath.isEmpty())
355             return nil;
356
357         String storageDirectory = directoryName(storagePath);
358
359         if (!fileExists(storageDirectory)) {
360             if (!makeAllDirectories(storageDirectory))
361                 return nil;
362         }
363
364         m_contentKeySession = adoptNS([[getAVContentKeySessionClass() alloc] initWithStorageDirectoryAtURL:[NSURL fileURLWithPath:storagePath]]);
365         m_contentKeySession.get().delegate = m_contentKeySessionDelegate.get();
366     }
367
368     return m_contentKeySession.get();
369 }
370
371 }
372
373 #endif