From c0f0fb3ff8e46a7cd3983d6f9bb35120fac8c84a Mon Sep 17 00:00:00 2001 From: Hua Wu Date: Wed, 13 Nov 2024 17:08:36 -0800 Subject: [PATCH] Update partner repo. This includes: - Trap key changes - Disable PKV2 Bug: b/378970789 --- whitebox/api/BUILD | 35 +- whitebox/api/plain_aes.cc | 431 ++++++++++++++++++ whitebox/api/plain_aes.h | 47 ++ whitebox/api/trap_key_test.cc | 260 +++++++++++ whitebox/api/trap_keys.cc | 119 +++++ whitebox/api/trap_keys.h | 40 ++ whitebox/reference/impl/BUILD | 4 +- whitebox/reference/impl/license_parser.cc | 18 +- .../reference/impl/license_whitebox_impl.cc | 66 +++ whitebox/reference/tests/BUILD | 10 + 10 files changed, 1026 insertions(+), 4 deletions(-) create mode 100644 whitebox/api/plain_aes.cc create mode 100644 whitebox/api/plain_aes.h create mode 100644 whitebox/api/trap_key_test.cc create mode 100644 whitebox/api/trap_keys.cc create mode 100644 whitebox/api/trap_keys.h diff --git a/whitebox/api/BUILD b/whitebox/api/BUILD index 7121077..e457988 100644 --- a/whitebox/api/BUILD +++ b/whitebox/api/BUILD @@ -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 = [ diff --git a/whitebox/api/plain_aes.cc b/whitebox/api/plain_aes.cc new file mode 100644 index 0000000..ccf50a3 --- /dev/null +++ b/whitebox/api/plain_aes.cc @@ -0,0 +1,431 @@ +#include "plain_aes.h" +#include + +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); + } +} \ No newline at end of file diff --git a/whitebox/api/plain_aes.h b/whitebox/api/plain_aes.h new file mode 100644 index 0000000..3db9aa3 --- /dev/null +++ b/whitebox/api/plain_aes.h @@ -0,0 +1,47 @@ +#include +#include + +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); diff --git a/whitebox/api/trap_key_test.cc b/whitebox/api/trap_key_test.cc new file mode 100644 index 0000000..c580964 --- /dev/null +++ b/whitebox/api/trap_key_test.cc @@ -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 +#include +#include +#include +#include +#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> { + protected: + void SetUp() { + LicenseWhiteboxTestBase::SetUp(); + ExpandTrapKeys(); // prepares expanded trap keys for the test and the + // reference implementation + } + + public: + const std::array kTrapTestIv = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35}; + const std::vector 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 ciphertext; + const std::vector* 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 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 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 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 diff --git a/whitebox/api/trap_keys.cc b/whitebox/api/trap_keys.cc new file mode 100644 index 0000000..c199a64 --- /dev/null +++ b/whitebox/api/trap_keys.cc @@ -0,0 +1,119 @@ +#include "trap_keys.h" +#include +#include "api/plain_aes.h" + +const std::array trap_key_post_unwrap = { + 0x45, 0x33, 0x72, 0x61, 0x0f, 0xdb, 0xfc, 0xea, + 0x06, 0x6b, 0x25, 0x21, 0xf1, 0xe5, 0x74, 0x6c}; + +const std::array trap_key_post_unwrap_modified = { + 0xc1, 0xfb, 0xd5, 0x86, 0x2c, 0x07, 0x34, 0x82, + 0xa9, 0x90, 0x78, 0x70, 0xb4, 0x78, 0xcd, 0x3f}; + +const std::array 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 + 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 + 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 + 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 + 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 + 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 + 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 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 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 trap_key_post_key_scheduling_multi_expanded = {0}; +std::array + trap_key_post_key_scheduling_multi_cbc_modified_expanded = {0}; +std::array + trap_key_post_key_scheduling_multi_ctr_modified_expanded = {0}; +std::array trap_key_post_key_scheduling_single_expanded = {0}; +std::array + 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); + } +} diff --git a/whitebox/api/trap_keys.h b/whitebox/api/trap_keys.h new file mode 100644 index 0000000..f2e1124 --- /dev/null +++ b/whitebox/api/trap_keys.h @@ -0,0 +1,40 @@ +#include +#include +#include + +extern const std::array trap_key_post_unwrap; +extern const std::array trap_key_post_unwrap_modified; + +extern const std::array trap_key_post_key_scheduling_multi; +extern const std::array + trap_key_post_key_scheduling_multi_cbc_modified_0_3; +extern const std::array + trap_key_post_key_scheduling_multi_cbc_modified_4_6; +extern const std::array + trap_key_post_key_scheduling_multi_cbc_modified_7_10; +extern const std::array + trap_key_post_key_scheduling_multi_ctr_modified_0_3; +extern const std::array + trap_key_post_key_scheduling_multi_ctr_modified_4_6; +extern const std::array + trap_key_post_key_scheduling_multi_ctr_modified_7_10; + +extern const std::array trap_key_post_key_scheduling_single; +extern const std::array + trap_key_post_key_scheduling_single_modified; + +extern std::array trap_key_post_key_scheduling_multi_expanded; +extern std::array + trap_key_post_key_scheduling_multi_cbc_modified_expanded; +extern std::array + trap_key_post_key_scheduling_multi_ctr_modified_expanded; +extern std::array + trap_key_post_key_scheduling_single_expanded; +extern std::array + 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); diff --git a/whitebox/reference/impl/BUILD b/whitebox/reference/impl/BUILD index 8079545..3e0c96d 100644 --- a/whitebox/reference/impl/BUILD +++ b/whitebox/reference/impl/BUILD @@ -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", diff --git a/whitebox/reference/impl/license_parser.cc b/whitebox/reference/impl/license_parser.cc index c833cca..1920b77 100644 --- a/whitebox/reference/impl/license_parser.cc +++ b/whitebox/reference/impl/license_parser.cc @@ -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(&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); diff --git a/whitebox/reference/impl/license_whitebox_impl.cc b/whitebox/reference/impl/license_whitebox_impl.cc index 609642e..88564b7 100644 --- a/whitebox/reference/impl/license_whitebox_impl.cc +++ b/whitebox/reference/impl/license_whitebox_impl.cc @@ -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 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 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; diff --git a/whitebox/reference/tests/BUILD b/whitebox/reference/tests/BUILD index 66c0aa4..e75072e 100644 --- a/whitebox/reference/tests/BUILD +++ b/whitebox/reference/tests/BUILD @@ -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",