GPU-Based 内容保护
本主题介绍图形驱动程序可以提供的视频内容保护功能。
简介
下图显示了受保护的视频内容如何通过管道进行呈现的简化视图。
注意
此图中未描述 受保护的媒体路径 (PMP) 。 此处显示的数据流可能发生在 PMP 进程或应用程序进程中。
解码器从外部源接收加密的压缩视频数据。 此外,假定解码器还接收加密密钥来解密此数据。 本主题不介绍视频源和解码器之间的密钥交换,但 PMP 定义了一种可能的机制。 此阶段不涉及 GPU。
对于硬件加速解码,软件解码器将压缩的视频内容传递到 GPU。 为了保护此内容,解码器通常会使用 AES-CTR 重新加密数据,然后再将其传递给硬件加速器。 解码器和图形驱动程序之间定义了密钥交换机制。
解码的视频帧存储在视频内存中,通常存储在明文中。 此时,将处理帧,然后呈现。 有两个用于演示main选项。
- 可以使用硬件覆盖来呈现帧。 有关详细信息,请参阅 硬件覆盖支持。
- 使用共享图面的桌面窗口管理 (DWM) 可以呈现帧。
最后一步是在监视器上显示帧,这可能需要图形卡和显示设备之间的链接保护。 链接保护的一个示例是 High-Bandwidth 数字内容保护 (HDCP) 。 链接保护是使用 输出保护管理器 (OPM) 配置的。 本主题不介绍 OPM;有关详细信息,请参阅 使用输出保护管理器。
解码过程概述
在硬件加速解码期间,软件解码器必须将压缩的视频数据传递到图形卡。 对于高级内容,通常必须使用对称密钥加密对此数据进行加密,然后才能将其发送到 GPU。
为了加密视频进行解码,软件解码器使用以下接口:
- IDirectXVideoDecoder。 表示 DXVA 解码器设备,也称为加速器。
- IDirect3DCryptoSession9。 表示提供加密密钥的加密会话。
- IDirect3DAuthenticatedChannel9。 表示经过身份验证的通道,使软件解码器能够将加密会话与 DXVA 解码器相关联。
所有这些接口都是从 Direct3D 设备获取的,如下所示:
接口 | 创建 |
---|---|
IDirectXVideoDecoder | 调用 IDirectXVideoDecoderService::CreateVideoDecoder。 DXVA 解码器设备由 DXVA 配置文件 GUID 标识。 |
IDirect3DCryptoSession9 | 调用 IDirect3DDevice9Video::CreateCryptoSession。 |
IDirect3DAuthenticatedChannel9 | 调用 IDirect3DDevice9Video::CreateAuthenticatedChannel。 |
注意
若要获取指向 IDirect3DDevice9Video 接口的 指针,请在 D3D9Ex 设备上调用 QueryInterface 。
经过身份验证的通道在软件解码器和驱动程序之间提供受信任的信道。 通信通道的工作方式如下:
- 驱动程序提供根证书由 Microsoft 签名的 X.509 证书链。
- 证书包含驱动程序的 RSA 公钥。
- 软件解码器使用公钥向驱动程序发送 128 位 AES 会话密钥。
- 软件解码器将查询和命令发送到经过身份验证的通道。
- 会话密钥用于计算查询和命令 (MAC) 的消息身份验证代码。 驱动程序使用 MAC 验证查询/命令数据的完整性,软件解码器使用它们来验证驱动程序响应数据的完整性。
加密解码器的压缩视频缓冲区
下面是加密和解码过程的高级概述:
软件解码器从视频源接收加密数据流。 解码器解密此流。
软件解码器与加密会话协商会话密钥。
软件解码器使用经过身份验证的通道将加密会话与 DXVA 解码器设备相关联。
软件解码器将压缩数据放入从 DXVA 解码器设备 (加速器) 获取的 DXVA 缓冲区中。 对于受保护的内容,软件编码器使用会话密钥加密放入 DXVA 缓冲区的数据。
注意
某些驱动程序使用内容密钥而不是会话密钥进行加密。 内容键可能会从一个帧更改为下一个帧。
解码器将加密的压缩缓冲区提交到加速器。 对于 AES-CTR,解码器还会传递初始化向量。 如果使用内容密钥,解码器将传递内容密钥,并使用会话密钥进行加密。
Direct3D 具有对 128 位 AES-CTR 的标准支持,但旨在扩展到其他加密类型。
接下来的五个部分提供了更详细的步骤。
1. 查询驱动程序的内容保护功能
在尝试应用加密之前,请获取驱动程序的内容保护功能。
- 获取指向 Direct3D 9 设备的指针。
- 为 IDirect3DDevice9Video 接口调用 QueryInterface。
- 调用 IDirect3DDevice9Video::GetContentProtectionCaps。 此方法使用驱动程序的内容保护功能填充 D3DCONTENTPROTECTIONCAPS 结构。
具体而言,请查找以下功能:
- 如果 Caps 成员包含 D3DCPCAPS_SOFTWARE 或 D3DCPCAPS_HARDWARE 标志,驱动程序可以执行加密。
- KeyExchangeType 成员指定如何为会话密钥执行密钥交换。
- 如果 Caps 成员包含 D3DCPCAPS_CONTENTKEY 标志,驱动程序将使用单独的内容密钥进行加密。 生成会话密钥时,这一点非常重要。
Caps 成员中指示了其他功能。
2.配置经过身份验证的通道
下一步是配置经过身份验证的通道。
调用 IDirect3DDevice9Video::CreateAuthenticatedChannel 来创建经过身份验证的通道。 对于 ChannelType 参数,请指定与驱动程序功能匹配的通道类型。
- D3DAUTHENTICATEDCHANNEL_DRIVER_SOFTWARE通道类型对应于D3DCPCAPS_SOFTWARE。
- D3DAUTHENTICATEDCHANNEL_DRIVER_HARDWARE通道类型对应于D3DCPCAPS_HARDWARE。
CreateAuthenticatedChannel 方法返回指向 IDirect3DAuthenticatedChannel9 接口的指针以及通道的句柄。 句柄稍后用于将加密会话与经过身份验证的通道相关联。
调用 IDirect3DAuthenticatedChannel9::GetCertificateSize 以获取驱动程序的 X.509 证书的大小。 分配所需大小的缓冲区。
调用 IDirect3DAuthenticatedChannel9::GetCertificate 获取证书。 方法将证书复制到上一步中分配的缓冲区中。
验证驱动程序的证书是否已由 Microsoft 签名且尚未吊销。
从证书获取公钥。
生成随机 RSA 会话密钥。 此会话密钥用于对发送到经过身份验证的通道的数据进行签名。 使用驱动程序的公钥加密会话密钥。
调用 IDirect3DAuthenticatedChannel9::NegotiateKeyExchange 将加密的会话密钥发送到驱动程序。
初始化安全通道,如下所示:
- 按照文档中所述填写 D3DAUTHENTICATEDCHANNEL_CONFIGUREINITIALIZE 结构。
- 通过调用 IDirect3DAuthenticatedChannel9::Configure 发送D3DAUTHENTICATEDCONFIGURE_INITIALIZE命令,如发送经过身份验证的通道命令部分中所述。 此命令包含发送到经过身份验证的通道的命令和查询的起始序列号。
通过向经过身份验证的通道发送 D3DAUTHENTICATEDQUERY_CHANNELTYPE 查询来验证通道类型,如 发送经过身份验证的通道查询部分所述。 检查通道类型是否与 在 CreateAuthenticatedChannel 方法中指定的内容匹配。
3.配置加密会话
接下来,配置加密会话并建立会话密钥。
- 调用 IDirect3DDevice9Video::CreateCryptoSession 来创建加密会话。 此方法返回指向 IDirect3DCryptoSession9 接口的指针以及加密会话的句柄。
- 调用 IDirect3DCryptoSession9::GetCertificateSize 以获取驱动程序的 X.509 证书的大小。 分配所需大小的缓冲区。
- 调用 IDirect3DCryptoSession9::GetCertificate 获取证书。 方法将证书复制到上一步中分配的缓冲区中。
- 验证驱动程序的证书是否已由 Microsoft 签名且尚未吊销。
- 从证书获取公钥。
- 生成随机 RSA 会话密钥。 这是与经过身份验证的通道会话密钥不同的会话密钥。 使用驱动程序的公钥加密会话密钥。
- 调用 IDirect3DCryptoSession9::NegotiateKeyExchange 将加密的会话密钥发送到驱动程序。
- 如果内容保护功能包括 D3DCPCAPS_CONTENTKEY,请创建随机 RSA 内容密钥。 稍后将在解码过程中使用此函数。
4. 获取 DXVA 解码器设备的句柄
下一步需要 DXVA 解码器设备的句柄。 若要获取此句柄,请填充DXVA2_DecodeExecuteParams结构,如下所示:
HANDLE hDecodeDeviceHandle;
DXVA2_DecodeExecuteParams execParams = {0};
DXVA2_DecodeExtensionData ExtensionExecute = {0};
execParams.NumCompBuffers = 0;
execParams.pCompressedBuffers = NULL;
execParams.pExtensionData = &ExtensionExecute;
ExtensionExecute.Function = DXVA2_DECODE_GET_DRIVER_HANDLE;
ExtensionExecute.pPrivateInputData = NULL;
ExtensionExecute.PrivateInputDataSize = 0;
ExtensionExecute.pPrivateOutputData = &hDecodeDeviceHandle;
ExtensionExecute.PrivateOutputDataSize = sizeof(HANDLE);
将 DXVA2_DecodeExecuteParams 结构的 pExtensionData 成员设置为 DXVA2_DecodeExtensionData 结构的地址。
在 DXVA2_DecodeExtensionData 结构中,将 Function 成员设置为 DXVA2_DECODE_GET_DRIVER_HANDLE。 将 pPrivateOutputData 设置为足以存储 HANDLE 值的缓冲区的地址。 (在上一示例中,此缓冲区是 hDecodeDeviceHandle variable.)
然后调用 IDirectXVideoDecoder::Execute 并传入 DXVA2_DecodeExecuteParams 结构的地址。 DXVA 解码器的句柄在 pPrivateOutputData 中返回。
5. 将 DXVA 解码器与加密会话关联
接下来,将 DXVA 解码器设备与 Direct3D 设备和加密会话相关联,如下所示:
- 获取 DXVA 解码器设备的句柄,如上一部分所述。
- 通过将 D3DAUTHENTICATEDQUERY_DEVICEHANDLE 查询发送到经过身份验证的通道,获取 Direct3D 设备的句柄。
- 使用以下信息填写 D3DAUTHENTICATEDCHANNEL_CONFIGURECRYPTOSESSION 结构:
- 将 DXVA2DecodeHandle 成员设置为 DXVA 解码器设备的句柄。
- 将 CryptoSessionHandle 成员设置为加密会话的句柄。 此句柄由 IDirect3DDevice9Video::CreateCryptoSession 方法返回。
- 将 DeviceHandle 成员设置为 Direct3D 设备句柄。
- 调用 IDirect3DAuthenticatedChannel9::Configure 将 D3DAUTHENTICATEDCONFIGURE_CRYPTOSESSION 命令发送到经过身份验证的通道。
下图演示了句柄的交换:
软件解码器现在可以使用加密会话密钥来加密压缩的视频缓冲区。 每个压缩缓冲区都有自己的初始化向量 (IV) 在DXVA2_DecodeBufferDesc结构的 pvPVPState 成员中指定的。
发送经过身份验证的通道命令
定义了一组命令,用于配置经过身份验证的通道和设置各种内容保护。 有关命令的列表,请参阅 内容保护命令。
若要将命令发送到经过身份验证的通道,请执行以下步骤。
- 填写输入数据结构。 此数据结构始终是 D3DAUTHENTICATEDCHANNEL_CONFIGURE_INPUT 结构,后跟其他字段。 填写 D3DAUTHENTICATEDCHANNEL_CONFIGURE_INPUT 结构,如下表所示。
成员 | 说明 |
---|---|
omac | 暂时跳过此字段。 |
ConfigureType | 标识命令的 GUID。 有关命令的列表,请参阅 内容保护命令。 |
hChannel | 经过身份验证的通道的句柄。 |
SequenceNumber | 序列号。 第一个序列号是通过发送 D3DAUTHENTICATEDCONFIGURE_INITIALIZE 命令指定的。 每次发送另一个命令时,此数字递增 1。 序列号可防止重播攻击。
注意: 使用两个单独的序列号,一个用于命令,一个用于查询。 |
- 计算在输入结构的 omac 成员之后显示的数据块的 OMAC 标记。 然后将此标记值复制到 omac 成员中。
- 调用 IDirect3DAuthenticatedChannel9::Configure。
- 驱动程序将 命令的输出置于 D3DAUTHENTICATEDCHANNEL_CONFIGURE_OUTPUT 结构中。
- 计算在输出结构的 omac 成员之后显示的数据块的 OMAC 标记。 将此值与 omac 成员的值进行比较。 如果不匹配,则失败。
- 将输出结构中 ConfigureType、 hChannel 和 SequenceNumber 成员的值与这些成员的值进行比较。 如果不匹配,则失败。
- 递增下一个命令的序列号。
发送经过身份验证的通道查询
定义了一组查询,用于检索有关经过身份验证的通道的信息。 有关查询的列表,请参阅 内容保护查询。
若要将命令发送到经过身份验证的通道,请执行以下步骤。
- 填写输入数据结构。 此数据结构始终是 D3DAUTHENTICATEDCHANNEL_QUERY_INPUT 结构,可能后跟其他字段。 填写 D3DAUTHENTICATEDCHANNEL_QUERY_INPUT 结构,如下表所示。
成员 | 说明 |
---|---|
QueryType | 标识查询的 GUID。 有关查询的列表,请参阅 内容保护查询。 |
hChannel | 经过身份验证的通道的句柄。 |
SequenceNumber | 序列号。 第一个序列号是通过发送 D3DAUTHENTICATEDCONFIGURE_INITIALIZE 命令指定的。 每次发送另一个查询时,请将此数字递增 1。 序列号可防止重播攻击。
注意: 使用两个单独的序列号,一个用于命令,一个用于查询。 |
- 调用 IDirect3DAuthenticatedChannel9::Query。
- 驱动程序将查询的输出置于 D3DAUTHENTICATEDCHANNEL_QUERY_OUTPUT 结构中。 此结构后跟其他字段,具体取决于查询类型。
- 计算在输出结构的 omac 成员之后显示的数据块的 OMAC 标记。 将此值与 omac 成员的值进行比较。 如果不匹配,则失败。
- 将输出结构中 ConfigureType、 hChannel 和 SequenceNumber 成员的值与这些成员的值进行比较。 如果不匹配,则失败。
- 递增下一个查询的序列号。