Not
Åtkomst till denna sida kräver auktorisation. Du kan prova att logga in eller byta katalog.
Åtkomst till denna sida kräver auktorisation. Du kan prova att byta katalog.
Det här avsnittet innehåller exempelkod för att använda Output Protection Manager.
Exempelkoden i det här avsnittet visar hur du utför OPM-handskakningen, skickar en statusbegäran och skickar ett OPM-kommando. För kryptografiska åtgärder använder koden kryptografi-API: Nästa generation (CNG). Fokus för det här avsnittet är att visa OPM-funktioner, så uppgifter som är relaterade till X.509-certifikatet, till exempel parsning och validering av certifikatet, visas inte.
De procedurer som visas i det här avsnittet beskrivs mer detaljerat i Using Output Protection Manager.
- Utföra OPM-handskakningen
- Skickar en OPM-statusbegäran
- skicka ett OPM-kommando
- Beräkning av OMAC-1-värdet
- Relaterade ämnen
Utföra OPM-handskakningen
När du har räknat upp OPM-enheterna och valt videoutdata (visas inte) är det första steget att anropa IOPMVideoOutput::StartInitialization för att hämta enhetens X.509-certifikatkedja:
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; }Programmet måste verifiera certifikatkedjan och hämta den offentliga nyckeln från lövcertifikatet i kedjan. Dessa steg visas inte här.
När du har den offentliga nyckeln kan du importera nyckeln till en CNG-algoritmprovider. Anropa funktionen BCryptOpenAlgorithmProvider för att läsa in providern. Den programdefinierade
ImportRsaPublicKey-funktionen importerar nyckeln och returnerar ett handtag till den importerade nyckeln: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; }Förbered sedan bufferten som innehåller startsekvensnumren och AES-sessionsnyckeln.
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));Kryptera bufferten med RSAES-OAEP kryptering med hjälp av drivrutinens offentliga nyckel.
//-------------------------------------------------------------------- // 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; }Anropa IOPMVideoOutput::FinishInitialization för att slutföra handskakningen.
// Complete the handshake. hr = pVideoOutput->FinishInitialization( (OPM_ENCRYPTED_INITIALIZATION_PARAMETERS *)pbDataOut ); if (FAILED(hr)) { goto done; }
Skicka en OPM-statusbegäran
I nästa exempel visas hur du skickar OPM_GET_CONNECTOR_TYPE statusbegäran.
Fyll i en OPM_GET_INFO_PARAMETERS struktur med information för statusbegäran.
//-------------------------------------------------------------------- // 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; }Den omac- medlem i OPM_GET_INFO_PARAMETERS-strukturen är en ennyckels CBC MAC (OMAC) som beräknas för resten av strukturen. Funktionen ComputeOMAC (visas senare) deklareras på följande sätt:
HRESULT ComputeOMAC( OPM_RANDOM_NUMBER& AesKey, // Session key PUCHAR pb, // Data DWORD cb, // Size OPM_OMAC *pTag // Receives the OMAC );Anropa IOPMVideoOutput::GetInformation för att skicka statusbegäran.
// Send the status request. hr = pVideoOutput->GetInformation(&StatusInput, &StatusOutput); if (FAILED(hr)) { goto done; }Drivrutinen skriver svaret på OPM_REQUESTED_INFORMATION struktur. Svarsstrukturen innehåller ett OMAC-värde som beräknas för resten av strukturen. Kontrollera det här värdet innan du litar på svarsdata:
//-------------------------------------------------------------------- // 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++;abRequestedInformation medlem i OPM_REQUESTED_INFORMATION-strukturen innehåller svarsdata. För OPM_GET_CONNECTOR_TYPE begäran består svarsdata av en OPM_STANDARD_INFORMATION struktur.
// 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;
Skicka ett OPM-kommando
I nästa exempel visas hur du aktiverar High-Bandwidth HDCP (Digital Content Protection) genom att skicka kommandot OPM_SET_PROTECTION_LEVEL.
Alla OPM-kommandon använder den OPM_CONFIGURE_PARAMETERS strukturen för indata. abParameters matris i den här strukturen innehåller kommandospecifika data. För kommandot OPM_SET_PROTECTION_LEVEL innehåller matrisen abParameters en OPM_SET_PROTECTION_LEVEL_PARAMETERS struktur. Fyll i den här strukturen på följande sätt:
//-------------------------------------------------------------------- // 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;Fyll sedan i OPM_CONFIGURE_PARAMETERS struktur och beräkna 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; }Om du vill skicka kommandot anropar du IOPMVideoOutput::Configure. Kom ihåg att öka kommandosekvensnumret efter varje kommando.
// 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++;Om du vill kontrollera att HDCP är aktiverat skickar du en OPM_GET_VIRTUAL_PROTECTION_LEVEL statusbegäran (visas inte).
Beräkna OMAC-1-värdet
Följande kod visar hur du beräknar omac-1-värdet som används för att signera OPM-kommando- och begärandestrukturerna.
// 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;
}
OMAC-1-algoritmen beskrivs i detalj på https://www.nuee.nagoya-u.ac.jp/labs/tiwata/omac/omac.html.
Relaterade ämnen