-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor protection and fix key rotation #3805
Merged
dsilhavy
merged 8 commits into
Dash-Industry-Forum:development
from
Orange-OpenSource:fix-key-rotation
Nov 15, 2021
Merged
Changes from 5 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
803e76d
Refactor protection module in order to fix key rotation to be based o…
bbert 159d472
Remove unuseful ProtectionController::_getSessionType()
bbert 2fbb05b
fix unit tests
bbert bb37190
update index.d.ts
bbert 144c2dd
Merge branch 'development' into fix-key-rotation
bbert 7ad6349
Fix protData in _getKeySystemConfiguration()
bbert 610450e
Do not check for key id and initData in _initiateWithExistingKeySyste…
bbert 4975b70
Merge branch 'development' into fix-key-rotation
bbert File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -129,7 +129,7 @@ function ProtectionController(config) { | |
|
||
// ContentProtection elements are specified at the AdaptationSet level, so the CP for audio | ||
// and video will be the same. Just use one valid MediaInfo object | ||
let supportedKS = protectionKeyController.getSupportedKeySystemsFromContentProtection(mediaInfo.contentProtection); | ||
let supportedKS = protectionKeyController.getSupportedKeySystemsFromContentProtection(mediaInfo.contentProtection, protDataSet, sessionType); | ||
|
||
// Reorder key systems according to priority order provided in protectionData | ||
supportedKS = supportedKS.sort((ksA, ksB) => { | ||
|
@@ -181,7 +181,7 @@ function ProtectionController(config) { | |
|
||
// Add all key systems to our request list since we have yet to select a key system | ||
for (let i = 0; i < supportedKS.length; i++) { | ||
const keySystemConfiguration = _getKeySystemConfiguration(supportedKS[i].ks); | ||
const keySystemConfiguration = _getKeySystemConfiguration(supportedKS[i]); | ||
requestedKeySystems.push({ | ||
ks: supportedKS[i].ks, | ||
configs: [keySystemConfiguration] | ||
|
@@ -218,7 +218,7 @@ function ProtectionController(config) { | |
for (ksIdx = 0; ksIdx < pendingKeySystemData[i].length; ksIdx++) { | ||
if (selectedKeySystem === pendingKeySystemData[i][ksIdx].ks) { | ||
const current = pendingKeySystemData[i][ksIdx] | ||
_loadOrCreateKeySession(protData, current) | ||
_loadOrCreateKeySession(current) | ||
break; | ||
} | ||
} | ||
|
@@ -241,7 +241,7 @@ function ProtectionController(config) { | |
* @param {array} supportedKS | ||
* @private | ||
*/ | ||
function _initiateWithExistingKeySystem(supportedKS,) { | ||
function _initiateWithExistingKeySystem(supportedKS) { | ||
const ksIdx = supportedKS.findIndex((entry) => { | ||
return entry.ks === selectedKeySystem; | ||
}); | ||
|
@@ -251,59 +251,62 @@ function ProtectionController(config) { | |
return; | ||
} | ||
|
||
|
||
// we only need to create or load a new key session if the key id has changed | ||
if (_isKeyIdDuplicate(current.keyId)) { | ||
return; | ||
} | ||
|
||
// we only need to create or load a new key session if the init data has changed | ||
const initDataForKs = CommonEncryption.getPSSHForKeySystem(selectedKeySystem, current.initData); | ||
if (_isInitDataDuplicate(initDataForKs)) { | ||
return; | ||
} | ||
|
||
const protData = _getProtDataForKeySystem(selectedKeySystem); | ||
_loadOrCreateKeySession(protData, current); | ||
_loadOrCreateKeySession(current); | ||
} | ||
|
||
/** | ||
* Loads an existing key session if we already have a session id. Otherwise we create a new key session | ||
* @param {object} protData | ||
* @param {object} keySystemInfo | ||
* @private | ||
*/ | ||
function _loadOrCreateKeySession(protData, keySystemInfo) { | ||
function _loadOrCreateKeySession(keySystemInfo) { | ||
// Clearkey | ||
if (protectionKeyController.isClearKey(selectedKeySystem)) { | ||
// For Clearkey: if parameters for generating init data was provided by the user, use them for generating | ||
// initData and overwrite possible initData indicated in encrypted event (EME) | ||
if (protData && protData.hasOwnProperty('clearkeys')) { | ||
const initData = { kids: Object.keys(protData.clearkeys) }; | ||
if (keySystemInfo.protData && keySystemInfo.protData.hasOwnProperty('clearkeys')) { | ||
const initData = { kids: Object.keys(keySystemInfo.protData.clearkeys) }; | ||
keySystemInfo.initData = new TextEncoder().encode(JSON.stringify(initData)); | ||
} | ||
} | ||
|
||
// Reuse existing KeySession | ||
if (keySystemInfo.sessionId) { | ||
// Load MediaKeySession with sessionId | ||
loadKeySession(keySystemInfo.sessionId, keySystemInfo.initData); | ||
loadKeySession(keySystemInfo); | ||
} | ||
|
||
// Create a new KeySession | ||
else if (keySystemInfo.initData !== null) { | ||
// Create new MediaKeySession with initData | ||
createKeySession(keySystemInfo.initData, keySystemInfo.cdmData); | ||
createKeySession(keySystemInfo); | ||
} | ||
} | ||
|
||
/** | ||
* Loads a key session with the given session ID from persistent storage. This essentially creates a new key session | ||
* | ||
* @param {string} sessionID | ||
* @param {string} initData | ||
* @param {object} ksInfo | ||
* @memberof module:ProtectionController | ||
* @instance | ||
* @fires ProtectionController#KeySessionCreated | ||
* @ignore | ||
*/ | ||
function loadKeySession(sessionID, initData) { | ||
function loadKeySession(keySystemInfo) { | ||
checkConfig(); | ||
protectionModel.loadKeySession(sessionID, initData, _getSessionType(selectedKeySystem)); | ||
protectionModel.loadKeySession(keySystemInfo); | ||
} | ||
|
||
/** | ||
|
@@ -316,29 +319,32 @@ function ProtectionController(config) { | |
* @fires ProtectionController#KeySessionCreated | ||
* @ignore | ||
*/ | ||
function createKeySession(initData, cdmData) { | ||
const initDataForKS = CommonEncryption.getPSSHForKeySystem(selectedKeySystem, initData); | ||
const protData = _getProtDataForKeySystem(selectedKeySystem); | ||
function createKeySession(keySystemInfo) { | ||
const initDataForKS = CommonEncryption.getPSSHForKeySystem(selectedKeySystem, keySystemInfo ? keySystemInfo.initData : null); | ||
|
||
if (initDataForKS) { | ||
|
||
// Check for duplicate key id | ||
if (_isKeyIdDuplicate(keySystemInfo.keyId)) { | ||
return; | ||
} | ||
|
||
// Check for duplicate initData | ||
if (_isInitDataDuplicate(initDataForKS)) { | ||
dsilhavy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return; | ||
} | ||
|
||
try { | ||
const sessionType = _getSessionType(selectedKeySystem) | ||
protectionModel.createKeySession(initDataForKS, protData, sessionType, cdmData); | ||
keySystemInfo.initData = initDataForKS; | ||
protectionModel.createKeySession(keySystemInfo); | ||
} catch (error) { | ||
eventBus.trigger(events.KEY_SESSION_CREATED, { | ||
data: null, | ||
error: new DashJSError(ProtectionErrors.KEY_SESSION_CREATED_ERROR_CODE, ProtectionErrors.KEY_SESSION_CREATED_ERROR_MESSAGE + error.message) | ||
}); | ||
} | ||
} else if (initData) { | ||
const sessionType = _getSessionType(selectedKeySystem) | ||
protectionModel.createKeySession(initData, protData, sessionType, cdmData); | ||
} else if (keySystemInfo && keySystemInfo.initData) { | ||
protectionModel.createKeySession(keySystemInfo); | ||
} else { | ||
eventBus.trigger(events.KEY_SESSION_CREATED, { | ||
data: null, | ||
|
@@ -364,18 +370,6 @@ function ProtectionController(config) { | |
return null; | ||
} | ||
|
||
/** | ||
* Returns the session type either from the protData or as defined via setSessionType() | ||
* @param keySystem | ||
* @return {*} | ||
* @private | ||
*/ | ||
function _getSessionType(keySystem) { | ||
const protData = _getProtDataForKeySystem(keySystem); | ||
|
||
return (protData && protData.sessionType) ? protData.sessionType : sessionType; | ||
} | ||
|
||
/** | ||
* Removes all entries from the mediaInfoArr | ||
*/ | ||
|
@@ -400,7 +394,32 @@ function ProtectionController(config) { | |
*/ | ||
function getSupportedKeySystemsFromContentProtection(cps) { | ||
checkConfig(); | ||
return protectionKeyController.getSupportedKeySystemsFromContentProtection(cps); | ||
return protectionKeyController.getSupportedKeySystemsFromContentProtection(cps, protDataSet, sessionType); | ||
} | ||
|
||
/** | ||
* Checks if a session has already created for the provided key id | ||
* @param {string} keyId | ||
* @return {boolean} | ||
* @private | ||
*/ | ||
function _isKeyIdDuplicate(keyId) { | ||
|
||
if (!keyId) { | ||
return false; | ||
} | ||
|
||
try { | ||
const sessions = protectionModel.getSessions(); | ||
for (let i = 0; i < sessions.length; i++) { | ||
if (sessions[i].getKeyId() === keyId) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} catch (e) { | ||
return false; | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -595,13 +614,13 @@ function ProtectionController(config) { | |
* @return {KeySystemConfiguration} | ||
* @private | ||
*/ | ||
function _getKeySystemConfiguration(keySystem) { | ||
const protData = _getProtDataForKeySystem(keySystem); | ||
function _getKeySystemConfiguration(keySystemData) { | ||
const protData = keySystemData; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs to be changed to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
const audioCapabilities = []; | ||
const videoCapabilities = []; | ||
const audioRobustness = (protData && protData.audioRobustness && protData.audioRobustness.length > 0) ? protData.audioRobustness : robustnessLevel; | ||
const videoRobustness = (protData && protData.videoRobustness && protData.videoRobustness.length > 0) ? protData.videoRobustness : robustnessLevel; | ||
const ksSessionType = _getSessionType(keySystem); | ||
const ksSessionType = keySystemData.sessionType; | ||
const distinctiveIdentifier = (protData && protData.distinctiveIdentifier) ? protData.distinctiveIdentifier : 'optional'; | ||
const persistentState = (protData && protData.persistentState) ? protData.persistentState : (ksSessionType === 'temporary') ? 'optional' : 'required'; | ||
|
||
|
@@ -658,7 +677,7 @@ function ProtectionController(config) { | |
|
||
// Message not destined for license server | ||
if (!licenseServerModelInstance) { | ||
logger.debug('DRM: License server request not required for this message (type = ' + e.data.messageType + '). Session ID = ' + sessionToken.getSessionID()); | ||
logger.debug('DRM: License server request not required for this message (type = ' + e.data.messageType + '). Session ID = ' + sessionToken.getSessionId()); | ||
_sendLicenseRequestCompleteEvent(eventData); | ||
return; | ||
} | ||
|
@@ -770,7 +789,7 @@ function ProtectionController(config) { | |
const reqMethod = licenseServerData.getHTTPMethod(messageType); | ||
const responseType = licenseServerData.getResponseType(keySystemString, messageType); | ||
const timeout = protData && !isNaN(protData.httpTimeout) ? protData.httpTimeout : LICENSE_SERVER_REQUEST_DEFAULT_TIMEOUT; | ||
const sessionId = sessionToken.getSessionID() || null; | ||
const sessionId = sessionToken.getSessionId() || null; | ||
|
||
let licenseRequest = new LicenseRequest(url, reqMethod, responseType, reqHeaders, withCredentials, messageType, sessionId, reqPayload); | ||
const retryAttempts = !isNaN(settings.get().streaming.retryAttempts[HTTPRequest.LICENSE]) ? settings.get().streaming.retryAttempts[HTTPRequest.LICENSE] : LICENSE_SERVER_REQUEST_RETRIES; | ||
|
@@ -1021,7 +1040,7 @@ function ProtectionController(config) { | |
|
||
logger.debug('DRM: initData:', String.fromCharCode.apply(null, new Uint8Array(abInitData))); | ||
|
||
const supportedKS = protectionKeyController.getSupportedKeySystems(abInitData, protDataSet); | ||
const supportedKS = protectionKeyController.getSupportedKeySystems(abInitData, protDataSet, sessionType); | ||
if (supportedKS.length === 0) { | ||
logger.debug('DRM: Received needkey event with initData, but we don\'t support any of the key systems!'); | ||
return; | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can remove this, we have the keyId check above
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was an open question. Do we still need to check initData if we rely on key id?
I was especially thinking about contents that do not signal key id in manifest, i.e. for contents with in-band key rotation.
For example test stream "DRM (Modern) / "Multiperiod - Number + Timeline - Compact manifest - Thumbnails (1 track) - Encryption (1 key) PlayReady/Widevine (DRMtoday) - Key rotation (60s)"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest to handle streams without
default_kid
in the manifest as if the kid did not change.We then rely on the
kid
to change in the segments and handle this as part ofneedkey
handler in the ProtectionController. There we extract thekid
fromsgpd
and check if it has changed.