Test Key Control Block with HDCP Version

This is a copy of the Widevine CL:
https://widevine-internal-review.googlesource.com/#/c/9480/

This change is part of OEMCrypto API version 9.

This CL adds verification that a key control block which requires a
specific version of HDCP can be loaded.  Also, if secure data path is
not set, it verifies that data is still decrypted.

This CL also adds test that verify DecryptCTR fails when the current
HDCP version is below that in the key control block.  The expected
error is OEMCrypto_ERROR_INSUFFICIENT_HDCP.  This error code is newly
introduced in this CL.

This is one attempt to clarify HDCP, as specified in b/13626021, and
is a slight modification from previous behavior for the mock and the
level 3 haystacked code.

This CL also tests the two valid verification codes "kctl"
and "kc09".

bug: 13626021
Change-Id: If380709d2306a3489470b29fb148a45b609b089d
This commit is contained in:
Fred Gylys-Colwell
2014-04-10 16:22:25 -07:00
parent 45dc595f92
commit 026a04701e
9 changed files with 188 additions and 16 deletions

View File

@@ -58,6 +58,7 @@ typedef enum OEMCryptoResult {
OEMCrypto_ERROR_INVALID_RSA_KEY = 35,
OEMCrypto_ERROR_KEY_EXPIRED = 36,
OEMCrypto_ERROR_INSUFFICIENT_RESOURCES = 37,
OEMCrypto_ERROR_INSUFFICIENT_HDCP = 38,
} OEMCryptoResult;
/*
@@ -889,7 +890,7 @@ OEMCryptoResult OEMCrypto_SelectKey(const OEMCrypto_SESSION session,
* then the current version of HDCP for the device and the display combined will
* be compared against the version specified in the control block. If the
* current version is not at least as high as that in the control block, then
* return OEMCrypto_ERROR_DECRYPT_FAILED.
* return OEMCrypto_ERROR_INSUFFICIENT_HDCP.
*
* 1. If the current session has an entry in the Usage Table, and the status of
* that entry is “inactive”, then return OEMCrypto_ERROR_INVALID_SESSION.[h]
@@ -935,6 +936,7 @@ OEMCryptoResult OEMCrypto_SelectKey(const OEMCrypto_SESSION session,
* OEMCrypto_ERROR_INVALID_CONTEXT
* OEMCrypto_ERROR_DECRYPT_FAILED
* OEMCrypto_ERROR_KEY_EXPIRED
* OEMCrypto_ERROR_INSUFFICIENT_HDCP
* OEMCrypto_ERROR_INSUFFICIENT_RESOURCES
* OEMCrypto_ERROR_UNKNOWN_FAILURE
*
@@ -1609,12 +1611,10 @@ OEMCryptoResult OEMCrypto_Generic_Encrypt(OEMCrypto_SESSION session,
* not, return OEMCrypto_ERROR_DECRYPT_FAILED.
* 2. If the current keys control block has the Data_Path_Type bit set, then
* return OEMCrypto_ERROR_DECRYPT_FAILED.
* 1. If the current keys control block has the HDCP bit set, then return
* OEMCrypto_ERROR_DECRYPT_FAILED.
* 2. If the current keys control block has a nonzero Duration field, then the
* 3. If the current keys control block has a nonzero Duration field, then the
* API shall verify that the duration is greater than the sessions elapsed time
* clock. If not, return OEMCrypto_ERROR_KEY_EXPIRED.
* 3. If the current session has an entry in the Usage Table, and the status of
* 4. If the current session has an entry in the Usage Table, and the status of
* that entry is “inactive”, then return OEMCrypto_ERROR_INVALID_SESSION.
*
* Parameters:

View File

@@ -317,7 +317,6 @@ bool SessionContext::GenerateRSASignature(const uint8_t* message,
} else { // Bad RSA_Padding_Scheme
return false;
}
return true;
}
@@ -366,7 +365,22 @@ bool SessionContext::ParseKeyControl(
LOGD(" valid: %d", key_control_block.valid());
LOGD(" duration: %d", key_control_block.duration());
LOGD(" nonce: %08X", key_control_block.nonce());
LOGD(" magic: %08X", key_control_block.verification());
LOGD(" bits: %08X", key_control_block.control_bits());
switch(key_control_block.control_bits() & kControlReplayMask) {
case kControlNonceRequired:
LOGD(" bits kControlReplay kControlNonceRequired.");
break;
case kControlNonceOrEntry:
LOGD(" bits kControlReplay kControlNonceOrEntry.");
break;
default:
LOGD(" bits kControlReplay unset.");
break;
}
LOGD(" bits kControlKDCPVersion 0x%02x.",
(key_control_block.control_bits() & kControlHDCPVersionMask)
>> kControlHDCPVersionShift);
LOGD(" bit kControlAllowEncrypt %s.",
(key_control_block.control_bits() & kControlAllowEncrypt) ? "set" : "unset");
LOGD(" bit kControlAllowDecrypt %s.",
@@ -870,6 +884,12 @@ CryptoEngine::CryptoEngine() :
ce_state_(CE_INITIALIZED), current_session_(NULL) {
valid_ = true;
ERR_load_crypto_strings();
// These are made up numbers, just for illustration.
current_hdcp_capability_ = 0x1;
maximum_hdcp_capability_ = 0x2;
// If local_display_ is true, we pretend we are using a built-in display,
// instead of HDMI or WiFi output.
local_display_ = false;
}
CryptoEngine::~CryptoEngine() {
@@ -958,11 +978,20 @@ OEMCryptoResult CryptoEngine::DecryptCTR(SessionContext* session,
return OEMCrypto_ERROR_DECRYPT_FAILED;
}
}
if (control.control_bits() & kControlHDCPRequired) {
// For reference implementation, we do not implement any HDCP.
LOGE("[DecryptCTR(): DECRYPT FAILED: HDCP Required]");
return OEMCrypto_ERROR_DECRYPT_FAILED;
if (!local_display_) { // Only look at HDCP if the display is not local.
if (control.control_bits() & kControlHDCPRequired) {
uint8_t required_hdcp
= (control.control_bits() & kControlHDCPVersionMask)
>> kControlHDCPVersionShift;
// For reference implementation, we pretend we can handle the current
// HDCP version.
if (required_hdcp > current_hdcp_capability()
|| current_hdcp_capability() == 0) {
return OEMCrypto_ERROR_INSUFFICIENT_HDCP;
}
}
}
if (control.duration() > 0) {
if (control.duration() < session->CurrentTimer()) {
LOGE("[DecryptCTR(): KEY_EXPIRED]");

View File

@@ -248,6 +248,14 @@ class CryptoEngine {
size_t cipher_data_length, bool is_encrypted,
uint8_t* clear_data, BufferType buffer_type);
const OEMCrypto_HDCP_Capability current_hdcp_capability() {
return local_display_ ? 0xFF : current_hdcp_capability_;
}
const OEMCrypto_HDCP_Capability maximum_hdcp_capability() {
return maximum_hdcp_capability_;
}
private:
bool valid_;
@@ -256,6 +264,9 @@ class CryptoEngine {
ActiveSessions sessions_;
WvKeybox keybox_;
wvcdm::Lock session_table_lock_;
OEMCrypto_HDCP_Capability current_hdcp_capability_;
OEMCrypto_HDCP_Capability maximum_hdcp_capability_;
bool local_display_;
CORE_DISALLOW_COPY_AND_ASSIGN(CryptoEngine);
};

View File

@@ -17,9 +17,10 @@ namespace wvoec_mock {
bool KeyControlBlock::Validate() {
valid_ = false;
if (0x6b63746c != verification_) { // kctl.
LOGE("KCB: BAD verification string: %08X (not %08X)", verification_,
0x6b63746c);
if ((0x6b63746c != verification_) && // kctl.
(0x6b633039 != verification_)) { // kc09.
LOGE("KCB: BAD verification string: %08X (not %08X or %08X)",
verification_, 0x6b63746c, 0x6b633039);
return false;
}

View File

@@ -29,6 +29,11 @@ enum KeyType {
const uint32_t kControlObserveDataPath = (1<<31);
const uint32_t kControlObserveHDCP = (1<<30);
const uint32_t kControlObserveCGMS = (1<<29);
const uint32_t kControlReplayMask = (0x03<<13);
const uint32_t kControlNonceRequired = (0x01<<13);
const uint32_t kControlNonceOrEntry = (0x02<<13);
const uint32_t kControlHDCPVersionShift = 9;
const uint32_t kControlHDCPVersionMask = (0x0F<<kControlHDCPVersionShift);
const uint32_t kControlAllowEncrypt = (1<<8);
const uint32_t kControlAllowDecrypt = (1<<7);
const uint32_t kControlAllowSign = (1<<6);
@@ -55,6 +60,7 @@ class KeyControlBlock {
uint32_t duration() const { return duration_; }
void set_duration(uint32_t duration) { duration_ = duration; }
uint32_t nonce() const { return nonce_; }
uint32_t verification() const { return verification_; }
uint32_t control_bits() const { return control_bits_; }
private:

View File

@@ -1021,8 +1021,8 @@ OEMCryptoResult OEMCrypto_GetHDCPCapability(OEMCrypto_HDCP_Capability *current,
OEMCrypto_HDCP_Capability *maximum) {
if (current == NULL) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
if (maximum == NULL) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
*current = 0;
*maximum = 0;
*current = crypto_engine->current_hdcp_capability();
*maximum = crypto_engine->maximum_hdcp_capability();
return OEMCrypto_SUCCESS;
}

View File

@@ -1093,7 +1093,12 @@ class Session {
OEMCrypto_GetRandom(data->keys[i].key_iv, sizeof(data->keys[i].key_iv));
OEMCrypto_GetRandom(data->keys[i].control_iv,
sizeof(data->keys[i].control_iv));
memcpy(data->keys[i].control.verification, "kctl", 4);
if (control & (wvoec_mock::kControlHDCPVersionMask
| wvoec_mock::kControlReplayMask)) {
memcpy(data->keys[i].control.verification, "kc09", 4);
} else {
memcpy(data->keys[i].control.verification, "kctl", 4);
}
data->keys[i].control.duration = htonl(duration);
data->keys[i].control.nonce = htonl(nonce);
data->keys[i].control.control_bits = htonl(control);
@@ -1601,6 +1606,39 @@ TEST_F(OEMCryptoClientTest, NormalGetKeyData) {
testTearDown();
}
const char* HDCPCapabilityAsString(OEMCrypto_HDCP_Capability value) {
switch(value) {
case 0x0:
return "No HDCP supported, no secure data path";
case 0x1:
return "HDCP version 1.0";
case 0x2:
return "HDCP version 2.0";
case 0x3:
return "HDCP version 2.1";
case 0x4:
return "HDCP version 2.2";
case 0xFF:
return "No HDCP device attached/using local display with secure path";
default:
return "<INVALID VALUE>";
}
}
TEST_F(OEMCryptoClientTest, CheckHDCPCapability) {
testSetUp();
OEMCryptoResult sts;
OEMCrypto_HDCP_Capability current, maximum;
sts = OEMCrypto_GetHDCPCapability(&current, &maximum);
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
printf(" Current HDCP Capability: 0x%02x = %s.\n", current,
HDCPCapabilityAsString(current));
printf(" Maximum HDCP Capability: 0x%02x = %s.\n", maximum,
HDCPCapabilityAsString(maximum));
testTearDown();
}
TEST_F(OEMCryptoClientTest, KeyboxValid) {
bool success;
success = init();
@@ -2448,6 +2486,93 @@ TEST_F(DISABLED_TestKeybox, LoadKeysWithNoDerivedKeys) {
testTearDown();
}
class DISABLED_DecryptWithHDCP : public DISABLED_TestKeybox {
public:
void DecryptWithHDCP(OEMCrypto_HDCP_Capability version) {
OEMCryptoResult sts;
testSetUp();
InstallKeybox(kDefaultKeybox, true);
OEMCrypto_HDCP_Capability current, maximum;
sts = OEMCrypto_GetHDCPCapability(&current, &maximum);
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
Session& s = createSession("ONE");
s.open();
s.GenerateDerivedKeys();
s.LoadTestKeys(0, (version << wvoec_mock::kControlHDCPVersionShift)
| wvoec_mock::kControlObserveHDCP |
wvoec_mock::kControlHDCPRequired, 0);
// Select the key (from FillSimpleMessage)
vector<uint8_t> keyId = wvcdm::a2b_hex("000000000000000000000000");
sts = OEMCrypto_SelectKey(s.session_id(), &keyId[0], keyId.size());
ASSERT_EQ(OEMCrypto_SUCCESS, sts)
<< "SelectKey failed with version = " << version;
// Set up our expected input and output
vector<uint8_t> encryptedData = wvcdm::a2b_hex(
"ec261c115f9d5cda1d5cc7d33c4e37362d1397c89efdd1da5f0065c4848b0462"
"337ba14693735203c9b4184e362439c0cea5e5d1a628425eddf8a6bf9ba901ca"
"46f5a9fd973cffbbe3c276af9919e2e8f6f3f420538b7a0d6dc41487874d96b8"
"efaedb45a689b91beb8c20d36140ad467d9d620b19a5fc6f223b57e0e6a7f913"
"00fd899e5e1b89963e83067ca0912aa5b79df683e2530b55a9645be341bc5f07"
"cffc724790af635c959e2644e51ba7f23bae710eb55a1f2f4e060c3c1dd1387c"
"74415dc880492dd1d5b9ecf3f01de48a44baeb4d3ea5cc4f8d561d0865afcabb"
"fc14a9ab9647e6e31adabb72d792f0c9ba99dc3e9205657d28fc7771d64e6d4b");
vector<uint8_t> encryptionIv = wvcdm::a2b_hex(
"719dbcb253b2ec702bb8c1b1bc2f3bc6");
vector<uint8_t> unencryptedData = wvcdm::a2b_hex(
"19ef4361e16e6825b336e2012ad8ffc9ce176ab2256e1b98aa15b7877bd8c626"
"fa40b2e88373457cbcf4f1b4b9793434a8ac03a708f85974cff01bddcbdd7a8e"
"e33fd160c1d5573bfd8104efd23237edcf28205c3673920553f8dd5e916604b0"
"1082345181dceeae5ea39d829c7f49e1850c460645de33c288723b7ae3d91a17"
"a3f04195cd1945ba7b0f37fef7e82368be30f04365d877766f6d56f67d22a244"
"ef2596d3053f657c1b5d90b64e11797edf1c198a23a7bfc20e4d44c74ae41280"
"a8317f443255f4020eda850ff0954e308f53a634cbce799ae58911bc59ccd6a5"
"de2ac53ee0fa7ea15fc692cc892acc0090865dc57becacddf362a092dfd3040b");
// Describe the output
uint8_t outputBuffer[256];
memset(outputBuffer, 0, sizeof(outputBuffer));
OEMCrypto_DestBufferDesc destBuffer;
destBuffer.type = OEMCrypto_BufferType_Clear;
destBuffer.buffer.clear.address = outputBuffer;
destBuffer.buffer.clear.max_length = sizeof(outputBuffer);
// Decrypt the data
sts = OEMCrypto_DecryptCTR(s.session_id(), &encryptedData[0],
encryptedData.size(), true, &encryptionIv[0], 0,
&destBuffer,
OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample);
if (version > current) {
EXPECT_EQ(OEMCrypto_ERROR_INSUFFICIENT_HDCP, sts)
<< "DecryptCTR did not fail with HDCP version = " << (int)version
<< ", min=" << (int)current;
EXPECT_NE(0, memcmp(&unencryptedData[0], outputBuffer,
unencryptedData.size()))
<< "DecryptCTR decrypted with HDCP version = " << (int)version
<< ", min=" << (int)current;
} else {
EXPECT_EQ(OEMCrypto_SUCCESS, sts)
<< "DecryptCTR failed with HDCP version = " << (int)version
<< ", min=" << (int)current;
EXPECT_EQ(0, memcmp(&unencryptedData[0], outputBuffer,
unencryptedData.size()))
<< "DecryptCTR failed with HDCP version = " << (int)version
<< ", min=" << (int)current;
}
s.close();
testTearDown();
}
};
TEST_F(DISABLED_DecryptWithHDCP, DecryptWithHDCP) {
DecryptWithHDCP(1); // Version 1.0
DecryptWithHDCP(2); // Version 2.0
DecryptWithHDCP(3); // Version 2.1
DecryptWithHDCP(4); // Version 2.2
}
///////////////////////////////////////////////////
// Load, Refresh Keys Test
///////////////////////////////////////////////////