Skip to content

Commit

Permalink
Synchronizes SEIs for legacy code
Browse files Browse the repository at this point in the history
At validation starts, SEIs are not synchronized with an I-frame. It is
unknown how many SEIs that cannot be used to validate frames, since
they sign GOPs not present.

This commit loops through all possible validation cases with the SEI
by temporarily use tmp_validation_status and a few more flags. The
validation_status is then updated when the SEI is not feasible to use
in a validation.
  • Loading branch information
bjornvolcker committed Dec 6, 2024
1 parent 38ce246 commit 72dbe7e
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 70 deletions.
169 changes: 112 additions & 57 deletions lib/src/legacy/legacy_h26x_auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,13 @@ legacy_verify_hashes_with_hash_list(legacy_sv_t *self,
// legacy_verify_hashes_without_sei().
switch (self->gop_info->verified_signature_hash) {
case -1:
sei->validation_status = 'E';
sei->tmp_validation_status = 'E';
return legacy_verify_hashes_without_sei(self);
case 0:
sei->validation_status = 'N';
sei->tmp_validation_status = 'N';
return legacy_verify_hashes_without_sei(self);
case 1:
assert(sei->validation_status == 'P');
assert(sei->tmp_validation_status == 'P');
break;
default:
// We should not end up here.
Expand All @@ -169,7 +169,7 @@ legacy_verify_hashes_with_hash_list(legacy_sv_t *self,
// against the feasible hashes in the received |hash_list|.
while (item && !(found_next_gop || found_item_after_sei)) {
// If this item is not Pending, move to the next one.
if (item->validation_status != 'P') {
if (item->tmp_validation_status != 'P') {
item = item->next;
continue;
}
Expand All @@ -178,7 +178,7 @@ legacy_verify_hashes_with_hash_list(legacy_sv_t *self,
// Check if this is the item right after the |sei|.
found_item_after_sei = (item->prev == sei);
// Check if this |is_first_nalu_in_gop|, but not used before.
found_next_gop = (item->nalu->is_first_nalu_in_gop && !item->need_second_verification);
found_next_gop = (item->nalu->is_first_nalu_in_gop && !item->tmp_need_second_verification);
// If this is a SEI, it is not part of the hash list and should not be verified.
if (item->nalu->is_gop_sei) {
item = item->next;
Expand All @@ -190,7 +190,17 @@ legacy_verify_hashes_with_hash_list(legacy_sv_t *self,

// Fetch the |hash_to_verify|, which normally is the item->hash, but if this is NALU has been
// used in a previous verification we use item->second_hash.
uint8_t *hash_to_verify = item->need_second_verification ? item->second_hash : item->hash;
uint8_t *hash_to_verify = item->tmp_need_second_verification ? item->second_hash : item->hash;
// If the item is the very first (hashable) item in the stream, both the |second_hash| and the
// |hash| will be identical. The |hash| is actually wrong since it should have been hashed with
// reference. Since it is not feasible to use that hash there is no reason to try to match it
// against the hashes in the list. Below it is determined if one should |skip_check|.
const bool has_same_hash =
(item->second_hash && (memcmp(item->second_hash, item->hash, hash_size) == 0));
const bool is_first_received_nalu =
item->nalu->is_first_nalu_in_gop && has_same_hash && !item->tmp_need_second_verification;
const bool is_first_stream_sei = (num_expected_hashes == 1);
const bool skip_check = is_first_received_nalu && !is_first_stream_sei;

// Compare |hash_to_verify| against all the |expected_hashes| since the |latest_match_idx|. Stop
// when we get a match or reach the end.
Expand All @@ -199,17 +209,17 @@ legacy_verify_hashes_with_hash_list(legacy_sv_t *self,
while (compare_idx < num_expected_hashes) {
uint8_t *expected_hash = &expected_hashes[compare_idx * hash_size];

if (memcmp(hash_to_verify, expected_hash, hash_size) == 0) {
if (memcmp(hash_to_verify, expected_hash, hash_size) == 0 && !skip_check) {
// We have a match. Set validation_status and add missing nalus if we have detected any.
if (item->second_hash && !item->need_second_verification &&
if (item->second_hash && !item->tmp_need_second_verification &&
item->nalu->is_first_nalu_in_gop) {
// If this |is_first_nalu_in_gop| it should be verified twice. If this the first time we
// signal that we |need_second_verification|.
DEBUG_LOG("This NALU needs a second verification");
item->need_second_verification = true;
item->tmp_need_second_verification = true;
} else {
item->validation_status = item->first_verification_not_authentic ? 'N' : '.';
item->need_second_verification = false;
item->tmp_validation_status = item->tmp_first_verification_not_authentic ? 'N' : '.';
item->tmp_need_second_verification = false;
}
// Add missing items to |nalu_list|.
int num_detected_missing_nalus =
Expand All @@ -229,15 +239,15 @@ legacy_verify_hashes_with_hash_list(legacy_sv_t *self,
if (latest_match_idx != compare_idx) {
// We have compared against all feasible hashes in |hash_list| without a match. Mark as NOT
// OK, or keep pending for second use.
if (item->second_hash && !item->need_second_verification) {
item->need_second_verification = true;
if (item->second_hash && !item->tmp_need_second_verification) {
item->tmp_need_second_verification = true;
// If this item will be used in a second verification the flag
// |first_verification_not_authentic| is set.
item->first_verification_not_authentic = true;
item->tmp_first_verification_not_authentic = true;
} else {
// Reset |need_second_verification|.
item->need_second_verification = false;
item->validation_status = 'N';
item->tmp_need_second_verification = false;
item->tmp_validation_status = 'N';
}
// Update counters.
num_invalid_nalus_since_latest_match++;
Expand All @@ -254,7 +264,7 @@ legacy_verify_hashes_with_hash_list(legacy_sv_t *self,
// first item. If the first item needs a second opinion, that is, it has already been verified
// once, we append that item. Otherwise, prepend it with missing items.
const bool append =
nalu_list->first_item->second_hash && !nalu_list->first_item->need_second_verification;
nalu_list->first_item->second_hash && !nalu_list->first_item->tmp_need_second_verification;
// No need to check the return value. A failure only affects the statistics. In the worst case
// we may signal SV_AUTH_RESULT_OK instead of SV_AUTH_RESULT_OK_WITH_MISSING_INFO.
legacy_h26x_nalu_list_add_missing(nalu_list, num_missing_nalus, append, nalu_list->first_item);
Expand All @@ -266,10 +276,10 @@ legacy_verify_hashes_with_hash_list(legacy_sv_t *self,
if (last_used_item) {
if (latest_match_idx != compare_idx) {
// Last verified hash is invalid.
last_used_item->first_verification_not_authentic = true;
last_used_item->tmp_first_verification_not_authentic = true;
// Give this NALU a second verification because it could be that it is present in the next GOP
// and brought in here due to some lost NALUs.
last_used_item->need_second_verification = true;
last_used_item->tmp_need_second_verification = true;
} else {
// Last received hash is valid. Check if there are unused hashes in |hash_list|. Note that the
// index of the hashes span from 0 to |num_expected_hashes| - 1, so if |latest_match_idx| =
Expand All @@ -291,7 +301,7 @@ legacy_verify_hashes_with_hash_list(legacy_sv_t *self,

// Done with the SEI. Mark as valid, because if we failed verifying the |document_hash| we would
// not be here.
sei->validation_status = '.';
sei->tmp_validation_status = '.';

if (num_expected_nalus) *num_expected_nalus = num_expected_hashes;
if (num_received_nalus) *num_received_nalus = num_verified_hashes;
Expand All @@ -316,13 +326,14 @@ legacy_set_validation_status_of_items_used_in_gop_hash(legacy_h26x_nalu_list_t *
if (item->used_in_gop_hash) {
// Items used in two verifications should not have |validation_status| set until it has been
// used twice. If this is the first time we set the flag |first_verification_not_authentic|.
if (item->second_hash && !item->need_second_verification) {
if (item->second_hash && !item->tmp_need_second_verification) {
DEBUG_LOG("This NALU needs a second verification");
item->need_second_verification = true;
item->first_verification_not_authentic = (validation_status != '.') ? true : false;
item->tmp_need_second_verification = true;
item->tmp_first_verification_not_authentic = (validation_status != '.') ? true : false;
} else {
item->validation_status = item->first_verification_not_authentic ? 'N' : validation_status;
item->need_second_verification = false;
item->tmp_validation_status =
item->tmp_first_verification_not_authentic ? 'N' : validation_status;
item->tmp_need_second_verification = false;
num_marked_items++;
}
}
Expand Down Expand Up @@ -428,24 +439,24 @@ legacy_verify_hashes_without_sei(legacy_sv_t *self)
bool found_next_gop = false;
while (item && !found_next_gop) {
// Skip non-pending items.
if (item->validation_status != 'P') {
if (item->tmp_validation_status != 'P') {
item = item->next;
continue;
}

// A new GOP starts if the NALU |is_first_nalu_in_gop|. Such a NALU is hashed twice; as an
// initial hash AND as a linking hash between GOPs. If this is the first time is is used in
// verification it also marks the start of a new GOP.
found_next_gop = item->nalu->is_first_nalu_in_gop && !item->need_second_verification;
found_next_gop = item->nalu->is_first_nalu_in_gop && !item->tmp_need_second_verification;

// Mark the item as 'Not Authentic' or keep it for a second verification.
if (found_next_gop) {
// Keep the item pending and mark the first verification as not authentic.
item->need_second_verification = true;
item->first_verification_not_authentic = true;
} else if (item->validation_status == 'P') {
item->need_second_verification = false;
item->validation_status = 'N';
item->tmp_need_second_verification = true;
item->tmp_first_verification_not_authentic = true;
} else if (item->tmp_validation_status == 'P') {
item->tmp_need_second_verification = false;
item->tmp_validation_status = 'N';
num_marked_items++;
}
item = item->next;
Expand Down Expand Up @@ -576,7 +587,8 @@ legacy_validate_authenticity(legacy_sv_t *self)
num_received_nalus = -1;
// If validation was tried with the very first SEI in stream it cannot be part at.
// Reset the first validation to be able to validate a segment in the middle of the stream.
self->validation_flags.reset_first_validation = (self->gop_info->num_sent_nalus == 1);
self->validation_flags.reset_first_validation =
(self->gop_info->num_sent_nalus == 1) || !has_valid_nalus;
}
}
if (latest->public_key_has_changed) valid = SV_AUTH_RESULT_NOT_OK;
Expand All @@ -600,6 +612,37 @@ legacy_remove_used_in_gop_hash(legacy_h26x_nalu_list_t *nalu_list)
}
}

/* Updates validation status for SEI that is |in_validation|. */
static void
legacy_update_sei_validation(legacy_sv_t *self,
bool reset_in_validation,
char *get_validation_status,
char *set_validation_status)
{
legacy_h26x_nalu_list_item_t *item = self->nalu_list->first_item;
while (item) {
if (item->nalu && item->nalu->is_gop_sei && item->in_validation) {
if (reset_in_validation) {
item->in_validation = false;
}
if (get_validation_status) {
if (item->tmp_validation_status == '.') {
*get_validation_status = item->tmp_validation_status;
}
item->tmp_validation_status = item->validation_status;
}
if (set_validation_status) {
if (item->next && item->next->nalu) {
item->validation_status = *set_validation_status;
item->tmp_validation_status = *set_validation_status;
}
}
break;
}
item = item->next;
}
}

/* Computes the gop_hash of the oldest pending GOP in the nalu_list and completes the recursive
* operation with the hash of the |sei|. */
static svrc_t
Expand Down Expand Up @@ -637,7 +680,7 @@ legacy_compute_gop_hash(legacy_sv_t *self, legacy_h26x_nalu_list_item_t *sei)
item = nalu_list->first_item;
while (item && !(found_next_gop || found_item_after_sei)) {
// If this item is not Pending, move to the next one.
if (item->validation_status != 'P') {
if (item->tmp_validation_status != 'P') {
item = item->next;
continue;
}
Expand All @@ -646,7 +689,7 @@ legacy_compute_gop_hash(legacy_sv_t *self, legacy_h26x_nalu_list_item_t *sei)
// Check if this is the item after the |sei|.
found_item_after_sei = (item->prev == sei);
// Check if this |is_first_nalu_in_gop|, but used in verification for the first time.
found_next_gop = (item->nalu->is_first_nalu_in_gop && !item->need_second_verification);
found_next_gop = (item->nalu->is_first_nalu_in_gop && !item->tmp_need_second_verification);
// If this is the SEI associated with the GOP, or any SEI, we skip it. The SEI hash will be
// added to |gop_hash| as the last hash.
if (item->nalu->is_gop_sei) {
Expand All @@ -656,7 +699,7 @@ legacy_compute_gop_hash(legacy_sv_t *self, legacy_h26x_nalu_list_item_t *sei)

// Fetch the |hash_to_add|, which normally is the item->hash, but if the item has been used
// ones in verification we use the |second_hash|.
hash_to_add = item->need_second_verification ? item->second_hash : item->hash;
hash_to_add = item->tmp_need_second_verification ? item->second_hash : item->hash;
// Copy to the |nalu_hash| slot in the memory and update the gop_hash.
memcpy(nalu_hash, hash_to_add, hash_size);
SV_THROW(legacy_update_gop_hash(self->crypto_handle, gop_info));
Expand Down Expand Up @@ -703,6 +746,9 @@ legacy_prepare_for_validation(legacy_sv_t *self)
svrc_t status = SV_UNKNOWN_FAILURE;
SV_TRY()
legacy_h26x_nalu_list_item_t *sei = legacy_h26x_nalu_list_get_next_sei_item(nalu_list);
if (sei) {
sei->in_validation = true;
}
if (sei && !sei->has_been_decoded) {
// Decode the SEI and set signature->hash
const uint8_t *tlv_data = sei->nalu->tlv_data;
Expand All @@ -728,7 +774,7 @@ legacy_prepare_for_validation(legacy_sv_t *self)
#ifdef SV_VENDOR_AXIS_COMMUNICATIONS
// If "Axis Communications AB" can be identified from the |product_info|, get
// |supplemental_authenticity| from |vendor_handle|.
if (self->product_info->manufacturer &&
if (sei && self->product_info->manufacturer &&
strcmp(self->product_info->manufacturer, "Axis Communications AB") == 0) {

sv_vendor_axis_supplemental_authenticity_t *supplemental_authenticity = NULL;
Expand Down Expand Up @@ -816,8 +862,9 @@ legacy_has_pending_gop(legacy_sv_t *self)
legacy_gop_state_update(gop_state, item->nalu);
// Collect statistics from pending and hashable NALUs only. The others are either out of date or
// not part of the validation.
if (item->validation_status == 'P' && item->nalu && item->nalu->is_hashable) {
num_pending_gop_ends += (item->nalu->is_first_nalu_in_gop && !item->need_second_verification);
if (item->tmp_validation_status == 'P' && item->nalu && item->nalu->is_hashable) {
num_pending_gop_ends +=
(item->nalu->is_first_nalu_in_gop && !item->tmp_need_second_verification);
found_pending_gop_sei |= item->nalu->is_gop_sei;
found_pending_nalu_after_gop_sei |=
last_hashable_item && last_hashable_item->nalu->is_gop_sei;
Expand Down Expand Up @@ -909,6 +956,9 @@ legacy_maybe_validate_gop(legacy_sv_t *self, legacy_h26x_nalu_t *nalu)

svrc_t status = SV_UNKNOWN_FAILURE;
SV_TRY()
bool update_validation_status = false;
bool public_key_has_changed = false;
char sei_validation_status = 'U';
// Keep validating as long as there are pending GOPs.
bool stop_validating = false;
while (legacy_has_pending_gop(self) && !stop_validating) {
Expand All @@ -917,7 +967,11 @@ legacy_maybe_validate_gop(legacy_sv_t *self, legacy_h26x_nalu_t *nalu)
latest->number_of_expected_picture_nalus = -1;
latest->number_of_received_picture_nalus = -1;
latest->number_of_pending_picture_nalus = -1;
latest->public_key_has_changed = false;
latest->public_key_has_changed = public_key_has_changed;

if (validation_flags->is_first_validation) {
legacy_update_sei_validation(self, true, NULL, NULL);
}

SV_THROW(legacy_prepare_for_validation(self));

Expand All @@ -930,33 +984,34 @@ legacy_maybe_validate_gop(legacy_sv_t *self, legacy_h26x_nalu_t *nalu)
legacy_validate_authenticity(self);
}

if (validation_flags->is_first_validation && (latest->authenticity != SV_AUTH_RESULT_OK)) {
legacy_update_sei_validation(self, false, &sei_validation_status, NULL);
}
// The flag |is_first_validation| is used to ignore the first validation if we start the
// validation in the middle of a stream. Now it is time to reset it.
validation_flags->is_first_validation = !validation_flags->signing_present;

if (validation_flags->reset_first_validation) {
validation_flags->is_first_validation = true;
legacy_h26x_nalu_list_item_t *item = self->nalu_list->first_item;
while (item) {
if (item->nalu && item->nalu->is_first_nalu_in_gop) {
item->need_second_verification = false;
item->first_verification_not_authentic = false;
break;
}
item = item->next;
}
validation_flags->reset_first_validation = false;
} else {
update_validation_status = true;
}
self->gop_info->verified_signature_hash = -1;
self->validation_flags.has_auth_result = true;

// All statistics but pending NALUs have already been collected.
latest->number_of_pending_picture_nalus = legacy_h26x_nalu_list_num_pending_items(nalu_list);

DEBUG_LOG("Validated GOP as %s", kLegacyAuthResultValidStr[latest->authenticity]);
DEBUG_LOG("Expected number of NALUs = %d", latest->number_of_expected_picture_nalus);
DEBUG_LOG("Received number of NALUs = %d", latest->number_of_received_picture_nalus);
DEBUG_LOG("Number of pending NALUs = %d", latest->number_of_pending_picture_nalus);
public_key_has_changed |= latest->public_key_has_changed;
}
SV_THROW(legacy_h26x_nalu_list_update_status(nalu_list, update_validation_status));
if (validation_flags->is_first_validation) {
legacy_update_sei_validation(self, false, NULL, &sei_validation_status);
}
// All statistics but pending NALUs have already been collected.
latest->number_of_pending_picture_nalus = legacy_h26x_nalu_list_num_pending_items(nalu_list);

DEBUG_LOG("Validated GOP as %s", kLegacyAuthResultValidStr[latest->authenticity]);
DEBUG_LOG("Expected number of NALUs = %d", latest->number_of_expected_picture_nalus);
DEBUG_LOG("Received number of NALUs = %d", latest->number_of_received_picture_nalus);
DEBUG_LOG("Number of pending NALUs = %d", latest->number_of_pending_picture_nalus);
SV_CATCH()
SV_DONE(status)

Expand Down
4 changes: 3 additions & 1 deletion lib/src/legacy/legacy_h26x_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -819,7 +819,9 @@ legacy_hash_and_add_for_auth(legacy_sv_t *self, legacy_h26x_nalu_list_item_t *it
// Check if we have a potential transition to a new GOP. This happens if the current NALU
// |is_first_nalu_in_gop|. If we have lost the first NALU of a GOP we can still make a guess by
// checking if |has_sei| flag is set. It is set if the previous hashable NALU was SEI.
if (nalu->is_first_nalu_in_gop || (gop_state->validate_after_next_nalu && !nalu->is_gop_sei)) {
if (nalu->is_first_nalu_in_gop ||
(gop_state->validate_after_next_nalu && !nalu->is_gop_sei &&
gop_info->global_gop_counter_is_synced)) {
// Updates counters and reset flags.
gop_info->has_reference_hash = false;

Expand Down
5 changes: 5 additions & 0 deletions lib/src/legacy/legacy_h26x_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ struct _legacy_h26x_nalu_list_item_st {
bool has_been_decoded; // Marks a SEI as decoded. Decoding it twice might overwrite vital
// information.
bool used_in_gop_hash; // Marks the NALU as being part of a computed |gop_hash|.
bool in_validation; // Marks the SEI that is currently up for use. Necessary for synchronization.
// Temporary flags used before updating the final ones.
char tmp_validation_status;
bool tmp_need_second_verification;
bool tmp_first_verification_not_authentic;

// Linked list
legacy_h26x_nalu_list_item_t
Expand Down
Loading

0 comments on commit 72dbe7e

Please sign in to comment.