Código de ejemplo de OPM
Este tema contiene código de ejemplo para usar el Administrador de protección de salida.
En el código de ejemplo de este tema se muestra cómo realizar el protocolo de enlace de OPM, enviar una solicitud de estado y enviar un comando OPM. Para las operaciones criptográficas, el código usa Cryptography API: Next Generation (CNG). El enfoque de este tema es mostrar la funcionalidad de OPM, por lo que no se muestran las tareas relacionadas con el certificado X.509, como el análisis y la validación del certificado.
Los procedimientos que se muestran en este tema se explican con más detalle en Uso del Administrador de protección de salida.
- Realización del protocolo de enlace de OPM
- Envío de una solicitud de estado de OPM
- Envío de un comando OPM
- Calcular el valor de OMAC-1
- Temas relacionados
Realización del protocolo de enlace de OPM
Después de enumerar los dispositivos OPM y seleccionar una salida de vídeo (no se muestra), el primer paso es llamar a IOPMVideoOutput::StartInitialization para obtener la cadena de certificados X.509 del dispositivo:
OPM_RANDOM_NUMBER random; // Random number from driver. ZeroMemory(&random, sizeof(random)); BYTE *pbCertificate = NULL; // Pointer to a buffer to hold the certificate. ULONG cbCertificate = 0; // Size of the certificate in bytes. PUBLIC_KEY_VALUES *pKey = NULL; // The driver's public key. // Get the driver's certificate chain + random number hr = pVideoOutput->StartInitialization( &random, &pbCertificate, &cbCertificate ); if (FAILED(hr)) { goto done; } // Validate the X.509 certificate. (Not shown.) hr = ValidateX509Certificate(pbCertificate, cbCertificate); if (FAILED(hr)) { goto done; } // Get the public key from the certificate. (Not shown.) hr = GetPublicKeyFromCertificate( pbCertificate, cbCertificate, &pKey ); if (FAILED(hr)) { goto done; } // Load and initialize a CNG provider (Cryptography API: Next Generation) BCRYPT_ALG_HANDLE hAlg = 0; hr = BCryptOpenAlgorithmProvider( &hAlg, BCRYPT_RSA_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0 ); if (FAILED(hr)) { goto done; } // Import the public key into the CNG provider. BCRYPT_KEY_HANDLE hPublicKey = 0; // Import the RSA public key. hr = ImportRsaPublicKey(hAlg, pKey, &hPublicKey); if (FAILED(hr)) { goto done; }
La aplicación debe validar la cadena de certificados y obtener la clave pública del certificado hoja de la cadena. Estos pasos no se muestran aquí.
Una vez que tenga la clave pública, puede importar la clave en un proveedor de algoritmos CNG. Llame a la función BCryptOpenAlgorithmProvider para cargar el proveedor. La función definida por
ImportRsaPublicKey
la aplicación importa la clave y devuelve un identificador a la clave importada:void ReverseMemCopy(BYTE *pbDest, BYTE const *pbSource, DWORD cb) { for (DWORD i = 0; i < cb; i++) { pbDest[cb - 1 - i] = pbSource[i]; } }
//------------------------------------------------------------------------ // // ImportRsaPublicKey // // Converts an RSA public key from an RSAPUBKEY blob into an // BCRYPT_RSAKEY_BLOB and sets the public key on the CNG provider. // //------------------------------------------------------------------------ HRESULT ImportRsaPublicKey( BCRYPT_ALG_HANDLE hAlg, // CNG provider PUBLIC_KEY_VALUES *pKey, // Pointer to the RSAPUBKEY blob. BCRYPT_KEY_HANDLE *phKey // Receives a handle the imported public key. ) { HRESULT hr = S_OK; BYTE *pbPublicKey = NULL; DWORD cbKey = 0; // Layout of the RSA public key blob: // +----------------------------------------------------------------+ // | BCRYPT_RSAKEY_BLOB | BE( dwExp ) | BE( Modulus ) | // +----------------------------------------------------------------+ // // sizeof(BCRYPT_RSAKEY_BLOB) cbExp cbModulus // <--------------------------><------------><----------------------> // // BE = Big Endian Format DWORD cbModulus = (pKey->rsapubkey.bitlen + 7) / 8; DWORD dwExp = pKey->rsapubkey.pubexp; DWORD cbExp = (dwExp & 0xFF000000) ? 4 : (dwExp & 0x00FF0000) ? 3 : (dwExp & 0x0000FF00) ? 2 : 1; BCRYPT_RSAKEY_BLOB *pRsaBlob; PBYTE pbCurrent; hr = DWordAdd(cbModulus, sizeof(BCRYPT_RSAKEY_BLOB), &cbKey); if (FAILED(hr)) { goto done; } cbKey += cbExp; pbPublicKey = (BYTE*)CoTaskMemAlloc(cbKey); if (NULL == pbPublicKey) { hr = E_OUTOFMEMORY; goto done; } ZeroMemory(pbPublicKey, cbKey); pRsaBlob = (BCRYPT_RSAKEY_BLOB *)(pbPublicKey); // Make the Public Key Blob Header pRsaBlob->Magic = BCRYPT_RSAPUBLIC_MAGIC; pRsaBlob->BitLength = pKey->rsapubkey.bitlen; pRsaBlob->cbPublicExp = cbExp; pRsaBlob->cbModulus = cbModulus; pRsaBlob->cbPrime1 = 0; pRsaBlob->cbPrime2 = 0; pbCurrent = (PBYTE)(pRsaBlob + 1); // Copy pubExp Big Endian ReverseMemCopy(pbCurrent, (PBYTE)&dwExp, cbExp); pbCurrent += cbExp; // Copy Modulus Big Endian ReverseMemCopy(pbCurrent, pKey->modulus, cbModulus); // Set the key. hr = BCryptImportKeyPair( hAlg, NULL, BCRYPT_RSAPUBLIC_BLOB, phKey, (PUCHAR)pbPublicKey, cbKey, 0 ); done: CoTaskMemFree(pbPublicKey); return hr; }
A continuación, prepare el búfer que contiene los números de secuencia iniciales y la clave de sesión de AES.
void CopyAndAdvancePtr(BYTE*& pDest, const BYTE* pSrc, DWORD cb) { memcpy(pDest, pSrc, cb); pDest += cb; }
//-------------------------------------------------------------------- // Prepare the signature for key exchnage. //-------------------------------------------------------------------- UINT uStatusSeq = 0; // Status sequence number. UINT uCommandSeq = 0; // Command sequence number. OPM_RANDOM_NUMBER AesKey; // Session key // Generate the starting sequence number for queries. hr = BCryptGenRandom( NULL, (BYTE*)&uStatusSeq, sizeof(UINT), BCRYPT_USE_SYSTEM_PREFERRED_RNG ); if (FAILED(hr)) { goto done; } // Generate the starting sequence number for commands. hr = BCryptGenRandom( NULL, (BYTE*)&uCommandSeq, sizeof(UINT), BCRYPT_USE_SYSTEM_PREFERRED_RNG ); if (FAILED(hr)) { goto done; } // Generate the AES session key. hr = BCryptGenRandom( NULL, (BYTE*)&AesKey, sizeof(AesKey), BCRYPT_USE_SYSTEM_PREFERRED_RNG ); if (FAILED(hr)) { goto done; } // Fill in the initialization structure. OPM_ENCRYPTED_INITIALIZATION_PARAMETERS initParams; ZeroMemory(&initParams, sizeof(initParams)); // Use a temporary pointer for copying into the array. BYTE *pBuffer = &initParams.abEncryptedInitializationParameters[0]; CopyAndAdvancePtr(pBuffer, random.abRandomNumber, sizeof(random)); // Random number from the friver. CopyAndAdvancePtr(pBuffer, AesKey.abRandomNumber, sizeof(AesKey)); // Session key. CopyAndAdvancePtr(pBuffer, (BYTE*)&uStatusSeq, sizeof(uStatusSeq)); CopyAndAdvancePtr(pBuffer, (BYTE*)&uCommandSeq, sizeof(uCommandSeq));
Cifre este búfer con cifrado RSAES-OAEP mediante la clave pública del controlador.
//-------------------------------------------------------------------- // RSAES-OAEP encrypt the signature. Use SHA2 hashing algorithm. //-------------------------------------------------------------------- PBYTE pbDataIn = &initParams.abEncryptedInitializationParameters[0]; ULONG cbDataIn = (ULONG)(pBuffer - pbDataIn); DWORD cbOutput = 0; DWORD cbDataOut= 0; BYTE *pbDataOut = NULL; BCRYPT_OAEP_PADDING_INFO paddingInfo; ZeroMemory(&paddingInfo, sizeof(paddingInfo)); paddingInfo.pszAlgId = BCRYPT_SHA512_ALGORITHM; //Encrypt the signature. hr = BCryptEncrypt( hPublicKey, (PUCHAR)pbDataIn, cbDataIn, &paddingInfo, NULL, 0, NULL, 0, &cbOutput, BCRYPT_PAD_OAEP ); if (FAILED(hr)) { goto done; } pbDataOut = new (std::nothrow) BYTE[cbOutput]; if (NULL == pbDataOut) { hr = E_OUTOFMEMORY; goto done; } hr = BCryptEncrypt( hPublicKey, (PUCHAR)pbDataIn, cbDataIn, &paddingInfo, NULL, 0, pbDataOut, cbOutput, &cbDataOut, BCRYPT_PAD_OAEP ); if (FAILED(hr)) { goto done; }
Llame a IOPMVideoOutput::FinishInitialization para completar el protocolo de enlace.
// Complete the handshake. hr = pVideoOutput->FinishInitialization( (OPM_ENCRYPTED_INITIALIZATION_PARAMETERS *)pbDataOut ); if (FAILED(hr)) { goto done; }
Envío de una solicitud de estado de OPM
En el ejemplo siguiente se muestra cómo enviar la solicitud de estado OPM_GET_CONNECTOR_TYPE .
Rellene una estructura de OPM_GET_INFO_PARAMETERS con la información de la solicitud de estado.
//-------------------------------------------------------------------- // Prepare the status request structure. //-------------------------------------------------------------------- OPM_GET_INFO_PARAMETERS StatusInput; OPM_REQUESTED_INFORMATION StatusOutput; ZeroMemory(&StatusInput, sizeof(StatusInput)); ZeroMemory(&StatusOutput, sizeof(StatusOutput)); hr = BCryptGenRandom( NULL, (BYTE*)&(StatusInput.rnRandomNumber), OPM_128_BIT_RANDOM_NUMBER_SIZE, BCRYPT_USE_SYSTEM_PREFERRED_RNG ); if (FAILED(hr)) { goto done; } StatusInput.guidInformation = OPM_GET_CONNECTOR_TYPE; // Request GUID. StatusInput.ulSequenceNumber = uStatusSeq; // Sequence number. // Sign the request structure, not including the omac field. hr = ComputeOMAC( AesKey, // Session key. (BYTE*)&StatusInput + OPM_OMAC_SIZE, // Data sizeof(OPM_GET_INFO_PARAMETERS) - OPM_OMAC_SIZE, // Size &StatusInput.omac // Receives the OMAC ); if (FAILED(hr)) { goto done; }
El miembro omac de la estructura OPM_GET_INFO_PARAMETERS es un CBC MAC (OMAC) de clave única calculado para el resto de la estructura. La función ComputeOMAC (que se muestra más adelante) se declara de la siguiente manera:
HRESULT ComputeOMAC( OPM_RANDOM_NUMBER& AesKey, // Session key PUCHAR pb, // Data DWORD cb, // Size OPM_OMAC *pTag // Receives the OMAC );
Llame a IOPMVideoOutput::GetInformation para enviar la solicitud de estado.
// Send the status request. hr = pVideoOutput->GetInformation(&StatusInput, &StatusOutput); if (FAILED(hr)) { goto done; }
El controlador escribe la respuesta en la estructura OPM_REQUESTED_INFORMATION . La estructura de respuesta incluye un valor OMAC, calculado durante el resto de la estructura. Compruebe este valor antes de confiar en los datos de respuesta:
//-------------------------------------------------------------------- // Verify the signature. //-------------------------------------------------------------------- OPM_OMAC rgbSignature = { 0 }; // Calculate our own signature. hr = ComputeOMAC( AesKey, (BYTE*)&StatusOutput + OPM_OMAC_SIZE, sizeof(OPM_REQUESTED_INFORMATION) - OPM_OMAC_SIZE, &rgbSignature ); if (FAILED(hr)) { goto done; } if (memcmp(StatusOutput.omac.abOMAC, rgbSignature.abOMAC, OPM_OMAC_SIZE)) { // The signature does not match. hr = E_FAIL; goto done; } // Update the sequence number. uStatusSeq++;
El miembro abRequestedInformation de la estructura OPM_REQUESTED_INFORMATION contiene los datos de respuesta. Para la solicitud OPM_GET_CONNECTOR_TYPE , los datos de respuesta constan de una estructura de OPM_STANDARD_INFORMATION .
// Examine the response. // The response data is an OPM_STANDARD_INFORMATION structure. OPM_STANDARD_INFORMATION StatusInfo; ZeroMemory(&StatusInfo, sizeof(StatusInfo)); ULONG cbLen = min(sizeof(OPM_STANDARD_INFORMATION), StatusOutput.cbRequestedInformationSize); if (cbLen != 0) { // Copy the repinse into the array. CopyMemory((BYTE*)&StatusInfo, StatusOutput.abRequestedInformation, cbLen); } // Verify the random number. if (0!= memcmp( (BYTE*)&StatusInfo.rnRandomNumber, (BYTE*)&StatusInput.rnRandomNumber, sizeof(OPM_RANDOM_NUMBER)) ) { hr = E_FAIL; goto done; } // Verify the status of the OPM session. if (StatusInfo.ulStatusFlags != OPM_STATUS_NORMAL) { // Abnormal status hr = E_FAIL; goto done; } ULONG ConnectorType = StatusInfo.ulInformation & OPM_BUS_TYPE_MASK;
Envío de un comando OPM
En el ejemplo siguiente se muestra cómo habilitar High-Bandwidth Protección de contenido digital (HDCP) mediante el envío del comando OPM_SET_PROTECTION_LEVEL .
Todos los comandos de OPM usan la estructura OPM_CONFIGURE_PARAMETERS para los datos de entrada. La matriz abParameters de esta estructura contiene datos específicos del comando. Para el comando OPM_SET_PROTECTION_LEVEL , la matriz abParameters contiene una estructura de OPM_SET_PROTECTION_LEVEL_PARAMETERS . Rellene esta estructura de la siguiente manera:
//-------------------------------------------------------------------- // Prepare the command structure. //-------------------------------------------------------------------- // Data specific to the OPM_SET_PROTECTION_LEVEL command. OPM_SET_PROTECTION_LEVEL_PARAMETERS CommandInput; ZeroMemory(&CommandInput, sizeof(CommandInput)); CommandInput.ulProtectionType = OPM_PROTECTION_TYPE_HDCP; CommandInput.ulProtectionLevel = OPM_HDCP_ON; ULONG ulAdditionalParametersSize = 0; BYTE* pbAdditionalParameters = NULL;
A continuación, rellene la estructura de OPM_CONFIGURE_PARAMETERS y calcule el OMAC.
// Common command parameters OPM_CONFIGURE_PARAMETERS Command; ZeroMemory(&Command, sizeof(Command)); Command.guidSetting = OPM_SET_PROTECTION_LEVEL; Command.ulSequenceNumber = uCommandSeq; Command.cbParametersSize = sizeof(OPM_SET_PROTECTION_LEVEL_PARAMETERS); CopyMemory(&Command.abParameters[0], (BYTE*)&CommandInput, Command.cbParametersSize); // Sign the command structure, not including the omac field. hr = ComputeOMAC( AesKey, (BYTE*)&Command + OPM_OMAC_SIZE, sizeof(OPM_CONFIGURE_PARAMETERS) - OPM_OMAC_SIZE, &Command.omac ); if (FAILED(hr)) { goto done; }
Para enviar el comando, llame a IOPMVideoOutput::Configure. Recuerde incrementar el número de secuencia de comandos después de cada comando.
// Send the command. hr = pVideoOutput->Configure( &Command, 0, // Size of additional command data. NULL // Additional command data. ); if (FAILED(hr)) { goto done; } // Update the sequence number. uCommandSeq++;
Para comprobar que HDCP está habilitado, envíe una solicitud de estado de OPM_GET_VIRTUAL_PROTECTION_LEVEL (no se muestra).
Calcular el valor de OMAC-1
En el código siguiente se muestra cómo calcular el valor OMAC-1 que se usa para firmar el comando OPM y las estructuras de solicitud.
// Helper functions for some bitwise operations.
#define AES_BLOCKLEN (16)
#define AES_KEYSIZE_128 (16)
inline void XOR(
BYTE *lpbLHS,
const BYTE *lpbRHS,
DWORD cbSize = AES_BLOCKLEN
)
{
for( DWORD i = 0; i < cbSize; i++ )
{
lpbLHS[i] ^= lpbRHS[i];
}
}
inline void LShift(const BYTE *lpbOpd, BYTE *lpbRes)
{
for( DWORD i = 0; i < AES_BLOCKLEN; i++ )
{
lpbRes[i] = lpbOpd[i] << 1;
if( i < AES_BLOCKLEN - 1 )
{
lpbRes[i] |= ( (unsigned char)lpbOpd[i+1] ) >> 7;
}
}
}
// Generate OMAC1 signature using AES128
HRESULT ComputeOMAC(
OPM_RANDOM_NUMBER& AesKey, // Session key
PUCHAR pb, // Data
DWORD cb, // Size of the data
OPM_OMAC *pTag // Receives the OMAC
)
{
HRESULT hr = S_OK;
BCRYPT_ALG_HANDLE hAlg = NULL;
BCRYPT_KEY_HANDLE hKey = NULL;
DWORD cbKeyObject = 0;
DWORD cbData = 0;
PBYTE pbKeyObject = NULL;
PUCHAR Key = (PUCHAR)AesKey.abRandomNumber;
struct
{
BCRYPT_KEY_DATA_BLOB_HEADER Header;
UCHAR Key[AES_KEYSIZE_128];
} KeyBlob;
KeyBlob.Header.dwMagic = BCRYPT_KEY_DATA_BLOB_MAGIC;
KeyBlob.Header.dwVersion = BCRYPT_KEY_DATA_BLOB_VERSION1;
KeyBlob.Header.cbKeyData = AES_KEYSIZE_128;
CopyMemory(KeyBlob.Key, Key, sizeof(KeyBlob.Key));
BYTE rgbLU[OPM_OMAC_SIZE];
BYTE rgbLU_1[OPM_OMAC_SIZE];
BYTE rBuffer[OPM_OMAC_SIZE];
hr = BCryptOpenAlgorithmProvider(
&hAlg,
BCRYPT_AES_ALGORITHM,
MS_PRIMITIVE_PROVIDER,
0
);
// Get the size needed for the key data
if (S_OK == hr)
{
hr = BCryptGetProperty(
hAlg,
BCRYPT_OBJECT_LENGTH,
(PBYTE)&cbKeyObject,
sizeof(DWORD),
&cbData,
0
);
}
// Allocate the key data object
if (S_OK == hr)
{
pbKeyObject = new (std::nothrow) BYTE[cbKeyObject];
if (NULL == pbKeyObject)
{
hr = E_OUTOFMEMORY;
}
}
// Set to CBC chain mode
if (S_OK == hr)
{
hr = BCryptSetProperty(
hAlg,
BCRYPT_CHAINING_MODE,
(PBYTE)BCRYPT_CHAIN_MODE_CBC,
sizeof(BCRYPT_CHAIN_MODE_CBC),
0
);
}
// Set the key
if (S_OK == hr)
{
hr = BCryptImportKey(hAlg, NULL, BCRYPT_KEY_DATA_BLOB, &hKey,
pbKeyObject, cbKeyObject, (PUCHAR)&KeyBlob, sizeof(KeyBlob), 0);
}
// Encrypt 0s
if (S_OK == hr)
{
DWORD cbBuffer = sizeof(rBuffer);
ZeroMemory(rBuffer, sizeof(rBuffer));
hr = BCryptEncrypt(hKey, rBuffer, cbBuffer, NULL, NULL, 0,
rBuffer, sizeof(rBuffer), &cbBuffer, 0);
}
// Compute OMAC1 parameters
if (S_OK == hr)
{
const BYTE bLU_ComputationConstant = 0x87;
LPBYTE pbL = rBuffer;
LShift( pbL, rgbLU );
if( pbL[0] & 0x80 )
{
rgbLU[OPM_OMAC_SIZE - 1] ^= bLU_ComputationConstant;
}
LShift( rgbLU, rgbLU_1 );
if( rgbLU[0] & 0x80 )
{
rgbLU_1[OPM_OMAC_SIZE - 1] ^= bLU_ComputationConstant;
}
}
// Generate the hash.
if (S_OK == hr)
{
// Redo the key to restart the CBC.
BCryptDestroyKey(hKey);
hKey = NULL;
hr = BCryptImportKey(hAlg, NULL, BCRYPT_KEY_DATA_BLOB, &hKey,
pbKeyObject, cbKeyObject, (PUCHAR)&KeyBlob, sizeof(KeyBlob), 0);
}
if (S_OK == hr)
{
PUCHAR pbDataInCur = pb;
cbData = cb;
do
{
DWORD cbBuffer = 0;
if (cbData > OPM_OMAC_SIZE)
{
CopyMemory( rBuffer, pbDataInCur, OPM_OMAC_SIZE );
hr = BCryptEncrypt(hKey, rBuffer, sizeof(rBuffer), NULL,
NULL, 0, rBuffer, sizeof(rBuffer), &cbBuffer, 0);
pbDataInCur += OPM_OMAC_SIZE;
cbData -= OPM_OMAC_SIZE;
}
else
{
if (cbData == OPM_OMAC_SIZE)
{
CopyMemory(rBuffer, pbDataInCur, OPM_OMAC_SIZE);
XOR(rBuffer, rgbLU);
}
else
{
ZeroMemory( rBuffer, OPM_OMAC_SIZE );
CopyMemory( rBuffer, pbDataInCur, cbData );
rBuffer[ cbData ] = 0x80;
XOR(rBuffer, rgbLU_1);
}
hr = BCryptEncrypt(hKey, rBuffer, sizeof(rBuffer), NULL, NULL,
0, (PUCHAR)pTag->abOMAC, OPM_OMAC_SIZE, &cbBuffer, 0);
cbData = 0;
}
} while( S_OK == hr && cbData > 0 );
}
// Clean up
if (hKey)
{
BCryptDestroyKey(hKey);
}
if (hAlg)
{
BCryptCloseAlgorithmProvider(hAlg, 0);
}
delete [] pbKeyObject;
return hr;
}
El algoritmo OMAC-1 se describe en detalle en https://www.nuee.nagoya-u.ac.jp/labs/tiwata/omac/omac.html.
Temas relacionados