Skip to content

Commit 3fcd829

Browse files
committed
Adds a new option to signer app
With the option '-p' it is possible to sign the video with a provisioned key. The key and certificate chain is read from file and set through the corresponding Axis API. If ONVIF Media Signing is installed, the video will be signed with that for codecs H.26x. This requires the library to have been installed with vendor Axis. Further, key, certificate chain and generator scripts have been added to the test-files folder.
1 parent 7e9c0a1 commit 3fcd829

File tree

8 files changed

+649
-9
lines changed

8 files changed

+649
-9
lines changed

apps/signer/gst-plugin/gstsigning.c

Lines changed: 174 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,34 @@
2727
#ifdef HAVE_CONFIG_H
2828
#include <config.h>
2929
#endif
30+
#include <stdio.h> // FILE, etc
31+
#include <string.h> // strstr, strcat
32+
#if defined(_WIN32) || defined(_WIN64)
33+
#include <direct.h>
34+
#define getcwd _getcwd // "deprecation" warning
35+
#else
36+
#include <unistd.h> // getcwd
37+
#endif
3038

3139
#include "gstsigning.h"
3240
#include "gstsigning_defines.h"
3341
#include <signed-video-framework/signed_video_common.h>
3442
#include <signed-video-framework/signed_video_openssl.h>
3543
#include <signed-video-framework/signed_video_sign.h>
44+
#include <signed-video-framework/sv_vendor_axis_communications.h>
3645

3746
GST_DEBUG_CATEGORY_STATIC(gst_signing_debug);
3847
#define GST_CAT_DEFAULT gst_signing_debug
3948

49+
enum
50+
{
51+
PROP_0,
52+
PROP_PROVISIONED
53+
};
54+
#define DEFAULT_PROVISIONED 0 // Key is not provisioned
55+
4056
struct _GstSigningPrivate {
57+
gint provisioned;
4158
signed_video_t *signed_video;
4259
GstClockTime last_pts;
4360
};
@@ -72,6 +89,42 @@ setup_signing(GstSigning *signing, GstCaps *caps);
7289
static gboolean
7390
terminate_signing(GstSigning *signing);
7491

92+
static void
93+
gst_signing_get_property(GObject * object, guint prop_id, GValue * value, GParamSpec * pspec)
94+
{
95+
GstSigning *signing = GST_SIGNING(object);
96+
97+
GST_OBJECT_LOCK(signing);
98+
switch (prop_id) {
99+
case PROP_PROVISIONED:
100+
g_value_set_int(value, signing->priv->provisioned);
101+
break;
102+
default:
103+
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
104+
break;
105+
}
106+
GST_OBJECT_UNLOCK(signing);
107+
}
108+
109+
static void
110+
gst_signing_set_property(GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec)
111+
{
112+
GstSigning *signing = GST_SIGNING(object);
113+
GstSigningPrivate *priv = signing->priv;
114+
115+
GST_OBJECT_LOCK(signing);
116+
switch (prop_id) {
117+
case PROP_PROVISIONED:
118+
priv->provisioned = g_value_get_int(value);
119+
GST_DEBUG_OBJECT(object, "new provisioned value: %d", priv->provisioned);
120+
break;
121+
default:
122+
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
123+
break;
124+
}
125+
GST_OBJECT_UNLOCK(signing);
126+
}
127+
75128
static void
76129
gst_signing_class_init(GstSigningClass *klass)
77130
{
@@ -89,13 +142,20 @@ gst_signing_class_init(GstSigningClass *klass)
89142
transform_class->sink_event = GST_DEBUG_FUNCPTR(gst_signing_sink_event);
90143

91144
gst_element_class_set_static_metadata(element_class, "Signed Video", "Formatter/Video",
92-
"Add SEI nalus containing signatures for authentication.",
145+
"Add SEIs containing signatures for authentication.",
93146
"Signed Video Framework <github.com/AxisCommunications/signed-video-framework-examples>");
94147

95148
gst_element_class_add_static_pad_template(element_class, &sink_template);
96149
gst_element_class_add_static_pad_template(element_class, &src_template);
97150

98151
gobject_class->finalize = gst_signing_finalize;
152+
gobject_class->get_property = gst_signing_get_property;
153+
gobject_class->set_property = gst_signing_set_property;
154+
155+
// Install properties
156+
g_object_class_install_property(gobject_class, PROP_PROVISIONED,
157+
g_param_spec_int("provisioned", "Provisioned key", "Use pre-generated key and certificate",
158+
0, 1, DEFAULT_PROVISIONED, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
99159
}
100160

101161
static void
@@ -168,7 +228,8 @@ create_buffer_with_current_time(GstSigning *signing)
168228
* Returns the number of nalus that were prepended to @current_au,
169229
* or -1 on error. */
170230
static gint
171-
get_and_add_sei(GstSigning *signing, GstBuffer * current_au, gint idx, const guint8 * peek_nalu, gsize peek_nalu_size)
231+
get_and_add_sei(GstSigning *signing, GstBuffer * current_au, gint idx, const guint8 * peek_nalu,
232+
gsize peek_nalu_size)
172233
{
173234
SignedVideoReturnCode sv_rc;
174235
gint prepend_count = 0;
@@ -180,7 +241,8 @@ get_and_add_sei(GstSigning *signing, GstBuffer * current_au, gint idx, const gui
180241
* signed_video_get_sei(signed_video_t *self, uint8_t **sei, size_t *sei_size,
181242
* unsigned *payload_offset, const uint8_t *peek_nalu,
182243
* size_t peek_nalu_size, unsigned *num_pending_seis); */
183-
sv_rc = signed_video_get_sei (signing->priv->signed_video, &sei, &sei_size, NULL, peek_nalu, peek_nalu_size, NULL);
244+
sv_rc = signed_video_get_sei (signing->priv->signed_video, &sei, &sei_size, NULL, peek_nalu,
245+
peek_nalu_size, NULL);
184246
while (sv_rc == SV_OK && sei_size > 0 && sei) {
185247
GstMemory *prepend_mem;
186248

@@ -193,7 +255,8 @@ get_and_add_sei(GstSigning *signing, GstBuffer * current_au, gint idx, const gui
193255
gst_buffer_insert_memory(current_au, idx, prepend_mem);
194256
prepend_count++;
195257

196-
sv_rc = signed_video_get_sei(signing->priv->signed_video, &sei, &sei_size, NULL, peek_nalu, peek_nalu_size, NULL);
258+
sv_rc = signed_video_get_sei(signing->priv->signed_video, &sei, &sei_size, NULL, peek_nalu,
259+
peek_nalu_size, NULL);
197260
}
198261

199262
if (sv_rc != SV_OK) {
@@ -220,7 +283,8 @@ gst_signing_transform_ip(GstBaseTransform *trans, GstBuffer *buf)
220283
priv->last_pts = GST_BUFFER_PTS(buf);
221284
// last_pts is an GstClockTime object, which is measured in nanoseconds.
222285
const gint64 timestamp_usec = (const gint64)(priv->last_pts / 1000);
223-
const gint64 *timestamp_usec_ptr = priv->last_pts == GST_CLOCK_TIME_NONE ? NULL : &timestamp_usec;
286+
const gint64 *timestamp_usec_ptr =
287+
priv->last_pts == GST_CLOCK_TIME_NONE ? NULL : &timestamp_usec;
224288

225289
GST_DEBUG_OBJECT(signing, "got buffer with %d memories", gst_buffer_n_memory(buf));
226290
while (idx < gst_buffer_n_memory(buf)) {
@@ -331,6 +395,82 @@ terminate_signing(GstSigning *signing)
331395
return TRUE;
332396
}
333397

398+
#define MAX_PATH_LENGTH 500
399+
static gboolean
400+
read_file_content(const char *filename, char **content, gsize *content_size)
401+
{
402+
gboolean success = FALSE;
403+
FILE *fp = NULL;
404+
char full_path[MAX_PATH_LENGTH] = {0};
405+
char cwd[MAX_PATH_LENGTH] = {0};
406+
407+
*content = NULL;
408+
*content_size = 0;
409+
410+
if (!getcwd(cwd, sizeof(cwd))) {
411+
goto done;
412+
}
413+
414+
// Find the root location of the library.
415+
char *lib_root = NULL;
416+
char *next_lib_root = strstr(cwd, "signed-video-framework-examples");
417+
if (!next_lib_root) {
418+
// Current location is not inside signed-video-framework. Assuming current working directory is
419+
// the parent directory, to give it another try. If that is not the case opening the |full_path|
420+
// will fail, which is fine since the true location is not known anyhow.
421+
strcat(cwd, "/signed-video-framework-examples");
422+
next_lib_root = strstr(cwd, "signed-video-framework-examples");
423+
}
424+
while (next_lib_root) {
425+
lib_root = next_lib_root;
426+
next_lib_root = strstr(next_lib_root + 1, "signed-video-framework-examples");
427+
}
428+
if (!lib_root) {
429+
goto done;
430+
}
431+
// Terminate string after lib root.
432+
memset(lib_root + strlen("signed-video-framework-examples"), '\0', 1);
433+
434+
// Get certificate chain from folder test-files/.
435+
strcat(full_path, cwd);
436+
strcat(full_path, "/test-files/");
437+
strcat(full_path, filename);
438+
439+
fp = fopen(full_path, "rb");
440+
if (!fp) {
441+
goto done;
442+
}
443+
444+
fseek(fp, 0L, SEEK_END);
445+
size_t file_size = ftell(fp);
446+
if (file_size == 0) {
447+
goto done;
448+
}
449+
450+
*content = calloc(1, file_size + 1); // One extra byte for '\0' in case the content is a string.
451+
if (!(*content)) {
452+
goto done;
453+
}
454+
455+
rewind(fp);
456+
if (fread(*content, sizeof(char), file_size / sizeof(char), fp) == 0) {
457+
goto done;
458+
}
459+
*content_size = file_size;
460+
461+
success = TRUE;
462+
463+
done:
464+
if (fp) {
465+
fclose(fp);
466+
}
467+
if (!success) {
468+
free(*content);
469+
}
470+
471+
return success;
472+
}
473+
334474
static gboolean
335475
setup_signing(GstSigning *signing, GstCaps *caps)
336476
{
@@ -340,6 +480,8 @@ setup_signing(GstSigning *signing, GstCaps *caps)
340480
SignedVideoCodec codec;
341481
char *private_key = NULL;
342482
size_t private_key_size = 0;
483+
char *certificate_chain = NULL;
484+
size_t certificate_chain_size = 0;
343485

344486
g_assert(caps != NULL);
345487

@@ -367,9 +509,26 @@ setup_signing(GstSigning *signing, GstCaps *caps)
367509
GST_ERROR_OBJECT(signing, "could not create Signed Video object");
368510
goto create_failed;
369511
}
370-
if (signed_video_generate_ecdsa_private_key(PATH_TO_KEY_FILES, &private_key, &private_key_size) != SV_OK) {
371-
GST_DEBUG_OBJECT(signing, "failed to generate pem file");
372-
goto generate_private_key_failed;
512+
513+
if (!priv->provisioned) {
514+
if (signed_video_generate_ecdsa_private_key(PATH_TO_KEY_FILES, &private_key, &private_key_size) != SV_OK) {
515+
GST_DEBUG_OBJECT(signing, "failed to generate pem file");
516+
goto generate_private_key_failed;
517+
}
518+
} else {
519+
if (!read_file_content("private_ecdsa_key.pem", &private_key, &private_key_size)) {
520+
goto generate_private_key_failed;
521+
}
522+
if (!read_file_content("cert_chain.pem", &certificate_chain, &certificate_chain_size)) {
523+
goto read_cert_failed;
524+
}
525+
// Use the Axis api to set the certificate chain without attestation. This will
526+
// trigger factory provisioned signing in the library.
527+
if (sv_vendor_axis_communications_set_attestation_report(priv->signed_video, NULL, 0,
528+
certificate_chain) != SV_OK) {
529+
GST_DEBUG_OBJECT(signing, "failed to set certificate chain content");
530+
goto set_cert_failed;
531+
}
373532
}
374533
if (signed_video_set_private_key(priv->signed_video, private_key, private_key_size) != SV_OK) {
375534
GST_DEBUG_OBJECT(signing, "failed to set private key content");
@@ -384,12 +543,18 @@ setup_signing(GstSigning *signing, GstCaps *caps)
384543
goto product_info_failed;
385544
}
386545

546+
g_free(certificate_chain);
547+
g_free(private_key);
548+
387549
return TRUE;
388550

389551
product_info_failed:
390552
set_private_key_failed:
391-
generate_private_key_failed:
553+
set_cert_failed:
554+
g_free(certificate_chain);
555+
read_cert_failed:
392556
g_free(private_key);
557+
generate_private_key_failed:
393558
signed_video_free(priv->signed_video);
394559
priv->signed_video = NULL;
395560
create_failed:

apps/signer/main.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ main(gint argc, gchar *argv[])
102102
"Usage:\n%s [-h] [-c codec] filename\n\n"
103103
"Optional\n"
104104
" -c codec : 'h264' (default) or 'h265'\n"
105+
" -p : provisioned key, i.e., public key in cert (needs lib to be built with Axis)'\n"
105106
"Required\n"
106107
" filename : Name of the file to be signed.\n",
107108
argv[0]);
@@ -112,6 +113,7 @@ main(gint argc, gchar *argv[])
112113
gchar *mux_str = "mp4mux";
113114
gchar *filename = NULL;
114115
gchar *outfilename = NULL;
116+
gboolean provisioned = FALSE;
115117

116118
GstElement *pipeline = NULL;
117119
GstElement *filesrc = NULL;
@@ -139,6 +141,8 @@ main(gint argc, gchar *argv[])
139141
} else if (strcmp(argv[arg], "-c") == 0) {
140142
arg++;
141143
codec_str = argv[arg];
144+
} else if (strcmp(argv[arg], "-p") == 0) {
145+
provisioned = TRUE;
142146
} else if (strncmp(argv[arg], "-", 1) == 0) {
143147
// Unknown option.
144148
g_message("Unknown option: %s\n%s", argv[arg], usage);
@@ -216,6 +220,9 @@ main(gint argc, gchar *argv[])
216220
parser = gst_element_factory_make("h265parse", NULL);
217221
}
218222
signedvideo = gst_element_factory_make("signing", NULL);
223+
if (provisioned) {
224+
g_object_set(G_OBJECT(signedvideo), "provisioned", 1, NULL);
225+
}
219226
muxer = gst_element_factory_make(mux_str, NULL);
220227
filesink = gst_element_factory_make("filesink", NULL);
221228

apps/validator/main.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,11 @@ on_new_sample_from_sink(GstElement *elt, ValidationData *data)
386386
}
387387
// Allocate memory and copy version strings.
388388
if (strlen(data->auth_report->this_version) > 0) {
389+
if (strstr(data->auth_report->this_version, "ONVIF") != NULL) {
390+
g_free(data->this_version);
391+
data->this_version = g_malloc0(strlen(data->auth_report->this_version) + 1);
392+
strcpy(data->this_version, data->auth_report->this_version);
393+
}
389394
if (strcmp(data->this_version, data->auth_report->this_version) != 0) {
390395
g_error("unexpected mismatch in 'this_version'");
391396
}

test-files/cert_chain.pem

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIBaDCCAQ8CFG6wXFCygzYra7AmaFv2FoSsZfHGMAoGCCqGSM49BAMCMDcxEDAO
3+
BgNVBAoMB1NvbWVPcmcxDTALBgNVBAsMBFRlc3QxFDASBgNVBAMMC1Rlc3QgUm9v
4+
dENBMB4XDTI1MDMwNDExMDUxOFoXDTI2MDMwNDExMDUxOFowNzEQMA4GA1UECgwH
5+
U29tZU9yZzENMAsGA1UECwwEVGVzdDEUMBIGA1UEAwwLVGVzdCBjYW1lcmEwWTAT
6+
BgcqhkjOPQIBBggqhkjOPQMBBwNCAAQNWFD88M9YY2Ru/4TPFPSaoK/ffAnwb9GK
7+
0N3Oh6AQu6ZjAudSvo8ppTEF4RnXIP8Pi0Tzy3SmvQLasNOKv7YxMAoGCCqGSM49
8+
BAMCA0cAMEQCIA/TMei9Djg/MqZsRDE+iEVWGjhrp3AzskFa8+pQXrPrAiAazMme
9+
PARDL6NyRB9t/yu/D1ivho/YwD/7s80UJrdQ6w==
10+
-----END CERTIFICATE-----
11+
-----BEGIN CERTIFICATE-----
12+
MIIBwDCCAWagAwIBAgIBATAKBggqhkjOPQQDAjA3MRAwDgYDVQQKDAdTb21lT3Jn
13+
MQ0wCwYDVQQLDARUZXN0MRQwEgYDVQQDDAtUZXN0IFJvb3RDQTAeFw0yNTAzMDQx
14+
MTA1MThaFw0yNzEyMjMxMTA1MThaMDcxEDAOBgNVBAoMB1NvbWVPcmcxDTALBgNV
15+
BAsMBFRlc3QxFDASBgNVBAMMC1Rlc3QgUm9vdENBMFkwEwYHKoZIzj0CAQYIKoZI
16+
zj0DAQcDQgAEBFUk0epqqkErED/dVSnxpUqLtIwJso7jysocYt7YHORcAN1vA32F
17+
hopjFFjqW3uJwNqyW92t27sL/6jMS+YDFaNjMGEwHQYDVR0OBBYEFPNF4anpKL4C
18+
n3PNj9DRRj0FgJNRMB8GA1UdIwQYMBaAFPNF4anpKL4Cn3PNj9DRRj0FgJNRMBIG
19+
A1UdEwEB/wQIMAYBAf8CAQEwCwYDVR0PBAQDAgEGMAoGCCqGSM49BAMCA0gAMEUC
20+
IAN7nh6b0Xele60l5u+di/T8sYNogZAMZX8/xH19uB5ZAiEA+XIQAu2ASRE3ZIIH
21+
Lh4X5Z39xEGH8vVyCyLhBXac6KE=
22+
-----END CERTIFICATE-----

test-files/generate-cert.sh

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/bin/bash
2+
3+
# Create CA private key (provide a password for the key):
4+
openssl ecparam -name prime256v1 -genkey -noout -out ca_ec.key
5+
6+
# Create CA certificate (provide suitable input when asked):
7+
openssl req -x509 -new -nodes -key ca_ec.key -sha256 -days 1024 -set_serial 1 -out ca_ec.pem -subj "/O=SomeOrg/OU=Test/CN=Test RootCA" -config ./test_openssl.cnf
8+
9+
# Print the CA
10+
openssl x509 -in ca_ec.pem -text -noout
11+
12+
# ## Intermediate EC cert ##
13+
# # Generate test private keys
14+
# openssl ecparam -name prime256v1 -genkey -noout -out intermediate_signing.key
15+
16+
# # Create CSR requirements file:
17+
# openssl req -new -key intermediate_signing.key -out intermediate_signing.csr -subj "/O=SomeOrg/OU=Test/CN=Test camera"
18+
19+
# # Sign CSR
20+
# openssl x509 -req -in intermediate_signing.csr -CA ca_ec.pem -CAkey ca_ec.key -CAcreateserial -out intermediate_signing.crt -days 365 -sha256 -extensions req_ext
21+
22+
# # Print signed certificate
23+
# openssl x509 -in intermediate_signing.crt -text -noout
24+
25+
# # Verify certificate
26+
# openssl verify -verbose -CAfile ca_ec.pem intermediate_signing.crt
27+
28+
## EC ##
29+
# Create CSR requirements file:
30+
# openssl req -new -key ec_signing.key -out ec_signing.csr -subj "/O=SomeOrg/OU=Test/CN=Test camera"
31+
openssl req -new -key private_ecdsa_key.pem -out ec_signing.csr -subj "/O=SomeOrg/OU=Test/CN=Test camera"
32+
33+
# Sign CSR
34+
openssl x509 -req -in ec_signing.csr -CA ca_ec.pem -CAkey ca_ec.key -CAcreateserial -out ec_signing.crt -days 365 -sha256 -extensions req_ext
35+
# openssl x509 -req -in ec_signing.csr -CA intermediate_signing.crt -CAkey intermediate_signing.key -CAcreateserial -out ec_signing.crt -days 365 -sha256 -extensions req_ext
36+
37+
# Print signed certificate
38+
openssl x509 -in ec_signing.crt -text -noout
39+
40+
# Verify certificate
41+
openssl verify -verbose -CAfile ca_ec.pem ec_signing.crt
42+
# openssl verify -verbose -CAfile intermediate_signing.crt ec_signing.crt
43+
44+
# Concatenate certificates
45+
cat ec_signing.crt ca_ec.pem > cert_chain.pem
46+
# cat ec_signing.crt intermediate_signing.crt ca_ec.pem > cert_chain.pem

0 commit comments

Comments
 (0)