Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Ten temat zawiera przykładowy kod do używania programu Output Protection Manager.
Przykładowy kod w tym temacie pokazuje, jak wykonać uzgadnianie opm, wysłać żądanie stanu i wysłać polecenie OPM. W przypadku operacji kryptograficznych kod używa interfejsu API kryptografii: Następnej generacji (CNG). Celem tego tematu jest pokazanie funkcji opm, więc zadania związane z certyfikatem X.509, takie jak analizowanie i weryfikowanie certyfikatu, nie są wyświetlane.
Procedury przedstawione w tym temacie zostały szczegółowo opisane w Using Output Protection Manager.
- wykonywanie uzgadniania OPM
- wysyłanie żądania stanu opm
- wysyłanie poleceń OPM
- obliczenia wartości OMAC-1
- Tematy pokrewne
Wykonywanie uzgadniania OPM
Po wyliczenie urządzeń OPM i wybraniu danych wyjściowych wideo (nie pokazano), pierwszym krokiem jest wywołanie IOPMVideoOutput::StartInitialization, aby uzyskać łańcuch certyfikatów X.509 urządzenia:
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; }Aplikacja musi zweryfikować łańcuch certyfikatów i pobrać klucz publiczny z certyfikatu liścia w łańcuchu. Te kroki nie są tutaj wyświetlane.
Po utworzeniu klucza publicznego możesz zaimportować klucz do dostawcy algorytmu CNG. Wywołaj funkcję BCryptOpenAlgorithmProvider, aby załadować dostawcę. Funkcja
ImportRsaPublicKeyzdefiniowana przez aplikację importuje klucz i zwraca uchwyt do zaimportowanego klucza: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; }Następnie przygotuj bufor zawierający numery sekwencji początkowej i klucz sesji 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));Zaszyfruj ten bufor przy użyciu szyfrowania RSAES-OAEP przy użyciu klucza publicznego sterownika.
//-------------------------------------------------------------------- // 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; }Wywołaj IOPMVideoOutput::FinishInitialization, aby ukończyć uzgadnianie.
// Complete the handshake. hr = pVideoOutput->FinishInitialization( (OPM_ENCRYPTED_INITIALIZATION_PARAMETERS *)pbDataOut ); if (FAILED(hr)) { goto done; }
Wysyłanie żądania stanu platformy OPM
W następnym przykładzie pokazano, jak wysłać żądanie stanu OPM_GET_CONNECTOR_TYPE.
Wypełnij strukturę OPM_GET_INFO_PARAMETERS z informacjami dotyczącymi żądania stanu.
//-------------------------------------------------------------------- // 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; }Element omac struktury OPM_GET_INFO_PARAMETERS jest jednym kluczem CBC MAC (OMAC) obliczonym dla pozostałej części struktury. Funkcja ComputeOMAC (pokazana później) jest zadeklarowana w następujący sposób:
HRESULT ComputeOMAC( OPM_RANDOM_NUMBER& AesKey, // Session key PUCHAR pb, // Data DWORD cb, // Size OPM_OMAC *pTag // Receives the OMAC );Wywołaj IOPMVideoOutput::GetInformation, aby wysłać żądanie stanu.
// Send the status request. hr = pVideoOutput->GetInformation(&StatusInput, &StatusOutput); if (FAILED(hr)) { goto done; }Sterownik zapisuje odpowiedź na strukturę OPM_REQUESTED_INFORMATION. Struktura odpowiedzi zawiera wartość OMAC obliczoną dla pozostałej części struktury. Przed zaufaniem danych odpowiedzi sprawdź tę wartość:
//-------------------------------------------------------------------- // 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++;Element abRequestedInformation struktury OPM_REQUESTED_INFORMATION zawiera dane odpowiedzi. W przypadku żądania OPM_GET_CONNECTOR_TYPE dane odpowiedzi składają się ze struktury 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;
Wysyłanie polecenia OPM
W następnym przykładzie pokazano, jak włączyć High-Bandwidth Digital Content Protection (HDCP) przez wysłanie polecenia OPM_SET_PROTECTION_LEVEL.
Wszystkie polecenia OPM używają struktury OPM_CONFIGURE_PARAMETERS danych wejściowych. Tablica abParameters w tej strukturze zawiera dane specyficzne dla polecenia. W przypadku polecenia OPM_SET_PROTECTION_LEVEL tablica abParameters zawiera strukturę OPM_SET_PROTECTION_LEVEL_PARAMETERS. Wypełnij tę strukturę w następujący sposób:
//-------------------------------------------------------------------- // 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;Następnie wypełnij strukturę OPM_CONFIGURE_PARAMETERS i oblicz 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; }Aby wysłać polecenie, wywołaj metodę IOPMVideoOutput::Configure. Pamiętaj, aby zwiększać numer sekwencji poleceń po każdym poleceniu.
// 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++;Aby sprawdzić, czy usługa HDCP jest włączona, wyślij żądanie stanu OPM_GET_VIRTUAL_PROTECTION_LEVEL (nie pokazano).
Obliczanie wartości OMAC-1
Poniższy kod przedstawia sposób obliczania wartości OMAC-1 używanej do podpisywania poleceń i struktur żądań opm.
// 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;
}
Algorytm OMAC-1 został szczegółowo opisany w https://www.nuee.nagoya-u.ac.jp/labs/tiwata/omac/omac.html.
Tematy pokrewne
-
programu Output Protection Manager