androidx / media

Jetpack Media3 support libraries for media use cases, including ExoPlayer, an extensible media player for Android
https://developer.android.com/media/media3
Apache License 2.0
1.69k stars 405 forks source link

Packaging Widevine for key rotation #1048

Closed stevemayhew closed 1 week ago

stevemayhew commented 9 months ago

Using the Shaka Packager I packaged a 10min mp4 source file to DASH and HLS Widevine.

The packager command is:

    --v 10 \
    --stderrthreshold 0 \
    --enable_widevine_encryption \
    --key_server_url https://license.uat.widevine.com/cenc/getcontentkey/widevine_test \
    --signer widevine_test \
    --aes_signing_key 1ae8ccd0e7985cc0b6203a55855a1034afc252980e970ca90e5202689f947ab9 \
    --aes_signing_iv d58ce954203b7c9a9a9d467f59839249 \
    --clear_lead 0 \
    --content_id 89569042 \
    --crypto_period_duration 40 \
    --protection_scheme cenc \
    --segment_duration 6 \
    --hls_master_playlist_output index.m3u8 \
    --mpd_output index.mpd \
    --generate_static_live_mpd \
    input=../Summers.mp4,stream=audio,segment_template='audio-$Number%03d$.mp4',playlist_name='audio.m3u8',init_segment='audio-init.mp4' \
    input=../Summers.mp4,stream=video,segment_template='video-$Number%03d$.mp4',playlist_name='video.m3u8',init_segment='video-init.mp4'

Packager generates a series of widevine PSSH Boxes with the content ID and a rotation index, e.g.

PSSH Box v0
  System ID: Widevine edef8ba9-79d6-4ace-a3c8-27dcd51d21ed
  PSSH Data (size: 16):
    Widevine Data:
      Content ID: 89569042
      Crypto Period Index: 14
      Protection Scheme: b'cenc'

As well as a a second box that has the list of KeyID's:

PSSH Box v1
  System ID: Common 1077efec-c0b2-4d02-ace3-3c1e52e2fb4b
  Key IDs (50):
    db39b2a1-e3bc-5695-90f4-9ed204550465
    ...

These are in the moof for the segments, there is no PSSH in the MPD or the init segment.

When ExoPlayer plays the content it makes a license request for every rotation.

My question is simple, isn't there a way to code the PSSH so ExoPlayer will make a single request and get all the keys?

icbaker commented 9 months ago

It's possible I'm misunderstanding, but wouldn't you use key rotation to allow more easily revoking access to content (by denying the next key request, and then playback is forced to stop) - in which case doesn't allowing a player to request all the keys used in the rotation up-front defeat this? In this set-up it seems there's no advantage to the rotation compared to using a single key.

google-oss-bot commented 8 months ago

Hey @stevemayhew. We need more information to resolve this issue but there hasn't been an update in 14 weekdays. I'm marking the issue as stale and if there are no new updates in the next 7 days I will close it automatically.

If you have more information that will help us get to the bottom of this, just add a comment!

stevemayhew commented 8 months ago

It's possible I'm misunderstanding, but wouldn't you use key rotation to allow more easily revoking access to content (by denying the next key request, and then playback is forced to stop)

There are two concepts:

  1. license expiration — decribed in the DASH-IF, so revoking is performed by this mechanism
  2. key rotation — periodically change the actual encryption key to mitigate cost of a key compromi
  • in which case doesn't allowing a player to request all the keys used in the rotation up-front defeat this? In this set-up it seems there's no advantage to the rotation compared to using a single key.

Assume you have a revocation requirement of within 24 hours, but want to rotate encryption keys every 30mins. This mechanism provides the 48 required keys in one license request / response

This capability is implied in the CWIP docs (I will not go into details for obvious reasons).

I think packager is generating the correct encryption sequence for this, but either the metadata (CMAF and MPD/HLS Playlist) or how ExoPlayer is using the metadata is preventing a request the license with all the keys.

  1. the KID list is in the common PSSH box in each video/audio segment
  2. the Widevine PSSH box in the CMAF is a version 0 PSSH, it has only a content id in it.

If you look at the HLS metadata, the Widevine PSSH boxes all have the same content ID but only differ in the crypto period index, example:

#EXTM3U
#EXT-X-VERSION:6
## Generated with https://github.com/shaka-project/shaka-packager version 3e71302ba4-debug
#EXT-X-TARGETDURATION:7
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-MAP:URI="video-init.mp4"
#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="data:text/plain;base64,AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAiBIlWkEI4AEjj3JWbBlAo",KEYID=0xbd5fe65fa05650f0977eaf967d80b80f,KEYFORMATVERSIONS="1",KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"
#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="data:text/plain;base64,vV/mX6BWUPCXfq+WfYC4Dw==",KEYFORMAT="identity"
#EXTINF:6.006,
video-001.mp4
#EXTINF:6.006,
video-002.mp4
#EXTINF:6.006,
video-003.mp4
#EXTINF:6.006,
video-004.mp4
#EXTINF:6.006,
video-005.mp4
#EXTINF:6.006,
video-006.mp4
#EXTINF:6.006,
video-007.mp4
#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="data:text/plain;base64,AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAiBIlWkEI4AUjj3JWbBlAo",KEYID=0x0ce7a2ffd1795cb49571a8ccacc8dfbc,KEYFORMATVERSIONS="1",KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"
#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="data:text/plain;base64,DOei/9F5XLSVcajMrMjfvA==",KEYFORMAT="identity"

So, seems the only way this would work is If the ExoPlayer DrmSessionManager somehow:

  1. issued a license request that somehow included the common encryption PSSH metadata with the list of keys to fetch
  2. decoded the PSSH boxes and noticed the only change in the EXT-X-KEY is the crypto period that was already in the license it had.

I'll look at Shaka-player a bit and see how (if) it does this.

stevemayhew commented 8 months ago

This requires either an ExoPlayer change or a packager change to work correctly.

Currently ExoPlayer uses the entire PSSH as a "key" for caching sessions from the Widevine License Server, this will not work correctly if the KID is already fetched by the initial license request and the rotation event only indicates the period has changed.

This is easy to verify:

  1. Add the switch --mp4_include_pssh_in_stream=false to the packager parameters above. With this, the actual rotated KID's are in the CencSampleEncryptionInformationGroupEntry for each rotation and the PSSH is only in the m3u8.
  2. Edit the m3u8 to make all the PSSH boxes identical (so ExoPlayer only fetches the license on the first rotation)

With this, the content plays perfectly and the DefaultDrmSessionManager only contacts the license server once.

There are two ways to fix this:

  1. Fix ExoPlayer to identify a cached rotation enabled DrmSession based on the contentId, first period and count of included periods.
  2. Packager does not generate EXT-X-KEY metadata unless a fresh license request is needed.
stevemayhew commented 1 week ago

I've answered my own question. @icbaker The answer is in the CWIP partner documentation "Modular DRM Key Rotation" document.

Basically, if coded correctly, it is possible for the PSSH to request multiple keys for a rotation period. This allows the content key to change without a new PSSH. Each disparate KID would be presented in the moof/traf/sgpd for the segment, each segment could have a unique key if one wanted to go this far.

The PSSH change would trigger re-authentication and authorization for the content.

This is also roughly described in the DASH-IF IOP in section "9.3 In-band key rotation signalling". Shaka Packager does not (at least I could not figure out how) produce this encoding, but if it did ExoPlayer would support it perfectly.