Correctly parse v18.0 provisioning requests
The previous code fixed a backwards compatibility error for v18 provisioning requests being parsed by a v17 or older server. This bumped the minor version number to v18.1. v17 servers are still expected to fail when trying to parse v18.0 provisioning requests, and v18.1 requests will pass. However, it did not correctly account for existing v18.0 requests on v18.1+ servers. v18.0 messages were naively detected by a failure to parse, and the parse function was not run again. This left the resulting nonce and session_id values uninitialized. This CL fixes that by explicitly handling the v18.0 and v18.1+ cases, ensuring that the parse function succeeds and all relevant information is copied over. Furthermore, the unit test that was meant to catch this edge case has been improved to validate the resulting parsed message. All code changes affect the server. This does not affect the client code eg OEMCrypto PiperOrigin-RevId: 523714529 Merged from https://widevine-internal-review.googlesource.com/170110 Change-Id: I21911c4bb4304de2d93f092f356402bbd4240874
This commit is contained in:
committed by
Robert Shih
parent
c6e7c70a6b
commit
a2a27c44ef
@@ -16,10 +16,10 @@ extern "C" {
|
||||
|
||||
/* The version of this library. */
|
||||
#define ODK_MAJOR_VERSION 18
|
||||
#define ODK_MINOR_VERSION 1
|
||||
#define ODK_MINOR_VERSION 2
|
||||
|
||||
/* ODK Version string. Date changed automatically on each release. */
|
||||
#define ODK_RELEASE_DATE "ODK v18.1 2023-04-05"
|
||||
#define ODK_RELEASE_DATE "ODK v18.2 2023-04-12"
|
||||
|
||||
/* The lowest version number for an ODK message. */
|
||||
#define ODK_FIRST_VERSION 16
|
||||
|
||||
@@ -162,44 +162,26 @@ bool CoreRenewalRequestFromMessage(const std::string& oemcrypto_core_message,
|
||||
bool CoreProvisioningRequestFromMessage(
|
||||
const std::string& oemcrypto_core_message,
|
||||
ODK_ProvisioningRequest* core_provisioning_request) {
|
||||
// We can't tell if V18 format or older. Need to partially parse in order
|
||||
// to get the nonce values, which will tell us.
|
||||
// Need to partially parse in order to get the nonce values, which will tell
|
||||
// us the major/minor version
|
||||
ODK_NonceValues nonce;
|
||||
if (!GetNonceFromMessage(oemcrypto_core_message, &nonce)) return false;
|
||||
|
||||
if (nonce.api_major_version == 18) {
|
||||
// Proceed with V18 types
|
||||
const auto unpacker = Unpack_ODK_PreparedProvisioningRequest;
|
||||
// Use special case unpacker for v18.0
|
||||
const auto unpacker = nonce.api_minor_version == 0
|
||||
? Unpack_ODK_PreparedProvisioningRequestV180
|
||||
: Unpack_ODK_PreparedProvisioningRequest;
|
||||
ODK_PreparedProvisioningRequest prepared_provision = {};
|
||||
|
||||
if (!ParseRequest(ODK_Provisioning_Request_Type, oemcrypto_core_message,
|
||||
core_provisioning_request, &prepared_provision,
|
||||
unpacker)) {
|
||||
// check for edge case: initial v18 message which is 4 bytes smaller and
|
||||
// has 0's in the message counter struct
|
||||
const uint8_t* buf =
|
||||
reinterpret_cast<const uint8_t*>(oemcrypto_core_message.c_str());
|
||||
const size_t buf_length = oemcrypto_core_message.size();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(buf_length + 4 == ODK_PROVISIONING_REQUEST_SIZE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Expected zero padding. Size is the new ODK Provisioning Request (core
|
||||
// message + const uint32_t + the rest) without the core message and const
|
||||
// uint32_t.
|
||||
uint8_t zeros[ODK_PROVISIONING_REQUEST_SIZE - 4 - ODK_CORE_MESSAGE_SIZE] =
|
||||
{0};
|
||||
|
||||
// Compare zeros against the old Provisioning Request (core message + the
|
||||
// rest).
|
||||
if (memcmp(zeros, buf + ODK_CORE_MESSAGE_SIZE, sizeof(zeros)) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(&prepared_provision.counter_info, 0,
|
||||
sizeof(prepared_provision.counter_info));
|
||||
} else if (!CopyCounterInfo(&core_provisioning_request->counter_info,
|
||||
&prepared_provision.counter_info)) {
|
||||
if (!CopyCounterInfo(&core_provisioning_request->counter_info,
|
||||
&prepared_provision.counter_info)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -28,7 +28,7 @@ CoreMessageFeatures CoreMessageFeatures::DefaultFeatures(
|
||||
features.maximum_minor_version = 2; // 17.2
|
||||
break;
|
||||
case 18:
|
||||
features.maximum_minor_version = 1; // 18.0
|
||||
features.maximum_minor_version = 2; // 18.2
|
||||
break;
|
||||
default:
|
||||
features.maximum_minor_version = 0;
|
||||
|
||||
@@ -494,6 +494,12 @@ void Unpack_ODK_PreparedProvisioningRequest(
|
||||
Unpack_ODK_MessageCounterInfo(msg, &obj->counter_info);
|
||||
}
|
||||
|
||||
void Unpack_ODK_PreparedProvisioningRequestV180(
|
||||
ODK_Message* msg, ODK_PreparedProvisioningRequest* obj) {
|
||||
Unpack_ODK_CoreMessage(msg, &obj->core_message);
|
||||
Unpack_ODK_MessageCounterInfo(msg, &obj->counter_info);
|
||||
}
|
||||
|
||||
void Unpack_ODK_PreparedProvisioningRequestV17(
|
||||
ODK_Message* msg, ODK_PreparedProvisioningRequestV17* obj) {
|
||||
Unpack_ODK_CoreMessage(msg, &obj->core_message);
|
||||
|
||||
@@ -69,6 +69,8 @@ void Unpack_ODK_PreparedRenewalRequest(ODK_Message* msg,
|
||||
ODK_PreparedRenewalRequest* obj);
|
||||
void Unpack_ODK_PreparedProvisioningRequest(
|
||||
ODK_Message* msg, ODK_PreparedProvisioningRequest* obj);
|
||||
void Unpack_ODK_PreparedProvisioningRequestV180(
|
||||
ODK_Message* msg, ODK_PreparedProvisioningRequest* obj);
|
||||
void Unpack_ODK_PreparedProvisioningRequestV17(
|
||||
ODK_Message* msg, ODK_PreparedProvisioningRequestV17* obj);
|
||||
void Unpack_ODK_PreparedProvisioning40Request(
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
// source code may only be used and distributed under the Widevine
|
||||
// License Agreement.
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "OEMCryptoCENCCommon.h"
|
||||
#include "core_message_deserialize.h"
|
||||
#include "core_message_features.h"
|
||||
#include "core_message_serialize_proto.h"
|
||||
#include "core_message_types.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "odk.h"
|
||||
@@ -18,6 +22,9 @@ using oemcrypto_core_message::ODK_CommonRequest;
|
||||
using oemcrypto_core_message::ODK_ProvisioningRequest;
|
||||
using oemcrypto_core_message::deserialize::CoreCommonRequestFromMessage;
|
||||
using oemcrypto_core_message::deserialize::CoreProvisioningRequestFromMessage;
|
||||
using oemcrypto_core_message::features::CoreMessageFeatures;
|
||||
using oemcrypto_core_message::serialize::
|
||||
CreateCoreProvisioningResponseFromProto;
|
||||
|
||||
TEST(CoreMessageTest, RenwalRequest) {
|
||||
std::string oem =
|
||||
@@ -69,40 +76,121 @@ TEST(CoreMessageTest, ParseCoreCommonRequestFromMessage) {
|
||||
EXPECT_EQ(odk_common_request.session_id, 1);
|
||||
}
|
||||
|
||||
// Make sure that the first version of the V18 provisioning request (no hidden
|
||||
// 4-byte value, all 0s in message counter struct) will still parse with current
|
||||
// v18 code.
|
||||
TEST(CoreMessageTest, ProvisionRequestRoundtrip_V18_Initial) {
|
||||
std::vector<std::string> should_pass = {
|
||||
// Pulled from ODKTest provision round trip, extra 4 bytes removed
|
||||
"000000050000005e00000012deadbeefcafebabe000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
"00000000000000000000000000000000000000000000",
|
||||
// Same thing but v17 in nonce. Almost like testing on the v17 server (but
|
||||
// not quite since the v17 parsing code has been slightly changed anyway)
|
||||
"000000050000005e00000011deadbeefcafebabe000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
"00000000000000000000000000000000000000000000",
|
||||
};
|
||||
struct TestParameters_18V0 {
|
||||
std::string message;
|
||||
uint16_t expected_api_minor_version;
|
||||
uint16_t expected_api_major_version;
|
||||
uint32_t expected_nonce;
|
||||
uint32_t expected_session_id;
|
||||
uint64_t expected_master_generation_number;
|
||||
};
|
||||
|
||||
ODK_ProvisioningRequest request;
|
||||
for (auto& tc : should_pass) {
|
||||
ASSERT_TRUE(CoreProvisioningRequestFromMessage(absl::HexStringToBytes(tc),
|
||||
&request));
|
||||
}
|
||||
|
||||
// Fail cases have non-zero values after the bytes interpreted as length
|
||||
std::vector<std::string> should_fail = {
|
||||
// Change a 0 to a 1 in the message counter
|
||||
"000000050000005e00000012deadbeefcafebabe000000000000000100000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
"00000000000000000000000000000000000000000000",
|
||||
};
|
||||
|
||||
for (auto& tc : should_fail) {
|
||||
ASSERT_FALSE(CoreProvisioningRequestFromMessage(absl::HexStringToBytes(tc),
|
||||
&request));
|
||||
}
|
||||
void PrintTo(const TestParameters_18V0& p, std::ostream* os) {
|
||||
*os << "request = " << p.message << ", expected : {version = v"
|
||||
<< p.expected_api_major_version << "." << p.expected_api_minor_version
|
||||
<< ", nonce = " << p.expected_nonce
|
||||
<< ", session_id = " << p.expected_session_id
|
||||
<< ", master_generation_number = " << p.expected_master_generation_number
|
||||
<< "}";
|
||||
}
|
||||
|
||||
class ProvisioningRoundTripTest_18V0
|
||||
: public ::testing::Test,
|
||||
public ::testing::WithParamInterface<TestParameters_18V0> {};
|
||||
|
||||
// Make sure that the first version of the V18 provisioning request (no hidden
|
||||
// 4-byte value, all 0s in message counter struct) will still parse with
|
||||
// current v18 code. This test is in this file rather than odk_test.cpp
|
||||
// because of the use of absl::HexStringToBytes
|
||||
TEST_P(ProvisioningRoundTripTest_18V0, ProvisioningRoundtrip) {
|
||||
TestParameters_18V0 tc = GetParam();
|
||||
|
||||
ODK_ProvisioningRequest request;
|
||||
|
||||
// Make sure we can parse the request
|
||||
ASSERT_TRUE(CoreProvisioningRequestFromMessage(
|
||||
absl::HexStringToBytes(tc.message), &request));
|
||||
EXPECT_EQ(request.api_minor_version, tc.expected_api_minor_version);
|
||||
EXPECT_EQ(request.api_major_version, tc.expected_api_major_version);
|
||||
EXPECT_EQ(request.nonce, tc.expected_nonce);
|
||||
EXPECT_EQ(request.session_id, tc.expected_session_id);
|
||||
|
||||
if (request.api_major_version >= 18) {
|
||||
EXPECT_EQ(request.counter_info.master_generation_number,
|
||||
tc.expected_master_generation_number);
|
||||
}
|
||||
|
||||
// Make sure we can create a response from that request with the same core
|
||||
// message
|
||||
const CoreMessageFeatures features =
|
||||
CoreMessageFeatures::DefaultFeatures(ODK_MAJOR_VERSION);
|
||||
std::string serialized_provisioning_resp;
|
||||
video_widevine::ProvisioningResponse provisioning_response;
|
||||
provisioning_response.set_device_certificate("device_certificate");
|
||||
provisioning_response.set_device_rsa_key("device_rsa_key");
|
||||
provisioning_response.set_device_rsa_key_iv("device_rsa_key_iv");
|
||||
if (!provisioning_response.SerializeToString(&serialized_provisioning_resp)) {
|
||||
FAIL() << "Cannot set up prov response";
|
||||
}
|
||||
std::string oemcrypto_core_message;
|
||||
EXPECT_TRUE(CreateCoreProvisioningResponseFromProto(
|
||||
features, serialized_provisioning_resp, request,
|
||||
OEMCrypto_RSA_Private_Key, &oemcrypto_core_message));
|
||||
|
||||
// Extract core message from generated prov response and match values with
|
||||
// request
|
||||
ODK_CommonRequest odk_common_request;
|
||||
ASSERT_TRUE(CoreCommonRequestFromMessage(oemcrypto_core_message,
|
||||
&odk_common_request));
|
||||
EXPECT_EQ(odk_common_request.message_type, 6u);
|
||||
EXPECT_EQ(odk_common_request.nonce, tc.expected_nonce);
|
||||
EXPECT_EQ(odk_common_request.session_id, tc.expected_session_id);
|
||||
}
|
||||
|
||||
std::vector<TestParameters_18V0> TestCases() {
|
||||
return std::vector<TestParameters_18V0>{
|
||||
// Source: ODKTest ProvisionRequestRoundtrip running on v18.0 ODK checkout
|
||||
{"000000050000005a00000012deadbeefcafebabe12345678abcdffff0000000c0000003"
|
||||
"200000154001200000004ffffffffffffffffffffffffffffffffdddddddddddddddddd"
|
||||
"ddddddddddddddeeeeeeeeeeeeeeeeeeeeeeee",
|
||||
0, 18, 0xdeadbeef, 0xcafebabe, 0x12345678abcdffff},
|
||||
// same as previous request, but replace counter info with all 0s
|
||||
{"000000050000005a00000012deadbeefcafebabe0000000000000000000000000000000"
|
||||
"00000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
"00000000000000000000000000000000000000",
|
||||
0, 18, 0xdeadbeef, 0xcafebabe, 0x0},
|
||||
// Source: ODKTest ProvisionRequestRoundtrip running on v17.2 ODK checkout
|
||||
{"000000050000005800020011deadbeefcafebabe00000020fffffffffffffffffffffff"
|
||||
"fffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000"
|
||||
"0000000000000000000000000000000000",
|
||||
2, 17, 0xdeadbeef, 0xcafebabe, 0x0},
|
||||
// Source: ODKTest ProvisionRequestRoundtrip running on v18.2 ODK checkout
|
||||
{"000000050000005e00020012deadbeefcafebabe0000004012345678abcdffff0000000"
|
||||
"c0000003200000154001200020004ffffffffffffffffffffffffffffffffdddddddddd"
|
||||
"ddddddddddddddddddddddeeeeeeeeeeeeeeeeeeeeeeee",
|
||||
2, 18, 0xdeadbeef, 0xcafebabe, 0x12345678abcdffff},
|
||||
// Source: CDM unit tests on oemcrypto-v18 internal commit 5c77383 (pre
|
||||
// v18.0 -> v18.1 ODK bump)
|
||||
{"000000050000005a00000012b85dfa09000000000000000000000000000000000000000"
|
||||
"00000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
"00000000000000000000000000000000000000",
|
||||
0, 18, 0xb85dfa09, 0x0, 0x0},
|
||||
// Same as above but non-zero counter info
|
||||
{"000000050000005a00000012b85dfa09000000001000000000000001000000000000000"
|
||||
"00000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
"00000000000000000000000000000000000000",
|
||||
0, 18, 0xb85dfa09, 0x0, 0x1000000000000001},
|
||||
// Source: CDM unit tests on oemcrypto-v18 internal commit fc46827a (post
|
||||
// v18.0 -> v18.1 ODK bump)
|
||||
{"000000050000005e000100127c8ac703000000000000004000000000000000000000000"
|
||||
"00000000000000000001200010000746573740000000000000000000000007465737400"
|
||||
"0000000000000000000000000000000000000000000000",
|
||||
1, 18, 0x7c8ac703, 0x0, 0x0},
|
||||
};
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(ProvisioningRoundTripTests_18V0,
|
||||
ProvisioningRoundTripTest_18V0,
|
||||
::testing::ValuesIn(TestCases()));
|
||||
|
||||
} // namespace wvodk_test
|
||||
|
||||
@@ -1105,20 +1105,21 @@ std::vector<VersionParameters> TestCases() {
|
||||
// number.
|
||||
{16, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 16, 5},
|
||||
{17, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 17, 2},
|
||||
{18, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 18, 1},
|
||||
{18, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 18, 2},
|
||||
// Here are some known good versions. Make extra sure they work.
|
||||
{ODK_MAJOR_VERSION, 16, 3, 16, 3},
|
||||
{ODK_MAJOR_VERSION, 16, 4, 16, 4},
|
||||
{ODK_MAJOR_VERSION, 16, 5, 16, 5},
|
||||
{ODK_MAJOR_VERSION, 17, 1, 17, 1},
|
||||
{ODK_MAJOR_VERSION, 17, 2, 17, 2},
|
||||
{ODK_MAJOR_VERSION, 18, 0, 18, 0},
|
||||
{ODK_MAJOR_VERSION, 18, 1, 18, 1},
|
||||
{ODK_MAJOR_VERSION, 18, 2, 18, 2},
|
||||
{0, 16, 3, 16, 3},
|
||||
{0, 16, 4, 16, 4},
|
||||
{0, 16, 5, 16, 5},
|
||||
{0, 17, 1, 17, 1},
|
||||
{0, 17, 2, 17, 2},
|
||||
{0, 18, 0, 17, 2}, // Change to 18 when the default version is updated.
|
||||
{0, 18, 2, 17, 2}, // Change to 18 when the default version is updated.
|
||||
};
|
||||
return test_cases;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user