From 2a8911b9e9c9e3138a107d81cc0b4f683d3638b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20V=C3=B6lcker?= Date: Tue, 30 Jun 2026 16:07:55 +0200 Subject: [PATCH 1/2] Adds support for factory provisioned signing When factory provisioned keys will be used, Signed Video will use the ONVIF Media Signing format for H.264 and H.265. For AV1 it will still use the same factory provisioned key, but within the signed video framework. Therefore, the normal attestation procedure is updated to also work with certificates including the public key. Varification can now also handle multiple trusted CAs, which is necessary since the factory provisioned keys will have different CAs. The tests have been updated with a new certificate chain including an intermediate certificate. --- lib/src/sv_auth.c | 13 ++- lib/src/sv_axis_communications.c | 136 +++++++++++++++++++------- lib/src/sv_axis_communications.h | 2 +- lib/src/sv_tlv.c | 2 +- media-signing-framework | 2 +- meson.build | 1 + tests/cert_chain.pem | 43 +++++--- tests/check/check_signed_video_auth.c | 4 +- tests/generate-cert.sh | 55 +++++------ 9 files changed, 168 insertions(+), 90 deletions(-) diff --git a/lib/src/sv_auth.c b/lib/src/sv_auth.c index a69f7806..541a9d58 100644 --- a/lib/src/sv_auth.c +++ b/lib/src/sv_auth.c @@ -1408,7 +1408,7 @@ detect_onvif_media_signing(signed_video_t *self, const bu_info_t *bu) return SV_OK; } - const char *trusted_certificate = NULL; + const char **trusted_certificates = NULL; size_t trusted_certificate_size = 0; // Map codec to ONVIF enum. MediaSigningCodec codec = OMS_CODEC_NUM; @@ -1428,10 +1428,13 @@ detect_onvif_media_signing(signed_video_t *self, const bu_info_t *bu) self->onvif = onvif_media_signing_create(codec); SV_THROW_IF(!self->onvif, SV_EXTERNAL_ERROR); // Get the root CA certificate from Axis code. - trusted_certificate = get_axis_communications_trusted_certificate(); - trusted_certificate_size = strlen(trusted_certificate); - SV_THROW(msrc_to_svrc(onvif_media_signing_set_trusted_certificate( - self->onvif, trusted_certificate, trusted_certificate_size, false))); + trusted_certificates = get_axis_communications_trusted_certificate(); + while (*trusted_certificates) { + trusted_certificate_size = strlen(*trusted_certificates); + SV_THROW(msrc_to_svrc(onvif_media_signing_set_trusted_certificate( + self->onvif, *trusted_certificates, trusted_certificate_size))); + trusted_certificates++; + } // If the ONVIF Media Signing session has successfully been set up, register all // queued Bitstream Units to the ONVIF session. SV_THROW(reregister_bu(self)); diff --git a/lib/src/sv_axis_communications.c b/lib/src/sv_axis_communications.c index 39829181..febdf41e 100644 --- a/lib/src/sv_axis_communications.c +++ b/lib/src/sv_axis_communications.c @@ -21,6 +21,7 @@ #include "sv_axis_communications.h" #include +#include // toupper #include // BIO_* #include // EVP_* #include // PEM_* @@ -42,7 +43,7 @@ #define PUBLIC_KEY_UNCOMPRESSED_PREFIX 0x04 #define BINARY_RAW_DATA_SIZE 40 -static const char *kTrustedAxisRootCA = +static const char *kTrustedRootCAs[] = { "-----BEGIN CERTIFICATE-----\n" "MIIClDCCAfagAwIBAgIBATAKBggqhkjOPQQDBDBcMR8wHQYDVQQKExZBeGlzIENv\n" "bW11bmljYXRpb25zIEFCMRgwFgYDVQQLEw9BeGlzIEVkZ2UgVmF1bHQxHzAdBgNV\n" @@ -58,7 +59,24 @@ static const char *kTrustedAxisRootCA = "hwJBUfwiBK0TIRJebWm9/nsNAEkjbxao40oeMUg+I3mDNr7guNJUo4ugOfToGpnm\n" "3QLOhEJzyHqPBHTChxEd5bGVUW8CQgDR/ZAr405Ohk5kpM/gmzELP+fYDZfuTFut\n" "w3S8HMYSvMWbTCzN+qnq+GV1goSS6vjVr95EpDxCVIxkKOvuxhyVDg==\n" - "-----END CERTIFICATE-----\n"; + "-----END CERTIFICATE-----\n", + "-----BEGIN CERTIFICATE-----\n" + "MIIChjCCAeegAwIBAgIBATAKBggqhkjOPQQDBDBWMQswCQYDVQQGEwJTRTEfMB0G\n" + "A1UECgwWQXhpcyBDb21tdW5pY2F0aW9ucyBBQjEmMCQGA1UEAwwdQXhpcyBURUUg\n" + "U2lnbmVkIFZpZGVvIFJvb3QgQ0EwHhcNMjYwMzIzMTAyMTE0WhcNNDEwMzE5MTAy\n" + "MTE0WjBWMQswCQYDVQQGEwJTRTEfMB0GA1UECgwWQXhpcyBDb21tdW5pY2F0aW9u\n" + "cyBBQjEmMCQGA1UEAwwdQXhpcyBURUUgU2lnbmVkIFZpZGVvIFJvb3QgQ0EwgZsw\n" + "EAYHKoZIzj0CAQYFK4EEACMDgYYABAByYn89N6rzNsUTEHPRZXsdQQqorBJPX8W/\n" + "nV7j00ANnFioFDWp9WdSW/UC1ALY+SKoArUBjnzGnqTPBPtfV3UbRQBOHacPkVgL\n" + "//1OxEJfwyhhQrGTcn9KKeG8iJTKFoXArvicU+lilsjE8PV9+uUsE6zbIf4lFQLE\n" + "oU5hx+vTSxGRA6NjMGEwHwYDVR0jBBgwFoAUvtSs3PsLSpSPdTDJjXMBivgRJo4w\n" + "DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFL7UrNz7\n" + "C0qUj3UwyY1zAYr4ESaOMAoGCCqGSM49BAMEA4GMADCBiAJCAUjQy+PomgLYoKn1\n" + "4bcF1Y4Bv80jm285vdy+hQ8AdmaG6ixvHGMSaXolOhSDWKOuwDg0kIYGVF1quHCI\n" + "9vEKNZw0AkIB2b20NGna+9QE3hB0nuTmK1uYsqXfbwnQYj0dOv4YfRicHeys73u+\n" + "4+x8cqidPOf4i0Xdf+zFg9hRWxW/WGbvmA4=\n" + "-----END CERTIFICATE-----\n", + NULL}; #define ATTRIBUTES_LENGTH 37 static const uint8_t kAttributes[ATTRIBUTES_LENGTH] = {0x7b, 0x00, 0x02, 0x01, 0x29, 0x01, 0x00, @@ -106,7 +124,7 @@ typedef struct _sv_vendor_axis_communications_t { // Information needed for public key validation. EVP_MD_CTX *md_ctx; // Message digest context for verifying the public key - X509 *trusted_ca; // The trusted Axis root CA in X509 form. + X509_STORE *trusted_cas; // The trusted Axis root CAs in X509 form. uint8_t chip_id[CHIP_ID_SIZE]; struct attestation_report attestation_report; @@ -121,7 +139,8 @@ typedef struct _sv_vendor_axis_communications_t { // Declarations of static functions. static svrc_t -verify_certificate_chain(X509 *trusted_ca, STACK_OF(X509) * untrusted_certificates); +verify_certificate_chain(sv_vendor_axis_communications_t *self, + STACK_OF(X509) * untrusted_certificates); static svrc_t verify_and_parse_certificate_chain(sv_vendor_axis_communications_t *self); static svrc_t @@ -138,20 +157,16 @@ get_untrusted_certificates_size(const sv_vendor_axis_communications_t *self); * Uses the |trusted_ca| to verify the first certificate in the chain. The last certificate verified * is the |attestation_certificate| which will be used to verify the transmitted public key. */ static svrc_t -verify_certificate_chain(X509 *trusted_ca, STACK_OF(X509) * untrusted_certificates) +verify_certificate_chain(sv_vendor_axis_communications_t *self, + STACK_OF(X509) * untrusted_certificates) { - assert(trusted_ca && untrusted_certificates); + assert(self && untrusted_certificates); - X509_STORE *trust_store = NULL; + X509_STORE *trust_store = self->trusted_cas; X509_STORE_CTX *ctx = NULL; svrc_t status = SV_UNKNOWN_FAILURE; SV_TRY() - trust_store = X509_STORE_new(); - SV_THROW_IF(!trust_store, SV_EXTERNAL_ERROR); - // Load trusted CA certificate - SV_THROW_IF(X509_STORE_add_cert(trust_store, trusted_ca) != 1, SV_EXTERNAL_ERROR); - // Start a new context for certificate verification. ctx = X509_STORE_CTX_new(); SV_THROW_IF(!ctx, SV_EXTERNAL_ERROR); @@ -164,13 +179,19 @@ verify_certificate_chain(X509 *trusted_ca, STACK_OF(X509) * untrusted_certificat SV_THROW_IF( X509_STORE_CTX_init(ctx, trust_store, attestation_certificate, untrusted_certificates) != 1, SV_EXTERNAL_ERROR); - SV_THROW_IF(X509_verify_cert(ctx) != 1, SV_VENDOR_ERROR); + int verified = X509_verify_cert(ctx); + if (verified == 0) { + DEBUG_LOG("Certificate verification error: %s\n", + X509_verify_cert_error_string(X509_STORE_CTX_get_error(ctx))); + } + if (self->factory_provisioned) { + self->supplemental_authenticity.public_key_validation = verified; + } SV_CATCH() SV_DONE(status) X509_STORE_CTX_free(ctx); - X509_STORE_free(trust_store); return status; } @@ -215,17 +236,21 @@ verify_and_parse_certificate_chain(sv_vendor_axis_communications_t *self) // prevents from potential deadlock. // Get the first certificate from |stackbio|. X509 *certificate = PEM_read_bio_X509(stackbio, NULL, NULL, NULL); + SV_THROW_IF(!certificate, SV_VENDOR_ERROR); // The first certificate is the |attestation_certificate|. Keep a reference to it to extract // information from it. X509 *attestation_certificate = certificate; while (certificate && num_certificates < NUM_UNTRUSTED_CERTIFICATES + 1) { +#ifdef SIGNED_VIDEO_DEBUG + X509_print_fp(stdout, certificate); +#endif num_certificates = sk_X509_push(untrusted_certificates, certificate); // Get the next certificate. certificate = PEM_read_bio_X509(stackbio, NULL, NULL, NULL); } SV_THROW_IF(num_certificates > NUM_UNTRUSTED_CERTIFICATES, SV_VENDOR_ERROR); - SV_THROW_WITH_MSG(verify_certificate_chain(self->trusted_ca, untrusted_certificates), + SV_THROW_WITH_MSG(verify_certificate_chain(self, untrusted_certificates), "Failed verifying certificate chain"); // Extract |chip_id| from the |attestation_certificate|. @@ -235,6 +260,32 @@ verify_and_parse_certificate_chain(sv_vendor_axis_communications_t *self) // Found CN in certificate. Read that entry and convert to UTF8. entry_data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject, common_name_index)); SV_THROW_IF(ASN1_STRING_to_UTF8(&common_name_str, entry_data) <= 0, SV_EXTERNAL_ERROR); + + if (self->factory_provisioned) { + // Extract serial number from CommonName. It shall be present between the first two + // '-' characters. + char *start_pos = strstr((char *)common_name_str, "-"); + SV_THROW_IF(!start_pos, SV_VENDOR_ERROR); + start_pos++; // Skip the "-" character. + char *end_pos = strstr(start_pos, "-"); + SV_THROW_IF(!end_pos, SV_VENDOR_ERROR); + char *serial_number_str = OPENSSL_strndup(start_pos, end_pos - start_pos); + SV_THROW_IF(!serial_number_str, SV_EXTERNAL_ERROR); + start_pos = serial_number_str; + // Convert to upper case. + while (*start_pos != '\0') { + *start_pos = toupper(*start_pos); + start_pos++; + } + // Copy only if necessary. + if (strcmp(self->supplemental_authenticity.serial_number, serial_number_str)) { + memset(self->supplemental_authenticity.serial_number, 0, SV_VENDOR_AXIS_SER_NO_MAX_LENGTH); + strcpy(self->supplemental_authenticity.serial_number, serial_number_str); + } + OPENSSL_free(serial_number_str); + goto catch_error; + } + // Find the Chip ID string, which shows up right after "Axis Edge Vault Attestation ". char *chip_id_str = strstr((char *)common_name_str, AXIS_EDGE_VAULT_ATTESTATION_STR); SV_THROW_IF(!chip_id_str, SV_VENDOR_ERROR); @@ -248,7 +299,6 @@ verify_and_parse_certificate_chain(sv_vendor_axis_communications_t *self) } // Check that the chip ID has correct prefix. SV_THROW_IF(memcmp(self->chip_id, kChipIDPrefix, CHIP_ID_PREFIX_SIZE) != 0, SV_VENDOR_ERROR); - // Extract |serial_number| from the |attestation_certificate|. int ser_no_index = X509_NAME_get_index_by_NID(subject, NID_serialNumber, -1); SV_THROW_IF(ser_no_index < 0, SV_VENDOR_ERROR); @@ -377,6 +427,10 @@ verify_axis_communications_public_key(sv_vendor_axis_communications_t *self) // Initiate verification to not feasible/error. int verified_signature = -1; + if (self->factory_provisioned) { + return SV_OK; + } + svrc_t status = SV_UNKNOWN_FAILURE; SV_TRY() // If no message digest context exists, the |public_key| cannot be validated. @@ -477,26 +531,38 @@ sv_vendor_axis_communications_setup(void) if (!self) return NULL; - // Store the |kTrustedAxisRootCA| in X509 format. - BIO *ca_bio = BIO_new(BIO_s_mem()); - if (ca_bio) { - if (BIO_puts(ca_bio, kTrustedAxisRootCA) > 0) { - // Successfully written |kTrustedAxisRootCA| to |ca_bio|. Convert to X509. - self->trusted_ca = PEM_read_bio_X509(ca_bio, NULL, NULL, NULL); + self->trusted_cas = X509_STORE_new(); + if (!self->trusted_cas) { + DEBUG_LOG("Could not convert Axis root CAs to X509"); + sv_vendor_axis_communications_teardown((void *)self); + self = NULL; + return NULL; + } + + const char **trusted_certificates = kTrustedRootCAs; + while (*trusted_certificates) { + // Store each |kTrustedRootCAs| in X509_STORE. + BIO *ca_bio = BIO_new(BIO_s_mem()); + if (ca_bio) { + if (BIO_write(ca_bio, *trusted_certificates, (int)strlen(*trusted_certificates)) > 0) { + // Successfully written |*trusted_certificates| to |ca_bio|. Convert to X509. + X509 *trusted_certificate_x509 = PEM_read_bio_X509(ca_bio, NULL, NULL, NULL); + // Load trusted CA certificate + if (X509_STORE_add_cert(self->trusted_cas, trusted_certificate_x509) != 1) { + // Add better debug print + DEBUG_LOG("Failed to add trusted certificate to trust store"); + } + X509_free(trusted_certificate_x509); + } + BIO_free(ca_bio); } - BIO_free(ca_bio); + trusted_certificates++; } // Initialize |public_key_validation| to unknown/error. self->supplemental_authenticity.public_key_validation = -1; strcpy(self->supplemental_authenticity.serial_number, SERIAL_NUMBER_UNKNOWN); - if (!self->trusted_ca) { - DEBUG_LOG("Could not convert Axis root CA to X509"); - sv_vendor_axis_communications_teardown((void *)self); - self = NULL; - } - return (void *)self; } @@ -509,7 +575,7 @@ sv_vendor_axis_communications_teardown(void *handle) free(self->attestation); free(self->certificate_chain); - X509_free(self->trusted_ca); + X509_STORE_free(self->trusted_cas); free(self); } @@ -533,11 +599,6 @@ get_untrusted_certificates_size(const sv_vendor_axis_communications_t *self) const char *cert_chain_ptr = self->certificate_chain; const char *cert_ptr = self->certificate_chain; int certs_left = NUM_UNTRUSTED_CERTIFICATES + 1; - if (self->factory_provisioned) { - // Temporary solution until it is known how many (if any) intermediate certificates there are. - // Assuming no intermediate certificates. - certs_left = 2; // Leaf and root certificate. - } while (certs_left > 0 && cert_ptr) { cert_ptr = strstr(cert_chain_ptr, "-----BEGIN CERTIFICATE-----"); certs_left--; @@ -654,6 +715,7 @@ decode_axis_communications_handle(void *handle, const uint8_t *data, size_t data data_ptr += cert_size; SV_THROW_IF(data_ptr != data + data_size, SV_AUTHENTICATION_ERROR); + self->factory_provisioned = attestation_size == 0; #ifdef PRINT_DECODED_SEI char *cert_chain_str = calloc(1, cert_size + 1); SV_THROW_IF(!cert_chain_str, SV_MEMORY); @@ -900,8 +962,8 @@ sv_vendor_axis_communications_set_attestation_report(signed_video_t *sv, return SV_MEMORY; } -const char * +const char ** get_axis_communications_trusted_certificate(void) { - return kTrustedAxisRootCA; + return kTrustedRootCAs; } diff --git a/lib/src/sv_axis_communications.h b/lib/src/sv_axis_communications.h index afb5ba5f..9e2706ba 100644 --- a/lib/src/sv_axis_communications.h +++ b/lib/src/sv_axis_communications.h @@ -147,7 +147,7 @@ get_axis_communications_supplemental_authenticity(void *handle, * * @return A pointer to the trusted certificate as a null-terminated string. */ -const char * +const char ** get_axis_communications_trusted_certificate(void); /** diff --git a/lib/src/sv_tlv.c b/lib/src/sv_tlv.c index 67256a27..671e1223 100644 --- a/lib/src/sv_tlv.c +++ b/lib/src/sv_tlv.c @@ -1179,7 +1179,7 @@ sv_tlv_find_tag(const uint8_t *tlv_data, size_t tlv_data_size, sv_tlv_tag_t tag, length |= sv_read_byte(&last_two_bytes, &tlv_data_ptr, with_ep); } if (tlv_data_ptr + length > tlv_data + tlv_data_size) { - DEBUG_LOG("TLV length (%u) too large", length); + DEBUG_LOG("TLV (%d) length (%u) too large", this_tag, length); return NULL; } // Scan past the data diff --git a/media-signing-framework b/media-signing-framework index c3aaf737..7634c1b6 160000 --- a/media-signing-framework +++ b/media-signing-framework @@ -1 +1 @@ -Subproject commit c3aaf73734119ce35e4b2d968cf2e0c62b3ca398 +Subproject commit 7634c1b6303263211650b533c81226d5653d1843 diff --git a/meson.build b/meson.build index 299068d8..f2661758 100644 --- a/meson.build +++ b/meson.build @@ -33,6 +33,7 @@ endif if get_option('debugprints') add_global_arguments('-DSIGNED_VIDEO_DEBUG', language : 'c') + add_global_arguments('-DONVIF_MEDIA_SIGNING_DEBUG', language : 'c') endif if get_option('parsesei') add_global_arguments('-DPRINT_DECODED_SEI', language : 'c') diff --git a/tests/cert_chain.pem b/tests/cert_chain.pem index 3f2dd476..623a79fa 100644 --- a/tests/cert_chain.pem +++ b/tests/cert_chain.pem @@ -1,22 +1,35 @@ -----BEGIN CERTIFICATE----- -MIIBaTCCAQ8CFHZeHZ2d5CCWIkLHsQTLwEsgk1/PMAoGCCqGSM49BAMCMDcxEDAO -BgNVBAoMB1NvbWVPcmcxDTALBgNVBAsMBFRlc3QxFDASBgNVBAMMC1Rlc3QgUm9v -dENBMB4XDTI1MDEwODExMjUxOFoXDTI2MDEwODExMjUxOFowNzEQMA4GA1UECgwH -U29tZU9yZzENMAsGA1UECwwEVGVzdDEUMBIGA1UEAwwLVGVzdCBjYW1lcmEwWTAT -BgcqhkjOPQIBBggqhkjOPQMBBwNCAAQNWFD88M9YY2Ru/4TPFPSaoK/ffAnwb9GK -0N3Oh6AQu6ZjAudSvo8ppTEF4RnXIP8Pi0Tzy3SmvQLasNOKv7YxMAoGCCqGSM49 -BAMCA0gAMEUCIQCgwvrFNOSVGEn/6g7cQwJ2Lv5QhIFSioHxLKD0old4eQIgdGyq -ZIvk9mQjqyVUbMpRspztBTTh75mYRLYZ1EfYqUM= +MIIBgDCCAScCFHvP0k2/NrM7lWzf7hOnYaPbi0/PMAoGCCqGSM49BAMCMD8xEDAO +BgNVBAoMB1NvbWVPcmcxDTALBgNVBAsMBFRlc3QxHDAaBgNVBAMME1Rlc3QgSW50 +ZXJtZWRpYXRlQ0EwHhcNMjYwNjMwMTIxNDMyWhcNMjcwNjMwMTIxNDMyWjBHMRAw +DgYDVQQKDAdTb21lT3JnMQ0wCwYDVQQLDARUZXN0MSQwIgYDVQQDDBtUZXN0IGNh +bWVyYS1zZXJpYWxfbm8tMTIzNDUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQN +WFD88M9YY2Ru/4TPFPSaoK/ffAnwb9GK0N3Oh6AQu6ZjAudSvo8ppTEF4RnXIP8P +i0Tzy3SmvQLasNOKv7YxMAoGCCqGSM49BAMCA0cAMEQCIAY3Y5LX1sGWCOm2NJci +hKPZ8WfTRyXc1fMENb8vlac/AiAj8C0KS6rw8y4ToVHW4CCNHH+0WBPqXfceeNgp +QKR3rg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB2zCCAYGgAwIBAgIUOwLiJb9PxRB1NIyvfRdYZOoHOMIwCgYIKoZIzj0EAwIw +NzEQMA4GA1UECgwHU29tZU9yZzENMAsGA1UECwwEVGVzdDEUMBIGA1UEAwwLVGVz +dCBSb290Q0EwHhcNMjYwNjMwMTIxNDMyWhcNMjgwNjI5MTIxNDMyWjA/MRAwDgYD +VQQKDAdTb21lT3JnMQ0wCwYDVQQLDARUZXN0MRwwGgYDVQQDDBNUZXN0IEludGVy +bWVkaWF0ZUNBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERGNh3m2KPe9oAyoQ +RjJ1PV8AmSrtpYpbD58cY8OFFuoElPA1OyYnMjvxYp9gpG8YunauzLQ1g4NW69gv +bk0y8qNjMGEwHQYDVR0OBBYEFMuOiZO0+odgzXt19wY2R16L3L/uMB8GA1UdIwQY +MBaAFD5PGjACsS2Lgn5mgEDr3J8Bv1rmMBIGA1UdEwEB/wQIMAYBAf8CAQEwCwYD +VR0PBAQDAgEGMAoGCCqGSM49BAMCA0gAMEUCICFY0ydQFhhNx7uCf9mnNQDK4fvz +6RdaQwsukl/M/kQdAiEA1ilK/rfiaxWcmk0Eymuf6tGm8TXKxRNqwNIW/n/89a4= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIBwDCCAWagAwIBAgIBATAKBggqhkjOPQQDAjA3MRAwDgYDVQQKDAdTb21lT3Jn -MQ0wCwYDVQQLDARUZXN0MRQwEgYDVQQDDAtUZXN0IFJvb3RDQTAeFw0yNTAxMDgx -MTI1MThaFw0yNzEwMjkxMTI1MThaMDcxEDAOBgNVBAoMB1NvbWVPcmcxDTALBgNV +MQ0wCwYDVQQLDARUZXN0MRQwEgYDVQQDDAtUZXN0IFJvb3RDQTAeFw0yNjA2MzAx +MjE0MzJaFw0yOTA0MTkxMjE0MzJaMDcxEDAOBgNVBAoMB1NvbWVPcmcxDTALBgNV BAsMBFRlc3QxFDASBgNVBAMMC1Rlc3QgUm9vdENBMFkwEwYHKoZIzj0CAQYIKoZI -zj0DAQcDQgAEBXOyToMP/r5D7WauMm6t3/QE950vOermkjWWTtPrvJFuZUGli4B7 -wd3yWCertJvvAFCUs6K2BgAAKqOKSrR4S6NjMGEwHQYDVR0OBBYEFEXCHE2/WOnU -pjpM91GiBW2OXeceMB8GA1UdIwQYMBaAFEXCHE2/WOnUpjpM91GiBW2OXeceMBIG +zj0DAQcDQgAE1NMbp9bugqUvJlVSFoBw4l/b4Qqzkz8YABNrls27ArZ26JKAZbH/ +MneShe4vIEoOZoI8ONJxci+3rzXknRLkzKNjMGEwHQYDVR0OBBYEFD5PGjACsS2L +gn5mgEDr3J8Bv1rmMB8GA1UdIwQYMBaAFD5PGjACsS2Lgn5mgEDr3J8Bv1rmMBIG A1UdEwEB/wQIMAYBAf8CAQEwCwYDVR0PBAQDAgEGMAoGCCqGSM49BAMCA0gAMEUC -IBeea9/SVCdgLh54Olv4+Xw8c3EEXqWh73VjmxU+0r8wAiEAt0WmO4HqJrCCgyYD -I8LEz40c9xR+reGxEGpnx46NSK0= +IGsd1JtrQpxgYk/3pcO3/LYFWKZAAwGPf+/vZAPYAGiSAiEAgms1xr7IyktZ0g9t +2S1Hfbps4RVLQr+79h16eA7Yuw8= -----END CERTIFICATE----- diff --git a/tests/check/check_signed_video_auth.c b/tests/check/check_signed_video_auth.c index b2167bf0..f979a449 100644 --- a/tests/check/check_signed_video_auth.c +++ b/tests/check/check_signed_video_auth.c @@ -1420,7 +1420,7 @@ START_TEST(vendor_axis_communications_operation) ck_assert_int_eq(sv_rc, SV_OK); free(attestation); - sv_rc = signed_video_set_product_info(sv, HW_ID, FW_VER, NULL, "Axis Communications AB", ADDR); + sv_rc = signed_video_set_product_info(sv, HW_ID, FW_VER, SER_NO, "Axis Communications AB", ADDR); ck_assert_int_eq(sv_rc, SV_OK); // Mimic a GOP with 1 P-frame between 2 I-frames to trigger an SEI message. @@ -1536,7 +1536,7 @@ START_TEST(factory_provisioned_key) ck_assert_int_eq(sv_rc, SV_OK); free(certificate_chain); - sv_rc = signed_video_set_product_info(sv, HW_ID, FW_VER, NULL, "Axis Communications AB", ADDR); + sv_rc = signed_video_set_product_info(sv, HW_ID, FW_VER, SER_NO, "Axis Communications AB", ADDR); ck_assert_int_eq(sv_rc, SV_OK); // Mimic a GOP with 1 P-frame between 2 I-frames to trigger an SEI message. diff --git a/tests/generate-cert.sh b/tests/generate-cert.sh index 9ff0c98d..570fbd0e 100755 --- a/tests/generate-cert.sh +++ b/tests/generate-cert.sh @@ -1,46 +1,45 @@ #!/bin/bash -# Create CA private key (provide a password for the key): +# 1. Create ROOT CA +# Create private key for Root CA openssl ecparam -name prime256v1 -genkey -noout -out ca_ec.key -# Create CA certificate (provide suitable input when asked): +# Create Root CA certificate 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 -# Print the CA +# Print Root CA certificate openssl x509 -in ca_ec.pem -text -noout -# ## Intermediate EC cert ## -# # Generate test private keys -# openssl ecparam -name prime256v1 -genkey -noout -out intermediate_signing.key -# # Create CSR requirements file: -# openssl req -new -key intermediate_signing.key -out intermediate_signing.csr -subj "/O=SomeOrg/OU=Test/CN=Test camera" +# 2. Create INTERMEDIATE CA (NEW STEP) +# Create private key for Intermediate CA +openssl ecparam -name prime256v1 -genkey -noout -out intermediate_ec.key -# # Sign CSR -# 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 +# Create CSR (Certificate Signing Request) for Intermediate CA +openssl req -new -key intermediate_ec.key -out intermediate_ec.csr -subj "/O=SomeOrg/OU=Test/CN=Test IntermediateCA" -# # Print signed certificate -# openssl x509 -in intermediate_signing.crt -text -noout +# Sign Intermediate CSR with Root CA (Important: requires CA extensions in your cnf file) +openssl x509 -req -in intermediate_ec.csr -CA ca_ec.pem -CAkey ca_ec.key -CAcreateserial -out intermediate_ec.pem -days 730 -sha256 -extfile ./test_openssl.cnf -extensions v3_ca -# # Verify certificate -# openssl verify -verbose -CAfile ca_ec.pem intermediate_signing.crt +# Print Intermediate CA certificate +openssl x509 -in intermediate_ec.pem -text -noout -## EC ## -# Create CSR requirements file: -# openssl req -new -key ec_signing.key -out ec_signing.csr -subj "/O=SomeOrg/OU=Test/CN=Test camera" -openssl req -new -key private_ecdsa_key.pem -out ec_signing.csr -subj "/O=SomeOrg/OU=Test/CN=Test camera" +# 3. CREATE AND SIGN END-ENTITY CERTIFICATE (MODIFIED) +# Create CSR for end-entity certificate (e.g., camera) +openssl req -new -key private_ecdsa_key.pem -out ec_signing.csr -subj "/O=SomeOrg/OU=Test/CN=Test camera-serial_no-12345" -# Sign CSR -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 -# 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 +# Sign camera's CSR with INTERMEDIATE CA (not Root CA) +openssl x509 -req -in ec_signing.csr -CA intermediate_ec.pem -CAkey intermediate_ec.key -CAcreateserial -out ec_signing.crt -days 365 -sha256 -extensions req_ext -# Print signed certificate +# Print end-entity certificate openssl x509 -in ec_signing.crt -text -noout -# Verify certificate -openssl verify -verbose -CAfile ca_ec.pem ec_signing.crt -# openssl verify -verbose -CAfile intermediate_signing.crt ec_signing.crt +# 4. VERIFY AND CREATE CHAIN (MODIFIED) +# Create a file with the entire CA chain for verification +cat intermediate_ec.pem ca_ec.pem > ca_chain.pem -# Concatenate certificates -cat ec_signing.crt ca_ec.pem > cert_chain.pem -# cat ec_signing.crt intermediate_signing.crt ca_ec.pem > cert_chain.pem +# Verify camera's certificate against the entire chain +openssl verify -verbose -CAfile ca_chain.pem ec_signing.crt + +# Create the final certificate chain (End-entity -> Intermediate -> Root) +cat ec_signing.crt intermediate_ec.pem ca_ec.pem > cert_chain.pem From 5638eb9d0d3d410289db676419c2e787c6291fe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20V=C3=B6lcker?= Date: Tue, 30 Jun 2026 16:17:22 +0200 Subject: [PATCH 2/2] Fix ONVIF API --- lib/src/sv_onvif.c | 3 +-- lib/src/sv_onvif.h | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/src/sv_onvif.c b/lib/src/sv_onvif.c index 901c5f13..b5bd5c2b 100644 --- a/lib/src/sv_onvif.c +++ b/lib/src/sv_onvif.c @@ -166,8 +166,7 @@ onvif_media_signing_authenticity_report_free( MediaSigningReturnCode onvif_media_signing_set_trusted_certificate(onvif_media_signing_t ATTR_UNUSED *self, const char ATTR_UNUSED *trusted_certificate, - size_t ATTR_UNUSED trusted_certificate_size, - bool ATTR_UNUSED user_provisioned) + size_t ATTR_UNUSED trusted_certificate_size) { return OMS_NOT_SUPPORTED; } diff --git a/lib/src/sv_onvif.h b/lib/src/sv_onvif.h index 4fbab95b..aaf6bc57 100644 --- a/lib/src/sv_onvif.h +++ b/lib/src/sv_onvif.h @@ -192,8 +192,7 @@ onvif_media_signing_authenticity_report_free( MediaSigningReturnCode onvif_media_signing_set_trusted_certificate(onvif_media_signing_t *self, const char *trusted_certificate, - size_t trusted_certificate_size, - bool user_provisioned); + size_t trusted_certificate_size); #endif // NO_ONVIF_MEDIA_SIGNING #endif // __SV_ONVIF_H__