Files
android/libwvdrmengine/oemcrypto/odk/src/odk_timer.c
Rahul Frias 8046719c10 Correct copyright header
[ Merge of http://go/wvgerrit/186825 ]

Remove the words ` All rights reserved.` from Widevine's
existing copyright headers.

For context, see cl/578224540

Bug: 330655176
Test: WVTS

PiperOrigin-RevId: 580020267
Change-Id: I43e845b83f438e4ef7f0f542c2f4e427a188ab06
2024-03-20 23:24:17 -07:00

515 lines
21 KiB
C

// Copyright 2019 Google LLC. This file and proprietary
// source code may only be used and distributed under the Widevine
// License Agreement.
#include <stdint.h>
#include <string.h>
#include "odk.h"
#include "odk_attributes.h"
#include "odk_overflow.h"
#include "odk_structs_priv.h"
/* Private function. Checks to see if the license is active. Returns
* ODK_TIMER_EXPIRED if the license is valid but inactive. Returns
* OEMCrypto_SUCCESS if the license is active. Returns
* OEMCrypto_ERROR_UNKNOWN_FAILURE on other errors. */
static OEMCryptoResult ODK_LicenseActive(const ODK_TimerLimits* timer_limits,
ODK_ClockValues* clock_values) {
/* Check some basic errors. */
if (clock_values == NULL || timer_limits == NULL) {
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
/* Check if the license has not been loaded yet. */
if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_UNDEFINED ||
clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (clock_values->status > kActive) {
return ODK_TIMER_EXPIRED;
}
return OEMCrypto_SUCCESS;
}
/* Private function. Sets the timer_value to be the min(timer_value, new_value),
* with the convention that 0 means infinite. The convention that 0 means
* infinite is used for all Widevine license and duration values. */
static void ComputeMinimum(uint64_t* timer_value, uint64_t new_value) {
if (timer_value == NULL) return;
if (new_value > 0) {
if (*timer_value == 0 || *timer_value > new_value) {
*timer_value = new_value;
}
}
}
/* Private function. Check to see if the rental window restricts playback. If
* the rental enforcement is hard, or if this is the first playback, then we
* verify that system_time_seconds is within the rental window. If the
* enforcement is soft and we have already started playback, then there is no
* restriction.
* Return ODK_TIMER_EXPIRED if out of the window.
* Return ODK_TIMER_ACTIVE if within the window, and there is a hard limit.
* Return ODK_DISABLE_TIMER if no there should be no limit.
* Return other error on error.
* Also, if this function does compute a limit, the timer_value is reduced to
* obey that limit. If the limit is less restrictive than the current
* timer_value, then timer_value is not changed. */
static OEMCryptoResult ODK_CheckRentalWindow(
const ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values,
uint64_t system_time_seconds, uint64_t* timer_value) {
if (clock_values == NULL || timer_limits == NULL || timer_value == NULL) {
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
/* If playback has already started, and rental duration enforcement is soft,
* then there is no restriction. */
if (clock_values->time_of_first_decrypt > 0 &&
timer_limits->soft_enforce_rental_duration) {
return ODK_DISABLE_TIMER;
}
/* rental_clock = time since license signed. */
uint64_t rental_clock = 0;
if (odk_sub_overflow_u64(system_time_seconds,
clock_values->time_of_license_request_signed,
&rental_clock)) {
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
/* Check if it is before license is valid. This is an unusual case. First
* playback may still work if it occurs after the rental window opens. */
if (rental_clock < timer_limits->earliest_playback_start_seconds) {
return ODK_TIMER_EXPIRED;
}
/* If the rental duration is 0, there is no limit. */
if (timer_limits->rental_duration_seconds == 0) {
return ODK_DISABLE_TIMER;
}
/* End of rental window, based on rental clock (not system time). */
uint64_t end_of_rental_window = 0;
if (odk_add_overflow_u64(timer_limits->earliest_playback_start_seconds,
timer_limits->rental_duration_seconds,
&end_of_rental_window)) {
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
if (end_of_rental_window <= rental_clock) {
return ODK_TIMER_EXPIRED;
}
/* At this point system_time is within the rental window. */
if (timer_limits->soft_enforce_rental_duration) {
/* For soft enforcement, we allow playback, and do not adjust the timer. */
return ODK_DISABLE_TIMER;
}
uint64_t time_left = 0;
if (odk_sub_overflow_u64(end_of_rental_window, rental_clock, &time_left)) {
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
ComputeMinimum(timer_value, time_left);
return ODK_SET_TIMER;
}
/* Private function. Check to see if the playback window restricts
* playback. This should only be called if playback has started, so that
* clock_values->time_of_first_decrypt is nonzero.
* Return ODK_TIMER_EXPIRED if out of the window.
* Return ODK_SET_TIMER if within the window, and there is a hard limit.
* Return ODK_DISABLE_TIMER if no limit.
* Return other error on error.
* Also, if this function does compute a limit, the timer_value is reduced to
* obey that limit. If the limit is less restrictive than the current
* timer_value, then timer_value is not changed. */
static OEMCryptoResult ODK_CheckPlaybackWindow(
const ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values,
uint64_t system_time_seconds, uint64_t* timer_value) {
if (clock_values == NULL || timer_limits == NULL || timer_value == NULL) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
/* if the playback duration is 0, there is no limit. */
if (timer_limits->total_playback_duration_seconds == 0) {
return ODK_DISABLE_TIMER;
}
uint64_t end_of_playback_window = 0;
if (odk_add_overflow_u64(timer_limits->total_playback_duration_seconds,
clock_values->time_of_first_decrypt,
&end_of_playback_window)) {
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
if (end_of_playback_window <= system_time_seconds) {
return ODK_TIMER_EXPIRED;
}
/* At this point, system_time is within the total playback window. */
if (timer_limits->soft_enforce_playback_duration) {
/* For soft enforcement, we allow playback, and do not adjust the timer. */
return ODK_DISABLE_TIMER;
}
uint64_t time_left = 0;
if (odk_sub_overflow_u64(end_of_playback_window, system_time_seconds,
&time_left)) {
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
ComputeMinimum(timer_value, time_left);
return ODK_SET_TIMER;
}
/* Update the timer status. If playback has already started, we use the given
* status. However, if playback has not yet started, then we expect a call to
* ODK_AttemptFirstPlayback in the future, and we need to signal to it that we
* have already computed the timer limit. */
static void ODK_UpdateTimerStatusForRenewal(ODK_ClockValues* clock_values,
uint32_t new_status) {
if (clock_values == NULL) {
return; /* should not happen. */
}
if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED) {
/* Signal that the timer is already set. */
clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_RENEWAL_LOADED;
} else {
clock_values->timer_status = new_status;
}
}
/* Private function, but accessed from odk.c so cannot be static. This checks to
* see if a renewal message should restart the playback timer and sets the value
* appropriately. */
OEMCryptoResult ODK_ComputeRenewalDuration(const ODK_TimerLimits* timer_limits,
ODK_ClockValues* clock_values,
uint64_t system_time_seconds,
uint64_t new_renewal_duration,
uint64_t* timer_value) {
if (timer_limits == NULL || clock_values == NULL) {
return OEMCrypto_ERROR_INVALID_CONTEXT; /* should not happen. */
}
/* If this is before the license was signed, something is odd. Return an
* error. */
if (system_time_seconds < clock_values->time_of_license_request_signed) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
const OEMCryptoResult license_status =
ODK_LicenseActive(timer_limits, clock_values);
/* If the license is not active, then we cannot renew the license. */
if (license_status != OEMCrypto_SUCCESS) {
return license_status;
}
/* We start with the new renewal duration as the new timer limit. */
uint64_t new_timer_value = new_renewal_duration;
/* Then we factor in the rental window restrictions. This might decrease
* new_timer_value. */
const OEMCryptoResult rental_status = ODK_CheckRentalWindow(
timer_limits, clock_values, system_time_seconds, &new_timer_value);
/* If the rental status forbids playback, then we're done. */
if ((rental_status != ODK_DISABLE_TIMER) &&
(rental_status != ODK_SET_TIMER)) {
return rental_status;
}
/* If playback has already started and it has hard enforcement, then check
* total playback window. */
if (clock_values->time_of_first_decrypt > 0 &&
!timer_limits->soft_enforce_playback_duration) {
/* This might decrease new_timer_value. */
const OEMCryptoResult playback_status = ODK_CheckPlaybackWindow(
timer_limits, clock_values, system_time_seconds, &new_timer_value);
/* If the timer limits forbid playback in the playback window, then we're
* done. */
if ((playback_status != ODK_DISABLE_TIMER) &&
(playback_status != ODK_SET_TIMER)) {
return playback_status;
}
}
/* If new_timer_value is infinite (represented by 0), then there are no
* limits, so we can return now. */
if (new_timer_value == 0) {
clock_values->time_when_timer_expires = 0;
ODK_UpdateTimerStatusForRenewal(clock_values,
ODK_CLOCK_TIMER_STATUS_UNLIMITED);
return ODK_DISABLE_TIMER;
}
/* If the caller gave us a pointer to store the new timer value. Fill it. */
if (timer_value != NULL) {
*timer_value = new_timer_value;
}
if (odk_add_overflow_u64(system_time_seconds, new_timer_value,
&clock_values->time_when_timer_expires)) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
ODK_UpdateTimerStatusForRenewal(clock_values, ODK_CLOCK_TIMER_STATUS_ACTIVE);
return ODK_SET_TIMER;
}
/************************************************************************/
/************************************************************************/
/* Public functions, declared in odk.h. */
/* This is called when OEMCrypto opens a new session. */
OEMCryptoResult ODK_InitializeSessionValues(ODK_TimerLimits* timer_limits,
ODK_ClockValues* clock_values,
ODK_NonceValues* nonce_values,
uint32_t api_major_version,
uint32_t session_id) {
if (timer_limits == NULL || clock_values == NULL || nonce_values == NULL) {
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
timer_limits->soft_enforce_rental_duration = false;
timer_limits->soft_enforce_playback_duration = false;
timer_limits->earliest_playback_start_seconds = 0;
timer_limits->rental_duration_seconds = 0;
timer_limits->total_playback_duration_seconds = 0;
timer_limits->initial_renewal_duration_seconds = 0;
ODK_InitializeClockValues(clock_values, 0);
nonce_values->api_major_version = api_major_version;
// This needs to be updated with new version releases in the default features
// of core message features.
switch (nonce_values->api_major_version) {
case 16:
nonce_values->api_minor_version = 5;
break;
case 17:
nonce_values->api_minor_version = 2;
break;
case 18:
nonce_values->api_minor_version = 4;
break;
default:
nonce_values->api_minor_version = 0;
break;
}
nonce_values->nonce = 0;
nonce_values->session_id = session_id;
return OEMCrypto_SUCCESS;
}
/* This is called when OEMCrypto generates a new nonce in
* OEMCrypto_GenerateNonce. */
OEMCryptoResult ODK_SetNonceValues(ODK_NonceValues* nonce_values,
uint32_t nonce) {
if (nonce_values == NULL) {
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
/* Setting the nonce should only happen once per session. */
if (nonce_values->nonce != 0) {
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
nonce_values->nonce = nonce;
return OEMCrypto_SUCCESS;
}
/* This is called when OEMCrypto signs a license. */
OEMCryptoResult ODK_InitializeClockValues(ODK_ClockValues* clock_values,
uint64_t system_time_seconds) {
if (clock_values == NULL) {
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
clock_values->time_of_license_request_signed = system_time_seconds;
clock_values->time_of_first_decrypt = 0;
clock_values->time_of_last_decrypt = 0;
clock_values->time_of_renewal_request = 0;
clock_values->time_when_timer_expires = 0;
clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED;
clock_values->status = kUnused;
return OEMCrypto_SUCCESS;
}
/* This is called when OEMCrypto reloads a usage entry. */
OEMCryptoResult ODK_ReloadClockValues(ODK_ClockValues* clock_values,
uint64_t time_of_license_request_signed,
uint64_t time_of_first_decrypt,
uint64_t time_of_last_decrypt,
enum OEMCrypto_Usage_Entry_Status status,
uint64_t system_time_seconds UNUSED) {
if (clock_values == NULL) {
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
clock_values->time_of_license_request_signed = time_of_license_request_signed;
clock_values->time_of_first_decrypt = time_of_first_decrypt;
clock_values->time_of_last_decrypt = time_of_last_decrypt;
clock_values->time_when_timer_expires = 0;
clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED;
clock_values->status = status;
return OEMCrypto_SUCCESS;
}
/* This is called on the first playback for a session. */
OEMCryptoResult ODK_AttemptFirstPlayback(uint64_t system_time_seconds,
const ODK_TimerLimits* timer_limits,
ODK_ClockValues* clock_values,
uint64_t* timer_value) {
if (clock_values == NULL || timer_limits == NULL) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
/* All times are relative to when the license was signed. */
uint64_t rental_time = 0;
if (odk_sub_overflow_u64(system_time_seconds,
clock_values->time_of_license_request_signed,
&rental_time)) {
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
if (rental_time < timer_limits->earliest_playback_start_seconds) {
clock_values->timer_status = ODK_TIMER_EXPIRED;
return ODK_TIMER_EXPIRED;
}
/* If the license is inactive or not loaded, then playback is not allowed. */
OEMCryptoResult status = ODK_LicenseActive(timer_limits, clock_values);
if (status != OEMCrypto_SUCCESS) {
return status;
}
/* We start with the initial renewal duration as the timer limit. */
uint64_t new_timer_value = timer_limits->initial_renewal_duration_seconds;
/* However, if a renewal was loaded before this first playback, use the
* previously computed limit. */
if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_RENEWAL_LOADED) {
if (clock_values->time_when_timer_expires <= system_time_seconds) {
return ODK_TIMER_EXPIRED;
}
if (odk_sub_overflow_u64(clock_values->time_when_timer_expires,
system_time_seconds, &new_timer_value)) {
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
}
/* Then we factor in the rental window restrictions. This might decrease
* new_timer_value. */
status = ODK_CheckRentalWindow(timer_limits, clock_values,
system_time_seconds, &new_timer_value);
if ((status != ODK_DISABLE_TIMER) && (status != ODK_SET_TIMER)) {
return status;
}
/* If playback has not already started, then this is the first playback. */
if (clock_values->time_of_first_decrypt == 0) {
clock_values->time_of_first_decrypt = system_time_seconds;
clock_values->status = kActive;
}
/* Similar to the rental window, we check the playback window
* restrictions. This might decrease new_timer_value. */
status = ODK_CheckPlaybackWindow(timer_limits, clock_values,
system_time_seconds, &new_timer_value);
if ((status != ODK_DISABLE_TIMER) && (status != ODK_SET_TIMER)) {
return status;
}
/* We know we are allowed to decrypt. The rest computes the timer duration. */
clock_values->time_of_last_decrypt = system_time_seconds;
/* If new_timer_value is infinite (represented by 0), then there are no
* limits, so we can return now. */
if (new_timer_value == 0) {
clock_values->time_when_timer_expires = 0;
clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_UNLIMITED;
return ODK_DISABLE_TIMER;
}
/* If the caller gave us a pointer to store the new timer value. Fill it. */
if (timer_value) {
*timer_value = new_timer_value;
}
if (odk_add_overflow_u64(system_time_seconds, new_timer_value,
&clock_values->time_when_timer_expires)) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_ACTIVE;
return ODK_SET_TIMER;
}
/* This is called regularly during playback if OEMCrypto does not implement its
* own timer. */
OEMCryptoResult ODK_UpdateLastPlaybackTime(uint64_t system_time_seconds,
const ODK_TimerLimits* timer_limits,
ODK_ClockValues* clock_values) {
OEMCryptoResult status = ODK_LicenseActive(timer_limits, clock_values);
if (status != OEMCrypto_SUCCESS) {
return status;
}
switch (clock_values->timer_status) {
case ODK_CLOCK_TIMER_STATUS_UNLIMITED:
break;
case ODK_CLOCK_TIMER_STATUS_ACTIVE:
/* Note: we allow playback at the time when the timer expires, but not
* after. This is not important for business cases, but it makes it
* easier to write tests. */
if (clock_values->time_when_timer_expires > 0 &&
system_time_seconds > clock_values->time_when_timer_expires) {
clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_EXPIRED;
return ODK_TIMER_EXPIRED;
}
break;
default: /* Expired, error state, or never started. */
return ODK_TIMER_EXPIRED;
}
clock_values->time_of_last_decrypt = system_time_seconds;
return OEMCrypto_SUCCESS;
}
/* This is called from OEMCrypto_DeactivateUsageEntry. */
OEMCryptoResult ODK_DeactivateUsageEntry(ODK_ClockValues* clock_values) {
if (clock_values == NULL) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (clock_values->status == kUnused) {
clock_values->status = kInactiveUnused;
} else if (clock_values->status == kActive) {
clock_values->status = kInactiveUsed;
}
clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE;
return OEMCrypto_SUCCESS;
}
/* This is called when OEMCrypto loads a legacy v15 license, from
* OEMCrypto_LoadKeys. */
OEMCryptoResult ODK_InitializeV15Values(ODK_TimerLimits* timer_limits,
ODK_ClockValues* clock_values,
ODK_NonceValues* nonce_values,
uint32_t key_duration,
uint64_t system_time_seconds) {
if (timer_limits == NULL || clock_values == NULL || nonce_values == NULL) {
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
timer_limits->soft_enforce_playback_duration = false;
timer_limits->soft_enforce_rental_duration = false;
timer_limits->earliest_playback_start_seconds = 0;
timer_limits->rental_duration_seconds = 0;
timer_limits->total_playback_duration_seconds = 0;
timer_limits->initial_renewal_duration_seconds = key_duration;
nonce_values->api_major_version = 15;
nonce_values->api_minor_version = 0;
if (key_duration > 0) {
clock_values->time_when_timer_expires = system_time_seconds + key_duration;
} else {
clock_values->time_when_timer_expires = 0;
}
clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED;
return OEMCrypto_SUCCESS;
}
/* This is called when OEMCrypto loads a legacy license renewal in
* OEMCrypto_RefreshKeys. */
OEMCryptoResult ODK_RefreshV15Values(const ODK_TimerLimits* timer_limits,
ODK_ClockValues* clock_values,
const ODK_NonceValues* nonce_values,
uint64_t system_time_seconds,
uint32_t new_key_duration,
uint64_t* timer_value) {
if (timer_limits == NULL || clock_values == NULL || nonce_values == NULL) {
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
if (nonce_values->api_major_version != 15) {
return OEMCrypto_ERROR_INVALID_NONCE;
}
if (clock_values->status > kActive) {
clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE;
return ODK_TIMER_EXPIRED;
}
return ODK_ComputeRenewalDuration(timer_limits, clock_values,
system_time_seconds, new_key_duration,
timer_value);
}