// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. #include #include #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); }