Skip to content

Commit

Permalink
feat(HLS): Support for HLS key rotation (#4568)
Browse files Browse the repository at this point in the history
Fixes #741
  • Loading branch information
varvaruc authored Nov 8, 2022
1 parent 8f698c6 commit 3846eea
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 49 deletions.
122 changes: 73 additions & 49 deletions lib/hls/hls_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,19 @@ shaka.hls.HlsParser = class {

const mediaSequenceToStartTime =
this.getMediaSequenceToStartTimeFor_(streamInfo);
const {keyIds, drmInfos} = this.parseDrmInfo_(playlist, stream.mimeType);

const keysAreEqual =
(a, b) => a.size === b.size && [...a].every((value) => b.has(value));

if (!keysAreEqual(stream.keyIds, keyIds)) {
stream.keyIds = keyIds;
stream.drmInfos = drmInfos;
if (this.manifest_) {
this.playerInterface_.filter(this.manifest_);
}
}

const segments = this.createSegments_(
streamInfo.verbatimMediaPlaylistUri, playlist, stream.type,
stream.mimeType, mediaSequenceToStartTime, mediaVariables);
Expand Down Expand Up @@ -1851,55 +1864,8 @@ shaka.hls.HlsParser = class {
mediaVariables);
}

/** @type {!Array.<!shaka.hls.Tag>} */
const drmTags = [];
if (playlist.segments) {
for (const segment of playlist.segments) {
const segmentKeyTags = shaka.hls.Utils.filterTagsByName(segment.tags,
'EXT-X-KEY');
drmTags.push(...segmentKeyTags);
}
}

let encrypted = false;
let aesEncrypted = false;

/** @type {!Array.<shaka.extern.DrmInfo>}*/
const drmInfos = [];
const keyIds = new Set();

// TODO: May still need changes to support key rotation.
for (const drmTag of drmTags) {
const method = drmTag.getRequiredAttrValue('METHOD');
if (method != 'NONE') {
encrypted = true;

if (method == 'AES-128') {
// These keys are handled separately.
aesEncrypted = true;
} else {
// According to the HLS spec, KEYFORMAT is optional and implicitly
// defaults to "identity".
// https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-11#section-4.4.4.4
const keyFormat =
drmTag.getAttributeValue('KEYFORMAT') || 'identity';
const drmParser =
shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_[keyFormat];

const drmInfo = drmParser ? drmParser(drmTag, mimeType) : null;
if (drmInfo) {
if (drmInfo.keyIds) {
for (const keyId of drmInfo.keyIds) {
keyIds.add(keyId);
}
}
drmInfos.push(drmInfo);
} else {
shaka.log.warning('Unsupported HLS KEYFORMAT', keyFormat);
}
}
}
}
const {drmInfos, keyIds, encrypted, aesEncrypted} =
this.parseDrmInfo_(playlist, mimeType);

if (encrypted && !drmInfos.length && !aesEncrypted) {
throw new shaka.util.Error(
Expand Down Expand Up @@ -2016,6 +1982,64 @@ shaka.hls.HlsParser = class {
};
}

/**
* @param {!shaka.hls.Playlist} playlist
* @param {string} mimeType
* @private
*/
parseDrmInfo_(playlist, mimeType) {
/** @type {!Array.<!shaka.hls.Tag>} */
const drmTags = [];
if (playlist.segments) {
for (const segment of playlist.segments) {
const segmentKeyTags = shaka.hls.Utils.filterTagsByName(segment.tags,
'EXT-X-KEY');
drmTags.push(...segmentKeyTags);
}
}

let encrypted = false;
let aesEncrypted = false;

/** @type {!Array.<shaka.extern.DrmInfo>}*/
const drmInfos = [];
const keyIds = new Set();

// TODO: May still need changes to support key rotation.
for (const drmTag of drmTags) {
const method = drmTag.getRequiredAttrValue('METHOD');
if (method != 'NONE') {
encrypted = true;

if (method == 'AES-128') {
// These keys are handled separately.
aesEncrypted = true;
} else {
// According to the HLS spec, KEYFORMAT is optional and implicitly
// defaults to "identity".
// https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-11#section-4.4.4.4
const keyFormat =
drmTag.getAttributeValue('KEYFORMAT') || 'identity';
const drmParser =
shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_[keyFormat];

const drmInfo = drmParser ? drmParser(drmTag, mimeType) : null;
if (drmInfo) {
if (drmInfo.keyIds) {
for (const keyId of drmInfo.keyIds) {
keyIds.add(keyId);
}
}
drmInfos.push(drmInfo);
} else {
shaka.log.warning('Unsupported HLS KEYFORMAT', keyFormat);
}
}
}
}

return {drmInfos, keyIds, encrypted, aesEncrypted};
}

/**
* @param {!shaka.hls.Tag} drmTag
Expand Down
39 changes: 39 additions & 0 deletions test/hls/hls_live_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,45 @@ describe('HlsParser live', () => {
await testUpdate(
manifest, mediaWithSkippedSegments2, [ref1, ref2, ref3, ref4]);
});

it('updates encryption keys', async () => {
const initialKey = 'abc123';
const media = [
'#EXTM3U\n',
'#EXT-X-TARGETDURATION:6\n',
'#EXT-X-PLAYLIST-TYPE:EVENT\n',
'#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,',
'KEYID=0X' + initialKey + ',',
'KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed",',
'URI="data:text/plain;base64,',
'dGhpcyBpbml0IGRhdGEgY29udGFpbnMgaGlkZGVuIHNlY3JldHMhISE', '",\n',
'#EXT-X-MAP:URI="init.mp4"\n',
'#EXTINF:5,\n',
'#EXT-X-BYTERANGE:121090@616\n',
'main.mp4',
].join('');

const updatedKey = 'xyz345';
const updatedMedia = [
'#EXTM3U\n',
'#EXT-X-TARGETDURATION:6\n',
'#EXT-X-PLAYLIST-TYPE:EVENT\n',
'#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,',
'KEYID=0X' + updatedKey + ',',
'KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed",',
'URI="data:text/plain;base64,',
'dGhpcyBpbml0IGRhdGEgY29udGFpbnMgaGlkZGVuIHNlY3JldHMhISE', '",\n',
'#EXT-X-MAP:URI="init.mp4"\n',
'#EXTINF:5,\n',
'#EXT-X-BYTERANGE:121090@616\n',
'main.mp4',
].join('');

const manifest = await testInitialManifest(master, media, null);
await testUpdate(manifest, updatedMedia, null);
const keys = Array.from(manifest.variants[0].video.keyIds);
expect(keys[0]).toBe(updatedKey);
});
}); // describe('update')

describe('createSegmentIndex', () => {
Expand Down

0 comments on commit 3846eea

Please sign in to comment.