Verify DRM certificate validity

[ Merge of http://go/wvgerrit/120123 ]

DRM certificate creation and expiration times are now validated.

* New DRM (default) certificates will have an expiration time specified
by the provisioning service.

When stored, the client will include the time the certificate was
received. This allows for expiration calculation to occur when client
and provisioning service clocks are out of sync.

When read out, creation, expiration and acquisition times are
validated. The certificate is checked for expiry by making sure
that the time at the client since the license was acquired is not
greater than the expiration period. The time information stored at the
client may be tampered with. The license service will perform an
expiration check and reject the license request if tampered with.
The expiration time may be set to never expires/unlimited. This is not
a valid value for creation or acquisition time.

* Pre-existing (legacy) certificates from upgrading devices will not
have an expiration time set by the provisioning service. Instead
the client will calculate an expiration time 6 months with + or -
a random two month period in the future. This is stored along with the
certificate.

When read out, if no expiration time has been set by the client, one
will be calculated and written out. The certificate will be declared as
valid. If a client calculated expiration time is present, the
certificate will be validated. In case of tampering, the license service
can reject license requests and force reprovisioning when appropriate.

* ATSC certificates will continue to not have an expiration time.
No additional validation is required.

Other changes for non-ATSC licenses involve managing both default and
legacy certificate co-existance. When checking for DRM certificates,
the default certificate is attempted first. This is followed by a check
for the legacy certificate, if the default certificate is not present.

Bug: 169740403
Test: WV unit/integration tests
      DeviceFilesTest.StoreCertificateInvalidParams
      DeviceFilesTest.RetrieveAtscCertificate
      DeviceFilesTest.RetrieveAtscCertificateNotFound
      DeviceFilesTest.RetrieveCertificateInvalidParams
      DeviceFilesTest.RetrieveLegacyCertificateWithoutExpirationTime
      DeviceFilesTest.RetrieveLegacyCertificateWithClientExpirationTime
      DeviceFilesTest.RetrieveLegacyExpiredCertificateByClientExpirationTime
      DeviceFilesTest.RetrieveLegacyCertificateInvalidClientExpirationTime
      DeviceFilesTest.RetrieveCertificateWithoutKeyType
      DeviceFilesTest.RetrieveDefaultCertificate
      DeviceFilesTest.RetrieveDefaultCertificateNeverExpires
      DeviceFilesTest.HasCertificateAtsc
      DeviceFilesTest.HasCertificateDefault
      DeviceFilesTest.HasCertificateLegacy
      DeviceFilesTest.HasCertificateNone
      CertificateTest.StoreCertificateTest.DefaultAndLegacy/*
      CertificateTest.RetrieveLegacyCertificateTest.ErrorScenarios/*
      CertificateTest.RetrieveDefaultCertificateTest.ErrorScenarios/*

Change-Id: I7dbec7555fbd493c1ec61c6bb5d9428a2405b1fd
This commit is contained in:
Rahul Frias
2021-03-14 13:11:21 -07:00
parent 9a350eddbd
commit b21be96b1b
9 changed files with 2319 additions and 187 deletions

View File

@@ -4,12 +4,15 @@
#include "device_files.h"
#include <inttypes.h>
#include <string.h>
#include <algorithm>
#include <string>
#include "cdm_random.h"
#include "certificate_provisioning.h"
#include "clock.h"
#include "file_store.h"
#include "license_protocol.pb.h"
#include "log.h"
@@ -43,6 +46,18 @@ using video_widevine_client::sdk::
// Example: STRINGIFY(this_argument) -> "this_argument"
#define STRINGIFY(PARAM...) #PARAM
#define RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(PARAM) \
if ((PARAM) == nullptr) { \
LOGE("Output parameter |" STRINGIFY(PARAM) "| not provided"); \
return DeviceFiles::kCannotHandle; \
}
#define RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_UNINITIALIZED() \
if (!initialized_) { \
LOGE("Device files is not initialized"); \
return DeviceFiles::kCannotHandle; \
}
#define RETURN_FALSE_IF_NULL(PARAM) \
if ((PARAM) == nullptr) { \
LOGE("Output parameter |" STRINGIFY(PARAM) "| not provided"); \
@@ -78,6 +93,7 @@ const char kLicenseFileNameExt[] = ".lic";
const char kEmptyFileName[] = "";
const char kUsageTableFileName[] = "usgtable.bin";
const char kWildcard[] = "*";
constexpr int64_t kFourMonthsInSeconds = (2 * 30 + 2 * 31) * 24 * 60 * 60;
} // namespace
@@ -128,106 +144,259 @@ bool DeviceFiles::StoreCertificate(const std::string& certificate,
file.set_version(video_widevine_client::sdk::File::VERSION_1);
DeviceCertificate* device_certificate = file.mutable_device_certificate();
device_certificate->set_certificate(certificate);
device_certificate->set_wrapped_private_key(private_key.key());
switch (private_key.type()) {
case CryptoWrappedKey::kRsa:
device_certificate->set_key_type(DeviceCertificate::RSA);
break;
case CryptoWrappedKey::kEcc:
device_certificate->set_key_type(DeviceCertificate::ECC);
break;
case CryptoWrappedKey::kUninitialized: // Suppress compiler warnings.
default:
LOGE("Unexpected key type");
return false;
int64_t creation_time_seconds;
int64_t expiration_time_seconds;
uint32_t system_id;
if (!CertificateProvisioning::ExtractDeviceInfo(
certificate, nullptr, &system_id, &creation_time_seconds,
&expiration_time_seconds))
return false;
if (creation_time_seconds <= 0) {
LOGE("Invalid certificate creation time %" PRId64, creation_time_seconds);
return false;
}
const bool default_certificate = expiration_time_seconds >= 0;
if (!SetDeviceCertificate(certificate, private_key, device_certificate))
return false;
if (default_certificate) {
Clock clock;
device_certificate->set_acquisition_time_seconds(clock.GetCurrentTime());
} else {
// Since certificates of type kCertificateAtsc are not allowed to be
// stored, this is a certificate of type kCertificateLegacy.
// The only time when a legacy certificate is stored is when it does not
// have an expiration time. Set expiration time to 6 months +- 2 months.
Clock clock;
const int64_t current_time = clock.GetCurrentTime();
CdmRandomGenerator rng(current_time & 0xffffffff);
device_certificate->set_expiration_time_seconds(
current_time + kFourMonthsInSeconds +
rng.RandomInRange(kFourMonthsInSeconds));
}
std::string serialized_file;
file.SerializeToString(&serialized_file);
return StoreFileWithHash(GetCertificateFileName(false), serialized_file) ==
kNoError;
std::string certificate_file_name;
const CertificateType certificate_type =
default_certificate ? kCertificateDefault : kCertificateLegacy;
if (!GetCertificateFileName(certificate_type, &certificate_file_name)) {
LOGE("Unable to get certificate file name of type: %d", certificate_type);
return false;
}
return StoreFileWithHash(certificate_file_name, serialized_file) == kNoError;
}
bool DeviceFiles::RetrieveCertificate(bool atsc_mode_enabled,
std::string* certificate,
CryptoWrappedKey* private_key,
std::string* serial_number,
uint32_t* system_id) {
RETURN_FALSE_IF_UNINITIALIZED();
RETURN_FALSE_IF_NULL(certificate);
RETURN_FALSE_IF_NULL(private_key);
DeviceFiles::CertificateState DeviceFiles::RetrieveCertificate(
bool atsc_mode_enabled, std::string* certificate,
CryptoWrappedKey* private_key, std::string* serial_number,
uint32_t* system_id) {
RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_UNINITIALIZED();
RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(certificate);
RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(private_key);
if (!HasCertificate(atsc_mode_enabled)) {
return false;
LOGW("Unable to find certificate, atsc mode: %s",
atsc_mode_enabled ? "enabled" : "disabled");
return kCertificateNotFound;
}
if (atsc_mode_enabled)
return RetrieveCertificate(kCertificateAtsc, certificate, private_key,
serial_number, system_id);
if (HasCertificate(kCertificateDefault))
return RetrieveCertificate(kCertificateDefault, certificate, private_key,
serial_number, system_id);
return RetrieveCertificate(kCertificateLegacy, certificate, private_key,
serial_number, system_id);
}
DeviceFiles::CertificateState DeviceFiles::RetrieveCertificate(
CertificateType certificate_type, std::string* certificate,
CryptoWrappedKey* wrapped_private_key, std::string* serial_number,
uint32_t* system_id) {
RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(certificate);
RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(wrapped_private_key);
std::string certificate_file_name;
if (!GetCertificateFileName(certificate_type, &certificate_file_name)) {
LOGW("Unable to find certificate file name for type: %d", certificate_type);
return kCannotHandle;
}
video_widevine_client::sdk::File file;
if (RetrieveHashedFile(GetCertificateFileName(atsc_mode_enabled), &file) !=
kNoError) {
if (RetrieveHashedFile(certificate_file_name, &file) != kNoError) {
LOGW("Unable to retrieve certificate file");
return false;
return kCertificateNotFound;
}
if (file.type() != video_widevine_client::sdk::File::DEVICE_CERTIFICATE) {
LOGE("Certificate file is of incorrect file type: type = %d",
static_cast<int>(file.type()));
return false;
return kCertificateInvalid;
}
if (file.version() != video_widevine_client::sdk::File::VERSION_1) {
LOGE("Certificate file is of incorrect file version: version = %d",
static_cast<int>(file.version()));
return false;
return kCertificateInvalid;
}
if (!file.has_device_certificate()) {
LOGE("Certificate not present");
return false;
return kCertificateInvalid;
}
DeviceCertificate device_certificate = file.device_certificate();
*certificate = device_certificate.certificate();
private_key->Clear();
private_key->set_key(device_certificate.wrapped_private_key());
if (device_certificate.has_key_type()) {
const DeviceCertificate::PrivateKeyType key_type =
device_certificate.key_type();
switch (key_type) {
case DeviceCertificate::RSA:
private_key->set_type(CryptoWrappedKey::kRsa);
break;
case DeviceCertificate::ECC:
private_key->set_type(CryptoWrappedKey::kEcc);
break;
default:
LOGW("Unknown DRM key type, defaulting to RSA: type = %d", key_type);
private_key->set_type(CryptoWrappedKey::kRsa);
break;
const DeviceCertificate& device_certificate = file.device_certificate();
if (!ExtractFromDeviceCertificate(device_certificate, certificate,
wrapped_private_key)) {
LOGE("Unable to extract from device certificate");
return kCertificateInvalid;
}
int64_t creation_time_seconds;
int64_t expiration_time_seconds;
if (!CertificateProvisioning::ExtractDeviceInfo(
device_certificate.certificate(), serial_number, system_id,
&creation_time_seconds, &expiration_time_seconds))
return kCertificateInvalid;
Clock clock;
const int64_t current_time = clock.GetCurrentTime();
switch (certificate_type) {
case kCertificateDefault: {
// Validation check for DRM certificate that includes an expiration
// time set by the provisioning service. Since provisioning and
// client clocks may not be in sync, verify by comparing time
// elapsed since license was acquired with expiration period.
// First verify that all the fields are set to valid values.
// The service will validate certificate expiration so tampering of
// time values at the client is not a concern.
if (creation_time_seconds <= 0) {
LOGE("Invalid creation time of default certificate: %" PRId64,
creation_time_seconds);
return kCertificateInvalid;
}
if (expiration_time_seconds < 0) {
LOGE("Invalid expiration time of default certificate: %" PRId64,
expiration_time_seconds);
return kCertificateInvalid;
}
if (expiration_time_seconds == UNLIMITED_DURATION)
return kCertificateValid;
if (!device_certificate.has_acquisition_time_seconds()) {
LOGE("Acquisition time of default certificate not available");
return kCertificateInvalid;
}
const int64_t acquisition_time_seconds =
device_certificate.acquisition_time_seconds();
if (acquisition_time_seconds <= 0) {
LOGE("Invalid acquisition time of default certificate: %" PRId64,
acquisition_time_seconds);
return kCertificateInvalid;
}
if (current_time < acquisition_time_seconds) {
LOGE("Time not valid: current time: %" PRId64
", acquisition time: %" PRId64,
current_time, acquisition_time_seconds);
return kCannotHandle;
}
if (expiration_time_seconds < creation_time_seconds) {
LOGE("Time not valid: expiration time: %" PRId64
", creation time: %" PRId64,
expiration_time_seconds, creation_time_seconds);
return kCertificateInvalid;
}
if (current_time - acquisition_time_seconds >
expiration_time_seconds - creation_time_seconds) {
return kCertificateExpired;
}
return kCertificateValid;
}
} else {
// Possible that device certificate is from V15, in this case, the
// only supported key of at that time was RSA.
LOGD("No key type info, assuming RSA");
private_key->set_type(CryptoWrappedKey::kRsa);
}
return CertificateProvisioning::ExtractDeviceInfo(
device_certificate.certificate(), serial_number, system_id, nullptr,
nullptr);
case kCertificateLegacy: {
// Validation check for DRM certificate without an expiration
// time set by the provisioning service. Add an expiry time
// within the next 6 months +/- 2 months, if one has not been set.
if (!device_certificate.has_expiration_time_seconds()) {
StoreCertificate(*certificate, *wrapped_private_key);
return kCertificateValid;
}
const int64_t expiration_time_seconds =
device_certificate.expiration_time_seconds();
if (expiration_time_seconds <= 0) {
LOGE("Invalid expiration time of legacy certificate: %" PRId64,
expiration_time_seconds);
return kCertificateInvalid;
}
if (current_time > expiration_time_seconds) return kCertificateExpired;
return kCertificateValid;
}
case kCertificateAtsc:
// No expiration enforced
return kCertificateValid;
default:
// Should never happen. This should be detected earlier when fetching
// the file name
LOGE("Invalid certificate type: %d", certificate_type);
return kCertificateInvalid;
}
}
bool DeviceFiles::RetrieveLegacyCertificate(std::string* certificate,
CryptoWrappedKey* private_key,
std::string* serial_number,
uint32_t* system_id) {
RETURN_FALSE_IF_UNINITIALIZED();
RETURN_FALSE_IF_NULL(certificate);
RETURN_FALSE_IF_NULL(private_key);
if (!HasCertificate(kCertificateLegacy)) return false;
const CertificateState state = RetrieveCertificate(
kCertificateLegacy, certificate, private_key, serial_number, system_id);
if (state == kCertificateValid || state == kCertificateExpired) return true;
return false;
}
bool DeviceFiles::HasCertificate(bool atsc_mode_enabled) {
RETURN_FALSE_IF_UNINITIALIZED();
return FileExists(GetCertificateFileName(atsc_mode_enabled));
if (atsc_mode_enabled) return HasCertificate(kCertificateAtsc);
return HasCertificate(kCertificateDefault) ||
HasCertificate(kCertificateLegacy);
}
bool DeviceFiles::RemoveCertificate() {
RETURN_FALSE_IF_UNINITIALIZED()
return RemoveFile(GetCertificateFileName(false));
std::string certificate_file_name;
if (GetCertificateFileName(kCertificateLegacy, &certificate_file_name))
RemoveFile(certificate_file_name);
if (GetCertificateFileName(kCertificateDefault, &certificate_file_name))
return RemoveFile(certificate_file_name);
return true;
}
bool DeviceFiles::StoreLicense(const CdmLicenseData& license_data,
@@ -1086,6 +1255,70 @@ bool DeviceFiles::DeleteUsageTableInfo() {
return RemoveFile(GetUsageTableFileName());
}
bool DeviceFiles::HasCertificate(CertificateType certificate_type) {
RETURN_FALSE_IF_UNINITIALIZED();
std::string certificate_file_name;
if (!GetCertificateFileName(certificate_type, &certificate_file_name))
return false;
return FileExists(certificate_file_name);
}
bool DeviceFiles::SetDeviceCertificate(
const std::string& certificate, const CryptoWrappedKey& private_key,
DeviceCertificate* mutable_device_certificate) {
RETURN_FALSE_IF_NULL(mutable_device_certificate);
mutable_device_certificate->set_certificate(certificate);
mutable_device_certificate->set_wrapped_private_key(private_key.key());
switch (private_key.type()) {
case CryptoWrappedKey::kRsa:
mutable_device_certificate->set_key_type(DeviceCertificate::RSA);
return true;
case CryptoWrappedKey::kEcc:
mutable_device_certificate->set_key_type(DeviceCertificate::ECC);
return true;
case CryptoWrappedKey::kUninitialized: // Suppress compiler warnings.
default:
LOGE("Unexpected key type: %d", private_key.type());
return false;
}
}
bool DeviceFiles::ExtractFromDeviceCertificate(
const DeviceCertificate& device_certificate, std::string* certificate,
CryptoWrappedKey* private_key) {
RETURN_FALSE_IF_NULL(certificate);
RETURN_FALSE_IF_NULL(private_key);
*certificate = device_certificate.certificate();
private_key->Clear();
private_key->set_key(device_certificate.wrapped_private_key());
if (device_certificate.has_key_type()) {
const DeviceCertificate::PrivateKeyType key_type =
device_certificate.key_type();
switch (key_type) {
case DeviceCertificate::RSA:
private_key->set_type(CryptoWrappedKey::kRsa);
break;
case DeviceCertificate::ECC:
private_key->set_type(CryptoWrappedKey::kEcc);
break;
default:
LOGW("Unknown DRM key type, defaulting to RSA: type = %d", key_type);
private_key->set_type(CryptoWrappedKey::kRsa);
break;
}
} else {
// Possible that device certificate is from V15, in this case, the
// only supported key of at that time was RSA.
LOGD("No key type info, assuming RSA");
private_key->set_type(CryptoWrappedKey::kRsa);
}
return true;
}
DeviceFiles::ResponseType DeviceFiles::StoreFileWithHash(
const std::string& name, const std::string& serialized_file) {
std::string hash = Sha256Hash(serialized_file);
@@ -1268,8 +1501,22 @@ ssize_t DeviceFiles::GetFileSize(const std::string& name) {
return file_system_->FileSize(path);
}
std::string DeviceFiles::GetCertificateFileName(bool atsc_mode_enabled) {
return atsc_mode_enabled ? kAtscCertificateFileName : kCertificateFileName;
bool DeviceFiles::GetCertificateFileName(CertificateType certificate_type,
std::string* file_name) {
RETURN_FALSE_IF_NULL(file_name);
switch (certificate_type) {
case kCertificateDefault:
*file_name = kCertificateFileName;
return true;
case kCertificateLegacy:
*file_name = kLegacyCertificateFileName;
return true;
case kCertificateAtsc:
*file_name = kAtscCertificateFileName;
return true;
default:
return false;
}
}
std::string DeviceFiles::GetUsageTableFileName() { return kUsageTableFileName; }