Skip to content

Commit 27a3afa

Browse files
authored
Implements support for signing multiple GOPs (#429)
Through the API signed_video_set_signing_frequency(...) the user can specify how often GOPs should be signed. Unsigned GOPs will still produce SEIs, but without signatures. These SEIs are hashed like normal frames and eventually signed with the final SEI. A test has been added for signing multiple GOPs.
1 parent bd64713 commit 27a3afa

File tree

7 files changed

+116
-17
lines changed

7 files changed

+116
-17
lines changed

lib/src/includes/signed_video_sign.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,25 @@ SignedVideoReturnCode
332332
signed_video_set_authenticity_level(signed_video_t *self,
333333
SignedVideoAuthenticityLevel authenticity_level);
334334

335+
/**
336+
* @brief Sets the signing frequency for this Signed Video session
337+
*
338+
* The default behavior of the Signed Video library is to sign and generate a SEI/OBU
339+
* Metadata every GOP (Group Of Pictures). Due to hardware resource limitations and GOP
340+
* length settings, signing every GOP can become infeasible in real-time. For example,
341+
* when multiple streams are signed or if the GOP length is very short.
342+
*
343+
* This API allows the user to change the signing frequency at anytime during a session.
344+
* The signing frequency is measured in number of GOPs.
345+
*
346+
* @param self Pointer to the Signed Video session.
347+
* @param signing_frequency Number of GOPs between signatures (default 1)
348+
*
349+
* @returns A Signed Video Return Code.
350+
*/
351+
SignedVideoReturnCode
352+
signed_video_set_signing_frequency(signed_video_t *self, unsigned signing_frequency);
353+
335354
/**
336355
* @brief Sets the average recurrence interval for the signed video session in frames
337356
*

lib/src/sv_common.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ static void
371371
remove_epb_from_sei_payload(bu_info_t *bu)
372372
{
373373
assert(bu);
374-
if (!bu->is_hashable || !bu->is_sv_sei || (bu->is_valid <= 0)) return;
374+
if (!bu->is_sv_sei || (bu->is_valid <= 0)) return;
375375

376376
// The UUID (16 bytes) has by definition no emulation prevention bytes. Hence, read the
377377
// |reserved_byte| and point to the start of the TLV part.
@@ -560,6 +560,16 @@ parse_bu_info(const uint8_t *bu_data,
560560
}
561561

562562
remove_epb_from_sei_payload(&bu);
563+
if (bu.emulation_prevention_bytes >= 0 && (bu.hashable_data_size > bu.tlv_size)) {
564+
// Check if a signature TLV tag exists. If number of computed emulation prevention
565+
// bytes is negative, either the SEI is currupt or incomplete. Or if there is enough
566+
// TLV data.
567+
const uint8_t *signature_tag =
568+
sv_tlv_find_tag(bu.tlv_data, bu.tlv_size, SIGNATURE_TAG, false);
569+
bu.is_signed = (signature_tag != NULL);
570+
}
571+
// Update |is_hashable| w.r.t. signed or not.
572+
bu.is_hashable |= bu.is_sv_sei && !bu.is_signed;
563573
}
564574

565575
return bu;
@@ -968,6 +978,7 @@ signed_video_create(SignedVideoCodec codec)
968978
// Signing plugin is setup when the private key is set.
969979
self->authenticity_level = DEFAULT_AUTHENTICITY_LEVEL;
970980
self->signing_frequency = 1;
981+
self->num_gops_until_signing = self->signing_frequency;
971982
self->recurrence = RECURRENCE_ALWAYS;
972983
self->add_public_key_to_sei = true;
973984
self->sei_epb = codec != SV_CODEC_AV1;
@@ -1022,6 +1033,7 @@ signed_video_reset(signed_video_t *self)
10221033
if (self->onvif) {
10231034
SV_THROW(msrc_to_svrc(onvif_media_signing_reset(self->onvif)));
10241035
}
1036+
self->num_gops_until_signing = self->signing_frequency;
10251037
self->signing_started = false;
10261038
self->sei_generation_enabled = false;
10271039
gop_info_reset(self->gop_info);

lib/src/sv_internal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ typedef struct {
147147
bool is_last_bu_part; // True if the |bu_data| includes the last part
148148
bool with_epb; // Hashable data may include emulation prevention bytes
149149
bool is_golden_sei;
150+
bool is_signed; // True if the SEI is signed, i.e., has a signature
150151
} bu_info_t;
151152

152153
/**
@@ -285,6 +286,7 @@ struct _signed_video_t {
285286
SignedVideoAuthenticityLevel authenticity_level;
286287
size_t max_sei_payload_size; // Default 0 = unlimited
287288
unsigned signing_frequency; // Number of GOPs per signature (default 1)
289+
unsigned num_gops_until_signing; // Counter to track |signing_frequency|
288290
unsigned recurrence;
289291
unsigned max_signing_frames;
290292

lib/src/sv_sign.c

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,13 +148,14 @@ shift_sei_buffer_at_index(signed_video_t *self, int index)
148148
* any Bitstream Unit (BU) hash and added to the gop_hash. For SV_AUTHENTICITY_LEVEL_FRAME we sign
149149
* this hash instead of the gop_hash, which is the traditional principle of signing. */
150150
static svrc_t
151-
generate_sei_and_add_to_buffer(signed_video_t *self, bool ATTR_UNUSED force_signature)
151+
generate_sei_and_add_to_buffer(signed_video_t *self, bool force_signature)
152152
{
153153
sign_or_verify_data_t *sign_data = self->sign_data;
154154
const size_t hash_size = sign_data->hash_size;
155155
size_t num_optional_tags = 0;
156156
size_t num_mandatory_tags = 0;
157157
uint8_t *sei = NULL;
158+
bool sign_this_sei = (self->num_gops_until_signing == 0) || force_signature;
158159

159160
const sv_tlv_tag_t *optional_tags = sv_get_optional_tags(&num_optional_tags);
160161
const sv_tlv_tag_t *mandatory_tags = sv_get_mandatory_tags(&num_mandatory_tags);
@@ -182,6 +183,9 @@ generate_sei_and_add_to_buffer(signed_video_t *self, bool ATTR_UNUSED force_sign
182183
sv_tlv_list_encode_or_get_size(self, mandatory_tags, num_mandatory_tags, NULL);
183184
if (self->is_golden_sei) mandatory_tags_size = 0;
184185
signature_size = sv_tlv_list_encode_or_get_size(self, &signature_tag, 1, NULL);
186+
if (!sign_this_sei) {
187+
signature_size = 0;
188+
}
185189

186190
payload_size = signature_size + optional_tags_size + mandatory_tags_size;
187191
payload_size += UUID_LEN; // UUID
@@ -308,7 +312,7 @@ generate_sei_and_add_to_buffer(signed_video_t *self, bool ATTR_UNUSED force_sign
308312
// payload we need to hash the BU as it is so far and update the |gop_hash|. Parse a fake BU
309313
// with the data so far and we will automatically get the pointers to the |hashable_data| and
310314
// the size of it. Then we can use the sv_hash_and_add() function.
311-
{
315+
if (sign_this_sei) {
312316
size_t fake_payload_size = (sei_ptr - sei);
313317
// Force SEI to be hashable.
314318
bu_info_t bu_without_signature_data =
@@ -340,7 +344,12 @@ generate_sei_and_add_to_buffer(signed_video_t *self, bool ATTR_UNUSED force_sign
340344
// Reset the timestamp to avoid including a duplicate in the next SEI.
341345
gop_info->has_timestamp = false;
342346

343-
SV_THROW(sv_signing_plugin_sign(self->plugin_handle, sign_data->hash, sign_data->hash_size));
347+
if (sign_this_sei) {
348+
SV_THROW(sv_signing_plugin_sign(self->plugin_handle, sign_data->hash, sign_data->hash_size));
349+
} else {
350+
// If unsigned SEI, complete by adding Stop bit.
351+
sv_write_byte(last_two_bytes, &sei_ptr, 0x80, false);
352+
}
344353
SV_CATCH()
345354
{
346355
DEBUG_LOG("Failed to generate the SEI");
@@ -352,7 +361,7 @@ generate_sei_and_add_to_buffer(signed_video_t *self, bool ATTR_UNUSED force_sign
352361

353362
// Add |sei| to buffer. Will be picked up again when the signature has been generated.
354363
// If the SEI is not signed mark it as complete at once.
355-
add_sei_to_buffer(self, sei, sei_ptr, false);
364+
add_sei_to_buffer(self, sei, sei_ptr, !sign_this_sei);
356365

357366
return status;
358367
}
@@ -657,7 +666,14 @@ signed_video_add_nalu_part_for_signing_with_timestamp(signed_video_t *self,
657666
SV_THROW(sv_openssl_finalize_hash(self->crypto_handle, gop_info->computed_gop_hash, true));
658667
// The previous GOP is now completed. The gop_hash was reset right after signing and
659668
// adding it to the SEI.
660-
SV_THROW(generate_sei_and_add_to_buffer(self, true));
669+
SV_THROW(generate_sei_and_add_to_buffer(self, trigger_signing));
670+
if (new_gop && (self->num_gops_until_signing == 0)) {
671+
// Reset signing counter only upon new GOPs
672+
self->num_gops_until_signing = self->signing_frequency;
673+
}
674+
}
675+
if (new_gop) {
676+
self->num_gops_until_signing--;
661677
}
662678
self->sei_generation_enabled = true;
663679
}
@@ -954,6 +970,20 @@ signed_video_set_authenticity_level(signed_video_t *self,
954970
return status;
955971
}
956972

973+
SignedVideoReturnCode
974+
signed_video_set_signing_frequency(signed_video_t *self, unsigned signing_frequency)
975+
{
976+
if (!self || signing_frequency == 0) {
977+
return SV_INVALID_PARAMETER;
978+
}
979+
self->signing_frequency = signing_frequency;
980+
if (!self->signing_started) {
981+
self->num_gops_until_signing = signing_frequency;
982+
}
983+
984+
return SV_OK;
985+
}
986+
957987
SignedVideoReturnCode
958988
signed_video_set_recurrence_interval_frames(signed_video_t *self, unsigned recurrence)
959989
{

tests/check/check_signed_video_sign.c

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2020
*/
2121
#include <check.h>
22+
#include <stdbool.h>
2223
#include <stdint.h> // uint8_t
2324
#ifdef PRINT_DECODED_SEI
2425
#include <stdio.h>
@@ -58,18 +59,26 @@ verify_seis(test_stream_t *list, struct sv_setting setting)
5859
return;
5960
}
6061

62+
bool is_first_signed_sei = true;
63+
bool is_first_unsigned_sei = true;
6164
int num_seis = 0;
62-
size_t sei_size = 0;
65+
size_t sei_size_signed = 0;
66+
size_t sei_size_unsigned = 0;
6367
test_stream_item_t *item = list->first_item;
6468
while (item) {
6569
bu_info_t bu = parse_bu_info(item->data, item->data_size, list->codec, false, true);
6670
if (bu.is_sv_sei) {
67-
if (num_seis == 0) {
71+
if (bu.is_signed && is_first_signed_sei) {
72+
// Set the SEI size for the first detected SEI.
73+
sei_size_signed = item->data_size;
74+
is_first_signed_sei = false;
75+
}
76+
if (!bu.is_signed && is_first_unsigned_sei) {
6877
// Set the SEI size for the first detected SEI.
69-
sei_size = item->data_size;
78+
sei_size_unsigned = item->data_size;
79+
is_first_unsigned_sei = false;
7080
}
7181
num_seis++;
72-
const bool has_signature = tag_is_present(item, list->codec, SIGNATURE_TAG);
7382
const bool has_hash_list = tag_is_present(item, list->codec, HASH_LIST_TAG);
7483
const bool has_axis_tag = tag_is_present(item, list->codec, VENDOR_AXIS_COMMUNICATIONS_TAG);
7584
const bool has_public_key_tag = tag_is_present(item, list->codec, PUBLIC_KEY_TAG);
@@ -80,12 +89,12 @@ verify_seis(test_stream_t *list, struct sv_setting setting)
8089
// Verify SEI sizes if emulation prevention is turned off.
8190
if (setting.auth_level == SV_AUTHENTICITY_LEVEL_GOP && !setting.ep_before_signing) {
8291
// For GOP level authenticity, all SEIs should have the same size
83-
ck_assert(item->data_size == sei_size);
92+
ck_assert(item->data_size == (bu.is_signed ? sei_size_signed : sei_size_unsigned));
8493
}
8594
// Verify that SEI sizes increase over time.
8695
if (setting.increased_sei_size) {
87-
ck_assert_int_ge(item->data_size, sei_size);
88-
sei_size = item->data_size;
96+
ck_assert_int_ge(item->data_size, sei_size_signed);
97+
sei_size_signed = item->data_size;
8998
}
9099
if (setting.max_sei_payload_size > 0) {
91100
// Verify the SEI size. This overrides the authenticity level.
@@ -111,14 +120,14 @@ verify_seis(test_stream_t *list, struct sv_setting setting)
111120
ck_assert(has_optional_tags);
112121
// Verify that signatures follow the signing_frequency.
113122
if (num_seis % setting.signing_frequency == 0) {
114-
ck_assert(has_signature);
123+
ck_assert(bu.is_signed);
115124
} else {
116-
ck_assert(!has_signature);
125+
ck_assert(!bu.is_signed);
117126
}
118127
}
119128
// Verify that a golden SEI has a signature.
120129
if (bu.is_golden_sei) {
121-
ck_assert(has_signature);
130+
ck_assert(bu.is_signed);
122131
}
123132
// Verify that Axis vendor tag is present.
124133
ck_assert(!((setting.vendor_axis_mode > 0) ^ has_axis_tag));
@@ -198,6 +207,13 @@ START_TEST(api_inputs)
198207
sv_rc = signed_video_set_private_key(sv, private_key, private_key_size);
199208
ck_assert_int_eq(sv_rc, SV_OK);
200209

210+
// Check setting signing_frequency
211+
sv_rc = signed_video_set_signing_frequency(NULL, 1);
212+
ck_assert_int_eq(sv_rc, SV_INVALID_PARAMETER);
213+
sv_rc = signed_video_set_signing_frequency(sv, 0);
214+
ck_assert_int_eq(sv_rc, SV_INVALID_PARAMETER);
215+
sv_rc = signed_video_set_signing_frequency(sv, 2);
216+
ck_assert_int_eq(sv_rc, SV_OK);
201217
// Check setting recurrence
202218
sv_rc = signed_video_set_recurrence_interval_frames(NULL, 1);
203219
ck_assert_int_eq(sv_rc, SV_INVALID_PARAMETER);
@@ -884,6 +900,22 @@ START_TEST(limited_sei_payload_size)
884900
}
885901
END_TEST
886902

903+
/* Test description
904+
* Verifies the signing frequency setter, that is, signing multiple GOPs. */
905+
START_TEST(signing_multiple_gops)
906+
{
907+
struct sv_setting setting = settings[_i];
908+
// Select a signing frequency longer than every GOP.
909+
const unsigned signing_frequency = 2;
910+
setting.signing_frequency = signing_frequency;
911+
test_stream_t *list = create_signed_stream("IPPIPPIPPIPPIP", setting);
912+
test_stream_check_types(list, "IPPIsPPISPPIsPPISP");
913+
verify_seis(list, setting);
914+
915+
test_stream_free(list);
916+
}
917+
END_TEST
918+
887919
/* Test description
888920
* Verifies the setter for maximum Bitstream Units before signing, that is, triggers
889921
* signing partial GOPs. */
@@ -951,6 +983,7 @@ signed_video_suite(void)
951983
tcase_add_loop_test(tc, golden_sei_created, s, e);
952984
tcase_add_loop_test(tc, w_wo_emulation_prevention_bytes, s, e);
953985
tcase_add_loop_test(tc, limited_sei_payload_size, s, e);
986+
tcase_add_loop_test(tc, signing_multiple_gops, s, e);
954987
tcase_add_loop_test(tc, signing_partial_gops, s, e);
955988
tcase_add_loop_test(tc, signing_mulitslice_stream_partial_gops, s, e);
956989

tests/check/test_helpers.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,7 @@ get_initialized_signed_video(struct sv_setting settings, bool new_private_key)
506506
ck_assert_int_eq(signed_video_set_authenticity_level(sv, settings.auth_level), SV_OK);
507507
ck_assert_int_eq(signed_video_set_max_sei_payload_size(sv, settings.max_sei_payload_size), SV_OK);
508508
ck_assert_int_eq(signed_viedo_set_max_signing_frames(sv, settings.max_signing_frames), SV_OK);
509+
ck_assert_int_eq(signed_video_set_signing_frequency(sv, settings.signing_frequency), SV_OK);
509510
ck_assert_int_eq(signed_video_set_hash_algo(sv, settings.hash_algo_name), SV_OK);
510511
if (settings.codec != SV_CODEC_AV1) {
511512
ck_assert_int_eq(signed_video_set_sei_epb(sv, settings.ep_before_signing), SV_OK);

tests/check/test_stream.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,10 @@ get_type_char(const uint8_t *data, size_t data_size, SignedVideoCodec codec)
127127
type = 'Z';
128128
else if (bu.is_golden_sei)
129129
type = 'G';
130-
else
130+
else if (bu.is_signed)
131131
type = 'S';
132+
else
133+
type = 's';
132134
break;
133135
}
134136
default:

0 commit comments

Comments
 (0)