Using Certified Output Protection Protocol (COPP)

 

Microsoft Corporation

March 2006

 

Applies to:

   Microsoft® DirectShow®

 

Summary: Describes how to use Certified Output Protection Protocol (COPP) to protect video content as it travels from the graphics adapter to the display device.

Contents

Introduction

Overview of COPP

Obtaining the Driver's Certificate Chain

Validating the Certificate Chain

Initiating the COPP Session

Sending COPP Status Requests

Sending COPP Commands

COPP Query Reference

COPP Command Reference

COPP Structures

COPP Enumeration Types

For More Information

Introduction

Certified Output Protection Protocol (COPP) enables an application to protect a video stream as it travels from the graphics adapter to the display device. An application can use COPP to discover what kind of physical connector is attached to the display device, and what types of output protection are available. Protection mechanisms include the following:

  • High-Bandwidth Digital Content Protection (HDCP)
  • Copy Generation Management System — Analog (CGMS-A)
  • Analog Copy Protection (ACP)

If the graphics adapter supports one of these mechanisms, the application can use COPP to set the protection level.

COPP defines a protocol that is used to establish a secure communications channel with the graphics driver. It uses Message Authentication Codes (MACs) to verify the integrity of the COPP commands that are passed between the application and the display driver. The application uses COPP by calling methods on the IAMCertifiedOutputProtection interface of the DirectShow Video Mixing Renderer filter (VMR-7 or VMR-9).

COPP does not define anything about the digital rights policies that might apply to digital media content. Also, COPP itself does not implement any output protection systems. The COPP protocol simply provides a way to set and query protection levels on the graphics adapter, using the protection systems provided by the adapter.

This article assumes that you are familiar with the following technologies:

  • DirectShow
  • Windows Media Format SDK
  • XML
  • Public-key cryptography and symmetric cryptography

The code examples in this article use Microsoft's CryptoAPI to perform cryptographic operations.

Overview of COPP

Here are the basic steps that a COPP-aware application must perform.

Get the driver's certificate chain
  1. Build a DirectShow playback graph that renders video using the Video Mixing Renderer filter (VMR).
  2. Query the VMR for the IAMCertifiedOutputProtection interface.
  3. Call IAMCertifiedOutputProtection::KeyExchange. This method returns a 128-bit random number from the driver, along with a certificate chain that contains the driver's 2048-bit RSA public key.
Validate the certificate chain
  1. Validate the certificate chain. If the certificate chain is not valid, stop.
  2. Check the certificate revocation list (CRL). If any of the certificates in the certificate chain appears in the revocation list, stop.
  3. Get the RSA public key from the certificate chain.
Initialize the COPP Session
  1. Generate a 128-bit AES session key. This key is used to sign data and to verify that signed data has not been tampered with.
  2. Generate two cryptographically secure 32-bit random numbers. The first is a status sequence number, and the second is a command sequence number. Each time the application sends a command or status request, it increments the corresponding sequence number, and includes this number in the COPP command or request data.
  3. Concatenate the 128-bit random number from the graphics driver, the AES session key, the status sequence number, and the command sequence number. Encrypt this byte array using the driver's public key and pass the result to IAMCertifiedOutputProtection::SessionSequenceStart.
Send COPP Commands and Status Requests
  1. Query for the available protection types and other information by calling IAMCertifiedOutputProtection::ProtectionStatus.
  2. Set the desired protection levels by calling IAMCertifiedOutputProtection::ProtectionCommand.
  3. Periodically query for the current local protection level. Stop playback if the local protection level changes unexpectedly or if a problem is detected.

Obtaining the Driver's Certificate Chain

To use COPP, the application first must build a DirectShow graph that includes the Video Mixing Render filter (VMR-7 or VMR-9). The older Video Renderer filter does not support COPP. Before calling any COPP methods, the application must build a video playback graph and connect the decoder to the VMR filter's input pin. It is not necessary to play the video file.

After building the graph, query the VMR for the IAMCertifiedOutputProtection interface, and then call IAMCertifiedOutputProtection::KeyExchange. This method returns a 128-bit random number typed as a GUID, along with a pointer to a byte array that contains the driver's XML certificate chain in UTF-8 format. The following code shows how to get the certificate chain.

GUID guidRandom;
BYTE *pbCertificateChain = NULL;
DWORD cbCertificateChainLen;   // Size of the certificate chain, in bytes.
hr = pCOPP->KeyExchange(&guidRandom, &pbCertificateChain,
         &cbCertificateChainLen);
if (SUCCEEDED(hr))
{
    // TODO: Validate the certificate chain and get the driver's
    // public key. 

    // When you are done, free the buffer that contains the 
    // certificate chain.
    CoTaskMemFree(pbCertificateChain);
}

Validating the Certificate Chain

The graphic driver's certificate chain is an XML document. The certificate chain contains three certificates. The first certificate is called the leaf certificate, and is the driver's COPP certificate. The next certificate is the signing certificate of the Independent Hardware Vendor (IHV). The last certificate is Microsoft's signing certificate. To ensure that the graphics driver is a legitimate COPP device, the application must validate all three of these certificates. A malicious program can prevent COPP from working if an application does not correctly validate the certificates in the chain.

COPP certificate chains use the UTF-8 character set. Binary data in the certificates, such as the RSA public key, are base64-encoded and stored in big-endian order. (Big-endian means that the most significant byte is stored in the memory location with the lowest address.)

Some elements within a certificate contain Boolean values to denote that a feature of the certificate exists. If the feature exists, the corresponding child element value is set to 1. If a feature is not present, that child element is not present in the certificate.

XML Element Definitions

The following are definitions for elements in the certificate schema:

  • CertificateCollection. The root element of the XML document. It contains Certificate elements, one for each certificate in the chain.
  • Certificate. Contains one certificate. This element contains Data and Signature elements.
  • Data. Contains information about the certificate. In particular, it contains the certificate's public key and type. The Data element contains the following child elements:
    • PublicKey. Contains the certificate's RSA public key. The PublicKey element contains a KeyValue element, which contains an RSAKeyValue element. The RSAKeyValue element has two child elements, Modulus and Exponent, and these define the public key. The Modulus and Exponent elements are base64-encoded and stored in big-endian order.
    • KeyUsage. If the certificate is the driver's COPP certificate, this element should contain a child element called EncryptKey. If the certificate is the IHV's signing certificate or Microsoft's signing certificate, it should contain a child element called SignCertificate. Both of these child elements contain Boolean values.
    • SecurityLevel. This element should be ignored.
    • ManufacturerData. Specifies the manufacturer and model of the graphics device. This element is informational only.
    • Features. Contains child elements that specify the usage of the certificate. The only one relevant to COPP is the COPPCertificate element. Other child elements might be present; if so, they should be ignored. If the COPPCertificate element exists, the certificate is a COPP certificate. This element must be present in the leaf node of a valid COPP certificate. This element contains a Boolean value.
  • Signature. Contains the signature for this certificate. It contains the following child elements:
    • SignedInfo. Contains information about the signature. The important child element of this element is the DigestValue element, which contains the base64-encoded value of the SHA-1 hash over the Data element. The digest value is used when checking the certificate against the certificate revocation list (CRL).
    • SignatureValue. This value is computed over the Data element and is computed according to the RSASSA-PSS digital signature scheme defined in Public-Key Cryptography Standards (PKCS) #1 (version 2.1). For information about PKCS #1, visit https://www.rsasecurity.com/.
    • KeyInfo. Contains the RSA public key of the next certificate in the chain. This element is used to verify that the data in the Data element has not been tampered with. This element has the same format as the PublicKey element.

Certificate Validation

The application must perform the following steps to correctly validate the certificate chain. If any step fails, or if any element referred to in these procedures does not exist, the validation fails.

Validate Certificate Collection Procedure

To validate the certificate chain, perform the following steps:

  1. Verify that the CertificateCollection is well-formed XML.
  2. Verify that the CertificateCollection is encoded in UTF-8 format.
  3. Check that the Version attribute in the CertificateCollection element is 2.0 or later.
  4. Verify that the certificate chain contains exactly three Certificate elements.
  5. Loop through each Certificate element in the certificate chain and perform the Validate Certificate procedure, described below, on each.
Validate Certificate Procedure

To validate a certificate in the chain, perform the following steps:

  1. Verify that none of the child elements in the Data element is duplicated. For example, there should be only one PublicKey element.
  2. Ensure that the Data/PublicKey/KeyValue/RSAKeyValue/Modulus element exists. The base64-decoded value must be 256 bytes long for all certificates except the root. In the root certificate, this element must be 128 bytes long.
  3. Ensure that the Data/PublicKey/KeyValue/RSAKeyValue/Exponent element exists. The base64-decoded value must not be larger than 4 bytes.
  4. If this certificate is the leaf certificate, verify the following:
    • The KeyUsage element contains an EncryptKey element with the value 1.
    • The Features element contains a COPPCertificate element with the value 1.
  5. If this certificate is not the leaf certificate, verify the following:
    • The modulus and exponent from the Data/PublicKey element exactly match the modulus and exponent from the Signature/KeyInfo element of the previous certificate.
    • The KeyUsage element contains a SignCertificate element, with the value 1.
  6. Use the SHA-1 hash algorithm to hash every byte in the certificate's Data element. Every byte from the first character in the <Data> tag to the last character in the closing </Data> tag should be hashed. The hash value is used to check the certificate against the certificate revocation list (CRL), described in the next section.
  7. Compare the hash value from step 6 with the base64-decoded value of the Signature/SignedInfo/Reference/DigestValue element. These values must match.
  8. Perform the Verify Signature procedure, described below.
  9. If this certificate is not the final certificate in the chain, save the Signature/KeyInfo/KeyValue/RSAKeyValue value for the next iteration of the loop.
  10. If this certificate is the final certificate in the chain, ensure that the value of Signature/KeyInfo/KeyValue/RSAKeyValue matches Microsoft's public key. The Microsoft public key has the following base64-encoded values:
    • Modulus: pjoeWLSTLDonQG8She6QhkYbYott9fPZ8tHdB128ZETcghn5KHoyin7HkJEcPJ0Eg4UdSva0KDIYDjA3EXd69R3CN2Wp/QyOo0ZPYWYp3NXpJ700tKPgIplzo5wVd/69g7j+j8M66W7VNmDwaNs9mDc1p2+VVMsDhOsV/Au6E+E=
    • Exponent: AQAB
Verify Signature Procedure

The value of the SignatureValue element is computed over the Data element according to the RSASSA-PSS digital signature scheme defined in PKCS #1 version 2.1 (hereinafter referred to as PKCS). To verify this signature, perform the following steps:

  1. Decode the Modulus and Exponent values in the Signature/KeyInfo/KeyValue/RSAKeyValue element. These values define the RSA public key of the signing certificate.
  2. Decode the Signature/SignatureValue element.
  3. Compute the RSASSA-PSS-Verify operation, defined in section 8.1.2 of PKCS.

For the RSASSA-PSS-Verify operation, use the following inputs:

  • (n,e) is the public key from step 1.
  • M is all of the bytes in the Data element, including the <Data> and </Data> tags that enclose the element.
  • S is the decoded signature value from step 2.

The RSASSA-PSS-Verify operation uses the EMSA-PSS-ENCODE operation, defined in section 9.1.1. of PKCS. For this operation, COPP uses the following options:

  • Hash = SHA-1
  • hLen = 20
  • MGF (mask generation function) = MGF1
  • sLen = 0

The mask generation function MGF1 is defined in Appendix B.2 of PKCS. For this function, COPP uses the following options:

  • Hash = SHA-1
  • hLen = 20

The output of the RSASSA-PSS-Verify operation indicates whether the signature is valid or invalid.

Certificate Revocation Lists

The certificate revocation list (CRL) contains digests of revoked certificates and can be provided and signed only by Microsoft. The CRL is distributed through digital rights management (DRM) licenses. The CRL can revoke any certificate in the driver's certificates chain. If any certificate in the chain is revoked, then that certificate and all of the certificates below it in the chain are also revoked.

To get the CRL, the application must use the Windows Media Format SDK, version 9 or later, and perform the following steps:

  1. Call WMCreateReader to create the Windows Media Format SDK reader object.
  2. Query the reader object for the IWMDRMReader interface.
  3. Call IWMDRMReader::GetDRMProperty with a value of g_wszWMDRMNet_Revocation to get the CRL. You must call this method twice: Once to get the size of the buffer to allocate, and once to fill the buffer. The second call returns a string that contains the CRL. The entire string is base-64 encoded.
  4. Decode the base-64 encoded string. You can use the CryptStringToBinary function to do this. This function is part of CryptoAPI.

Note   To use the IWMDRMReader interface, you must obtain a static DRM library from Microsoft and link your application to this library file. For more information, see the topic "Obtaining the Required DRM Library" in the Windows Media Format SDK documentation.

If the CRL is not present on the user's computer, the GetDRMProperty method returns NS_E_DRM_UNSUPPORTED_PROPERTY. Currently, the only way to obtain the CRL is to acquire a DRM license.

The following code shows a function that returns the CRL:

////////////////////////////////////////////////////////////////////////
//  Name: GetCRL
//  Description: Gets the certificate revocation list (CRL).
//
//  ppBuffer: Receives a pointer to the buffer that contains the CRL.
//  pcbBuffer: Receives the size of the buffer returned in ppBuffer.
//
//  The caller must free the returned buffer by calling CoTaskMemFree.
////////////////////////////////////////////////////////////////////////
HRESULT GetCRL(BYTE **ppBuffer, DWORD *pcbBuffer)
{
    IWMReader *pReader = NULL;
    IWMDRMReader *pDrmReader = NULL;
    HRESULT hr = S_OK;

    // DRM attribute data.
    WORD cbAttributeLength = 0;
    BYTE *pDataBase64 = NULL;
    WMT_ATTR_DATATYPE type;

    // Buffer for base-64 decoded CRL.
    BYTE *pCRL = NULL;
    DWORD cbCRL = 0;

    // Create the WMReader object.
    hr = WMCreateReader(NULL, 0, &pReader);

    // Query for the IWMDRMReader interface.
    if (SUCCEEDED(hr))
    {
        hr = pReader->QueryInterface(
            IID_IWMDRMReader, (void**)&pDrmReader);
    }

    // Call GetDRMProperty once to find the size of the buffer.
    if (SUCCEEDED(hr))
    {
        hr = pDrmReader->GetDRMProperty(
            g_wszWMDRMNET_Revocation,
            &type,
            NULL,
            &cbAttributeLength
            );
    }

    // Allocate a buffer.
    if (SUCCEEDED(hr))
    {
        pDataBase64 = (BYTE*)CoTaskMemAlloc(cbAttributeLength);
        if (pDataBase64 == NULL)
        {
            hr = E_OUTOFMEMORY;
        }
    }

    // Call GetDRMProperty again to get the property.
    if (SUCCEEDED(hr))
    {
        hr = pDrmReader->GetDRMProperty(
            g_wszWMDRMNET_Revocation,
            &type,
            pDataBase64,
            &cbAttributeLength
            );
    }

    // Find the size of the buffer for the base-64 decoding.
    if (SUCCEEDED(hr))
    {
        BOOL bResult = CryptStringToBinary(
            (WCHAR*)pDataBase64,    // Base-64 encoded string.
            0,                      // Null-terminated.
            CRYPT_STRING_BASE64,    
            NULL,                   // Buffer (NULL).
            &cbCRL,                 // Receives the size of the buffer. 
            NULL, NULL              // Optional.
            );
        if (!bResult)
        {
            hr = __HRESULT_FROM_WIN32(GetLastError());
        }
    }

    // Allocate a buffer for the CRL.
    if (SUCCEEDED(hr))
    {
        pCRL = (BYTE*)CoTaskMemAlloc(cbCRL);
        if (pCRL == NULL)
        {
            hr = E_OUTOFMEMORY;
        }
    }

    // Base-64 decode to get the CRL.
    if (SUCCEEDED(hr))
    {
        BOOL bResult = CryptStringToBinary(
            (WCHAR*)pDataBase64,    // Base-64 encoded string.
            0,                      // Null-terminated.
            CRYPT_STRING_BASE64,    
            pCRL,                   // Buffer.
            &cbCRL,                 // Receives the size of the buffer. 
            NULL, NULL              // Optional.
            );
        if (!bResult)
        {
            hr = __HRESULT_FROM_WIN32(GetLastError());
        }
    }

    // Return the buffer to the caller. Caller must free the buffer.
    if (SUCCEEDED(hr))
    {
        *ppBuffer = pCRL;
        *pcbBuffer = cbCRL;
    }
    else
    {
        CoTaskMemFree(pCRL);
    }

    CoTaskMemFree(pDataBase64);
    SAFE_RELEASE(pReader);
    SAFE_RELEASE(pDrmReader);
    return hr;
}

Next, the application must verify that the CRL is valid. To do so, verify that the CRL certificate, which is part of the CRL, is directly signed by the Microsoft Root Certificate and has the SignCRL element value set to 1. Also, verify the signature of the CRL.

After the CRL is verified, the application can store it. The CRL version number should also be checked before storing so that the application always stores the newest version.

The CRL has the following format.

Section Contents
Header 32-bit CRL version
32-bit number of entries
Revocation Entries Multiple 160-bit revocation entries
Certificate 32-bit certificate length
Variable-length certificate
Signature 8-bit signature type
16-bit signature length
Variable-length signature

Note All integer values are unsigned and are represented in big-endian (network byte order) notation.

CRL Section Descriptions

Header

The header contains the version number of the CRL and the number of revocation entries in the CRL. A CRL can contain zero or more entries.

Revocation entries

Each revocation entry is the 160-bit digest of a revoked certificate. Compare this digest with the DigestValue element within the certificate.

Certificate

The certificate section contains a 32-bit value indicating the length (in bytes) of the XML certificate and its certificate chain, along with a byte array that contains both the XML certificate of the Certificate Authority (CA) and the certificate chain that has Microsoft as the Root. The certificate must be signed by a CA that has the authority to issue CRLs.

Note The certificate must not be null-terminated.

Signature

The signature section contains the signature type and length, and the digital signature itself. The 8-bit type is set to 2 to indicate that it uses SHA-1 with 1024-bit RSA encryption. The length is a 16-bit value containing the length of the digital signature in bytes. The digital signature is calculated over all prior sections of the CRL.

The signature is calculated using the RSASSA-PSS digital signature scheme that is defined in PKCS #1 (version 2.1). The hash function is SHA-1, which is defined in Federal Information Processing Standard (FIPS) 180-2, and the mask generation function is MGF1, which is defined in section B.2.1 in PKCS #1 (version 2.1). The RSASP1 and RSAVP1 operations use RSA with a 1024-bit modulus with a verification exponent of 65537.

Importing the Driver's Public Key

The driver's RSA public key is contained in the Modulus and Exponent tags of the certificate's leaf node. Both values are base64-encoded and must be decoded. If you are using Microsoft's CryptoAPI, you must import the key into a cryptographic service provider (CSP), which is the module that implements the cryptographic algorithms.

To convert the modulus and exponents from base64 encoding to binary arrays, use the CryptStringToBinary function, as shown in the following code. Call the function once to get the size of the byte array. Then allocate the buffer and call the function again.

DWORD cbLen = 0, dwSkip = 0, dwFlags = 0;
::CryptStringToBinary(
   pszModulus,  // String that contains the Base64-encoded modulus.
   cchModulus,  // Length of the string, not including the trailing NULL.
   CRYPT_STRING_BASE64,  // Base64 encoding.
   NULL,     // Do not convert yet. Just calculate the length.
   &cbLen,   // Receives the length of the buffer that is required.
   &dwSkip,  // Receives the number of skipped characters.
   &dwFlags  // Receives flags.
);

// Allocate a new buffer.
BYTE *pbBuffer = new BYTE [cbLen];
::CryptStringToBinary(pszModulus, cchModulus, CRYPT_STRING_BASE64, 
    pbBuffer, &cbLen, &dwSkip, &dwFlags);

// (Repeat these steps for the exponent.)

The base64-encoded array is in big-endian order, whereas the CryptoAPI expects the number in little-endian order, so you need to swap the byte order of the array that is returned from CryptStringToBinary. The modulus is 256 bytes, but the decoded byte array might be less than 256 bytes. If so, you will need to allocate a new array that is 256 bytes, copy the data into the new array, and pad the front of the array with zeros. The exponent is a DWORD (4-byte) value.

After you have the modulus and exponent values, you can import the key into the default cryptographic service provider (CSP), as shown in the following code:

// Assume the following values exist:
BYTE *pModulus;     // Byte array that contains the modulus.
DWORD cbModulus;    // Size of the modulus in bytes.
DWORD dwExponent;   // Exponent.

// Create a new key container to hold the key. 
::CryptAcquireContext(
    &hCSP,         // Receives a handle to the CSP.
    NULL,          // Use the default key container.
    NULL,          // Use the default CSP.
    PROV_RSA_AES,  // Use the AES provider (public-key algorithm).
    CRYPT_SILENT | CRYPT_NEWKEYSET 
);

// Move the key into the key container. 
// The data format is: PUBLICKEYSTRUC + RSAPUBKEY + key
DWORD cbKeyBlob = cbModulus + sizeof(PUBLICKEYSTRUC) + sizeof(RSAPUBKEY)
BYTE *pBlob = new BYTE[cbKeyBlob];

// Fill in the data.
PUBLICKEYSTRUC *pPublicKey = (PUBLICKEYSTRUC*)pBlob;
pPublicKey->bType = PUBLICKEYBLOB; 
pPublicKey->bVersion = CUR_BLOB_VERSION;  // Always use this value.
pPublicKey->reserved = 0;                 // Must be zero.
pPublicKey->aiKeyAlg = CALG_RSA_KEYX;     // RSA public-key key exchange. 

// The next block of data is the RSAPUBKEY structure.
RSAPUBKEY *pRsaPubKey = (RSAPUBKEY*)(pBlob + sizeof(PUBLICKEYSTRUC));
pRsaPubKey->magic = RSA1;            // Public key.
pRsaPubKey->bitlen = cbModulus * 8;  // Number of bits in the modulus.
pRsaPubKey->pubexp = dwExponent;     // Exponent.

// Copy the modulus into the blob. Put the modulus directly after the
// RSAPUBKEY structure in the blob.
BYTE *pKey = (BYTE*)(pRsaPubkey + sizeof(RSAPUBKEY));
CopyMemory(pKey, pModulus, cbModulus);

// Now import the key.
HCRYPTKEY hRSAKey;  // Receives a handle to the key.
CryptImportKey(hCSP, pBlob, cbKeyBlob, 0, 0, &hRSAKey) 

Now you can use the CryptoAPI to encrypt commands and status requests with the driver's public key.

Initiating the COPP Session

To initiate the COPP session, you must prepare a signature, which is an array that contains the concatenation of the following numbers:

  • The 128-bit random number returned by the driver. (This value is shown as guidRandom in Obtaining the Driver's Certificate Chain.)
  • A 128-bit symmetric AES key.
  • A 32-bit starting sequence number for status requests.
  • A 32-bit starting sequence number for COPP commands.

Generate a symmetric AES key as follows:

DWORD dwFlag = 0x80;         // Bit length: 128-bit AES.
dwFlag <<= 16;               // Move this value to the upper 16 bits.
dwFlag |= CRYPT_EXPORTABLE;  // We want to export the key.
CryptGenKey(
    hCSP,           // Handle to the CSP.
    CALG_AES_128,   // Use 128-bit AES block encryption algorithm.
    dwFlag,
    &m_hAESKey      // Receives a handle to the AES key.
);

The CryptGenKey function creates the symmetric key, but the key is still held in the CSP. To export the key into a byte array, use the CryptExportKey function. This is the reason for using the CRYPT_EXPORTABLE flag when calling CryptGenKey. The following code exports the key and copies it into the pData array.

DWORD cbData = 0; 
BYTE *pData = NULL;
// Get the size of the blob.
CryptExportKey(hAESKey, 0, PLAINTEXTKEYBLOB, 0, NULL, &cbData);  

// Allocate the array and call again.
pData = new BYTE[cbData];
CryptExportKey(hAESKey, 0, PLAINTEXTKEYBLOB, 0, pData, &cbData);  

The data returned in pData has the following layout:

BLOBHEADER header
DWORD      cbSize
BYTE       key[]

However, no structure that matches this layout is defined in the CryptoAPI headers. You can either define one or do some pointer arithmetic. For example, to verify the size of the key:

DWORD *pcbKey = (DWORD*)(pData + sizeof(BLOBHEADER));
if (*pcbKey != 16)
{
    // Wrong size! Should be 16 bytes (128 bits).
}

To get the pointer to the key itself:

BYTE *pKey = pData + sizeof(BLOBHEADER) + sizeof(DWORD);

Next, generate a 32-bit random number to use as the starting sequence for COPP status requests. The recommended way to create a random number is to call the CryptGenRandom function. Do not use the rand function in the C run-time library, because it is not truly random. Generate a second 32-bit random number to use as the starting sequence for COPP commands.

UINT uStatusSeq;     // Status sequence number.
UINT uCommandSeq;    // Command sequence number.
CryptGenRandom(hCSP, sizeof(UINT), &uStatusSeq);
CryptGenRandom(hCSP, sizeof(UINT), &uCommandSeq);

Now you can prepare the COPP signature. This is a 256-byte array, defined as the AMCOPPSignature structure. Initialize the contents of the array to zero. Then copy the four numbers into the array—the driver's random number, the AES key, the status sequence number, and the command sequence number, in that order. Finally, swap the byte order of the entire array.

According to the documentation for CryptEncrypt:

The length of plaintext data that can be encrypted with a call to CryptEncrypt with an RSA key is the length of the key modulus minus eleven bytes.

In this case, the modulus is 256 bytes, so the maximum message length is 245 bytes (256 – 11). The AMCOPPSignature structure is 256 bytes, but the meaningful data in the signature is only 40 bytes. The following code encrypts the signature and provides the result in CoppSig.

AMCOPPSignature CoppSig;
ZeroMemory(&CoppSig, sizeof(CoppSig));
// Copy the signature data into CoppSig. (Not shown.)

// Encrypt the signature:
const DWORD RSA_PADDING = 11;    // 11-byte padding.
DWORD cbDataOut = sizeof(AMCOPPSignature);
DWORD cbDataIn = cbDataOut - RSA_PADDING;
CryptEncrypt(
    hRSAKey, 
    NULL,     // No hash object.
    TRUE,     // Final block to encrypt.
    0,        // Reserved.
    &CoppSig, // COPP signature.
    &cbDataOut, 
    cbDataIn
);

Now pass the encrypted array to IAMCertifiedOutputProtection::SessionSequenceStart:

hr = pCOPP->SessionSequenceStart(&CoppSig);
if (SUCCEEDED(hr))
{
    // Ready to send COPP commands and status requests.
}

If this method succeeds, you are ready to send COPP commands and status requests to the driver.

Sending COPP Status Requests

To send a COPP status request, fill in an AMCOPPStatusInput structure with the request data. The structure members are:

  • rApp. A 128-bit random number, typed as a GUID. The same number is returned in the driver's response. You should allocate the random number on the heap and then copy it into structure. This guards against attacks where the attacker modifies the contents of the AMCOPPStatusInput structure.
  • guidStatusRequestID. A GUID that identifies the request. See COPP Query Reference.
  • dwSequence. The status sequence number. Increment this value after each status request. (This value is shown as uStatusSeq in the previous section.)
  • cbSizeData. The size, in bytes, of any additional data needed for the request.
  • StatusData. Data for the status request.

Pass the AMCOPPStatusInput structure to the IAMCertifiedOutputProtection::ProtectionStatus method. For example, the following code sends a status request that queries which protection mechanisms are available:

AMCOPPStatusInput input;
AMCOPPStatusOutput output;

// Create a 128-bit random number.
GUID *pGuid = new GUID();
if (pGuid == NULL)
{
    // Handle out-of-memory condition.
}
CryptGenRandom(hCSP, sizeof(GUID), (BYTE*)pGuid);  

// Copy the random number into the command structure.
memcpy(&input.rApp, pGuid, sizeof(GUID));

// Fill in the other data.
input.guidStatusRequestID = DXVA_COPPQueryProtectionType; // Request type.
input.dwSequence = uStatusSeq;  // Status sequence number.
input.cbSizeData = 0            // No other data for this query.

// Send the request.
hr = pCOPP->ProtectionStatus(&input, &output);

// Increment the sequence number each time.
++uStatusSeq;

The response is written into the COPPStatus member of the AMCOPPStatusOutput structure. The size of the valid data in the response is given in the cbSizeData member. To ensure the integrity of the message, the driver computes a message authentication code (MAC) using the OMAC 1 algorithm, and returns this value in the structure's macKDI member. The application should verify this value as follows:

  1. Calculate the OMAC tag for the block of data that appears after the macKDI member of the AMCOPPStatusOutput structure (in other words, cbSizeData plus COPPStatus).
  2. Compare this tag with the value in macKDI, using a straight memcmp.

The OMAC 1 algorithm is described in detail at https://crypt.cis.ibaraki.ac.jp/omac/omac.html. COPP uses the following OMAC-1 parameters:

  • E = AES
  • t = 128 bits

The data returned from the status request always starts with two items:

  • The same value of rApp that was passed by the application. You should verify that this value matches the original value stored on the heap.
  • A COPP_StatusFlags value that indicates whether the output protection status has changed.

Because the connection can be lost or reconfigured, the application should periodically poll the driver for the current status. If the COPP_RenegotiationRequired flag is set, the application should attempt to reset the protection level. If the COPP_LinkLost flag is set, the application should stop playing the content. For example, the COPP_LinkLost flag can be returned because the user disconnected the output connector. The application should release the current instance of the VMR and create a new instance.

Sending COPP Commands

To send a COPP command, fill in an AMCOPPCommand structure as follows:

  • guidCommandID. The GUID that identifies the command. See the COPP Command Reference.
  • dwSequence. The command sequence number. Increment this value after each command. (This value is shown as uCommandSeq in Initiating the COPP Session.)
  • cbSizeData. The size, in bytes, of any data needed for the command.
  • CommandData. Data for the command.

After you have filled in this data, calculate the MAC for the command:

  1. Calculate the OMAC-1 tag for the block of data that appears after the macKDI member of the AMCOPPCommand structure.
  2. Copy this value into the macKDI member of the structure.

Now pass the structure to the IAMCertifiedOutputProtection::ProtectionCommand method.

COPP Query Reference

This section describes the status queries that are supported by COPP. For each query, the GUID that defines the query is listed, along with the input data and return data.

Bus Data

Returns the type of I/O bus used by the graphics adapter.

  • GUID: DXVA_COPPQueryBusData
  • Input data: None.
  • Return data: Returns a DXVA_COPPStatusData structure. The bus type is returned in the dwData member as a flag from the COPP_BusType enumeration.

Connector Type

Returns the physical connector type.

  • GUID: DXVA_COPPQueryConnectorType
  • Input data: None.
  • Return data: Returns a DXVA_COPPStatusData structure. The connector type is returned in the dwData member as a flag from the COPP_ConnectorType enumeration.

Protection Type

Returns the protection mechanisms that are available for the connector.

  • GUID: DXVA_COPPQueryProtectionType
  • Input data: None.
  • Return data: Returns a DXVA_COPPStatusData structure. The protection mechanisms are returned in the dwData member as a combination of zero or more flags. See COPP Protection Type Flags. If more than one protection mechanism is available, the flags are combined with a bitwise OR.

Local Protection Level

Returns the local protection level for a specified protection mechanism.

The local protection level is the protection level that was requested through the current COPP session. The driver might set a higher protection level.

  • GUID: DXVA_COPPQueryLocalProtectionLevel
  • Input data: The protection mechanism to query, as a 32-bit integer. See COPP Protection Type Flags.
  • Return data: Returns a DXVA_COPPStatusData structure. The current protection level is returned in the dwData member. The meaning of this value depends on the protection mechanism that is queried. For each protection mechanism, the value of the dwData member is a flag from a different enumeration, as shown in the following table.
    Protection mechanism Enumeration
    ACP COPP_ACP_Protection_Level
    CGMS-A COPP_CGMSA_Protection_Level
    HDCP COPP_HDCP_Protection_Level

Global Protection Level

Returns the global protection level for a specified protection mechanism.

The global protection level is the protection level that is currently being applied on the connector, regardless of how the graphics driver was instructed to apply the protection. For example, an application can set the ACP protection level by calling the ChangeDisplaySettingsEx function. In that case, the global protection level would reflect this setting, even though it was not requested through COPP.

  • GUID: DXVA_COPPQueryGlobalProtectionLevel
  • Input data: The protection mechanism to query, specified as a 32-bit integer. See COPP Protection Type Flags.
  • Return data: Returns a DXVA_COPPStatusData structure. The current protection level is returned in the dwData member. The meaning of this value depends on the protection mechanism that is queried. For each protection mechanism, the value of the dwData member is a flag from a different enumeration, as shown in the following table.
    Protection mechanism Enumeration
    ACP COPP_ACP_Protection_Level
    CGMS-A COPP_CGMSA_Protection_Level
    HDCP COPP_HDCP_Protection_Level

Display Data

Returns a description of the video signal that is being transmitted over the connector.

The video signal that is transmitted over the connector does not necessarily have the same format as the desktop display mode. For example, the desktop display mode might be 1024x768 pixels at 85 Hz, while the connector might be an S-Video connector that transmits a video signal at 720x480 pixels, 60/1.01 Hz interlaced. In that case, the driver would return the resolution of the S-Video signal, not the desktop resolution.

  • GUID: DXVA_COPPQueryDisplayData
  • Input data: None.
  • Return data: Returns a DXVA_COPPStatusDisplayData structure.

HDCP Key Data

Returns the device's HDCP key selection vector (B-KSV).

The KSV is an identifier provided to the device manufacturer, and is used in the HDCP authentication and setup process. The application should check this value against the list of revoked KSVs. The mechanism for obtaining the KSV revocation list is outside the scope of the COPP protocol. For more information, consult the HDCP specification.

This query also determines whether the connected HDCP device is a monitor or an HDCP repeater. The application should not play protected content if the HDCP device is an HDCP repeater, because these are not supported by COPP.

  • GUID: DXVA_COPPQueryHDCPKeyData
  • Input data: None.
  • Return data: Returns a DXVA_COPPStatusHDCPKeyData structure.

COPP Command Reference

This section describes the COPP commands. Currently, only one command is supported, the Set Protection Level command.

Set Protection Level

Sets the protection level for a specified output protection mechanism. Depending on the connector, it might be possible to apply more than one protection mechanism on the same connector, with different settings for each mechanism.

GUID: DXVA_COPPSetProtectionLevel

Input data: A DXVA_COPPSetProtectionLevelCmdData structure.

COPP Structures

This section describes the structures that are used for COPP commands and status requests. Other COPP structures are documented in the DirectShow SDK documentation.

DXVA_COPPSetProtectionLevelCmdData

Contains data for the COPP Set Protection Level command.

Syntax

typedef struct _DXVA_COPPSetProtectionLevelCmdData {
  ULONG   ProtType;
  ULONG   ProtLevel;
  ULONG   ExtendedInfoChangeMask;
  ULONG   ExtendedInfoData;
} DXVA_COPPSetProtectionLevelCmdData;

Members

ProtType

Identifies the protection mechanism. See COPP Protection Type Flags.

ProtLevel

Specifies the protection level. The meaning of this value depends on the protection mechanism that is queried. For each protection mechanism, the value of the ProtLevel member is a flag from a different enumeration, as shown in the following table.

Protection mechanism Enumeration
ACP COPP_ACP_Protection_Level
CGMS-A COPP_CGMSA_Protection_Level
HDCP COPP_HDCP_Protection_Level

ExtendedInfoChangeMask

Reserved. Must be zero.

ExtendedInfoData

Reserved. Must be zero.

DXVA_COPPStatusData

Contains the result from a COPP status request.

Syntax

typedef struct _DXVA_COPPStatusData {
  GUID    rApp;
  ULONG   dwFlags;
  ULONG   dwData;
  ULONG   ExtendedInfoValidMask;
  ULONG   ExtendedInfoData;
} DXVA_COPPStatusData;

Members

rApp

A 128-bit random number that was passed by the application in the AMCOPPStatusInput structure.

dwFlags

Status flag. See COPP_StatusFlags.

dwData

Response to the status query. The meaning of this value depends on the status request. For more information, see COPP Query Reference.

ExtendedInfoValidMask

Reserved. Must be zero.

ExtendedInfoData

Reserved. Must be zero.

DXVA_COPPStatusDisplayData

Contains the result of a COPP Display Data query.

Syntax

typedef struct _DXVA_COPPStatusDisplayData {
  GUID    rApp;
  ULONG   dwFlags;
  ULONG   DisplayWidth;
  ULONG   DisplayHeight;
  ULONG   Format;
  ULONG   d3dFormat;
  ULONG   FreqNumerator;
  ULONG   FreqDenominator;
} DXVA_COPPStatusDisplayData;

Members

rApp

A 128-bit random number that was passed by the application in the AMCOPPStatusInput structure.

dwFlags

Status flag. See COPP_StatusFlags.

DisplayWidth

Width of the display mode, in pixels.

DisplayHeight

Height of the display mode, in pixels.

Format

Contains a DXVA_ExtendedFormat structure packed into a ULONG, describing the video format.

d3dFormat

Contains a D3DFORMAT value that describes the video format. For more information, see the Direct3D SDK documentation.

FreqNumerator

The numerator of the refresh rate of the current display mode.

FreqDenominator

The denominator of the refresh rate of the current display mode.

Remarks

The refresh rate is expressed as a fraction. For example, if the refresh rate is 72 Hz, FreqNumerator = 72 and FreqDenominator = 1. For NTSC television, the values are FreqNumerator = 60000 and FreqDenominator = 1001 (59.94 fields per second).

DXVA_COPPStatusHDCPKeyData

Contains the result from an HDCP Key Data query. This query returns the device's HDCP key selection vector (KSV).

Syntax

typedef struct _DXVA_COPPStatusHDCPKeyData {
    GUID    rApp;
    ULONG   dwFlags;
    ULONG   dwHDCPFlags;
    GUID    BKey;
    GUID    Reserved1;
    GUID    Reserved2;
} DXVA_COPPStatusHDCPKeyData;

Members

rApp

A 128-bit random number that was passed by the application in the AMCOPPStatusInput structure.

dwFlags

Status flag. See COPP_StatusFlags.

dwHDCPFlags

Receives zero or more flags from the COPP_StatusHDCPFlags enumeration. If the COPP_HDCPRepeater flag is present, the application should not play the content using this graphics adapter.

BKey

Receives the HDCP key selection vector, BKSV, from the HDSCP device attached to the graphics adapter.

Reserved1

Reserved. Must be zero.

Reserved2

Reserved. Must be zero.

COPP Enumeration Types

This section describes the enumeration types that are used for COPP commands and status requests. Other COPP enumeration types are documented in the DirectShow SDK documentation.

COPP_BusType

Specifies the type of I/O bus used by the graphics adapter.

Syntax

typedef enum _COPP_BusType {
  COPP_BusType_Unknown    = 0,
  COPP_BusType_PCI        = 1,
  COPP_BusType_PCIX       = 2,
  COPP_BusType_PCIExpress = 3,
  COPP_BusType_AGP        = 4,
  COPP_BusType_Integrated = 0x80000000,
  COPP_BusType_ForceDWORD = 0x7fffffff 
} COPP_BusType;

Members

COPP_BusType_Unknown

Unknown bus type.

COPP_BusType_PCI

PCI bus.

COPP_BusType_PCIX

PCI-X bus.

COPP_BusType_PCIExpress

PCI Express bus.

COPP_BusType_AGP

AGP bus.

COPP_BusType_Integrated

Integrated bus. This flag can be combined with the other flags. This flag indicates that the command and status signals between the graphics adapter and other subsystems on the computer are not available on an expansion bus that has a public specification and standard connector type, unless it is a memory bus.

COPP_BusType_ForceDWORD

Reserved.

COPP_HDCP_Protection_Level

Specifies the HDCP protection level.

Syntax

typedef enum _COPP_HDCP_Protection_Level {
  COPP_HDCP_Level0    = 0,
  COPP_HDCP_LevelMin  = COPP_HDCP_Level0,
  COPP_HDCP_Level1    = 1,
  COPP_HDCP_LevelMax  = COPP_HDCP_Level1,
  COPP_HDCP_ForceDWORD = 0x7fffffff
} COPP_HDCP_Protection_Level;

Members

COPP_HDCP_Level0

HDCP protection is not enabled. See Remarks.

COPP_HDCP_LevelMin

Minimum HDCP level. Equivalent to COPP_HDCP_Level0.

COPP_HDCP_Level1

HDCP is enabled.

COPP_HDCP_LevelMax

Maximum HDCP level. Equivalent to COPP_HDCP_Level1.

COPP_HDCP_ForceDWORD

Reserved.

Remarks

Some televisions do not have robust support for switching HDCP protection on and off. Because of this limitation, the graphics driver might leave HDCP enabled when the application sets the protection level to zero. If the application sets the HDCP level to zero, therefore, it might receive a COPP status message indicating that HDCP is still enabled. This is not an error.

For more information about HDCP, see https://www.digital-cp.com/home.

COPP_CGMSA_Protection_Level

Specifies the CGMS-A protection level.

Syntax

typedef enum _COPP_CGMSA_Protection_Level {
  COPP_CGMSA_Disabled = 0,
  COPP_CGMSA_LevelMin = COPP_CGMSA_Disabled,
  COPP_CGMSA_CopyFreely = 1,
  COPP_CGMSA_CopyNoMore = 2,
  COPP_CGMSA_CopyOneGeneration   = 3,
  COPP_CGMSA_CopyNever = 4,
  COPP_CGMSA_RedistributionControlRequired = 0x08,
  COPP_CGMSA_LevelMax = 
    (COPP_CGMSA_RedistributionControlRequired + COPP_CGMSA_CopyNever),
  COPP_CGMSA_ForceDWORD = 0x7fffffff
} COPP_CGMSA_Protection_Level;

Members

COPP_CGMSA_Disabled

CGMS-A is disabled.

COPP_CGMSA_LevelMin

Minimum CGMS-A level. Equivalent to COPP_CGMSA_Disabled.

COPP_CGMSA_CopyFreely

The protection level is Copy Freely.

COPP_CGMSA_CopyNoMore

The protection level is Copy No More.

COPP_CGMSA_CopyOneGeneration

The protection level is Copy One Generation.

COPP_CGMSA_CopyNever

The protection level is Copy Never.

COPP_CGMSA_RedistributionControlRequired

Redistribution control (or broadcast flag) is required. This flag can be combined with the other flags.

COPP_CGMSA_LevelMax

Maximum CGMS-A level.

COPP_CGMSA_ForceDWORD

Reserved.

COPP_ACP_Protection_Level

Specifies the ACP protection level.

Syntax

typedef enum _COPP_ACP_Protection_Level {
  COPP_ACP_Level0     = 0,
  COPP_ACP_LevelMin   = COPP_ACP_Level0,
  COPP_ACP_Level1     = 1,
  COPP_ACP_Level2     = 2,
  COPP_ACP_Level3     = 3,
  COPP_ACP_LevelMax   = COPP_ACP_Level3,
  COPP_ACP_ForceDWORD = 0x7fffffff
} COPP_ACP_Protection_Level;

Members

COPP_ACP_Level0

Level 0.

COPP_ACP_LevelMin

Minimum ACP level. Equivalent to COPP_ACP_Level0.

COPP_ACP_Level1

Level 1.

COPP_ACP_Level2

Level 2.

COPP_ACP_Level3

Level 3.

COPP_ACP_LevelMax

Maximum ACP level. Equivalent to COPP_ACP_Level3.

COPP_ACP_ForceDWORD

Reserved.

COPP_ConnectorType

Specifies the type of physical connector.

Syntax

typedef enum _COPP_ConnectorType {
  COPP_ConnectorType_Unknown = -1,
  COPP_ConnectorType_VGA = 0,
  COPP_ConnectorType_SVideo = 1,
  COPP_ConnectorType_CompositeVideo = 2,
  COPP_ConnectorType_ComponentVideo = 3,
  COPP_ConnectorType_DVI = 4,
  COPP_ConnectorType_HDMI = 5,
  COPP_ConnectorType_LVDS = 6,
  COPP_ConnectorType_TMDS = 7,
  COPP_ConnectorType_D_JPN = 8,
  COPP_ConnectorType_SDI = 9,
  COPP_ConnectorType_Internal = 0x80000000,
  COPP_ConnectorType_ForceDWORD = 0x7fffffff
} COPP_ConnectorType;

Members

COPP_ConnectorType_Unknown

Unknown connector type.

COPP_ConnectorType_VGA

VGA (Video Graphics Array) connector.

COPP_ConnectorType_SVideo

S-Video connector.

COPP_ConnectorType_CompositeVideo

Composite video connector.

COPP_ConnectorType_ComponentVideo

Component video connector.

COPP_ConnectorType_DVI

DVI (digital video interface) connector.

COPP_ConnectorType_HDMI

HDMI (high-definition multimedia interface) connector.

COPP_ConnectorType_LVDS

LVDS (Low voltage differential signaling) connector.

COPP_ConnectorType_TMDS

Reserved.

COPP_ConnectorType_D_JPN

Japanese D connector. (Connector conforming to the EIAJ RC-5237 standard.)

COPP_ConnectorType_SDI

Serial digital image connector.

COPP_ConnectorType_Internal

Internal connector. This flag can be combined with the other flags. This flag indicates that the connection between the graphics adapter and the display device is permanent and not accessible to the user.

COPP_StatusFlags

Specifies the status of the COPP session.

Syntax

typedef enum _COPP_StatusFlags {
    COPP_StatusNormal           = 0x00,
    COPP_LinkLost               = 0x01,
    COPP_RenegotiationRequired  = 0x02,
    COPP_StatusFlagsReserved    = 0xFFFFFFFC
} COPP_StatusFlags;

Members

COPP_StatusNormal

Normal status.

COPP_LinkLost

The integrity of the connection has been compromised. Examples of events that cause the driver to set this flag include:

   The driver can no longer enforce the current protection level.

   The driver detected an internal integrity error.

   The connector between the computer and the display device was unplugged.

COPP_RenegotiationRequired

The connection configuration has changed. For example, the user has changed the desktop display mode.

COPP_StatusFlagsReserved

Reserved. Must be zero.

COPP_StatusHDCPFlags

Contains HDCP status flags. This enumeration is used in the DXVA_COPPStatusHDCPKeyData structure.

Syntax

typedef enum _COPP_StatusHDCPFlags {
    COPP_HDCPRepeater       = 0x01,
    COPP_HDCPFlagsReserved  = 0xFFFFFFFE
} COPP_StatusHDCPFlags;

Members

COPP_HDCPRepeater

The device is an HDCP repeater.

COPP_HDCPFlagsReserved

Reserved. Must be zero.

COPP Protection Type Flags

Specifies output protection mechanisms.

Syntax

enum {
  COPP_ProtectionType_Unknown    = 0x80000000,
  COPP_ProtectionType_None       = 0x00000000,
  COPP_ProtectionType_HDCP       = 0x00000001,
  COPP_ProtectionType_ACP        = 0x00000002,
  COPP_ProtectionType_CGMSA      = 0x00000004,
  COPP_ProtectionType_Mask       = 0x80000007,
  COPP_ProtectionType_Reserved   = 0x7FFFFFF8
};

Members

COPP_ProtectionType_Unknown

Unknown protection mechanism.

COPP_ProtectionType_None

No protection mechanisms.

COPP_ProtectionType_HDCP

High-Bandwidth Digital Content Protection (HDCP).

COPP_ProtectionType_ACP

Analog Copy Protection (ACP).

COPP_ProtectionType_CGMSA

Copy Generation Management System — Analog (CGMS-A).

COPP_ProtectionType_Mask

Bitmask to isolate valid flags.

COPP_ProtectionType_Reserved

Reserved. Must be zero.

Remarks

Some methods return a single flag from this enumeration type, and some methods return a bitwise OR of one or more flags.

These flags are used in the following COPP queries and commands.

  • Global Protection Level
  • Local Protection Level
  • Protection Type
  • Set Protection Level

For More Information

Web addresses can change, so you might be unable to connect to the Web site or sites mentioned in this article.