Back


Widevine (Part 5) - Provisioning

Posted by Amir Mazzarella on October 24, 2019


Let’s talk about provisioning! Like I said in the last blog post, it’s a relationship between the client and server to get the keypair and client ID needed to play Widevine videos. Alright, let’s cut the crap and get to it.

Provisioning URL: https://www.googleapis.com/certificateprovisioning/v1/devicecertificates/create?key=AIzaSyB-5OLKTx2iU5mko18DfdwK5611JIjbUhE.

I found the provisioning URL by combing through the Android Widevine library, which has its symbols preserved for some reason. You could also make traffic captures of the device when it is provisioning. If you do, you’ll notice the provisioning request is just a POST request of a json with {“signedData”: PROVISIONING_PROTOBUF}. The PROVISIONING_PROTOBUF part is a URL-safe base64 encoded provisioning request. The structure for the signed provisioning request is in the proto we found in Part 3. Sample provisioning requests are floating all over Google, so here’s a Python function to construct a provisioning request, assuming your protobuf is imported as “license_protocol_pb2.”:

def build_provisioning_req(token):
    prov_req_pb = license_protocol_pb2.SignedProvisioningRequest()
    prov_req_pb.message.client_id.type = 0
    prov_req_pb.message.client_id.token = TOKEN
    prov_req_pb.message.nonce = get_random_bytes(4)
    prov_req_pb.message.options.certificate_type = 0
    prov_req_pb.message.options.certificate_authority = ''
    prov_req_pb.signature = sign_provisioning_req(AES_KEY, prov_req_pb.message)

    return prov_req_pb

It seems pretty simple. The token is just the ID part of the keybox struct, AES_KEY is the Key part of the struct, and get_random_bytes is just a function to get some random bytes. But what about that sign function? Let’s see how that looks:

def sign_provisioning_req(aes_key, prov_req):
    auth_key_base = b''.join([
        b'AUTHENTICATION\x00',
        prov_req.SerializeToString(),
        b'\x00\x00\x02\x00'
    ])

    auth_key_3 = b'\x03' + auth_key_base
    auth_key_4 = b'\x04' + auth_key_base

    cmac_obj = CMAC.new(aes_key, ciphermod=AES)
    cmac_obj.update(auth_key_3)
    auth_cmac_key_3 = cmac_obj.digest()

    cmac_obj = CMAC.new(aes_key, ciphermod=AES)
    cmac_obj.update(auth_key_4)
    auth_cmac_key_4 = cmac_obj.digest()

    auth_cmac_combined_2 = auth_cmac_key_3 + auth_cmac_key_4

    license_hmac = HMAC.new(auth_cmac_combined_2, digestmod=SHA256)
    license_hmac.update(prov_req.SerializeToString())

    return license_hmac.digest()

Woah, where did all this come from? Well, it involved a ton of reverse-engineering the signing function from the Android Widevine library. Instead of taking you through the details, that’s what it looks like in beautiful Python.

So now you have a provisioning request, you send it to the server, and you get back a big protobuf. Guess what, only half of it is encrypted. The client ID is in plaintext, but the private key is encrypted. But it’s not hard to decrypt at all. Parse the protobuf for the encrypted private key and use this function to decrypt:

def decrypt(prov_resp, prov_req_pb_msg):
    prov_resp_pb = license_protocol_pb2.SignedProvisioningResponse()
    prov_resp_pb.ParseFromString(prov_resp)

    enc_key_base = b''.join([
        b'ENCRYPTION\x00',
        prov_req_pb_msg.SerializeToString(),
        b'\x00\x00\x00\x80'
    ])

    enc_key = b'\x01' + enc_key_base

    cmac_obj = CMAC.new(AES_KEY, ciphermod=AES)
    cmac_obj.update(enc_key)
    enc_cmac_key = cmac_obj.digest()

    encrypted_device_key = prov_resp_pb.message.device_rsa_key
    device_key_iv = prov_resp_pb.message.device_rsa_key_iv

    cipher = AES.new(enc_cmac_key, AES.MODE_CBC, iv=device_key_iv)
    device_key = Padding.unpad(cipher.decrypt(encrypted_device_key), 16)

    return device_key

Congratulations! You know have the client ID and device key for a Level 1 device! You can use these to actually construct your own license requests and decrypt Widevine licenses for content keys! I’ll go through that in more detail in the next blog post. Thanks for reading!


   WIDEVINE    DRM    PROVISIONING   

 Share on: Twitter / Facebook / Google+ / Email