OPM Örnek Kodu
Bu konu, Output Protection Managerkullanmaya yönelik örnek kod içerir.
Bu konudaki örnek kodda OPM el sıkışması gerçekleştirme, durum isteği gönderme ve OPM komutu gönderme işlemleri gösterilmektedir. Şifreleme işlemleri için kod Şifreleme API'sini kullanır: Yeni Nesil (CNG). Bu konunun odak noktası OPM işlevselliğini göstermektir, bu nedenle X.509 sertifikasıyla ilgili sertifikayı ayrıştırma ve doğrulama gibi görevler gösterilmez.
Bu konuda gösterilen yordamlar, Çıkış Koruma Yöneticisi'ni Kullanma başlığı altında daha ayrıntılı olarak açıklanmıştır.
- opm el sıkışmasını gerçekleştirmeyi
- OPM Durum İsteği gönderme
- opm komut göndermeyi
- OMAC-1 Değeri Hesaplama
- İlgili konular
OPM El Sıkışmasını Gerçekleştirme
OPM cihazlarını numaralandırdıktan ve bir video çıkışı seçtikten (gösterilmiyor) sonra ilk adım, cihazın X.509 sertifika zincirini almak için IOPMVideoOutput::StartInitializationçağırmaktır:
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; }
Uygulamanın sertifika zincirini doğrulaması ve ortak anahtarı zincirdeki yaprak sertifikadan alması gerekir. Bu adımlar burada gösterilmez.
Ortak anahtarı aldıktan sonra, anahtarı bir CNG algoritma sağlayıcısına aktarabilirsiniz. Sağlayıcıyı yüklemek için BCryptOpenAlgorithmProvider işlevini çağırın. Uygulama tanımlı
ImportRsaPublicKey
işlevi anahtarı içeri aktarır ve içeri aktarılan anahtara bir tanıtıcı döndürür: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; }
Ardından, başlangıç dizisi numaralarını ve AES oturum anahtarını içeren arabelleği hazırlayın.
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));
Sürücünün ortak anahtarını kullanarak bu arabelleği RSAES-OAEP şifreleme ile şifreleyin.
//-------------------------------------------------------------------- // 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; }
El sıkışmasını tamamlamak için IOPMVideoOutput::FinishInitializationçağırın.
// Complete the handshake. hr = pVideoOutput->FinishInitialization( (OPM_ENCRYPTED_INITIALIZATION_PARAMETERS *)pbDataOut ); if (FAILED(hr)) { goto done; }
OPM Durum İsteği Gönderme
Sonraki örnek, OPM_GET_CONNECTOR_TYPE durum isteğinin nasıl gönder yapılacağını gösterir.
OPM_GET_INFO_PARAMETERS bir yapıyı durum isteğinin bilgileriyle doldurun.
//-------------------------------------------------------------------- // 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; }
OPM_GET_INFO_PARAMETERS yapısının omac üyesi, yapının geri kalanı için hesaplanan tek anahtarlı bir CBC MAC'tir (OMAC). ComputeOMAC işlevi (daha sonra gösterilir) aşağıdaki gibi bildirilir:
HRESULT ComputeOMAC( OPM_RANDOM_NUMBER& AesKey, // Session key PUCHAR pb, // Data DWORD cb, // Size OPM_OMAC *pTag // Receives the OMAC );
Durum isteğini göndermek için IOPMVideoOutput::GetInformationçağırın.
// Send the status request. hr = pVideoOutput->GetInformation(&StatusInput, &StatusOutput); if (FAILED(hr)) { goto done; }
Sürücü yanıtı OPM_REQUESTED_INFORMATION yapısına yazar. Yanıt yapısı, yapının geri kalanı için hesaplanan bir OMAC değeri içerir. Yanıt verilerine güvenmeden önce bu değeri doğrulayın:
//-------------------------------------------------------------------- // 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++;
OPM_REQUESTED_INFORMATION yapısının abRequestedInformation üyesi yanıt verilerini içerir. OPM_GET_CONNECTOR_TYPE isteği için yanıt verileri OPM_STANDARD_INFORMATION bir yapıdan oluşur.
// 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;
OPM Komutu Gönderme
Sonraki örnekte, OPM_SET_PROTECTION_LEVEL komutunu göndererek High-Bandwidth Dijital İçerik Koruması'nın (HDCP) nasıl etkinleştirileceği gösterilmektedir.
Tüm OPM komutları, giriş verileri için OPM_CONFIGURE_PARAMETERS yapısını kullanır. Bu yapıdaki abParameters dizisi komuta özgü veriler içerir. OPM_SET_PROTECTION_LEVEL komutu için abParameters dizisi OPM_SET_PROTECTION_LEVEL_PARAMETERS bir yapı içerir. Bu yapıyı aşağıdaki gibi doldurun:
//-------------------------------------------------------------------- // 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;
Ardından OPM_CONFIGURE_PARAMETERS yapısını doldurun ve OMAC'yi hesap edin.
// 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; }
komutunu göndermek için IOPMVideoOutput::Configureçağırın. Her komutun ardından komut dizisi numarasını artırmayı unutmayın.
// 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++;
HDCP'nin etkin olduğunu doğrulamak için bir OPM_GET_VIRTUAL_PROTECTION_LEVEL durum isteği gönderin (gösterilmiyor).
OMAC-1 Değerini Hesaplama
Aşağıdaki kod, OPM komutunu ve istek yapılarını imzalamak için kullanılan OMAC-1 değerinin nasıl hesaplandığını gösterir.
// 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 algoritması https://www.nuee.nagoya-u.ac.jp/labs/tiwata/omac/omac.htmlbölümünde ayrıntılı olarak açıklanmıştır.
İlgili konular