Update partner repo.

This includes:
  - Trap key changes
  - Disable PKV2

Bug: b/378970789
This commit is contained in:
Hua Wu
2024-11-13 17:08:36 -08:00
parent 3f6ce1d204
commit c0f0fb3ff8
10 changed files with 1026 additions and 4 deletions

View File

@@ -24,7 +24,9 @@ cc_library(
"//:is_old_vmpra": [],
"//conditions:default": [ # Chrome, including ChromeOS
"HAS_PROVIDER_KEYS",
"PROVIDER_KEY_SW_SECURE_CRYPTO_ABOVE",
"KEY_TRAP",
# Disable the flag since we are using PKV1
# "PROVIDER_KEY_SW_SECURE_CRYPTO_ABOVE",
],
}) + select({
"//:is_chromeos": ["WV_ENABLE_HW_VERIFICATION=1"],
@@ -293,6 +295,37 @@ cc_library(
alwayslink = True,
)
cc_library(
name = "trap_keys",
srcs = [
"plain_aes.cc",
"trap_keys.cc",
],
hdrs = [
"plain_aes.h",
"trap_keys.h",
],
visibility = ["//visibility:public"],
deps = [":shared_settings"],
)
cc_library(
name = "trap_key_test",
srcs = [
"trap_key_test.cc",
],
visibility = ["//visibility:public"],
deps = [
":license_whitebox",
":license_whitebox_test_base",
":shared_settings",
":test_license_builder",
":trap_keys",
"//chromium_deps/testing",
],
alwayslink = True,
)
cc_library(
name = "remote_attestation_and_verification_test",
srcs = [

431
whitebox/api/plain_aes.cc Normal file
View File

@@ -0,0 +1,431 @@
#include "plain_aes.h"
#include <string.h>
static const uint8_t kSbox[256] = {
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B,
0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0,
0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26,
0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2,
0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0,
0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 0x53, 0xD1, 0x00, 0xED,
0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F,
0x50, 0x3C, 0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5,
0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C, 0x13, 0xEC,
0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14,
0xDE, 0x5E, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C,
0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D,
0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F,
0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E,
0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11,
0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F,
0xB0, 0x54, 0xBB, 0x16};
static const uint8_t kInvSbox[256] = {
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e,
0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87,
0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32,
0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49,
0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16,
0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50,
0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05,
0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02,
0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41,
0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8,
0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89,
0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b,
0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59,
0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d,
0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d,
0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63,
0x55, 0x21, 0x0c, 0x7d};
unsigned int GetRoundCount(const size_t key_size) {
unsigned int round_cnt = 0;
if (key_size == 16) {
round_cnt = 10;
} else if (key_size == 32) {
round_cnt = 14;
} else if (key_size == 24) {
round_cnt = 12;
}
return round_cnt;
}
void ExpandKey(const uint8_t* key,
const size_t key_size,
uint8_t* expanded_key) {
const unsigned int round_cnt = GetRoundCount(key_size);
const unsigned int n = key_size / 4;
memcpy(expanded_key, key, key_size);
const uint8_t r_con[10] = {0x01, 0x02, 0x04, 0x08, 0x10,
0x20, 0x40, 0x80, 0x1b, 0x36};
for (unsigned int i = key_size; i < 16 * (round_cnt + 1); ++i) {
const unsigned int word_ind = i / 4;
if (word_ind % n == 0) {
const unsigned int byte_ind_in_word = i & 3;
if (byte_ind_in_word == 0) {
expanded_key[i] = expanded_key[i - key_size] ^
kSbox[expanded_key[i - 3]] ^
r_con[(i / key_size) - 1];
} else if (byte_ind_in_word == 3) {
expanded_key[i] =
expanded_key[i - key_size] ^ kSbox[expanded_key[i - 7]];
} else {
expanded_key[i] =
expanded_key[i - key_size] ^ kSbox[expanded_key[i - 3]];
}
} else if (n > 6 && word_ind % n == 4) {
expanded_key[i] = expanded_key[i - key_size] ^ kSbox[expanded_key[i - 4]];
} else {
expanded_key[i] = expanded_key[i - key_size] ^ expanded_key[i - 4];
}
}
}
static void AddRoundKey(uint8_t* state, const uint8_t* key) {
for (unsigned int i = 0; i < 16; i++) {
state[i] ^= key[i];
}
}
static void SubBytes(uint8_t* state) {
for (unsigned int i = 0; i < 16; i++) {
state[i] = kSbox[state[i]];
}
}
void InvSubBytes(uint8_t* state) {
for (unsigned int i = 0; i < 16; i++) {
state[i] = kInvSbox[state[i]];
}
}
void ShiftRows(uint8_t* state) {
uint8_t shifted_state[16];
// 0 row
shifted_state[0] = state[0];
shifted_state[4] = state[4];
shifted_state[8] = state[8];
shifted_state[12] = state[12];
// 1 row
shifted_state[1] = state[5];
shifted_state[5] = state[9];
shifted_state[9] = state[13];
shifted_state[13] = state[1];
// 2 row
shifted_state[2] = state[10];
shifted_state[6] = state[14];
shifted_state[10] = state[2];
shifted_state[14] = state[6];
// 3 row
shifted_state[3] = state[15];
shifted_state[7] = state[3];
shifted_state[11] = state[7];
shifted_state[15] = state[11];
memcpy(state, shifted_state, 16);
}
void InvShiftRows(uint8_t* state) {
uint8_t shifted_state[16];
// 0 row
shifted_state[0] = state[0];
shifted_state[4] = state[4];
shifted_state[8] = state[8];
shifted_state[12] = state[12];
// 1 row
shifted_state[5] = state[1];
shifted_state[9] = state[5];
shifted_state[13] = state[9];
shifted_state[1] = state[13];
// 2 row
shifted_state[10] = state[2];
shifted_state[14] = state[6];
shifted_state[2] = state[10];
shifted_state[6] = state[14];
// 3 row
shifted_state[15] = state[3];
shifted_state[3] = state[7];
shifted_state[7] = state[11];
shifted_state[11] = state[15];
memcpy(state, shifted_state, 16);
}
static uint8_t gf_mul_memoization[256][256] = {{0}};
static bool gf_mul_computed[256][256] = {{false}};
uint8_t GfMul(uint8_t x, uint8_t y) {
if (gf_mul_computed[x][y]) {
return gf_mul_memoization[x][y];
}
unsigned int tmp = (unsigned int)x;
unsigned int res = 0;
for (unsigned int i = 0; i < 8; i++) {
if ((y & (1 << i)) != 0) {
res ^= tmp;
}
tmp <<= 1;
if (tmp >= (1 << 8)) {
tmp ^= 283; // 283 = 0b100011011
}
}
gf_mul_memoization[x][y] = res;
gf_mul_memoization[y][x] = res;
gf_mul_computed[x][y] = true;
gf_mul_computed[y][x] = true;
return res;
}
void MixColumn(uint8_t* column) {
uint8_t mix_column[4][4] = {
{0x02, 0x03, 0x01, 0x01},
{0x01, 0x02, 0x03, 0x01},
{0x01, 0x01, 0x02, 0x03},
{0x03, 0x01, 0x01, 0x02},
};
uint8_t result[4];
result[0] =
GfMul(mix_column[0][0], column[0]) ^ GfMul(mix_column[0][1], column[1]) ^
GfMul(mix_column[0][2], column[2]) ^ GfMul(mix_column[0][3], column[3]);
result[1] =
GfMul(mix_column[1][0], column[0]) ^ GfMul(mix_column[1][1], column[1]) ^
GfMul(mix_column[1][2], column[2]) ^ GfMul(mix_column[1][3], column[3]);
result[2] =
GfMul(mix_column[2][0], column[0]) ^ GfMul(mix_column[2][1], column[1]) ^
GfMul(mix_column[2][2], column[2]) ^ GfMul(mix_column[2][3], column[3]);
result[3] =
GfMul(mix_column[3][0], column[0]) ^ GfMul(mix_column[3][1], column[1]) ^
GfMul(mix_column[3][2], column[2]) ^ GfMul(mix_column[3][3], column[3]);
memcpy(column, result, 4);
}
void MixColumns(uint8_t* state) {
MixColumn(state + 0);
MixColumn(state + 4);
MixColumn(state + 8);
MixColumn(state + 12);
}
void InvMixColumn(uint8_t* column) {
uint8_t mix_column[4][4] = {
{0x0e, 0x0b, 0x0d, 0x09},
{0x09, 0x0e, 0x0b, 0x0d},
{0x0d, 0x09, 0x0e, 0x0b},
{0x0b, 0x0d, 0x09, 0x0e},
};
uint8_t result[4];
result[0] =
GfMul(mix_column[0][0], column[0]) ^ GfMul(mix_column[0][1], column[1]) ^
GfMul(mix_column[0][2], column[2]) ^ GfMul(mix_column[0][3], column[3]);
result[1] =
GfMul(mix_column[1][0], column[0]) ^ GfMul(mix_column[1][1], column[1]) ^
GfMul(mix_column[1][2], column[2]) ^ GfMul(mix_column[1][3], column[3]);
result[2] =
GfMul(mix_column[2][0], column[0]) ^ GfMul(mix_column[2][1], column[1]) ^
GfMul(mix_column[2][2], column[2]) ^ GfMul(mix_column[2][3], column[3]);
result[3] =
GfMul(mix_column[3][0], column[0]) ^ GfMul(mix_column[3][1], column[1]) ^
GfMul(mix_column[3][2], column[2]) ^ GfMul(mix_column[3][3], column[3]);
memcpy(column, result, 4);
}
void InvMixColumns(uint8_t* state) {
InvMixColumn(state + 0);
InvMixColumn(state + 4);
InvMixColumn(state + 8);
InvMixColumn(state + 12);
}
void PlainAesEncrypt(const uint8_t* input,
const uint8_t* key,
const size_t key_size,
uint8_t* output,
bool is_key_expanded) {
const unsigned int round_cnt = GetRoundCount(key_size);
uint8_t* expanded_key = new unsigned char[(round_cnt + 1) * 16];
if (!is_key_expanded) {
ExpandKey(key, key_size, expanded_key);
} else {
memcpy(expanded_key, key, (round_cnt + 1) * 16);
}
uint8_t state[16];
memcpy(state, input, 16);
AddRoundKey(state, expanded_key);
for (unsigned int i = 0; i < round_cnt - 1; i++) {
SubBytes(state);
ShiftRows(state);
MixColumns(state);
AddRoundKey(state, expanded_key + 16 + 16 * i);
}
SubBytes(state);
ShiftRows(state);
AddRoundKey(state, expanded_key + 16 * round_cnt);
memcpy(output, state, 16);
delete[] expanded_key;
}
void PlainAesDecrypt(const uint8_t* input,
const uint8_t* key,
const size_t key_size,
uint8_t* output,
bool is_key_expanded) {
const unsigned int round_cnt = GetRoundCount(key_size);
unsigned char* expanded_key;
expanded_key = new unsigned char[(round_cnt + 1) * 16];
if (!is_key_expanded) {
ExpandKey(key, key_size, expanded_key);
} else {
memcpy(expanded_key, key, (round_cnt + 1) * 16);
}
uint8_t state[16];
memcpy(state, input, 16);
AddRoundKey(state, expanded_key + 16 * round_cnt);
for (int i = round_cnt - 2; i >= 0; i--) {
InvShiftRows(state);
InvSubBytes(state);
AddRoundKey(state, expanded_key + 16 + 16 * i);
InvMixColumns(state);
}
InvShiftRows(state);
InvSubBytes(state);
AddRoundKey(state, expanded_key);
memcpy(output, state, 16);
delete[] expanded_key;
}
static void IncrementCtr(uint8_t* iv) {
for (unsigned int i = 0; i < 16; i++) {
iv[15 - i]++;
if (iv[15 - i] != 0) {
return;
}
}
return;
}
void PlainAesCtrEncrypt(const uint8_t* input,
size_t input_size,
const uint8_t* iv,
const uint8_t* key,
const size_t key_size,
uint8_t* output,
bool is_key_expanded) {
uint8_t ctr[16];
memcpy(ctr, iv, 16);
for (unsigned int i = 0; i < input_size; i += 16) {
uint8_t aes_output[16];
PlainAesEncrypt(ctr, key, key_size, aes_output, is_key_expanded);
IncrementCtr(ctr);
for (unsigned int j = 0; j < ((input_size - i >= 16) ? 16 : input_size - i);
j++) {
output[i + j] = input[i + j] ^ aes_output[j];
}
}
}
void PlainAesCtrDecrypt(const uint8_t* input,
size_t input_size,
const uint8_t* iv,
const uint8_t* key,
const size_t key_size,
uint8_t* output,
bool is_key_expanded) {
PlainAesCtrEncrypt(input, input_size, iv, key, key_size, output,
is_key_expanded);
}
void PlainAesCbcEncrypt(const uint8_t* input,
size_t input_size,
const uint8_t* iv,
const uint8_t* key,
const size_t key_size,
uint8_t* output,
bool is_key_expanded) {
for (unsigned int i = 0; i < input_size; i += 16) {
uint8_t xor_block[16];
if (i == 0) {
for (int j = 0; j < 16; j++) {
xor_block[j] = iv[j] ^ input[i + j];
}
} else {
for (int j = 0; j < 16; j++) {
xor_block[j] = output[i - 16 + j] ^ input[i + j];
}
}
PlainAesEncrypt(xor_block, key, key_size, output + i, is_key_expanded);
}
}
void PlainAesCbcDecrypt(const uint8_t* input,
size_t input_size,
const uint8_t* iv,
const uint8_t* key,
const size_t key_size,
uint8_t* output,
bool is_key_expanded) {
uint8_t prev_ciphertext[16];
uint8_t current_ciphertext[16];
memcpy(prev_ciphertext, iv, 16);
for (size_t i = 0; i < input_size; i += 16) {
memcpy(current_ciphertext, input + i, 16);
uint8_t aes_output[16];
PlainAesDecrypt(current_ciphertext, key, key_size, aes_output,
is_key_expanded);
for (int j = 0; j < 16; j++) {
output[i + j] = prev_ciphertext[j] ^ aes_output[j];
}
memcpy(prev_ciphertext, current_ciphertext, 16);
}
}

47
whitebox/api/plain_aes.h Normal file
View File

@@ -0,0 +1,47 @@
#include <cstddef>
#include <cstdint>
void ExpandKey(const uint8_t* key,
const size_t key_size,
uint8_t* expanded_key);
void PlainAesEncrypt(const uint8_t* input,
const uint8_t* key,
const size_t key_size,
uint8_t* output,
bool is_key_expanded = false);
void PlainAesDecrypt(const uint8_t* input,
const uint8_t* key,
const size_t key_size,
uint8_t* output,
bool is_key_expanded = false);
void PlainAesCtrEncrypt(const uint8_t* input,
size_t input_size,
const uint8_t* iv,
const uint8_t* key,
const size_t key_size,
uint8_t* output,
bool is_key_expanded = false);
void PlainAesCtrDecrypt(const uint8_t* input,
size_t input_size,
const uint8_t* iv,
const uint8_t* key,
const size_t key_size,
uint8_t* output,
bool is_key_expanded = false);
void PlainAesCbcEncrypt(const uint8_t* input,
size_t input_size,
const uint8_t* iv,
const uint8_t* key,
const size_t key_size,
uint8_t* output,
bool is_key_expanded = false);
void PlainAesCbcDecrypt(const uint8_t* input,
size_t input_size,
const uint8_t* iv,
const uint8_t* key,
const size_t key_size,
uint8_t* output,
bool is_key_expanded = false);

View File

@@ -0,0 +1,260 @@
/*
The following is the description of the trap key feature.
There are three trap keys, each of them is checked in one or more places in the
whitebox code. When a certain trap check is reached, the content key value is
compared to the hardcoded trap key value, and if the values match, the content
key gets substituted with the corresponding modified key.
The point of the trap keys is to get hints on the StreamFab attack location. If
a license response containing a trap key is sent to the StreamFab server, and
the modified trap key is received, it means that the attack has happened after
the trap check. If the original key is received, it means that the attack has
happened before the check.
All the trap checks are located inside the algorithm instances that handle
content keys wrapped with provider keys. Therefore, only licenses with valid
provider key ID should be sent to the StreamFab server.
The changes in reference implementation reflect the behavior of the trap keys in
the whitebox. The outputs of the reference and the whitebox implementations
should match for licenses containing valid provider key ID. The test provided in
trap_key_test.cc should succeed for both the reference and whitebox
implementations. The variables containing all trap keys are defined in the
trap_keys.cc file. These variables are used for the test and the reference
implementation, and match the trap key values inserted in the whitebox.
The first trap key check is inserted right after the provider path unwrap. When
this check is reached and the corresponding content key value is
0x453372610fdbfcea066b2521f1e5746c (referred as trap_key_post_unwrap in code),
the key gets substituted with 0xc1fbd5862c073482a9907870b478cd3f
(trap_key_post_unwrap_modified). Therefore, when the license response containing
trap_key_post_unwrap is sent to the StreamFab server, we expect to receive
either the trap_key_post_unwrap_modified if the attack happens after the unwrap,
or trap_key_post_unwrap otherwise.
The remaining trap checks are inserted after the content key expansion
procedure, and they operate on the corresponding round key values.
When the license response containing the content key
0x13fb3f51a80f8cfe3059be304b918054 (trap_key_post_key_scheduling_multi) is sent
to the StreamFab server, there are multiple key values that we can expect to
receive back:
* 0x677f8aaa100a79a535e0832b195f94e1
(trap_key_post_key_scheduling_multi_cbc_modified_0_3) - the attack targets
round keys 0-3 in CBC decryption
* 0x1dccc69c1025d26e423792a4aaef3624
(trap_key_post_key_scheduling_multi_cbc_modified_4_6) - the attack targets
round keys 4-6 in CBC decryption
* 0xbd552637be61c64d5a9b549c0567be89
(trap_key_post_key_scheduling_multi_cbc_modified_7_10) - the attack targets
round keys 7-10 in CBC decryption
* 0xfc123d9871d633688763a7e161a8df27
(trap_key_post_key_scheduling_multi_ctr_modified_0_3) - the attack targets
round keys 0-3 in CTR decryption
* 0xa177851298422cf8657322da4e76fe84
(trap_key_post_key_scheduling_multi_ctr_modified_4_6) - the attack targets
round keys 4-6 in CTR decryption
* 0xc23b78c3b21c94266539d8d32fcf84e4
(trap_key_post_key_scheduling_multi_ctr_modified_7_10) - the attack targets
round keys 7-10 in CTR decryption
* 0x13fb3f51a80f8cfe3059be304b918054 (trap_key_post_key_scheduling_multi) -
the attack happens before key expansion
* other values - the attack happens after key expansion, but it is impossible
to tell in which round
Finally, when the license containing content key
0xa6b0fd045f89c793658c0865f160aaae (trap_key_post_key_scheduling_single) is sent
to the StreamFab server, we expect to receive back either the content key
0xf8d0147dcd4089d7f5565ad252d791f9
(trap_key_post_key_scheduling_single_modified), if the attack happens after the
check (after the content key expansion procedure), or the
trap_key_post_key_scheduling_single if the attack happens before.
Note that while both trap_key_post_key_scheduling_multi and
trap_key_post_key_scheduling_single checks happen after key expansion, the
trap_key_post_key_scheduling_single check is located later in the execution
sequence. As a consequence, it is possible to receive the modified key for the
TrapKeyPostKeySchedulingMulti check, and unmodified key for the
trap_key_post_key_scheduling_single check.
*/
#include <array>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include "api/license_whitebox.h"
#include "api/license_whitebox_test_base.h"
#include "api/test_license_builder.h"
#include "crypto_utils/rsa_key.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "api/plain_aes.h"
#include "api/trap_keys.h"
namespace widevine {
enum class TrapKeyType {
PostUnwrap,
PostKeySchedulingMulti,
PostKeySchedulingSingle,
NoTrap,
};
class TrapTest : public LicenseWhiteboxTestBase,
public testing::WithParamInterface<
std::tuple<TrapKeyType, WB_CipherMode>> {
protected:
void SetUp() {
LicenseWhiteboxTestBase::SetUp();
ExpandTrapKeys(); // prepares expanded trap keys for the test and the
// reference implementation
}
public:
const std::array<uint8_t, 16> kTrapTestIv = {
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35};
const std::vector<uint8_t> kTrapTestPlaintext = {
't', 'h', 'i', 's', ' ', 'i', 's', ' ', 't', 'h', 'e',
' ', 'p', 'l', 'a', 'i', 'n', 't', 'e', 'x', 't', ' ',
':', ' ', '3', '2', ' ', 'b', 'y', 't', 'e', 's',
};
const AesKey kNoTrapKey = {0x59, 0x45, 0x05, 0xf8, 0xa1, 0x6a, 0x2d, 0xea,
0xec, 0xf0, 0x1d, 0x83, 0x50, 0x30, 0xe5, 0x59};
};
// add the trap key to the license response, parse the license response and
// perform masked decryption observe the output and verify that the actual key
// used in the decryption was the corresponding modified key
TEST_P(TrapTest, Run) {
TrapKeyType trap_key_type;
WB_CipherMode mode;
std::tie(trap_key_type, mode) = GetParam();
TestLicenseBuilder builder;
TestLicenseBuilder::Settings settings;
#if defined(HAS_PROVIDER_KEYS)
const size_t kProviderKeyId = 1;
#else
const size_t kProviderKeyId = 0;
#endif
settings.provider_key_id = kProviderKeyId;
builder.SetSettings(settings);
std::vector<uint8_t> ciphertext;
const std::vector<uint8_t>* expected_plaintext;
const AesKey*
kTrapKeyValue; // trap key that will be included in the license response
const uint8_t* kTrapKeyValueModified; // corresponding substituted key
bool is_expanded_key = false;
#if !defined(KEY_TRAP)
trap_key_type = TrapKeyType::NoTrap;
#endif
switch (trap_key_type) {
case TrapKeyType::PostUnwrap:
kTrapKeyValue = &trap_key_post_unwrap;
kTrapKeyValueModified = trap_key_post_unwrap_modified.data();
break;
case TrapKeyType::PostKeySchedulingMulti:
kTrapKeyValue = &trap_key_post_key_scheduling_multi;
kTrapKeyValueModified =
mode == WB_CIPHER_MODE_CBC
? trap_key_post_key_scheduling_multi_cbc_modified_expanded.data()
: trap_key_post_key_scheduling_multi_ctr_modified_expanded.data();
is_expanded_key = true; // this is a custom expanded key; full expanded
// key will be passed to the encrypt function
break;
case TrapKeyType::PostKeySchedulingSingle:
kTrapKeyValue = &trap_key_post_key_scheduling_single;
kTrapKeyValueModified =
trap_key_post_key_scheduling_single_modified.data();
break;
case TrapKeyType::NoTrap: // random key, not matching any of the trap keys
kTrapKeyValue = &kNoTrapKey;
kTrapKeyValueModified =
kNoTrapKey.data(); // we expect to observe the same key
break;
default:
FAIL();
}
// add original trap key to the license
ContentKeyData trap_key = {
{0xFF, 2, 0, 0}, SecurityLevel::kSoftwareSecureDecode, *kTrapKeyValue};
builder.AddContentKey(trap_key);
auto server = TestServer::CreateDualKey();
License license;
builder.Build(*server, &license);
// create the ciphertext using the modified key
expected_plaintext = &kTrapTestPlaintext;
ciphertext.resize(expected_plaintext->size());
auto encryptFunc =
mode == WB_CIPHER_MODE_CBC ? PlainAesCbcEncrypt : PlainAesCtrEncrypt;
encryptFunc(expected_plaintext->data(), expected_plaintext->size(),
kTrapTestIv.data(), kTrapKeyValueModified, 16, ciphertext.data(),
is_expanded_key);
// process the license
ASSERT_EQ(WB_License_ProcessLicenseResponse(
whitebox_, WB_LICENSE_KEY_MODE_DUAL_KEY,
license.core_message.data(), license.core_message.size(),
license.message.data(), license.message.size(),
license.signature.data(), license.signature.size(),
license.session_key.data(), license.session_key.size(),
kProviderKeyId, license.request.data(), license.request.size()),
WB_RESULT_OK);
std::vector<uint8_t> output;
size_t output_size = ciphertext.size();
output.resize(output_size);
#ifdef ALWAYS_DECRYPT_TO_CLEAR
ASSERT_EQ(WB_License_Decrypt(whitebox_, mode, trap_key.id.data(),
trap_key.id.size(), ciphertext.data(),
ciphertext.size(), kTrapTestIv.data(),
kTrapTestIv.size(), output.data(), &output_size),
WB_RESULT_OK);
ASSERT_EQ(output, *expected_plaintext);
#else
ASSERT_EQ(WB_License_MaskedDecrypt(
whitebox_, mode, trap_key.id.data(), trap_key.id.size(),
ciphertext.data(), ciphertext.size(), kTrapTestIv.data(),
kTrapTestIv.size(), output.data(), &output_size),
WB_RESULT_OK);
size_t secret_string_size = 16;
std::vector<uint8_t> secret_string(16);
auto result = WB_License_GetSecretString(
whitebox_, mode, trap_key.id.data(), trap_key.id.size(),
secret_string.data(), &secret_string_size);
ASSERT_EQ(result, WB_RESULT_OK);
secret_string.resize(secret_string_size);
std::vector<uint8_t> plaintext;
plaintext.resize(output_size);
WB_License_Unmask(output.data(), 0, output_size, secret_string.data(),
secret_string.size(), plaintext.data());
ASSERT_EQ(plaintext, *expected_plaintext);
#endif
}
INSTANTIATE_TEST_SUITE_P(
,
TrapTest,
::testing::Combine(::testing::Values(TrapKeyType::PostUnwrap,
TrapKeyType::PostKeySchedulingMulti,
TrapKeyType::PostKeySchedulingSingle,
TrapKeyType::NoTrap),
::testing::Values(WB_CIPHER_MODE_CBC,
WB_CIPHER_MODE_CTR)));
} // namespace widevine

119
whitebox/api/trap_keys.cc Normal file
View File

@@ -0,0 +1,119 @@
#include "trap_keys.h"
#include <cstring>
#include "api/plain_aes.h"
const std::array<uint8_t, 16> trap_key_post_unwrap = {
0x45, 0x33, 0x72, 0x61, 0x0f, 0xdb, 0xfc, 0xea,
0x06, 0x6b, 0x25, 0x21, 0xf1, 0xe5, 0x74, 0x6c};
const std::array<uint8_t, 16> trap_key_post_unwrap_modified = {
0xc1, 0xfb, 0xd5, 0x86, 0x2c, 0x07, 0x34, 0x82,
0xa9, 0x90, 0x78, 0x70, 0xb4, 0x78, 0xcd, 0x3f};
const std::array<uint8_t, 16> trap_key_post_key_scheduling_multi = {
0x13, 0xfb, 0x3f, 0x51, 0xa8, 0x0f, 0x8c, 0xfe,
0x30, 0x59, 0xbe, 0x30, 0x4b, 0x91, 0x80, 0x54};
const std::array<uint8_t, 16>
trap_key_post_key_scheduling_multi_cbc_modified_0_3 = {
0x67, 0x7f, 0x8a, 0xaa, 0x10, 0x0a, 0x79, 0xa5,
0x35, 0xe0, 0x83, 0x2b, 0x19, 0x5f, 0x94, 0xe1};
const std::array<uint8_t, 16>
trap_key_post_key_scheduling_multi_cbc_modified_4_6 = {
0x1d, 0xcc, 0xc6, 0x9c, 0x10, 0x25, 0xd2, 0x6e,
0x42, 0x37, 0x92, 0xa4, 0xaa, 0xef, 0x36, 0x24};
const std::array<uint8_t, 16>
trap_key_post_key_scheduling_multi_cbc_modified_7_10 = {
0xbd, 0x55, 0x26, 0x37, 0xbe, 0x61, 0xc6, 0x4d,
0x5a, 0x9b, 0x54, 0x9c, 0x05, 0x67, 0xbe, 0x89};
const std::array<uint8_t, 16>
trap_key_post_key_scheduling_multi_ctr_modified_0_3 = {
0xfc, 0x12, 0x3d, 0x98, 0x71, 0xd6, 0x33, 0x68,
0x87, 0x63, 0xa7, 0xe1, 0x61, 0xa8, 0xdf, 0x27};
const std::array<uint8_t, 16>
trap_key_post_key_scheduling_multi_ctr_modified_4_6 = {
0xa1, 0x77, 0x85, 0x12, 0x98, 0x42, 0x2c, 0xf8,
0x65, 0x73, 0x22, 0xda, 0x4e, 0x76, 0xfe, 0x84};
const std::array<uint8_t, 16>
trap_key_post_key_scheduling_multi_ctr_modified_7_10 = {
0xc2, 0x3b, 0x78, 0xc3, 0xb2, 0x1c, 0x94, 0x26,
0x65, 0x39, 0xd8, 0xd3, 0x2f, 0xcf, 0x84, 0xe4};
const std::array<uint8_t, 16> trap_key_post_key_scheduling_single = {
0xa6, 0xb0, 0xfd, 0x04, 0x5f, 0x89, 0xc7, 0x93,
0x65, 0x8c, 0x08, 0x65, 0xf1, 0x60, 0xaa, 0xae};
const std::array<uint8_t, 16> trap_key_post_key_scheduling_single_modified = {
0xf8, 0xd0, 0x14, 0x7d, 0xcd, 0x40, 0x89, 0xd7,
0xf5, 0x56, 0x5a, 0xd2, 0x52, 0xd7, 0x91, 0xf9};
std::array<uint8_t, 11 * 16> trap_key_post_key_scheduling_multi_expanded = {0};
std::array<uint8_t, 11 * 16>
trap_key_post_key_scheduling_multi_cbc_modified_expanded = {0};
std::array<uint8_t, 11 * 16>
trap_key_post_key_scheduling_multi_ctr_modified_expanded = {0};
std::array<uint8_t, 11 * 16> trap_key_post_key_scheduling_single_expanded = {0};
std::array<uint8_t, 11 * 16>
trap_key_post_key_scheduling_single_modified_expanded = {0};
void ExpandTrapKeys() {
ExpandKey(trap_key_post_key_scheduling_multi.data(), 16,
trap_key_post_key_scheduling_multi_expanded.data());
// prepare expanded trap_key_post_key_scheduling_multi_cbc_modified_expanded
{
unsigned char expanded_key_temp[11 * 16];
ExpandKey(trap_key_post_key_scheduling_multi_cbc_modified_0_3.data(), 16,
expanded_key_temp);
memcpy(trap_key_post_key_scheduling_multi_cbc_modified_expanded.data(),
expanded_key_temp, 4 * 16);
ExpandKey(trap_key_post_key_scheduling_multi_cbc_modified_4_6.data(), 16,
expanded_key_temp);
memcpy(trap_key_post_key_scheduling_multi_cbc_modified_expanded.data() +
4 * 16,
expanded_key_temp + 4 * 16, 3 * 16);
ExpandKey(trap_key_post_key_scheduling_multi_cbc_modified_7_10.data(), 16,
expanded_key_temp);
memcpy(trap_key_post_key_scheduling_multi_cbc_modified_expanded.data() +
7 * 16,
expanded_key_temp + 7 * 16, 4 * 16);
}
// prepare expanded trap_key_post_key_scheduling_multi_ctr_modified_expanded
{
unsigned char expanded_key_temp[11 * 16];
ExpandKey(trap_key_post_key_scheduling_multi_ctr_modified_0_3.data(), 16,
expanded_key_temp);
memcpy(trap_key_post_key_scheduling_multi_ctr_modified_expanded.data(),
expanded_key_temp, 4 * 16);
ExpandKey(trap_key_post_key_scheduling_multi_ctr_modified_4_6.data(), 16,
expanded_key_temp);
memcpy(trap_key_post_key_scheduling_multi_ctr_modified_expanded.data() +
4 * 16,
expanded_key_temp + 4 * 16, 3 * 16);
ExpandKey(trap_key_post_key_scheduling_multi_ctr_modified_7_10.data(), 16,
expanded_key_temp);
memcpy(trap_key_post_key_scheduling_multi_ctr_modified_expanded.data() +
7 * 16,
expanded_key_temp + 7 * 16, 4 * 16);
}
ExpandKey(trap_key_post_key_scheduling_single.data(), 16,
trap_key_post_key_scheduling_single_expanded.data());
ExpandKey(trap_key_post_key_scheduling_single_modified.data(), 16,
trap_key_post_key_scheduling_single_modified_expanded.data());
}
void CompareToTrapKeyAndModify(uint8_t* key,
const uint8_t* trap_key,
const uint8_t* replacement_key,
size_t n) {
if (std::memcmp(key, trap_key, n) == 0) {
std::memcpy(key, replacement_key, n);
}
}

40
whitebox/api/trap_keys.h Normal file
View File

@@ -0,0 +1,40 @@
#include <array>
#include <cstddef>
#include <cstdint>
extern const std::array<uint8_t, 16> trap_key_post_unwrap;
extern const std::array<uint8_t, 16> trap_key_post_unwrap_modified;
extern const std::array<uint8_t, 16> trap_key_post_key_scheduling_multi;
extern const std::array<uint8_t, 16>
trap_key_post_key_scheduling_multi_cbc_modified_0_3;
extern const std::array<uint8_t, 16>
trap_key_post_key_scheduling_multi_cbc_modified_4_6;
extern const std::array<uint8_t, 16>
trap_key_post_key_scheduling_multi_cbc_modified_7_10;
extern const std::array<uint8_t, 16>
trap_key_post_key_scheduling_multi_ctr_modified_0_3;
extern const std::array<uint8_t, 16>
trap_key_post_key_scheduling_multi_ctr_modified_4_6;
extern const std::array<uint8_t, 16>
trap_key_post_key_scheduling_multi_ctr_modified_7_10;
extern const std::array<uint8_t, 16> trap_key_post_key_scheduling_single;
extern const std::array<uint8_t, 16>
trap_key_post_key_scheduling_single_modified;
extern std::array<uint8_t, 11 * 16> trap_key_post_key_scheduling_multi_expanded;
extern std::array<uint8_t, 11 * 16>
trap_key_post_key_scheduling_multi_cbc_modified_expanded;
extern std::array<uint8_t, 11 * 16>
trap_key_post_key_scheduling_multi_ctr_modified_expanded;
extern std::array<uint8_t, 11 * 16>
trap_key_post_key_scheduling_single_expanded;
extern std::array<uint8_t, 11 * 16>
trap_key_post_key_scheduling_single_modified_expanded;
void ExpandTrapKeys();
void CompareToTrapKeyAndModify(uint8_t* key,
const uint8_t* trap_key,
const uint8_t* replacement_key,
size_t n);

View File

@@ -69,6 +69,7 @@ cc_library(
":renewal_key",
"//api:result",
"//api:shared_settings",
"//api:trap_keys",
"//chromium_deps/cdm/protos",
"//crypto_utils:aes_cbc_decryptor",
"//crypto_utils:crypto_util",
@@ -89,8 +90,8 @@ cc_library(
cc_library(
name = "protobuf_license_parser",
hdrs = ["protobuf_license_parser.h"],
srcs = ["protobuf_license_parser.cc"],
hdrs = ["protobuf_license_parser.h"],
deps = [
":license_parser",
"//api:shared_settings",
@@ -171,6 +172,7 @@ cc_library(
"//api:license_whitebox",
"//api:result",
"//api:shared_settings",
"//api:trap_keys",
"//chromium_deps/cdm/keys:dev_certs",
"//chromium_deps/cdm/protos",
"//crypto_utils:aes_cbc_decryptor",

View File

@@ -8,6 +8,10 @@
#include "crypto_utils/aes_cbc_decryptor.h"
#include "crypto_utils/crypto_util.h"
#ifdef KEY_TRAP
#include "api/trap_keys.h"
#endif
namespace widevine {
bool LicenseParser::Decrypt(const std::string& key,
@@ -86,6 +90,13 @@ bool LicenseParser::UnwrapKey(
DVLOG(1) << "Failed to decrypt content key using Provider Key.";
return false;
}
#ifdef KEY_TRAP
CompareToTrapKeyAndModify(
reinterpret_cast<uint8_t*>(&final_key[0]), trap_key_post_unwrap.data(),
trap_key_post_unwrap_modified.data(), final_key.length());
#endif
unwrapped_key->swap(final_key);
return true;
}
@@ -117,8 +128,11 @@ WB_KeyStatus LicenseParser::GetKeyStatus(
}
InternalKey LicenseParser::CreateInternalKey(
KeyType key_type, video_widevine::License_KeyContainer_SecurityLevel level,
bool is_hw_verified, const std::string& key, uint32_t kcb_flags) {
KeyType key_type,
video_widevine::License_KeyContainer_SecurityLevel level,
bool is_hw_verified,
const std::string& key,
uint32_t kcb_flags) {
CHECK((kcb_flags & WB_KCB_FLAGS_GENERIC_MASK) == 0 ||
key_type == KeyType::kGenericCryptoKey);

View File

@@ -40,6 +40,11 @@
#include "third_party/boringssl/src/include/openssl/rsa.h"
#include "third_party/boringssl/src/include/openssl/sha.h"
#ifdef KEY_TRAP
#include "api/plain_aes.h"
#include "api/trap_keys.h"
#endif
namespace {
using AesCbcDecryptor = widevine::AesCbcDecryptor;
using AesCbcEncryptor = widevine::AesCbcEncryptor;
@@ -220,18 +225,79 @@ WB_Result DecryptBuffer(WB_CipherMode mode,
// By this point, we have verified everything that need to be verified.
// Decryption should just work.
if (mode == WB_CIPHER_MODE_CBC) {
#ifdef KEY_TRAP
std::array<uint8_t, 11 * 16> expanded_key;
ExpandKey(key, key_size, expanded_key.data());
for (int i = 0; i < 11; i++) {
CompareToTrapKeyAndModify(
/*key_to_compare=*/expanded_key.data() + 16 * i,
/*trap_key=*/trap_key_post_key_scheduling_multi_expanded.data() +
16 * i,
/*modified_trap_key=*/
trap_key_post_key_scheduling_multi_cbc_modified_expanded.data() +
16 * i,
/*key_size=*/16);
}
// ...
for (int i = 0; i < 11; i++) {
CompareToTrapKeyAndModify(
/*key_to_compare=*/expanded_key.data() + 16 * i,
/*trap_key=*/trap_key_post_key_scheduling_single_expanded.data() +
16 * i,
/*modified_trap_key=*/
trap_key_post_key_scheduling_single_modified_expanded.data() + 16 * i,
/*key_size=*/16);
}
PlainAesCbcDecrypt(input_data, input_data_size, iv, expanded_key.data(),
key_size, output_data, /*is_key_expanded=*/true);
#else
AesCbcDecryptor decryptor;
CHECK(decryptor.SetKey(key, key_size));
CHECK(decryptor.Decrypt(iv, iv_size, input_data, input_data_size,
output_data));
#endif
} else if (mode == WB_CIPHER_MODE_CTR) {
#ifdef KEY_TRAP
std::array<uint8_t, 11 * 16> expanded_key;
ExpandKey(key, key_size, expanded_key.data());
for (int i = 0; i < 11; i++) {
CompareToTrapKeyAndModify(
/*key_to_compare=*/expanded_key.data() + 16 * i,
/*trap_key=*/trap_key_post_key_scheduling_multi_expanded.data() +
16 * i,
/*modified_trap_key=*/
trap_key_post_key_scheduling_multi_ctr_modified_expanded.data() +
16 * i,
/*key_size=*/16);
}
// ...
for (int i = 0; i < 11; i++) {
CompareToTrapKeyAndModify(
/*key_to_compare=*/expanded_key.data() + 16 * i,
/*trap_key=*/trap_key_post_key_scheduling_single_expanded.data() +
16 * i,
/*modified_trap_key=*/
trap_key_post_key_scheduling_single_modified_expanded.data() + 16 * i,
/*key_size=*/16);
}
PlainAesCtrDecrypt(input_data, input_data_size, iv, expanded_key.data(),
key_size, output_data, /*is_key_expanded=*/true);
#else
AesCtrDecryptor decryptor;
CHECK(decryptor.SetKey(key, key_size));
// Encrypt and Decrypt for CTR use the same interface.
CHECK(decryptor.Encrypt(iv, iv_size, input_data, input_data_size,
output_data));
#endif
} else {
DVLOG(1) << "Invalid parameter: invalid cipher mode.";
return WB_RESULT_INVALID_PARAMETER;

View File

@@ -74,6 +74,16 @@ cc_test(
],
)
cc_test(
name = "trap_key_test",
size = "small",
deps = [
"//api:trap_key_test",
"//reference/impl:general_license_whitebox",
"//reference/impl:license_whitebox_provider_keys_test_data"
],
)
cc_test(
name = "license_whitebox_uat_test",
size = "small",