COPP 세션 시작

[이 페이지와 연결된 기능인 DirectShow는 레거시 기능입니다. MediaPlayer, IMFMediaEngineMedia Foundation의 오디오/비디오 캡처로 대체되었습니다. 이러한 기능은 Windows 10 및 Windows 11 최적화되었습니다. 가능한 경우 새 코드에서 DirectShow 대신 MediaPlayer, IMFMediaEngine오디오/비디오 캡처를 사용하는 것이 좋습니다. 가능한 경우 레거시 API를 사용하는 기존 코드를 다시 작성하여 새 API를 사용하도록 제안합니다.]

COPP(Certified Output Protection Protocol) 세션을 시작하려면 다음 숫자의 연결이 포함된 배열인 서명을 준비해야 합니다.

  • 드라이버에서 반환한 128비트 난수입니다. (이 값은 드라이버의 인증서 체인 가져오기에서 guidRandom으로 표시됩니다.)
  • 128비트 대칭 AES 키입니다.
  • 상태 요청에 대한 32비트 시작 시퀀스 번호입니다.
  • COPP 명령에 대한 32비트 시작 시퀀스 번호입니다.

다음과 같이 대칭 AES 키를 생성합니다.

DWORD dwFlag = 0x80;         // Bit length: 128-bit AES.
dwFlag <<= 16;               // Move this value to the upper 16 bits.
dwFlag |= CRYPT_EXPORTABLE;  // We want to export the key.
CryptGenKey(
    hCSP,           // Handle to the CSP.
    CALG_AES_128,   // Use 128-bit AES block encryption algorithm.
    dwFlag,
    &m_hAESKey      // Receives a handle to the AES key.
);

CryptGenKey 함수는 대칭 키를 만들지만 키는 여전히 CSP에 유지됩니다. 키를 바이트 배열로 내보내려면 CryptExportKey 함수를 사용합니다. 이는 CryptGenKey를 호출할 때 CRYPT_EXPORTABLE 플래그를 사용하는 이유입니다. 다음 코드는 키를 내보내고 에 복사합니다.

pData

배열이 아닌 경우

DWORD cbData = 0; 
BYTE *pData = NULL;
// Get the size of the blob.
CryptExportKey(hAESKey, 0, PLAINTEXTKEYBLOB, 0, NULL, &cbData);  

// Allocate the array and call again.
pData = new BYTE[cbData];
CryptExportKey(hAESKey, 0, PLAINTEXTKEYBLOB, 0, pData, &cbData);  

에서 반환된 데이터

pData

레이아웃은 다음과 같습니다.

BLOBHEADER header
DWORD      cbSize
BYTE       key[]

그러나 이 레이아웃과 일치하는 구조체는 CryptoAPI 헤더에 정의되어 있지 않습니다. 하나를 정의하거나 일부 포인터 산술 연산을 수행할 수 있습니다. 예를 들어 키의 크기를 확인하려면 다음을 수행합니다.

DWORD *pcbKey = (DWORD*)(pData + sizeof(BLOBHEADER));
if (*pcbKey != 16)
{
    // Wrong size! Should be 16 bytes (128 bits).
}

키 자체에 대한 포인터를 얻으려면 다음을 수행합니다.

BYTE *pKey = pData + sizeof(BLOBHEADER) + sizeof(DWORD);

다음으로 COPP 상태 요청의 시작 시퀀스로 사용할 32비트 난수를 생성합니다. 난수를 만드는 권장 방법은 CryptGenRandom 함수를 호출하는 것입니다. 실제로 임의가 아니므로 C 런타임 라이브러리에서 rand 함수를 사용하지 마세요. COPP 명령의 시작 시퀀스로 사용할 두 번째 32비트 난수를 생성합니다.

UINT uStatusSeq;     // Status sequence number.
UINT uCommandSeq;    // Command sequence number.
CryptGenRandom(hCSP, sizeof(UINT), &uStatusSeq);
CryptGenRandom(hCSP, sizeof(UINT), &uCommandSeq);

이제 COPP 서명을 준비할 수 있습니다. AMCOPPSignature 구조체로 정의된 256 바이트 배열입니다. 배열의 내용을 0으로 초기화합니다. 그런 다음, 드라이버의 난수, AES 키, 상태 시퀀스 번호 및 명령 시퀀스 번호 등 네 개의 숫자를 배열에 해당 순서로 복사합니다. 마지막으로 전체 배열의 바이트 순서를 교환합니다.

CryptEncrypt에 대한 설명서에 따르면:

"RSA 키를 사용하여 **CryptEncrypt**를 호출하여 암호화할 수 있는 일반 텍스트 데이터의 길이는 키 모듈러스에서 11바이트를 뺀 길이입니다. PKCS \#1 패딩에 대해 11바이트가 선택된 최소값입니다."

이 경우 모듈러스는 256바이트이므로 최대 메시지 길이는 245바이트(256 ~ 11)입니다. AMCOPPSignature 구조체는 256바이트이지만 서명의 의미 있는 데이터는 40바이트에 불과합니다. 다음 코드는 서명을 암호화하고 결과를 제공합니다.

CoppSig

.

AMCOPPSignature CoppSig;
ZeroMemory(&CoppSig, sizeof(CoppSig));
// Copy the signature data into CoppSig. (Not shown.)

// Encrypt the signature:
const DWORD RSA_PADDING = 11;    // 11-byte padding.
DWORD cbDataOut = sizeof(AMCOPPSignature);
DWORD cbDataIn = cbDataOut - RSA_PADDING;
CryptEncrypt(
    hRSAKey, 
    NULL,     // No hash object.
    TRUE,     // Final block to encrypt.
    0,        // Reserved.
    &CoppSig, // COPP signature.
    &cbDataOut, 
    cbDataIn
);

이제 암호화된 배열을 IAMCertifiedOutputProtection::SessionSequenceStart에 전달합니다.

hr = pCOPP->SessionSequenceStart(&CoppSig);
if (SUCCEEDED(hr))
{
    // Ready to send COPP commands and status requests.
}

이 메서드가 성공하면 COPP 명령 및 상태 요청을 드라이버에 보낼 준비가 된 것입니다.

COPP(Certified Output Protection Protocol) 사용