[EME] Support generateRequest() in CDMFairPlayStreaming
authorjer.noble@apple.com <jer.noble@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 7 Dec 2017 19:46:53 +0000 (19:46 +0000)
committerjer.noble@apple.com <jer.noble@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 7 Dec 2017 19:46:53 +0000 (19:46 +0000)
https://bugs.webkit.org/show_bug.cgi?id=179752

Reviewed by Eric Carlson.

Source/WebCore:

Tests: platform/mac/media/encrypted-media/fps-createSession.html
       platform/mac/media/encrypted-media/fps-generateRequest.html

Add support for generating license requests from initialization data in
CDMFairPlayStreaming. To do so, add explicit checks for FairPlay Steraming requirements:
reqests will fail if no server certificate exists, and will fail if no content key id exists
in the initialization data.

* platform/graphics/avfoundation/CDMFairPlayStreaming.cpp:
(WebCore::CDMPrivateFairPlayStreaming::extractKeyIDsSinf):
(WebCore::CDMPrivateFairPlayStreaming::sanitizeSinf):
(WebCore::CDMFactory::platformRegisterFactories):
* platform/graphics/avfoundation/CDMFairPlayStreaming.h:
* platform/graphics/avfoundation/objc/CDMInstanceFairPlayStreamingAVFObjC.mm:
(WebCore::CDMInstanceFairPlayStreamingAVFObjC::requestLicense):
(WebCore::CDMInstanceFairPlayStreamingAVFObjC::didProvideRequest):

LayoutTests:

* platform/mac/TestExpectations:
* platform/mac/media/encrypted-media/fps-createSession-expected.txt: Added.
* platform/mac/media/encrypted-media/fps-createSession.html: Added.
* platform/mac/media/encrypted-media/fps-generateRequest-expected.txt: Added.
* platform/mac/media/encrypted-media/fps-generateRequest.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@225637 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/platform/mac/TestExpectations
LayoutTests/platform/mac/media/encrypted-media/fps-createSession-expected.txt [new file with mode: 0644]
LayoutTests/platform/mac/media/encrypted-media/fps-createSession.html [new file with mode: 0644]
LayoutTests/platform/mac/media/encrypted-media/fps-generateRequest-expected.txt [new file with mode: 0644]
LayoutTests/platform/mac/media/encrypted-media/fps-generateRequest.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/platform/graphics/avfoundation/CDMFairPlayStreaming.cpp
Source/WebCore/platform/graphics/avfoundation/CDMFairPlayStreaming.h
Source/WebCore/platform/graphics/avfoundation/objc/CDMInstanceFairPlayStreamingAVFObjC.mm

index d60d98c..ec4bb9e 100644 (file)
@@ -1,3 +1,16 @@
+2017-12-07  Jer Noble  <jer.noble@apple.com>
+
+        [EME] Support generateRequest() in CDMFairPlayStreaming
+        https://bugs.webkit.org/show_bug.cgi?id=179752
+
+        Reviewed by Eric Carlson.
+
+        * platform/mac/TestExpectations:
+        * platform/mac/media/encrypted-media/fps-createSession-expected.txt: Added.
+        * platform/mac/media/encrypted-media/fps-createSession.html: Added.
+        * platform/mac/media/encrypted-media/fps-generateRequest-expected.txt: Added.
+        * platform/mac/media/encrypted-media/fps-generateRequest.html: Added.
+
 2017-12-07  Ryan Haddad  <ryanhaddad@apple.com>
 
         Unreviewed, rolling out r224931.
index 47f32b1..fa93932 100644 (file)
@@ -1748,6 +1748,8 @@ webkit.org/b/175998 http/tests/security/mixedContent/insecure-css-with-secure-co
 # AVContentKeySession not available pre-High Sierra
 [ ElCapitan Sierra ] platform/mac/media/encrypted-media/fps-createMediaKeys.html [ Skip ]
 [ ElCapitan Sierra ] platform/mac/media/encrypted-media/fps-requestMediaKeySystemAccess.html [ Skip ]
+[ ElCapitan Sierra ] platform/mac/media/encrypted-media/fps-createSession.html [ Skip ]
+[ ElCapitan Sierra ] platform/mac/media/encrypted-media/fps-generateRequest.html [ Skip ]
 
 # rdar://problem/35395437
 [ HighSierra+ ] imported/w3c/web-platform-tests/media-source/mediasource-play.html [ Failure ]
diff --git a/LayoutTests/platform/mac/media/encrypted-media/fps-createSession-expected.txt b/LayoutTests/platform/mac/media/encrypted-media/fps-createSession-expected.txt
new file mode 100644 (file)
index 0000000..f24f383
--- /dev/null
@@ -0,0 +1,8 @@
+RUN(promise = navigator.requestMediaKeySystemAccess("com.apple.fps", capabilities))
+Promise resolved OK
+RUN(promise = access.createMediaKeys())
+Promise resolved OK
+RUN(session = keys.createSession())
+EXPECTED (session != 'null') OK
+END OF TEST
+
diff --git a/LayoutTests/platform/mac/media/encrypted-media/fps-createSession.html b/LayoutTests/platform/mac/media/encrypted-media/fps-createSession.html
new file mode 100644 (file)
index 0000000..6b2a23b
--- /dev/null
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>fps-requestMediaKeySystemAccess</title>
+    <script src="../../../../media/video-test.js"></script>
+    <script>
+    var capabilities = [{
+        initDataTypes: ['sinf'],
+        videoCapabilities: [{ contentType: 'video/mp4', robustness: '' }],
+        distinctiveIdentifier: 'not-allowed',
+        persistentState: 'not-allowed',
+        sessionTypes: ['temporary'],
+    }];
+    var promise;
+    var access;
+    var keys;
+    var session;
+    var initData = null;
+
+    function startTest() {
+        run('promise = navigator.requestMediaKeySystemAccess("com.apple.fps", capabilities)');
+        shouldResolve(promise).then(gotAccess, endTest);
+    }
+
+    function gotAccess(access) {
+        window.access = access;
+        run('promise = access.createMediaKeys()');
+        shouldResolve(promise).then(createdKeys, endTest);
+    }
+
+    function createdKeys(keys) {
+        window.keys = keys;
+        run('session = keys.createSession()');
+        testExpected('session', 'null', '!=');
+        endTest();
+    }
+
+    window.addEventListener('load', startTest);
+    </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/LayoutTests/platform/mac/media/encrypted-media/fps-generateRequest-expected.txt b/LayoutTests/platform/mac/media/encrypted-media/fps-generateRequest-expected.txt
new file mode 100644 (file)
index 0000000..2baa768
--- /dev/null
@@ -0,0 +1,11 @@
+RUN(promise = navigator.requestMediaKeySystemAccess("com.apple.fps", capabilities))
+Promise resolved OK
+RUN(promise = access.createMediaKeys())
+Promise resolved OK
+RUN(keys.setServerCertificate(serverCertificate))
+RUN(session = keys.createSession())
+EXPECTED (session != 'null') OK
+RUN(promise = session.generateRequest("sinf", initData))
+Promise resolved OK
+END OF TEST
+
diff --git a/LayoutTests/platform/mac/media/encrypted-media/fps-generateRequest.html b/LayoutTests/platform/mac/media/encrypted-media/fps-generateRequest.html
new file mode 100644 (file)
index 0000000..7f5b1ec
--- /dev/null
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>fps-requestMediaKeySystemAccess</title>
+    <script src="../../../../media/video-test.js"></script>
+    <script>
+    var capabilities = [{
+        initDataTypes: ['sinf'],
+        videoCapabilities: [{ contentType: 'video/mp4', robustness: '' }],
+        distinctiveIdentifier: 'not-allowed',
+        persistentState: 'not-allowed',
+        sessionTypes: ['temporary'],
+    }];
+    var promise;
+    var access;
+    var keys;
+    var session;
+    var initData = new TextEncoder().encode(JSON.stringify({
+        sinf: [
+            "AAAADGZybWFtcDRhAAAAFHNjaG0AAAAAY2JjcwABAAAAAAA5c2NoaQAAADF0ZW5jAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAENwoLj77jCjwCAAw3SqKao8=",
+        ]
+    }));
+    var serverCertificateURI = "data:application/x-x509-ca-cert;base64,MIIEyDCCA7CgAwIBAgIID0/07cCHH0YwDQYJKoZIhvcNAQEFBQAwfzELMAkGA1UEBhMCVVMxEzARBgNVBAoMCkFwcGxlIEluYy4xJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MTMwMQYDVQQDDCpBcHBsZSBLZXkgU2VydmljZXMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTIxMjIwMDAzODQzWhcNMTQxMjIxMDAzODQzWjBIMQswCQYDVQQGEwJVUzESMBAGA1UECgwJQXBwbGUgSW5jMRIwEAYDVQQLDAlBcHBsZSBJbmMxETAPBgNVBAMMCGZwc3Rlc3Q0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCuHevDlphOM0sD67r3olFkN/2vC0oPl+dJ2CrBkL0tUJov9YbXud6ymJp2TkKkGqnubRaX5mI94+V9Cc/0zSlp+NTCDTcZ7y44E8j85Av/5XqozUf/wUyY+UYPBRD6BHUnH5YD6uuSlLcqE0DaE8ptXiQyN3SRteCFQ4nI9f11uQIDAQABo4ICATCCAf0wHQYDVR0OBBYEFBaVf1g1bfyAW776Sfveqj9N03EMMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUY+RHVMuFcVlGLIOszEQxZGcDLL4wgeIGA1UdIASB2jCB1zCB1AYJKoZIhvdjZAUBMIHGMIHDBggrBgEFBQcCAjCBtgyBs1JlbGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMDUGA1UdHwQuMCwwKqAooCaGJGh0dHA6Ly9jcmwuYXBwbGUuY29tL2tleXNlcnZpY2VzLmNybDAOBgNVHQ8BAf8EBAMCBSAwNAYLKoZIhvdjZAYNAQMBAf8EIgHGeq0WCxH3oALqbKRDeCl4lqDaHVSQjsQZnyCME2TecUEwSwYLKoZIhvdjZAYNAQQBAf8EOQHlHhdsgB8QPJZFb9aAmvc8kYSTQYbjF6/U2mq46sXWRKWPKGpqLyC5ActNQUuHjynZn6Y0vs0D8jANBgkqhkiG9w0BAQUFAAOCAQEAg8GkEw0gDRn7raA8w+V36NOvKlUx3Wh3pcqI8cRATv9Twp4zfyJ4FwxdT90/zCtUUjTVtau6yESTwX+LUFu/Y0kvaV0htgBIBu7MWYCvfLlSwS4MqbBtNdloDNvU7CNyWXaMpKpYKN6i0SqEnTvF0mSTpBliCT+QxuNIxaWq9h2cCn79kbXJM5+IC37mIWO0jvzQjeSlOXJPZrNHZ6Bjt0AyiCIiZVkPmVm5lV3Ycd0gf4mfhAVJYE/p6/dTk+mqnxDdUUMVFDUfu1OqBim6ldWnAh8PlWaGh5rLYVgGvxEEPDNeueYhxTrDnENbhCZ5n/by0Rp0s67t1924Wk+QUQ==";
+    var serverCertificate;
+
+    function startTest() {
+        run('promise = navigator.requestMediaKeySystemAccess("com.apple.fps", capabilities)');
+        shouldResolve(promise).then(gotAccess, endTest);
+    }
+
+    function gotAccess(access) {
+        window.access = access;
+        run('promise = access.createMediaKeys()');
+        shouldResolve(promise).then(createdKeys, endTest);
+    }
+
+    async function createdKeys(keys) {
+        window.keys = keys;
+        var response = await fetch(serverCertificateURI);
+        serverCertificate = await response.arrayBuffer();
+        run('keys.setServerCertificate(serverCertificate)');
+        run('session = keys.createSession()');
+        testExpected('session', 'null', '!=');
+        run('promise = session.generateRequest("sinf", initData)');
+        shouldResolve(promise).then(endTest, endTest);
+    }
+
+    window.addEventListener('load', startTest);
+    </script>
+</head>
+<body>
+</body>
+</html>
index 8926ece..830235e 100644 (file)
@@ -1,5 +1,29 @@
 2017-12-07  Jer Noble  <jer.noble@apple.com>
 
+        [EME] Support generateRequest() in CDMFairPlayStreaming
+        https://bugs.webkit.org/show_bug.cgi?id=179752
+
+        Reviewed by Eric Carlson.
+
+        Tests: platform/mac/media/encrypted-media/fps-createSession.html
+               platform/mac/media/encrypted-media/fps-generateRequest.html
+
+        Add support for generating license requests from initialization data in
+        CDMFairPlayStreaming. To do so, add explicit checks for FairPlay Steraming requirements:
+        reqests will fail if no server certificate exists, and will fail if no content key id exists
+        in the initialization data.
+
+        * platform/graphics/avfoundation/CDMFairPlayStreaming.cpp:
+        (WebCore::CDMPrivateFairPlayStreaming::extractKeyIDsSinf):
+        (WebCore::CDMPrivateFairPlayStreaming::sanitizeSinf):
+        (WebCore::CDMFactory::platformRegisterFactories):
+        * platform/graphics/avfoundation/CDMFairPlayStreaming.h:
+        * platform/graphics/avfoundation/objc/CDMInstanceFairPlayStreamingAVFObjC.mm:
+        (WebCore::CDMInstanceFairPlayStreamingAVFObjC::requestLicense):
+        (WebCore::CDMInstanceFairPlayStreamingAVFObjC::didProvideRequest):
+
+2017-12-07  Jer Noble  <jer.noble@apple.com>
+
         [EME] Possible deadlock when aborting a SourceBufferPrivateAVFObjC append operation
         https://bugs.webkit.org/show_bug.cgi?id=180486
 
index b3f2b9b..c151ee0 100644 (file)
 
 namespace WebCore {
 
+static const Vector<FourCC>& validFairPlayStreamingSchemes()
+{
+    static NeverDestroyed<Vector<FourCC>> validSchemes = Vector<FourCC>({ 
+        "cbcs",
+        "cbc2",
+        "cbc1",
+    });
+
+    return validSchemes;
+}
+
+static const String& sinfName()
+{
+    static NeverDestroyed<String> sinf { MAKE_STATIC_STRING_IMPL("sinf") };
+    return sinf;
+}
+
 static Vector<Ref<SharedBuffer>> extractSinfData(const SharedBuffer& buffer)
 {
     // JSON of the format: "{ sinf: [ <base64-encoded-string> ] }"
@@ -66,7 +83,7 @@ static Vector<Ref<SharedBuffer>> extractSinfData(const SharedBuffer& buffer)
         return { };
 
     RefPtr<JSON::Array> sinfArray;
-    if (!object->getArray("sinf", sinfArray))
+    if (!object->getArray(sinfName(), sinfArray))
         return { };
 
     Vector<Ref<SharedBuffer>> sinfs;
@@ -134,28 +151,21 @@ static SchemeAndKeyResult extractSchemeAndKeyIdFromSinf(const SharedBuffer& buff
     return result;
 }
 
-static Vector<Ref<SharedBuffer>> extractKeyIDsSinf(const SharedBuffer& buffer)
+Vector<Ref<SharedBuffer>> CDMPrivateFairPlayStreaming::extractKeyIDsSinf(const SharedBuffer& buffer)
 {
-    auto sinfs = extractSinfData(buffer);
-    if (sinfs.isEmpty())
-        return { };
 
     Vector<Ref<SharedBuffer>> keyIDs;
-    for (auto& sinf : sinfs) {
-        auto results = extractSchemeAndKeyIdFromSinf(sinf);
-        if (!results.size())
-            continue;
+    auto results = extractSchemeAndKeyIdFromSinf(buffer);
 
-        for (auto& result : results) {
-            if (result.first == 'cbcs')
-                keyIDs.append(SharedBuffer::create(result.second.data(), result.second.size()));
-        }
+    for (auto& result : results) {
+        if (validFairPlayStreamingSchemes().contains(result.first))
+            keyIDs.append(SharedBuffer::create(result.second.data(), result.second.size()));
     }
 
     return keyIDs;
 }
 
-static RefPtr<SharedBuffer> sanitizeSinf(const SharedBuffer& buffer)
+RefPtr<SharedBuffer> CDMPrivateFairPlayStreaming::sanitizeSinf(const SharedBuffer& buffer)
 {
     // Common SINF Box Format
     UNUSED_PARAM(buffer);
@@ -168,7 +178,7 @@ void CDMFactory::platformRegisterFactories(Vector<CDMFactory*>& factories)
     factories.append(&CDMFactoryClearKey::singleton());
     factories.append(&CDMFactoryFairPlayStreaming::singleton());
 
-    InitDataRegistry::shared().registerInitDataType("sinf", { sanitizeSinf, extractKeyIDsSinf });
+    InitDataRegistry::shared().registerInitDataType(sinfName(), { CDMPrivateFairPlayStreaming::sanitizeSinf, CDMPrivateFairPlayStreaming::extractKeyIDsSinf });
 }
 
 CDMFactoryFairPlayStreaming& CDMFactoryFairPlayStreaming::singleton()
@@ -200,12 +210,12 @@ CDMPrivateFairPlayStreaming::~CDMPrivateFairPlayStreaming() = default;
 
 bool CDMPrivateFairPlayStreaming::supportsInitDataType(const AtomicString& initDataType) const
 {
-    return initDataType == "sinf";
+    return initDataType == sinfName();
 }
 
 bool CDMPrivateFairPlayStreaming::supportsConfiguration(const CDMKeySystemConfiguration& configuration) const
 {
-    if (!configuration.initDataTypes.contains("sinf"))
+    if (!configuration.initDataTypes.contains(sinfName()))
         return false;
 
 #if HAVE(AVCONTENTKEYSESSION)
@@ -321,7 +331,7 @@ bool CDMPrivateFairPlayStreaming::supportsInitData(const AtomicString& initDataT
         return false;
 
     return WTF::anyOf(extractSchemeAndKeyIdFromSinf(initData), [](auto& result) {
-        return result.first == 'cbcs' || result.first == 'cbc2' || result.first == 'cbc1';
+        return validFairPlayStreamingSchemes().contains(result.first);
     });
 }
 
index d9aa868..7f4c43b 100644 (file)
@@ -64,6 +64,9 @@ public:
     bool supportsInitData(const AtomicString&, const SharedBuffer&) const override;
     RefPtr<SharedBuffer> sanitizeResponse(const SharedBuffer&) const override;
     std::optional<String> sanitizeSessionId(const String&) const override;
+
+    static Vector<Ref<SharedBuffer>> extractKeyIDsSinf(const SharedBuffer&);
+    static RefPtr<SharedBuffer> sanitizeSinf(const SharedBuffer&);
 };
 
 }
index 23bccf2..76e03cc 100644 (file)
@@ -28,6 +28,7 @@
 
 #if HAVE(AVCONTENTKEYSESSION)
 
+#import "CDMFairPlayStreaming.h"
 #import "CDMKeySystemConfiguration.h"
 #import "NotImplemented.h"
 #import "SharedBuffer.h"
@@ -226,6 +227,12 @@ void CDMInstanceFairPlayStreamingAVFObjC::requestLicense(LicenseType licenseType
         callback(SharedBuffer::create(), emptyString(), false, Failed);
         return;
     }
+
+    if (!m_serverCertificate) {
+        callback(SharedBuffer::create(), emptyString(), false, Failed);
+        return;
+    }
+
     m_requestLicenseCallback = WTFMove(callback);
 
     [m_session processContentKeyRequestWithIdentifier:nil initializationData:initData->createNSData().get() options:nil];
@@ -268,10 +275,15 @@ void CDMInstanceFairPlayStreamingAVFObjC::didProvideRequest(AVContentKeyRequest
     if (!m_requestLicenseCallback)
         return;
 
-#pragma clang diagnostic push 
-#pragma clang diagnostic ignored "-Wnonnull"
     RetainPtr<NSData> appIdentifier = m_serverCertificate ? m_serverCertificate->createNSData() : nullptr;
-    [m_request makeStreamingContentKeyRequestDataForApp:appIdentifier.get() contentIdentifier:nil options:nil completionHandler:[this, weakThis = createWeakPtr()] (NSData *contentKeyRequestData, NSError *error) mutable {
+    Vector<Ref<SharedBuffer>> keyIDs = CDMPrivateFairPlayStreaming::extractKeyIDsSinf(SharedBuffer::create(request.initializationData));
+    if (keyIDs.isEmpty()) {
+        m_requestLicenseCallback(SharedBuffer::create(), m_sessionId, false, Failed);
+        return;
+    }
+
+    RetainPtr<NSData> contentIdentifier = keyIDs.first()->createNSData();
+    [m_request makeStreamingContentKeyRequestDataForApp:appIdentifier.get() contentIdentifier:contentIdentifier.get() options:nil completionHandler:[this, weakThis = createWeakPtr()] (NSData *contentKeyRequestData, NSError *error) mutable {
         callOnMainThread([this, weakThis = WTFMove(weakThis), error = retainPtr(error), contentKeyRequestData = retainPtr(contentKeyRequestData)] {
             if (!weakThis || !m_requestLicenseCallback)
                 return;
@@ -282,7 +294,6 @@ void CDMInstanceFairPlayStreamingAVFObjC::didProvideRequest(AVContentKeyRequest
                 m_requestLicenseCallback(SharedBuffer::create(contentKeyRequestData.get()), m_sessionId, false, Succeeded);
         });
     }];
-#pragma clang diagnostic pop
 }
 
 void CDMInstanceFairPlayStreamingAVFObjC::didProvideRenewingRequest(AVContentKeyRequest *request)