使用 Output Protection Manager

本主题介绍如何使用 Output Protection Manager (OPM) 在视频内容通过物理连接器传送到显示设备时保护内容。 本主题包含以下各节:

高级视频内容通常会进行加密,以防止未经授权的复制。 当然,必须先对视频进行解密,然后才能显示它。 然后,解密的未压缩帧必须通过物理连接器前往显示设备。 内容提供商可能要求在此时保护视频帧,即它们在物理连接器中传输时。

出于此目的,存在各种保护机制,包括用于数字输出的高带宽数字内容保护 (HDCP) 和 DisplayPort 内容保护 (DPCP);用于模拟输出的复制生成管理系统 - 模拟 (CGMS-A)。 通常,这些机制会在信号进入显示器之前对其进行加密或加扰。

OPM 使应用程序能够对视频输出强制实施内容保护机制。 使用 OPM 时,应用程序通过受信任的安全通道将命令和状态请求发送到图形驱动程序。 OPM 使应用程序能够:

  • 验证图形驱动程序是否已由 Microsoft 签名。
  • 使用驱动程序设置受信任的信道。
  • 对物理输出强制实施内容保护机制。

OPM 取代了认证的输出保护协议 (COPP),并使用类似的 API。 为了向后兼容,OPM 接口可以仿真 COPP 接口。 OPM 和 COPP 之间的差异包括:

  • OPM 使用 X.509 证书,而 COPP 使用专有证书格式。
  • OPM 支持 HDCP 中继器。
  • 使用 OPM 的应用程序不必分析 HDCP 系统可续订性消息 (SRM)。
  • 当图形显示正在使用克隆模式时,可以使用 OPM。 COPP 不支持克隆模式。

如果应用程序使用受保护的媒体路径 (PMP) 播放视频内容,则无需使用 OPM API,因为 PMP 会进行所有必需的 OPM 调用。 OPM API 适用于不使用 PMP 的应用程序。

OPM 在 Windows Vista 及更高版本中可用,但 API 直到 Windows 7 才公开。 要在应用程序中使用 OPM,必须具有 Windows 7 SDK 中的标头和库文件。 无需重新分发任何 DLL 就可以在 Windows Vista 或 Windows Server 2008 中使用 OPM。

视频输出

图形适配器可以具有多个物理输出,每个输出都有自己的功能。 在应用程序播放受保护的内容之前,它必须在与显示视频的显卡关联的每个视频输出上设置适当的保护机制。 要应用的保护机制将取决于内容的使用规则。

每个视频输出都由 IOPMVideoOutput 接口的一个实例表示。 可以使用 Direct3D 设备或监视器句柄来获取视频输出。

使用 Direct3D 设备:

  1. 获取应用程序将用于创建图面以保存视频帧的 Direct3D 设备的 IDirect3DDevice9 指针。
  2. 调用 OPMGetVideoOutputsFromIDirect3DDevice9Object 函数。 此函数会分配一个 IOPMVideoOutput 指针的数组,每个输出一个。

使用监视器句柄:

  1. 调用 EnumDisplayMonitors 以获取对应于视频窗口的 HMONITOR 句柄。 多个监视器可以与同一窗口相关联,因此你可能会获得多个 HMONITOR 句柄。
  2. 对于每个监视器句柄,调用 OPMGetVideoOutputsFromHMONITOR。 此函数会分配一个 IOPMVideoOutput 指针的数组,每个输出一个。

初始化 OPM 会话

在应用程序发送 OPM 命令或状态请求之前,它必须验证视频输出是否受信任并建立会话密钥。 会话密钥用于对应用程序与图形驱动程序之间交换的数据进行签名。

OPM API 会定义建立信任并设置会话密钥的握手。 必须为 IOPMVideoOutput 接口的每个实例执行此握手,如下所示:

  1. 调用 IOPMVideoOutput::StartInitialization。 此方法会检索两段数据:

    • 一个由驱动程序生成的随机数。 你将使用此数完成握手。
    • 驱动程序的 X.509 证书链。
  2. 验证证书链是否已由 Microsoft 签名。

    注意

    证书吊销在 OPM 的范围外。

     

  3. 从证书链获取驱动程序的公钥。

  4. 生成 128 位 AES 会话密钥。

  5. 生成两个随机 32 位数字:

    • OPM 状态请求的起始序列号。
    • OPM 命令的起始序列号。

    必须使用加密安全的伪随机数生成器(例如 CryptGenRandom)生成这些数字。

  6. 按照 IOPMVideoOutput::FinishInitialization 中所述,将驱动程序的随机数(在步骤 1 中获取)、会话密钥和两个序列号复制到 OPM_ENCRYPTED_INITIALIZATION_PARAMETERS 结构中。

  7. 使用驱动程序的公钥(可在驱动程序的证书中找到)通过 RSAES-OAEP 加密 OPM_ENCRYPTED_INITIALIZATION_PARAMETERS 结构。

  8. 调用 IOPMVideoOutput::FinishInitialization

发送 OPM 状态请求

OPM 状态请求会返回有关视频输出的信息,例如物理连接器的类型和当前保护级别。 有关请求类型的列表,请参阅 OPM 状态请求

要发送状态请求,请执行以下步骤。

  1. 初始化 OPM_GET_INFO_PARAMETERS 结构,如下表所示。

    成员 说明
    omac 现在先跳过此字段。
    rnRandomNumber 加密安全的 128 位随机数。 每当你发出状态请求时,始终要生成新的随机数,即使发出的请求相同。 将数字存储在变量中,因为稍后需要引用它。
    guidInformation 标识状态请求的 GUID。 如需状态请求的列表,请参阅 OPM 状态请求
    ulSequenceNumber 序列号。 对于第一个状态请求,请使用在 IOPMVideoOutput::FinishInitialization 方法中指定的起始序列号(初始化 OPM 会话的步骤 5)。每次发出另一个状态请求时,将此数字递增 1。
    abParameters 包含请求的其他输入数据的字节数组。 参考主题中为每个状态请求列出了输入数据的格式。
    cbParametersSize abParameters 数组中有效数据的大小。 数组的其余部分的内容未定义。

     

  2. 计算单密钥 CBC MAC (OMAC-1) 以计算在 omac 成员之后出现的数据块的哈希,然后将 omac 成员设置为此值。 请参阅 OPM 示例代码

  3. 调用 IOPMVideoOutput::GetInformation 方法。 传入指向 OPM_GET_INFO_PARAMETERS 结构的指针和指向 OPM_REQUESTED_INFORMATION 结构的指针。 驱动程序的响应会写入 OPM_REQUESTED_INFORMATION 结构。

    • 此结构的 omac 成员包含为此成员之后的数据计算的 OMAC。
    • abRequestedInformation 成员是一个字节数组,其中包含响应的输出数据。 参考主题中为每个状态请求列出了输出数据的格式。
  4. 计算 OPM_REQUESTED_INFORMATION 结构的 OMAC,不包括 omac 成员。 验证 OMAC 是否与 omac 成员中的值匹配。

  5. 请确保 OPM_REQUESTED_INFORMATION 结构的 cbRequestedInformationSize 成员为输出数据提供正确的大小。 例如,OPM_GET_CONNECTOR_TYPE 查询的输出数据是一种 OPM_STANDARD_INFORMATION 结构,因此 cbRequestedInformationSize 的值应为 sizeof(OPM_STANDARD_INFORMATION)

  6. OPM_REQUESTED_INFORMATION 结构的 abRequestedInformation 成员强制转换为正确的输出数据结构。 例如,如果状态请求为 OPM_GET_CONNECTOR_TYPE,请将 abRequestedInformation 强制转换为 OPM_STANDARD_INFORMATION 结构。

  7. 确保输出数据结构的 rnRandomNumber 成员与步骤 1 中 rnRandomNumber 的值匹配。

  8. 检查输出数据结构的 ulStatusFlags 成员,如处理禁用的视频输出中所述。

如果步骤 5-8 中的任何检查失败,应用程序必须停止显示受保护的内容。

发送 OPM 命令

OPM 命令用于对视频输出设置保护级别和其他设置。 发送 OPM 命令类似于发送状态请求,只不过没有来自驱动程序的响应数据。 如需命令列表,请参阅 OPM 命令

要发送 OPM 命令,请执行以下步骤。

  1. 填写 OPM_CONFIGURE_PARAMETERS 结构,如下表所示。

    成员 说明
    omac 现在先跳过此字段。
    guidSetting 标识命令的 GUID。 如需命令列表,请参阅 OPM 命令
    ulSequenceNumber 序列号。 对于第一个命令,请使用在 IOPMVideoOutput::FinishInitialization 方法中指定的起始序列号(初始化 OPM 会话的步骤 5)。每次发送另一个命令时,将此数字递增 1。
    abParameters 包含命令的其他输入数据的字节数组。 参考主题中为每个命令列出了输入数据的格式。
    cbSettingDataSize abParameters 数组中有效数据的大小。 数组的其余部分的内容未定义。

     

  2. 计算在 omac 成员之后出现的数据块的 OMAC,然后将 omac 成员设置为此值。

  3. 调用 IOPMVideoOutput::Configure

对于大多数命令,有一个相应的状态请求会返回命令的状态。 例如,OPM_SET_PROTECTION_LEVEL 命令会设置保护级别,OPM_GET_VIRTUAL_PROTECTION_LEVEL 命令会获取当前保护级别。

处理禁用的视频输出

视频输出随时可能会禁用自身,以防止对视频内容未经授权的使用。 这可能是因为保护机制停止工作,因为驱动程序检测到篡改,或者因为显示器与物理连接器断开连接。 禁用视频输出时,不会显示任何视频帧。

启用内容保护时,应用程序应定期(至少每 2 秒一次)执行以下步骤。

  1. 调用 IOPMVideoOutput::GetInformation 以发送 OPM_GET_ACTUAL_PROTECTION_LEVELOPM_GET_VIRTUAL_PROTECTION_LEVEL 状态请求。 这两个命令的返回数据是 OPM_STANDARD_INFORMATION 结构。
  2. 检查 OPM_STANDARD_INFORMATION 结构的 ulInformation 成员。 此成员包含一个标志,指示内容保护是否仍启用。 如果内容保护已关闭,请立即停止播放视频。
  3. 如果内容保护处于打开状态,请检查 OPM_STANDARD_INFORMATION 结构的 ulStatusFlags 成员。 如果未设置任何标志,视频输出将正常工作。 否则,将禁用视频输出。

ulStatusFlags 定义了以下标志。

标记 说明
OPM_STATUS_LINK_LOST 输出保护因某种原因停止工作,例如,显示设备可能从连接器断开。 停止播放并关闭所有输出保护机制。
OPM_STATUS_RENEGOTIATION_REQUIRED 应用程序必须重新建立 OPM 会话。 响应方式如下:
  1. 停止播放。
  2. 关闭所有保护机制。
  3. 释放 IOPMVideoOutput 接口。
  4. 重新创建所有视频图面。
  5. 创建新的 OPM 对象并尝试重新建立内容保护。 如果此操作失败,请向用户显示错误消息。 不要再播放任何视频内容。
OPM_STATUS_REVOKED_HDCP_DEVICE_ATTACHED 此标志仅在使用 HDCP 时适用,它指示存在已吊销的 HDCP 设备。 停止播放并关闭此视频输出上的所有保护机制。 设置此标志时,还会设置 OPM_STATUS_LINK_LOST 标志。
OPM_STATUS_REVOKED_HDCP_DEVICE_ATTACHED 驱动程序检测到篡改。 停止播放,不要再使用此视频输出播放任何视频。 最好停止使用任何其他视频输出,因为系统可能遭到了入侵。

 

使用 HDCP 保护内容

本部分介绍如何使用 OPM 启用 HDCP 输出保护。 下面是应用程序必须执行的步骤的一般概述。 本部分稍后会提供详细信息。

  1. 应用程序可能必须向视频输出提供 SRM。 接收 SRM 的机制在 OPM 接口的范围之外。 例如,作为广播流的一部分,可能会传送 SRM。
  2. 应用程序启用 HDCP 输出保护。
  3. 应用程序播放视频内容。 应用程序定期轮询驱动程序,以确保 HDCP 启用。
  4. 播放完成后,应用程序关闭 HDCP。

设置 SRM

要设置 SRM,请执行以下步骤。

  1. 使用 SRM 版本号初始化 OPM_SET_HDCP_SRM_PARAMETERS 结构。
  2. 将 SRM 存储在变量中。
  3. OPM_SET_HDCP_SRM 命令发送到视频输出。 使用发送 OPM 命令中所述的过程。
  4. OPM_GET_CURRENT_HDCP_SRM_VERSION 状态请求发送到视频输出。 使用发送 OPM 状态请求中所述的过程。 此状态请求没有输入数据,因此未定义 OPM_GET_INFO_PARAMETERS 结构的 abParameters 成员的内容。
  5. IOPMVideoOutput::GetInformation 方法返回时,OPM_REQUESTED_INFORMATION 结构中的 abRequestedInformation 数组包含 OPM_STANDARD_INFORMATION 结构。 此结构的 ulInformation 成员包含当前 SRM 的版本号。 此值必须等于步骤 2 中的值。

启用 HDCP

要启用 HDCP,请执行以下步骤。

  1. 使用以下值初始化 OPM_SET_PROTECTION_LEVEL_PARAMETERS 结构:
    • ulProtectionType = OPM_PROTECTION_TYPE_HDCP
    • ulProtectionLevel = OPM_HDCP_ON
    • Reserved = 0
    • Reserved2 = 0
  2. 发送 OPM_SET_PROTECTION_LEVEL 命令。 abParameters 数组中的输入数据是 OPM_SET_PROTECTION_LEVEL_PARAMETERS 结构。
  3. 发送 OPM_GET_VIRTUAL_PROTECTION_LEVEL 状态请求,以检查是否已启用 HDCP。 OPM_GET_INFO_PARAMETERS 结构的 abParameters 成员的前 4 个字节包含值 OPM_PROTECTION_TYPE_HDCP

GetInformation 方法返回时,OPM_REQUESTED_INFORMATION 结构中的 abRequestedInformation 数组包含 OPM_STANDARD_INFORMATION 结构。 此结构的 ulInformation 成员包含来自 OPM_HDCP_PROTECTION_LEVEL 枚举的值。 如果该值等于 OPM_HDCP_ON,则表示已启用 HDCP。 否则,请重复步骤 1-2,直到 HDCP 启用或发生错误。 (请记得每次递增序列号并生成新的随机数。)

启用 HDCP 通常需要 100 到 200 毫秒,但可能需要更长的时间。 在验证完之前,请勿假定 HDCP 已启用。

当应用程序完成播放受保护的内容时,请关闭 HDCP。 这些步骤与启用 HDCP 的步骤相同,但在步骤 1 中,请将 ulProtectionLevel 设置为 OPM_HDCP_OFF

注意

如果连接器类型为 OPM_CONNECTOR_TYPE_DISPLAYPORT_EMBEDDED,请不要启用 HDCP。 (请参阅 OPM 连接器类型标志。)

 

Output Protection Manager