Skip to content

Commit

Permalink
feat: add serverCertificateUri in DRM advanced config (#3358)
Browse files Browse the repository at this point in the history
Fixes #1906
  • Loading branch information
valotvince authored May 3, 2021
1 parent d5f10bb commit 9b4502c
Show file tree
Hide file tree
Showing 14 changed files with 196 additions and 26 deletions.
16 changes: 9 additions & 7 deletions demo/common/asset.js
Original file line number Diff line number Diff line change
Expand Up @@ -358,24 +358,26 @@ const ShakaDemoAssetInfo = class {
getConfiguration() {
const config = /** @type {shaka.extern.PlayerConfiguration} */(
{drm: {advanced: {}}, manifest: {dash: {}}});

if (this.extraConfig) {
for (const key in this.extraConfig) {
config[key] = this.extraConfig[key];
}
}

if (this.licenseServers.size) {
config.drm.servers = {};
config.drm.servers = config.drm.servers || {};
this.licenseServers.forEach((value, key) => {
config.drm.servers[key] = value;
});
}

if (this.clearKeys.size) {
config.drm.clearKeys = {};
config.drm.clearKeys = config.drm.clearKeys || {};
this.clearKeys.forEach((value, key) => {
config.drm.clearKeys[key] = value;
});
}
if (this.extraConfig) {
for (const key in this.extraConfig) {
config[key] = this.extraConfig[key];
}
}
return config;
}

Expand Down
4 changes: 3 additions & 1 deletion demo/common/assets.js
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,9 @@ shakaAssets.testAssets = [
.addFeature(shakaAssets.Feature.SUBTITLES)
.addFeature(shakaAssets.Feature.WEBM)
.addFeature(shakaAssets.Feature.OFFLINE)
.addLicenseServer('com.widevine.alpha', 'https://cwip-shaka-proxy.appspot.com/no_auth'),
.addLicenseServer('com.widevine.alpha', 'https://cwip-shaka-proxy.appspot.com/no_auth')
.setExtraConfig({drm: {advanced: {'com.widevine.alpha': {serverCertificateUri:
'https://storage.googleapis.com/wvmedia/cert/cert_license_widevine_com_uat.bin'}}}}),
new ShakaDemoAssetInfo(
/* name= */ 'Sintel 4k (multicodec, Widevine, ads)',
/* iconUri= */ 'https://storage.googleapis.com/shaka-asset-icons/sintel.png',
Expand Down
1 change: 1 addition & 0 deletions demo/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -1728,6 +1728,7 @@ shakaDemo.Main = class {
audioRobustness: '',
sessionType: '',
serverCertificate: new Uint8Array(0),
serverCertificateUri: '',
individualizationServer: '',
};
}
Expand Down
5 changes: 5 additions & 0 deletions externs/shaka/manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ shaka.extern.InitDataOverride;
* audioRobustness: string,
* videoRobustness: string,
* serverCertificate: Uint8Array,
* serverCertificateUri: string,
* sessionType: string,
* initData: Array.<!shaka.extern.InitDataOverride>,
* keyIds: Set.<string>
Expand Down Expand Up @@ -150,6 +151,10 @@ shaka.extern.InitDataOverride;
* A key-system-specific server certificate used to encrypt license requests.
* Its use is optional and is meant as an optimization to avoid a round-trip
* to request a certificate.
* @property {string} serverCertificateUri
* <i>Defaults to '', e.g., server certificate will be requested from the
* given URI if serverCertificate is not provided. Can be filled in by
* advanced DRM config.</i>
* @property {Array.<!shaka.extern.InitDataOverride>} initData
* <i>Defaults to [], e.g., no override.</i> <br>
* A list of initialization data which override any initialization data found
Expand Down
5 changes: 5 additions & 0 deletions externs/shaka/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,7 @@ shaka.extern.EmsgInfo;
* videoRobustness: string,
* audioRobustness: string,
* serverCertificate: Uint8Array,
* serverCertificateUri: string,
* individualizationServer: string,
* sessionType: string
* }}
Expand Down Expand Up @@ -545,6 +546,10 @@ shaka.extern.EmsgInfo;
* A key-system-specific server certificate used to encrypt license requests.
* Its use is optional and is meant as an optimization to avoid a round-trip
* to request a certificate.
* @property {string} serverCertificateUri
* <i>Defaults to <code>''</code>.</i><br>
* If given, will make a request to the given URI to get the server
* certificate. This is ignored if <code>serverCertificate</code> is set.
* @property {string} individualizationServer
* The server that handles an <code>'individualiation-request'</code>. If the
* server isn't given, it will default to the license server.
Expand Down
109 changes: 91 additions & 18 deletions lib/media/drm_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ shaka.media.DrmEngine = class {
audioRobustness: '', // Not required by queryMediaKeys_
videoRobustness: '', // Same
serverCertificate: serverCertificate,
serverCertificateUri: '',
initData: null,
keyIds: null,
}];
Expand Down Expand Up @@ -452,25 +453,62 @@ shaka.media.DrmEngine = class {
goog.asserts.assert(this.initialized_,
'Must call init() before setServerCertificate');

if (this.mediaKeys_ &&
this.currentDrmInfo_ &&
this.currentDrmInfo_.serverCertificate &&
this.currentDrmInfo_.serverCertificate.length) {
if (!this.mediaKeys_ || !this.currentDrmInfo_) {
return;
}

if (this.currentDrmInfo_.serverCertificateUri &&
(!this.currentDrmInfo_.serverCertificate ||
!this.currentDrmInfo_.serverCertificate.length)) {
const request = shaka.net.NetworkingEngine.makeRequest(
[this.currentDrmInfo_.serverCertificateUri],
this.config_.retryParameters);

try {
const supported = await this.mediaKeys_.setServerCertificate(
this.currentDrmInfo_.serverCertificate);
if (!supported) {
shaka.log.warning('Server certificates are not supported by the ' +
'key system. The server certificate has been ' +
'ignored.');
}
} catch (exception) {
const operation = this.playerInterface_.netEngine.request(
shaka.net.NetworkingEngine.RequestType.SERVER_CERTIFICATE,
request);
const response = await operation.promise;

this.currentDrmInfo_.serverCertificate =
shaka.util.BufferUtils.toUint8(response.data);
} catch (error) {
// Request failed!
goog.asserts.assert(error instanceof shaka.util.Error,
'Wrong NetworkingEngine error type!');

throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.DRM,
shaka.util.Error.Code.INVALID_SERVER_CERTIFICATE,
exception.message);
shaka.util.Error.Code.SERVER_CERTIFICATE_REQUEST_FAILED,
error);
}

if (this.destroyer_.destroyed()) {
return;
}
}

if (!this.currentDrmInfo_.serverCertificate ||
!this.currentDrmInfo_.serverCertificate.length) {
return;
}

try {
const supported = await this.mediaKeys_.setServerCertificate(
this.currentDrmInfo_.serverCertificate);

if (!supported) {
shaka.log.warning('Server certificates are not supported by the ' +
'key system. The server certificate has been ' +
'ignored.');
}
} catch (exception) {
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.DRM,
shaka.util.Error.Code.INVALID_SERVER_CERTIFICATE,
exception.message);
}
}

Expand Down Expand Up @@ -1118,6 +1156,7 @@ shaka.media.DrmEngine = class {
audioRobustness: '',
videoRobustness: '',
serverCertificate: null,
serverCertificateUri: '',
sessionType: '',
initData: initDatas,
keyIds: new Set(keyIds),
Expand Down Expand Up @@ -1893,6 +1932,8 @@ shaka.media.DrmEngine = class {
videoRobustness: drm1.videoRobustness || drm2.videoRobustness,
audioRobustness: drm1.audioRobustness || drm2.audioRobustness,
serverCertificate: drm1.serverCertificate || drm2.serverCertificate,
serverCertificateUri: drm1.serverCertificateUri ||
drm2.serverCertificateUri,
initData,
keyIds,
};
Expand Down Expand Up @@ -1967,6 +2008,7 @@ shaka.media.DrmEngine = class {
audioRobustness: '',
videoRobustness: '',
serverCertificate: null,
serverCertificateUri: '',
initData: [],
keyIds: new Set(),
});
Expand Down Expand Up @@ -1997,6 +2039,9 @@ shaka.media.DrmEngine = class {
/** @type {!Array.<string>} */
const licenseServers = [];

/** @type {!Array.<string>} */
const serverCertificateUris = [];

/** @type {!Array.<!Uint8Array>} */
const serverCerts = [];

Expand All @@ -2006,8 +2051,9 @@ shaka.media.DrmEngine = class {
/** @type {!Set.<string>} */
const keyIds = new Set();

shaka.media.DrmEngine.processDrmInfos_(drmInfos, licenseServers,
serverCerts, initDatas, keyIds);
shaka.media.DrmEngine.processDrmInfos_(
drmInfos, licenseServers, serverCerts,
serverCertificateUris, initDatas, keyIds);

if (serverCerts.length > 1) {
shaka.log.warning('Multiple unique server certificates found! ' +
Expand All @@ -2019,6 +2065,11 @@ shaka.media.DrmEngine = class {
'Only the first will be used.');
}

if (serverCertificateUris.length > 1) {
shaka.log.warning('Multiple unique server certificate URIs found! ' +
'Only the first will be used.');
}

const defaultSessionType =
this.usePersistentLicenses_ ? 'persistent-license' : 'temporary';

Expand All @@ -2032,6 +2083,7 @@ shaka.media.DrmEngine = class {
audioRobustness: drmInfos[0].audioRobustness || '',
videoRobustness: drmInfos[0].videoRobustness || '',
serverCertificate: serverCerts[0],
serverCertificateUri: serverCertificateUris[0],
initData: initDatas,
keyIds,
};
Expand Down Expand Up @@ -2063,6 +2115,9 @@ shaka.media.DrmEngine = class {
/** @type {!Array.<string>} */
const licenseServers = [];

/** @type {!Array.<string>} */
const serverCertificateUris = [];

/** @type {!Array.<!Uint8Array>} */
const serverCerts = [];

Expand All @@ -2074,13 +2129,19 @@ shaka.media.DrmEngine = class {

// TODO: refactor, don't stick drmInfos onto MediaKeySystemConfiguration
shaka.media.DrmEngine.processDrmInfos_(
config['drmInfos'], licenseServers, serverCerts, initDatas, keyIds);
config['drmInfos'], licenseServers, serverCerts,
serverCertificateUris, initDatas, keyIds);

if (serverCerts.length > 1) {
shaka.log.warning('Multiple unique server certificates found! ' +
'Only the first will be used.');
}

if (serverCertificateUris.length > 1) {
shaka.log.warning('Multiple unique server certificate URIs found! ' +
'Only the first will be used.');
}

if (licenseServers.length > 1) {
shaka.log.warning('Multiple unique license server URIs found! ' +
'Only the first will be used.');
Expand All @@ -2102,6 +2163,7 @@ shaka.media.DrmEngine = class {
audioRobustness: audioRobustness || '',
videoRobustness: videoRobustness || '',
serverCertificate: serverCerts[0],
serverCertificateUri: serverCertificateUris[0],
initData: initDatas,
keyIds,
};
Expand All @@ -2114,12 +2176,14 @@ shaka.media.DrmEngine = class {
* @param {!Array.<shaka.extern.DrmInfo>} drmInfos
* @param {!Array.<string>} licenseServers
* @param {!Array.<!Uint8Array>} serverCerts
* @param {!Array.<string>} serverCertificateUris
* @param {!Array.<!shaka.extern.InitDataOverride>} initDatas
* @param {!Set.<string>} keyIds
* @private
*/
static processDrmInfos_(
drmInfos, licenseServers, serverCerts, initDatas, keyIds) {
drmInfos, licenseServers, serverCerts,
serverCertificateUris, initDatas, keyIds) {
/** @type {function(shaka.extern.InitDataOverride,
* shaka.extern.InitDataOverride):boolean} */
const initDataOverrideEqual = (a, b) => {
Expand All @@ -2138,6 +2202,11 @@ shaka.media.DrmEngine = class {
licenseServers.push(drmInfo.licenseServerUri);
}

// Build an array of unique license servers.
if (!serverCertificateUris.includes(drmInfo.serverCertificateUri)) {
serverCertificateUris.push(drmInfo.serverCertificateUri);
}

// Build an array of unique server certs.
if (drmInfo.serverCertificate) {
const found = serverCerts.some(
Expand Down Expand Up @@ -2250,6 +2319,10 @@ shaka.media.DrmEngine = class {
if (advancedConfig.sessionType) {
drmInfo.sessionType = advancedConfig.sessionType;
}

if (!drmInfo.serverCertificateUri) {
drmInfo.serverCertificateUri = advancedConfig.serverCertificateUri;
}
}

// Chromecast has a variant of PlayReady that uses a different key
Expand Down
1 change: 1 addition & 0 deletions lib/net/networking_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,7 @@ shaka.net.NetworkingEngine.RequestType = {
'LICENSE': 2,
'APP': 3,
'TIMING': 4,
'SERVER_CERTIFICATE': 5,
};


Expand Down
6 changes: 6 additions & 0 deletions lib/util/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,12 @@ shaka.util.Error.Code = {
*/
'INIT_DATA_TRANSFORM_ERROR': 6016,

/**
* The server certificate request failed.
* <br> error.data[0] is a shaka.util.Error from the networking engine.
*/
'SERVER_CERTIFICATE_REQUEST_FAILED': 6017,


/**
* The call to Player.load() was interrupted by a call to Player.unload()
Expand Down
1 change: 1 addition & 0 deletions lib/util/manifest_parser_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ shaka.util.ManifestParserUtils = class {
audioRobustness: '',
videoRobustness: '',
serverCertificate: null,
serverCertificateUri: '',
sessionType: '',
initData: initData || [],
keyIds: new Set(),
Expand Down
1 change: 1 addition & 0 deletions lib/util/player_configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ shaka.util.PlayerConfiguration = class {
audioRobustness: '',
sessionType: '',
serverCertificate: new Uint8Array(0),
serverCertificateUri: '',
individualizationServer: '',
},
};
Expand Down
Loading

0 comments on commit 9b4502c

Please sign in to comment.