Skip to content

Commit

Permalink
Emit an error if the wrong keys are retrieved.
Browse files Browse the repository at this point in the history
Now there will be an error if the wrong keys are retrieved from the
license server.  This can happen if the manifest is incorrect or if
the license server returns the wrong keys.

Closes #301

Change-Id: Id141cb74d02513f8e83205fd5d3242c887468d4e
  • Loading branch information
TheModMaker committed Apr 13, 2016
1 parent 1f9818a commit 6ec2771
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 65 deletions.
45 changes: 43 additions & 2 deletions lib/media/drm_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,16 @@ shaka.media.DrmEngine = function(networkingEngine, onError, onKeyStatus) {


/**
* @typedef {{initData: !Uint8Array, session: !MediaKeySession}}
* @typedef {{
* loaded: boolean,
* initData: !Uint8Array,
* session: !MediaKeySession
* }}
*
* @description A record to track sessions and suppress duplicate init data.
* @property {boolean} loaded
* True once the key status has been updated (to a non-pending state). This
* does not mean the session is 'usable'.
* @property {!Uint8Array} initData
* The init data used to create the session.
* @property {!MediaKeySession} session
Expand Down Expand Up @@ -234,6 +242,10 @@ shaka.media.DrmEngine.prototype.attach = function(video) {
});
}

// Listen to 'waitingforkey' to detect key ID not found.
this.eventManager_.listen(
this.video_, 'waitingforkey', this.onWaitingForKey_.bind(this));

return Promise.all([setMediaKeys, setServerCertificate]).then(function() {
if (this.destroyed_) return Promise.reject();

Expand Down Expand Up @@ -590,6 +602,24 @@ shaka.media.DrmEngine.prototype.processDrmInfos_ =
};


/**
* @param {Event} event
* @private
*/
shaka.media.DrmEngine.prototype.onWaitingForKey_ = function(event) {
if (this.activeSessions_.some(function(s) { return !s.loaded; })) {
// There are still sessions being loaded, one of them might be the required
// key. Once the request is complete, we will get another waitingforkey
// event if we still don't have the keys.
return;
}

// We don't have some of the required keys, so dispatch an error.
this.onError_(new shaka.util.Error(
shaka.util.Error.Category.DRM, shaka.util.Error.Code.WRONG_KEYS));
};


/**
* @param {!MediaEncryptedEvent} event
* @private
Expand Down Expand Up @@ -639,7 +669,8 @@ shaka.media.DrmEngine.prototype.createTemporarySession_ =
this.onKeyStatusesChange_.bind(this));

var p = session.generateRequest(initDataType, initData.buffer);
this.activeSessions_.push({initData: initData, session: session});
this.activeSessions_.push(
{initData: initData, session: session, loaded: false});

p.catch(function(error) {
if (this.destroyed_) return;
Expand Down Expand Up @@ -815,6 +846,16 @@ shaka.media.DrmEngine.prototype.onKeyStatusesChange_ = function(event) {
status = 'usable';
}

if (status != 'status-pending' && status != 'internal-error') {
// The session has been loaded, update the active sessions.
var activeSession = this.activeSessions_.filter(function(s) {
return s.session == session;
})[0];
goog.asserts.assert(activeSession != null,
'Unexpected session in key status map');
activeSession.loaded = true;
}

var keyIdHex = shaka.util.Uint8ArrayUtils.toHex(new Uint8Array(keyId));
keyStatusByKeyId[keyIdHex] = status;
}.bind(this));
Expand Down
12 changes: 11 additions & 1 deletion lib/util/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -474,5 +474,15 @@ shaka.util.Error.Code = {
* The manifest does not specify any DRM info, but the content is encrypted.
* Either the manifest or the manifest parser are broken.
*/
'ENCRYPTED_CONTENT_WITHOUT_DRM_INFO': 6010
'ENCRYPTED_CONTENT_WITHOUT_DRM_INFO': 6010,

/**
* The media is encrypted with keys that were not fetched. This can happen
* under several conditions:
* - The license server (or proxy) returned the wrong keys.
* - The PSSH in the manifest does not contain the required key IDs.
* - The PSSH in the media does not contain the required key IDs. This
* means that the PSSH does not match the key ID in the media.
*/
'WRONG_KEYS': 6011
};
203 changes: 166 additions & 37 deletions test/drm_engine_integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ describe('DrmEngine', function() {
var video;
var mediaSource;
var manifest;
var config;

var onErrorSpy;
var onKeyStatusSpy;
Expand Down Expand Up @@ -102,7 +101,7 @@ describe('DrmEngine', function() {

drmEngine = new shaka.media.DrmEngine(
networkingEngine, onErrorSpy, onKeyStatusSpy);
config = {
var config = {
retryParameters: shaka.net.NetworkingEngine.defaultRetryParameters(),
clearKeys: {},
advanced: {},
Expand All @@ -115,29 +114,17 @@ describe('DrmEngine', function() {
};
drmEngine.configure(config);

var drmInfos = [{
keySystem: 'com.widevine.alpha',
distinctiveIdentifierRequired: false,
persistentStateRequired: false
},{
keySystem: 'com.microsoft.playready',
distinctiveIdentifierRequired: false,
persistentStateRequired: false
}];

manifest = {
periods: [{
streamSets: [{
type: 'video',
drmInfos: drmInfos,
streams: [{mimeType: 'video/mp4', codecs: 'avc1.640015'}]
},{
type: 'audio',
drmInfos: drmInfos,
streams: [{mimeType: 'audio/mp4', codecs: 'mp4a.40.2'}]
}]
}]
};
manifest = new shaka.test.ManifestGenerator()
.addPeriod(0)
.addStreamSet('video')
.addDrmInfo('com.widevine.alpha')
.addDrmInfo('com.microsoft.playready')
.addStream(1).mime('video/mp4', 'avc1.640015')
.addStreamSet('audio')
.addDrmInfo('com.widevine.alpha')
.addDrmInfo('com.microsoft.playready')
.addStream(1).mime('audio/mp4', 'mp4a.40.2')
.build();

eventManager = new shaka.util.EventManager();

Expand Down Expand Up @@ -169,17 +156,6 @@ describe('DrmEngine', function() {
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
});

function checkKeySystems() {
// Our test asset for this suite can use any of these key systems:
if (!support['com.widevine.alpha'] &&
!support['com.microsoft.playready']) {
// pending() throws a special exception that Jasmine uses to skip a test.
// It can only be used from inside it(), not describe() or beforeEach().
pending('Skipping DrmEngine tests.');
// The rest of the test will not run.
}
}

describe('basic flow', function() {
it('gets a license and can play encrypted segments', function(done) {
checkKeySystems();
Expand Down Expand Up @@ -272,5 +248,158 @@ describe('DrmEngine', function() {
expect(video.currentTime).toBeGreaterThan(0);
}).catch(fail).then(done);
});
});
}); // describe('basic flow')

describe('missing keys error', function() {
it('fires when manifest PSSH does not match key ID', function(done) {
setBadManifestData();
runMissingKeyTest(done);
});

it('fires even if the license request is delayed', function(done) {
setBadManifestData();

// Delay the license request by 3 seconds.
var originalRequest = networkingEngine.request;
networkingEngine.request = jasmine.createSpy('request');
networkingEngine.request.and.callFake(function(type, request) {
return shaka.test.Util.delay(3).then(function() {
return originalRequest.call(networkingEngine, type, request);
});
});

runMissingKeyTest(done);
});

it('fires when license server returns wrong key ID', function(done) {
// TODO: Update once we get a PlayReady license server that will return
// the wrong key IDs.
if (!support['com.widevine.alpha']) {
pending('Skipping DrmEngine tests.');
}

var config = {
retryParameters: shaka.net.NetworkingEngine.defaultRetryParameters(),
clearKeys: {},
advanced: {},
servers: {
'com.widevine.alpha': '//widevine-proxy.appspot.com/proxy'
}
};
drmEngine.configure(config);
networkingEngine.clearAllRequestFilters();

runMissingKeyTest(done);
});

function setBadManifestData() {
// Override the init data from the media. This is from Axinom's other
// test asset so we get a key response but for the wrong key ID.
manifest = new shaka.test.ManifestGenerator()
.addPeriod(0)
.addStreamSet('video')
.addDrmInfo('com.widevine.alpha')
.addCencInitData(
'AAAANHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABQIARIQFTDToGkE' +
'RGqRoTOhFaqMQQ==')
.addDrmInfo('com.microsoft.playready')
.addCencInitData(
'AAAB5HBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAcTEAQAAAQABALoB' +
'PABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQA' +
'cAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQA' +
'LgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkA' +
'UgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0A' +
'IgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQA' +
'RQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsA' +
'RQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8A' +
'QQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwA' +
'SwBJAEQAPgBvAE4ATQB3AEYAUQBSAHAAYQBrAFMAUgBvAFQATwBoAEYA' +
'YQBxAE0AUQBRAD0APQA8AC8ASwBJAEQAPgA8AC8ARABBAFQAQQA+ADwA' +
'LwBXAFIATQBIAEUAQQBEAEUAUgA+AA==')
.addStream(1).mime('video/mp4', 'avc1.640015')
.addStreamSet('audio')
.addDrmInfo('com.widevine.alpha')
.addDrmInfo('com.microsoft.playready')
.addStream(1).mime('audio/mp4', 'mp4a.40.2')
.build();

networkingEngine.clearAllRequestFilters();
networkingEngine.registerRequestFilter(function(type, request) {
if (type != shaka.net.NetworkingEngine.RequestType.LICENSE) return;

request.headers['X-AxDRM-Message'] = [
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ2ZXJzaW9uIjoxLCJjb21fa2V',
'5X2lkIjoiNjllNTQwODgtZTllMC00NTMwLThjMWEtMWViNmRjZDBkMTRlIiwibWV',
'zc2FnZSI6eyJ0eXBlIjoiZW50aXRsZW1lbnRfbWVzc2FnZSIsImtleXMiOlt7Iml',
'kIjoiMTUzMGQzYTAtNjkwNC00NDZhLTkxYTEtMzNhMTE1YWE4YzQxIn0seyJpZCI',
'6ImM4M2ViNjM5LWU2NjQtNDNmOC1hZTk4LTQwMzliMGMxM2IyZCJ9LHsiaWQiOiI',
'zZDhjYzc2Mi0yN2FjLTQwMGYtOTg5Zi04YWI1ZGM3ZDc3NzUifSx7ImlkIjoiYmQ',
'4ZGFkNTgtMDMyZC00YzI1LTg5ZmEtYzdiNzEwZTgyYWMyIn1dfX0.9t18lFmZFVH',
'MzpoZxYDyqOS0Bk_evGhTBw_F2JnAK2k'
].join('');
});
}

function runMissingKeyTest(done) {
checkKeySystems();

// The only error should be key ID not found.
var onErrorCalled = new shaka.util.PublicPromise();
onErrorSpy.and.callFake(function(error) {
onErrorCalled.resolve();
shaka.test.Util.expectToEqualError(
error,
new shaka.util.Error(
shaka.util.Error.Category.DRM,
shaka.util.Error.Code.WRONG_KEYS));
});

eventManager.listen(video, 'error', function() {
fail('MediaError code ' + video.error.code);
var extended = video.error.msExtendedCode;
if (extended) {
if (extended < 0) {
extended += Math.pow(2, 32);
}
fail('MediaError msExtendedCode ' + extended.toString(16));
}
});

drmEngine.init(manifest, /* offline */ false).then(function() {
return drmEngine.attach(video);
}).then(function() {
return mediaSourceEngine.appendBuffer(
'video', videoInitSegment, null, null);
}).then(function() {
return mediaSourceEngine.appendBuffer(
'audio', audioInitSegment, null, null);
}).then(function() {
// waitingforkeys only fires once we are trying to play.
return mediaSourceEngine.appendBuffer(
'video', videoSegment, null, null);
}).then(function() {
return mediaSourceEngine.appendBuffer(
'audio', audioSegment, null, null);
}).then(function() {
video.play();
// Try to play for 6 seconds.
return Promise.race([shaka.test.Util.delay(6), onErrorCalled]);
}).then(function() {
// There should have been an error and the video should not play.
expect(video.currentTime).toBe(0);
expect(onErrorSpy).toHaveBeenCalled();
}).catch(fail).then(done);
}
}); // describe('missing keys')

function checkKeySystems() {
// Our test asset for this suite can use any of these key systems:
if (!support['com.widevine.alpha'] &&
!support['com.microsoft.playready']) {
// pending() throws a special exception that Jasmine uses to skip a test.
// It can only be used from inside it(), not describe() or beforeEach().
pending('Skipping DrmEngine tests.');
// The rest of the test will not run.
}
}
});
34 changes: 9 additions & 25 deletions test/drm_engine_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,31 +56,15 @@ describe('DrmEngine', function() {
});

beforeEach(function() {
manifest = {
periods: [{
streamSets: [{
type: 'video',
drmInfos: [{
keySystem: 'drm.abc',
distinctiveIdentifierRequired: false,
persistentStateRequired: false
}],
streams: [
{mimeType: 'video/foo', codecs: 'vbar'}
]
},{
type: 'audio',
drmInfos: [{
keySystem: 'drm.def',
distinctiveIdentifierRequired: false,
persistentStateRequired: false
}],
streams: [
{mimeType: 'audio/foo', codecs: 'abar'}
]
}]
}]
};
manifest = new shaka.test.ManifestGenerator()
.addPeriod(0)
.addStreamSet('video')
.addDrmInfo('drm.abc')
.addStream(0).mime('video/foo', 'vbar')
.addStreamSet('audio')
.addDrmInfo('drm.def')
.addStream(1).mime('audio/foo', 'abar')
.build();

// Reset spies.
requestMediaKeySystemAccessSpy.calls.reset();
Expand Down
Loading

0 comments on commit 6ec2771

Please sign in to comment.