microsoft / media-foundation

Repository for Windows Media Foundation related tools and samples
MIT License
145 stars 31 forks source link

License persistent in IMFContentDecryptionModuleSession::Update #41

Closed cjw1115 closed 2 years ago

cjw1115 commented 2 years ago

I created a MF_MEDIAKEYSESSION_TYPE_TEMPORARY session, but I get a persistent PlayReady license after genereateRequest. Then I try to call IMFContentDecryptionModuleSession::Update, it will told me session type are not match.

Here is data format pass into IMFContentDecryptionModuleSession::Update(),

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Body>
        <AcquireLicenseResponse xmlns="http://schemas.microsoft.com/DRM/2007/03/protocols">
            <AcquireLicenseResult>
                <Response xmlns="http://schemas.microsoft.com/DRM/2007/03/protocols/messages">
                    <LicenseResponse xmlns="http://schemas.microsoft.com/DRM/2007/03/protocols">
                        <Version>1</Version>
                        <Licenses>
                            <License>{{base64 XMR data}}</License>
                        </Licenses>
                        <Acknowledgement>
                            <TransactionID>{{id}}</TransactionID>
                        </Acknowledgement>
                    </LicenseResponse>
                </Response>
            </AcquireLicenseResult>
        </AcquireLicenseResponse>
    </soap:Body>
</soap:Envelope>

the major license info is a base64 encoded. And it used XMR format. I didn't found any standard or document about XMR format. I can only get brief structure.

My question is how can I know the license is persistent or just temporary?

Because I want to create two sessions, one is temporary, and another one is persistent.

If I can know the response license type, then I can use correct one session to call IMFContentDecryptionModuleSession::Update

Thanks!

cjw1115 commented 2 years ago

Hi @swenkeratmicrosoft , could you help me about this?

I have a PlayReady DASH content and it hosted on my website(by default, it will be a persistent license), Edge browser can play it. But when I use my modified chromium: If I hardcode CDM session to Persistent, it will work; If I hardcode CDM session to Temporary it will fail with error code 0x80704005.

So I guess Edge did something internally to handle it. That's why I ask this quesiton.

Thanks!

swenkeratmicrosoft commented 2 years ago

Hi @cjw1115,

Edge doesn't do anything special to handle this scenario. It is the expectation and design of the EME interface that it's the responsibility of the caller to know, preemptively (before it calls generateRequest), whether the server is going to respond with a temporary or persistent license.

Furthermore, you can't create a license acquisition challenge in a persistent session and then process the response in a temporary session. The call to update must be on the same session as generateRequest.

In order to do what you're suggesting (determining the license type from the response), you'd not only have to create two sessions (one temporary and one persistent), you'd also need to call generateRequest on both, send two separate license acquisition requests to the server, get two responses, then throw away one of the responses. Since production-class license acquisition servers generally prevent you from acquiring unnecessary licenses, they will likely reject one of those requests, and if the other request's response doesn't have a license that matches the session from which its request was created, you're back where you started. In essence, creating two sessions doesn't realistically help you.

You shouldn't be hard-coding the CDM session to anything. You should be taking it in as input from the website/application, and the latter must be implemented to pass the right value based on what type of license it knows the server is going to return. If the website/application doesn't know that information a-priori, then that's a design flaw in the caller - they are failing to use the EME interfaces as designed.

Does that make sense? I hope that's helpful, but feel free to ask for further clarification. Happy to help.

-Sam Wenker Microsoft PlayReady Architect

cjw1115 commented 2 years ago

Hi @swenkeratmicrosoft, Thanks for your quick reply.

Yes, to set sessionType is responsibility of caller. Generally, browser just need to follow web app's settings in requestMediaKeySystem.
The strange thing is that, the same web app, same configurations to browser by EME interface. Edge works! That's why I guess Edge may have special processing;

For second point, I did what you said earlier, create two sessions together, and create to generateRequest together. For update, code is here,.

virtual HRESULT STDMETHODCALLTYPE Update(
        /* [size_is][in] */ __RPC__in_ecount_full(responseSize) const BYTE* response,
        /* [in] */ DWORD responseSize)
    {
        HRESULT hr = m_majorSession->Update(response, responseSize);
        if (hr == (HRESULT)0x80704005)
        {
            hr = m_backupSession->Update(response, responseSize);
            if (hr == S_OK)
            {
                m_useBackup = true;
                m_sessionCallbacks->KeyStatusChanged();
            }
        }
        return hr;
    }

but it not totally work. After m_majorSession->Update, if it failed, MediaEngineProtectionManager::EndEnableContent will be called and MF_MEDIA_ENGINE_EVENT_ERROR event be notified.

That why I ask how to distinguish license persistent type, if license persistent type is temporary, I will just call temporary session's update() interface, and drop persistent session, vice-versa.

BTW, I found there are some PlayReady drmxmr.h definition on github. I tried to use them to parse license response. but looks thay are not match. For XMR, Does this a public standard or Microsoft internal? can I got some API or docs?

Thanks!

swenkeratmicrosoft commented 2 years ago

Hi @cjw1115,

Why are you creating two sessions in the first place? Edge absolutely doesn't. Or is your website doing so?

-Sam Wenker Microsoft PlayReady Architect

cjw1115 commented 2 years ago

Since I'm not very familiar chromium's code, so I want to create them in same place. Make things easier.

May be I should try to create them one by one, if first one update failed, I should reinitialize it and create another session.

swenkeratmicrosoft commented 2 years ago

Hi @cjw1115,

I'm not sure what you mean by "create them in the same place".

When the website calls createSession, you should create a single session of the same type as the website requested.

Or am I missing something?

-Sam Wenker Microsoft PlayReady Architect

cjw1115 commented 2 years ago

Hi @cjw1115,

I'm not sure what you mean by "".

When the website calls createSession, you should create a single session of the same type as the website requested.

Or am I missing something?

-Sam Wenker

Microsoft PlayReady Architect

Yes, chromium will create session on MF CDM with the website requested session type, if website ignore the session type,it will be temporary by default.

When chromium create session on CDM,

  1. I add one line to create a backup session with opposite session type.
  2. When chromium call generateRequest on session, I also add one line to call it on backup session.

This is meaning of "create them in the same place", it's not too much elegant😀

swenkeratmicrosoft commented 2 years ago

Hi @cjw1115,

My question is: Why are you doing step 1 at all? What is the purpose of creating a backup session?

-Sam Wenker Microsoft PlayReady Architect

cjw1115 commented 2 years ago

Hi @swenkeratmicrosoft,

My purpose is: I want to play PlayReady encrypted content even if web app didn't set a session type explicitly.

I created a web app use DASH JS to play a PlayReady content, and I didn't set sessionType in navigator.requestMediaKeySystemAccess, DASH JS set it with a default Temporary session type. but my content's license is persistent license by default.

Then Edge can play it successfully. It's strange, a persistent license with temporary session type, expected result should be playback failure. so I have to guess Edge have some special processing here.

For this purpose, I have one walkaround.

  1. Create a Temporary CDM session according web app's request(Major one);
  2. Create a persistent CDM session as backup;
  3. generateRequest for major and backup CDM sessions;
  4. update() major session with license response from step 3.
    • if it fail with HRESULT 0x80704005, I will try to update() backup session. then drop major one.
    • if it successes, then drop backup one.
  5. call SetContentProtectionManager on media engine protectedMediaEngine->SetContentProtectionManager(m_protectionManager.get());
  6. If one session update() in step 4 succeed, content will be auto enable.

For step 5, I found if I call it before update session with license(step 4). the BeginEnableContent will be called. then if update() of major session failed in step 4, the 'EndEnableContent' will be called. and an error notified. I have no chance to call update() of backup session.

That's why I ask if there are some API or specs can detect license persistent type from a base64 string of XMR.

Thanks!

swenkeratmicrosoft commented 2 years ago

Hi @cjw1115,

Ok, now I understand what you're saying.

The EME spec/APIs are designed to not allow you to playback with a persistent license with temporary session type.

Per specification https://www.w3.org/TR/encrypted-media/#dom-mediakeysession-update

Refer to the section starting with "Process sanitized response, following the stipulation for the first matching condition from the following list:". The spec explicitly says that update is supposed to fail with TypeError in this case.

If you implement update to allow this as you describe, you are in direct violation of the EME specification. If Edge is allowing this behavior, it is a bug in Edge which we will fix. I'd like your help to determine if there's an Edge bug here, if that's all right?

  1. Reboot your machine.
  2. Download the following zip file and unzip it: https://github.com/microsoft/media-foundation/releases/download/V1/mftracelog.zip
  3. Launch mftracelog.exe from the appropriate architecture directory.
  4. Select "Simple" and "Full Traces". (This is NOT the default.)
  5. Click "start tracing"
  6. Launch Edge and reproduce the issue (store persistent license on temporary session).
  7. Click "stop tracing"
  8. Zip the resultant files and share them with me, e.g. using OneDrive or another web storage account.

Regardless of the result of the above Edge investigation:

  1. You should not create two sessions ("major" and 'backup") when the application asks for only one.
  2. You should not allow update to store a persistent license when the application used a temporary session or vice-versa because it would be a direct violation of the EME specification.
  3. There is no public API that can detect whether the license is persistent or not from XMR nor do we intend to add such an API. The XMR format is considered internal and subject to change, so providing a specification also would not help you as it could suddenly stop working in the future with no warning and cause your implementation to fail.

I hope this helps, and thanks for continuing to work with me on this issue.

-Sam Wenker Microsoft PlayReady Architect

cjw1115 commented 2 years ago

Hi @swenkeratmicrosoft, Thank you for such detailed reply. It's helpful! I used key system "com.microsoft.playready" in my webapp.

For my persistent license, I tried below combinations on Edge:

  1. com.microsoft.playready + temporary sessionType
    • update() successfully and got a usable KeyID
  2. com.microsoft.playready + persistent-license sessionType
    • generateRequest failed with 0x80700009
  3. com.microsoft.playready.recommendation + temporary sessionType
    • update() failed with code 0x80704005
  4. com.microsoft.playready.recommendation + persistent-license sessionType
    • update() successfully and got a usable KeyID

Edge works as your description. it has no bug here.

Thanks again. the issue can be closed.