Support PEM and DER formats in cert chain 46/323646/5
authorDongsun Lee <ds73.lee@samsung.com>
Fri, 2 May 2025 08:31:11 +0000 (17:31 +0900)
committerDongsun Lee <ds73.lee@samsung.com>
Fri, 2 May 2025 08:45:53 +0000 (17:45 +0900)
Change-Id: Icff5db3ffbadb3bcf19e4a558e0f25b6e3446a05

packaging/device-certificate-manager.spec
src/dcm-daemon/CMakeLists.txt
src/dcm-daemon/cert_utils.cpp [new file with mode: 0644]
src/dcm-daemon/cert_utils.h [new file with mode: 0644]
src/dcm-daemon/dcm_session.cpp
tests/CMakeLists.txt
tests/cert_test.cpp [new file with mode: 0644]

index d5bb70de38ebcf9a5316878cf5ba5f2748a568b3..d5c0f51d38b8a6f99d92f6768332b5768e78f32c 100644 (file)
@@ -17,6 +17,7 @@ BuildRequires: pkgconfig(cynara-creds-socket)
 BuildRequires: pkgconfig(cynara-session)
 BuildRequires: pkgconfig(capi-system-info)
 BuildRequires: pkgconfig(hal-api-security)
+BuildRequires: pkgconfig(openssl3)
 BuildRequires: boost-devel
 %if "%{build_type}" == "COVERAGE"
 BuildRequires: lcov
@@ -55,6 +56,7 @@ Summary:      Internal tests for Device Certificate Manager
 Group:         Security/Testing
 License:       Apache-2.0 and BSL-1.0
 BuildRequires: pkgconfig(security-manager)
+BuildRequires: pkgconfig(openssl3)
 Requires:      device-certificate-manager = %{version}-%{release}
 Requires:      boost-test
 Requires(post): /usr/bin/system-info-tool
index f5f568600a879d307cc6ac7af1ca5688a3ebb450..103753133bc9dd81536662e4459990e41eae7e14 100644 (file)
@@ -35,6 +35,7 @@ PKG_CHECK_MODULES(DAEMON_DEPS
        cynara-client
        cynara-creds-socket
        cynara-session
+       openssl3
        dlog)
 
 INCLUDE_DIRECTORIES(SYSTEM ${DAEMON_DEPS_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS})
@@ -48,6 +49,7 @@ ADD_EXECUTABLE(${TARGET_DAEMON}
        main.cpp
        dcm_server.cpp
        dcm_session.cpp
+       cert_utils.cpp
        ../shared/protobuf_asio.cpp
        ../shared/log.cpp
        ${PROTO_SRCS}
diff --git a/src/dcm-daemon/cert_utils.cpp b/src/dcm-daemon/cert_utils.cpp
new file mode 100644 (file)
index 0000000..28a384f
--- /dev/null
@@ -0,0 +1,83 @@
+/******************************************************************
+ *
+ * Copyright 2017 - 2025 Samsung Electronics All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ ******************************************************************/
+
+#include <openssl/pem.h>
+#include "cert_utils.h"
+#include "log.h"
+#include "device_certificate_manager.h"
+
+
+void parse_cert_chain(STACK_OF(X509) * chain_store, const char * buffer, size_t length, bool is_pem)
+{
+    X509 *cert = nullptr;
+    BIO_ptr bio(BIO_new_mem_buf(buffer, length), ::BIO_free);
+    while(true) {
+        if(is_pem) {
+            cert = PEM_read_bio_X509(bio.get(), NULL, NULL, NULL);
+        } else {
+            cert = d2i_X509_bio(bio.get(), NULL);
+        }
+
+        if (cert != nullptr) {
+            sk_X509_push(chain_store, cert);
+        } else {
+            break;
+        }
+    }
+}
+
+int x509_rewriter::parse(const char * buffer, size_t length)
+{
+    BIO_ptr bio(BIO_new_mem_buf(buffer, length), ::BIO_free);
+
+    parse_cert_chain(fChain, buffer, length, true); // Parse PEM type cert chain
+    if (sk_X509_num(fChain) == 0) { // Try for DER type cert chain
+        LOGW("No PEM Certificates in Cert Chain. So tring to parse DER certificates");
+        parse_cert_chain(fChain, buffer, length, false);
+    }
+
+    if (sk_X509_num(fChain) == 0) {
+        LOGE("No Certificates in Cert Chain. buffer length=" << length);
+        return DCM_ERROR_NO_DATA;
+    }
+
+    return DCM_ERROR_NONE;
+}
+
+std::string x509_rewriter::emit_pem()
+{
+    BIO_ptr bio(BIO_new(BIO_s_mem()), ::BIO_free);
+
+    if (sk_X509_num(fChain) == 0)
+        return std::string();
+
+    for (int i = 0; i < sk_X509_num(fChain); i++) {
+        X509_ptr cert(sk_X509_value(fChain, i), ::X509_free);
+        if (!PEM_write_bio_X509(bio.get(), cert.get())) {
+            // Error. Returns an empty string.
+            return std::string();
+        }
+    }
+
+    BUF_MEM *mem = nullptr;
+    BIO_get_mem_ptr(bio.get(), &mem);
+    if (mem)
+        return std::string(mem->data, mem->length);
+    else
+        return std::string();
+}
diff --git a/src/dcm-daemon/cert_utils.h b/src/dcm-daemon/cert_utils.h
new file mode 100644 (file)
index 0000000..9b162fc
--- /dev/null
@@ -0,0 +1,49 @@
+/******************************************************************
+ *
+ * Copyright 2017 Samsung Electronics All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ ******************************************************************/
+
+#ifndef DCM_DAEMON_CERT_UTILS_H_
+#define DCM_DAEMON_CERT_UTILS_H_
+
+#include <openssl/x509.h>
+#include <string>
+#include <memory>
+
+
+using BIO_ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
+using X509_ptr = std::unique_ptr<X509, decltype(&::X509_free)>;
+
+struct x509_rewriter {
+private:
+        STACK_OF(X509) * fChain;
+        size_t fChainSize = 0;
+        size_t fNumCerts = 0;
+
+public:
+        x509_rewriter() :
+                fChain(sk_X509_new_null()) {
+        }
+
+        ~x509_rewriter() {
+                sk_X509_free(fChain);
+        }
+
+        int parse(const char * buffer, size_t length);
+        std::string emit_pem();
+};
+
+#endif /* DCM_DAEMON_CERT_UTILS_H_ */
\ No newline at end of file
index d9c0b20d7c4b796db693889dd035b3df4d173348..cceabb3ee0610b8455309743ad30126c538e3c1f 100644 (file)
@@ -29,6 +29,7 @@
 #include "dcm_server.h"
 #include "device_certificate_manager_ext_types.h"
 #include "device_certificate_manager.h"
+#include "cert_utils.h"
 #include "log.h"
 
 #include <hal/hal-security-certs.h>
@@ -375,24 +376,14 @@ void dcm_session::handle_cert_chain(const RequestCertificateChain& message)
                        return;
                }
 
-               if (cert_chain->buffer != NULL && cert_chain->length >= sPEMHeader.length() &&
-                       !memcmp(sPEMHeader.c_str(), cert_chain->buffer, sPEMHeader.size()) &&
-                       cert_chain->buffer[cert_chain->length - 1] != '\0')
-               {
-                       // Add missing 0
-                       char* new_buffer = static_cast<char*>(realloc(cert_chain->buffer, cert_chain->length + 1));
-                       if (new_buffer == NULL) {
-                               reply_error(msg, certificateResponse, backend_to_dcm_error(-ENOMEM),
-                                                       "Failed reallocating memory for certificate chain");
-                               return;
-                       }
-
-                       cert_chain->buffer = new_buffer;
-                       cert_chain->length = cert_chain->length + 1;
-                       strncat(cert_chain->buffer, "\0", 1);
-               }
+               x509_rewriter parser;
+               error = parser.parse(cert_chain->buffer, cert_chain->length);
+               if (error != DCM_ERROR_NONE) {
+                       reply_error(msg, certificateResponse, error, "Failed to parse certificate chain");
+                       return;
+        }
 
-               *certificateResponse->mutable_cert_chain() = std::string(cert_chain->buffer, cert_chain->length);
+               *certificateResponse->mutable_cert_chain() = parser.emit_pem();
                certificateResponse->set_result(0);
                reply(msg);
        } catch (std::bad_alloc&) {
index 7364e22713f13c2111bba0599ac67033ec4567fa..12495dca752c7b109ba119aa7c2042f668f62266 100644 (file)
@@ -53,7 +53,7 @@ FIND_PACKAGE(Boost REQUIRED
        COMPONENTS
        unit_test_framework)
 
-PKG_CHECK_MODULES(TEST_DEPS REQUIRED dlog capi-system-info security-manager)
+PKG_CHECK_MODULES(TEST_DEPS REQUIRED dlog capi-system-info security-manager openssl3)
 
 INCLUDE_DIRECTORIES(SYSTEM ${Boost_INCLUDE_DIRS} ${TEST_DEPS_INCLUDE_DIRS})
 LINK_DIRECTORIES(${Boost_LIBRARY_DIRS} ${TEST_DEPS_LIBRARY_DIRS})
@@ -66,11 +66,13 @@ PROTOBUF_GENERATE_CPP(PROTO_SRCS PROTO_HDRS ../src/dcm-client/dcm_support.proto)
 ADD_EXECUTABLE(${TARGET_TESTS}
        device_certificate_manager_tests.cpp
        api_test.cpp
+       cert_test.cpp
        colour_log_formatter.cpp
        shared_test.cpp
        ../src/dcm-client/dcm_client.cpp
        ../src/dcm-client/device_certificate_manager.cpp
        ../src/dcm-client/device_certificate_manager_ext.cpp
+       ../src/dcm-daemon/cert_utils.cpp
        ../src/shared/log.cpp
        ../src/shared/protobuf_asio.cpp
        ${PROTO_SRCS}
diff --git a/tests/cert_test.cpp b/tests/cert_test.cpp
new file mode 100644 (file)
index 0000000..afe598a
--- /dev/null
@@ -0,0 +1,163 @@
+/******************************************************************
+ *
+ * Copyright 2017 - 2021 Samsung Electronics All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the \n"License\n");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an \n"AS IS\n" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ ******************************************************************/
+
+#include <iostream>
+#include <string>
+#include <openssl/pem.h>
+
+
+#include "cert_utils.h"
+#include "device_certificate_manager.h"
+
+#include "test_macros.h"
+
+namespace {
+
+    const std::string cert_leaf =
+        "-----BEGIN CERTIFICATE-----\n"
+        "MIIDezCCAmMCCQCsmy9mf2NyaTANBgkqhkiG9w0BAQsFADBWMRowGAYDVQQKDBFU\n"
+        "aXplbiBBc3NvY2lhdGlvbjEaMBgGA1UECwwRVGl6ZW4gQXNzb2NpYXRpb24xHDAa\n"
+        "BgNVBAMME1RpemVuIERldmVsb3BlcnMgQ0EwHhcNMTgxMTI4MDY1MTIyWhcNMjYx\n"
+        "MjI1MDY1MTIyWjCBqDELMAkGA1UEBhMCS08xETAPBgNVBAgMCEt5ZW9uZ2tpMQ4w\n"
+        "DAYDVQQHDAVTdXdvbjEuMCwGA1UECgwlU2Ftc3VuZyBFbGVjdHJvbmljcyBEaWdp\n"
+        "dGFsIEFwcGxpYW5jZTERMA8GA1UECwwIU2VjdXJpdHkxEDAOBgNVBAMMB2Jyby5r\n"
+        "aW0xITAfBgkqhkiG9w0BCQEWEnNkbC5kYUBzYW1zdW5nLmNvbTCCASIwDQYJKoZI\n"
+        "hvcNAQEBBQADggEPADCCAQoCggEBAPaIXmFsFZ/EXRUeoNyodAClCexSfTI+Y2Yr\n"
+        "MzxDlgPrv/BYeEkhxBzD/efOUWceHrzpxARWAqWk6NVUyAzXkSWbys52K6UDpVLL\n"
+        "JX1mrj9YiVVszhVV6/sajwxs1RFNPYqz5Evoxp7DuT1ryQdhud+ZvioeSL4eR6a0\n"
+        "B55SEG1172eJHKp97oU+l29q1eB7+nOlPM2zfYdd6y2O/WQ36XZG0/S8HY8dP66c\n"
+        "MovhB5Y4dAR+udQ5YLow0/Y0pmHJynFLaBhm9ySQYX1QTmSbu5uVbj/lbhriZPLX\n"
+        "3q/C/W1EkyvrsGRCVkHxT8XApdgoZO6+ld1nFpM/pH84tBxJ9bkCAwEAATANBgkq\n"
+        "hkiG9w0BAQsFAAOCAQEAPh5QN7x59G1TUTdUqJ1ZcMLLZsAACX1lphMUg0oTIM0r\n"
+        "ISdEBBuMLExkGwY5I8RqZ8rYeOESft2SbrIRf4Y830LaV99tJMDBCfzmzNm5UWpO\n"
+        "Fb2cT//N9VvLuc2Kigr+X5/RlAkWRnYUc4D+ZE1nXEc0VoSNUDzeqO8c971zeg10\n"
+        "bl+gzqvrZIK4GqCeJdCTc1HO1INjNq+t1c1ViwgtAzJv4PZlXEHhWQmsoOHRSb1l\n"
+        "iGpQ59qYv8lqmXLa+FgzlQPH+AdCIP9YSOoYBf6tJCTA4YjNyn1PiTMH1hMwER6k\n"
+        "MwFCQbyfmgXnKxENjk94FCQ/fBb4SxEwe0u6iccA4w==\n"
+        "-----END CERTIFICATE-----\n";
+
+    const std::string cert_ca =
+        "-----BEGIN CERTIFICATE-----\n"
+        "MIIDOTCCAiGgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMRowGAYDVQQKDBFUaXpl\n"
+        "biBBc3NvY2lhdGlvbjEaMBgGA1UECwwRVGl6ZW4gQXNzb2NpYXRpb24xHjAcBgNV\n"
+        "BAMMFVRpemVuIERldmVsb3BlcnMgUm9vdDAeFw0xMjAxMDEwMDAwMDBaFw0yNzAx\n"
+        "MDEwMDAwMDBaMFYxGjAYBgNVBAoMEVRpemVuIEFzc29jaWF0aW9uMRowGAYDVQQL\n"
+        "DBFUaXplbiBBc3NvY2lhdGlvbjEcMBoGA1UEAwwTVGl6ZW4gRGV2ZWxvcGVycyBD\n"
+        "QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANVGhRGmMIUyBA7oPCz8\n"
+        "Sxut6z6HNkF4oDIuzuKaMzRYPeWodwe9O0gmqAkToQHfwg2giRhE5GoPld0fq+OY\n"
+        "MMwSasCug8dwODx1eDeSYVuOLWRxpAmbTXOsSFi6VoWeyaPEm18JBHvZBsU5YQtg\n"
+        "Z6Kp7MqzvQg3pXOxtajjvyHxiatJl+xXrHgcXC1wgyG3buty7u/Fi2mvKXJ0PRJc\n"
+        "CjjK81dqe/Vr20sRUCrbk02zbm5ggFt/jIEhV8wbFRQpliobc7J4dSTKhFfrqGM8\n"
+        "rdd54LYhD7gSI1CFSe16pUXfcVR7FhJztRaiGLnCrwBEdyTZ248+D4L/qR/D0axb\n"
+        "3jcCAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAnOXX\n"
+        "Q/1O/QTDHyrmQDtFziqPY3xWlJBqJtEqXiT7Y+Ljpe66e+Ee/OjQMlZe8gu21/8c\n"
+        "KklH95RxjopMWCVedXDUbWdvS2+CdyvVW/quT2E0tjqIzXDekUTYwwhlPWlGxvfj\n"
+        "3VsxqSFq3p8Brl041Gx5RKAGyKVsMfTLhbbwSWwApuBUxYfcNpKwLWGPXkysu+Hc\n"
+        "tY03OKv4/xKBnVWiN8ex/Sgesi0M+OBAOMdZMPK32uJBTeKFx1xZgTLIhk45V0hP\n"
+        "OomPjZloiv0LSS11eyd451ufjW0iHRE7WlpR6EvIW6TFyZgMpQq+kg4hWl2SBTf3\n"
+        "s2VI8Ygz7gj8TMlClg==\n"
+        "-----END CERTIFICATE-----\n";
+
+    const std::string cert_root =
+        "-----BEGIN CERTIFICATE-----\n"
+        "MIIDOzCCAiOgAwIBAgIBADANBgkqhkiG9w0BAQUFADBYMRowGAYDVQQKDBFUaXpl\n"
+        "biBBc3NvY2lhdGlvbjEaMBgGA1UECwwRVGl6ZW4gQXNzb2NpYXRpb24xHjAcBgNV\n"
+        "BAMMFVRpemVuIERldmVsb3BlcnMgUm9vdDAeFw0xMjAxMDEwMDAwMDBaFw0zMjAx\n"
+        "MDEwMDAwMDBaMFgxGjAYBgNVBAoMEVRpemVuIEFzc29jaWF0aW9uMRowGAYDVQQL\n"
+        "DBFUaXplbiBBc3NvY2lhdGlvbjEeMBwGA1UEAwwVVGl6ZW4gRGV2ZWxvcGVycyBS\n"
+        "b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp2rCwXTYh28vcagX\n"
+        "WLIeVtEvXA5EeTR9UnL4Dzyd7hIq8rkxLbIMMOcCrXMTc7bEH2twFaTuXxyKXMW/\n"
+        "2c+id3m3Z1B5caCqwSPr72oKPSI4jSkvrAC5W7EHx16M818aG4tQkXIUBhDrtSmH\n"
+        "6dFOdt8zGq2fanj1sETfUmXAeLGE7OQYcEb2SoWGXR75Ytfp1LAw/L3luuG/kbzB\n"
+        "crZt1Cv05jfCP575eope6p5p80Gl0tieXyPYhSLVTLwhEdWx18CMaC7IXQo2Bm+J\n"
+        "djDH0Ruh/vTRnjFtmVB+nBOZNVzMHNOPUVFKSgysX/+PlM4jBTvbaTnPCZUkC/O7\n"
+        "5tYIpwIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBw\n"
+        "95ibcuAiKpAEqBMyTZtOf0okhSi9NYfs/AFIPLH5REnhtQkPmKsvDp21OSdzrFEL\n"
+        "42rV94K98QChD9tGO6Mwp1ZHM3No7/PLC3EelOwmn4dr3KPGdjvQNSwKRblGh0Hj\n"
+        "n4fI+studFLLv6ldCLIpA/Ssgf9GuUbcjTC8OWBYPVUQ6YoXAcuHbfhr6a2IXRTj\n"
+        "lJUCt3qWyciP2H/R+oNBSjtlq13ZT+D9AQMmIG/5w1tK0HzDRhORfWlKCo5JKn0A\n"
+        "iQq2fwtoB0JQEHRKCKZYWghG41HuKc82xLf6H7x24XWOAlXb0SpvVENT1i89XNrj\n"
+        "XS4modIY545rYjI1amfL\n"
+        "-----END CERTIFICATE-----\n";
+
+    const std::string cert_chain_in_pem = cert_leaf + cert_ca + cert_root;
+
+
+    void parse_cert(STACK_OF(X509) * chain_store, const std::string pem_cert)
+    {
+        X509 *cert = nullptr;
+        BIO_ptr bio(BIO_new_mem_buf(pem_cert.c_str(), pem_cert.size()), ::BIO_free);
+        cert = PEM_read_bio_X509(bio.get(), NULL, NULL, NULL);
+        if (cert != nullptr)
+            sk_X509_push(chain_store, cert);
+    }
+};
+
+
+
+BOOST_AUTO_TEST_SUITE(UNIT_TEST)
+
+POSITIVE_TEST_CASE(test11_cert_chain_in_pem)
+{
+    int ret = DCM_ERROR_NONE;
+
+    x509_rewriter parser;
+    ret = parser.parse(cert_chain_in_pem.c_str(), cert_chain_in_pem.size());
+    BOOST_REQUIRE_EQUAL(ret, DCM_ERROR_NONE);
+
+    std::string emitted_cert = parser.emit_pem();
+    BOOST_REQUIRE(emitted_cert.size() > 0);
+}
+
+POSITIVE_TEST_CASE(test12_cert_chain_in_der)
+{
+    int ret = DCM_ERROR_NONE;
+
+    STACK_OF(X509) *chain_store = sk_X509_new_null();
+    parse_cert(chain_store, cert_leaf);
+    parse_cert(chain_store, cert_ca);
+    parse_cert(chain_store, cert_root);
+
+    BIO_ptr bio(BIO_new(BIO_s_mem()), ::BIO_free);
+    for (int i = 0; i < sk_X509_num(chain_store); i++) {
+        X509_ptr cert(sk_X509_value(chain_store, i), ::X509_free);
+        if (!i2d_X509_bio(bio.get(), cert.get())) {
+            // error case
+            std::cout << "Error happended during cert conversion" << std::endl;
+        }
+    }
+    sk_X509_free(chain_store);
+
+    BUF_MEM *mem = nullptr;
+    BIO_get_mem_ptr(bio.get(), &mem);
+
+    x509_rewriter parser;
+    ret = parser.parse(mem->data, mem->length);
+    BOOST_REQUIRE_EQUAL(ret, DCM_ERROR_NONE);
+
+    std::string cert_chain_from_der = parser.emit_pem();
+    BOOST_REQUIRE(cert_chain_from_der.size() > 0);
+
+    x509_rewriter parser2;
+    ret = parser2.parse(cert_chain_in_pem.c_str(), cert_chain_in_pem.size());
+    BOOST_REQUIRE_EQUAL(ret, DCM_ERROR_NONE);
+    std::string cert_chain_from_pem = parser2.emit_pem();
+    BOOST_REQUIRE_EQUAL(cert_chain_from_pem, cert_chain_from_der);
+}
+
+BOOST_AUTO_TEST_SUITE_END()