Implement x509 certificate rewriter 89/163989/1
authorJaroslaw Pelczar <j.pelczar@samsung.com>
Thu, 14 Dec 2017 06:30:47 +0000 (07:30 +0100)
committerJaroslaw Pelczar <j.pelczar@samsung.com>
Thu, 14 Dec 2017 10:53:14 +0000 (11:53 +0100)
This class will rewrite broken x509 chains into correct order.

Change-Id: I58b7a312f39443d7740fcda2bef94b089ca24090
Signed-off-by: Jaroslaw Pelczar <j.pelczar@samsung.com>
dcm-daemon/CMakeLists.txt
dcm-daemon/cert_utils.cpp [new file with mode: 0644]
dcm-daemon/cert_utils.h [new file with mode: 0644]

index 2c50e1cf8d30e6815f891a21731ce0c6529d667b..2d8a157c48a33adfd7f470ab09fd3e15948a4461 100644 (file)
@@ -70,6 +70,7 @@ add_executable(device-certificate-managerd
        abstractcryptobackendcontext.cpp
        cryptobackendroster.cpp
        dllresolver.cpp
+       cert_utils.cpp
        ${PROTO_SRCS}
        ${PROTO_HDRS}
        ${DUMMY_BACKEND_OBJECTS}
diff --git a/dcm-daemon/cert_utils.cpp b/dcm-daemon/cert_utils.cpp
new file mode 100644 (file)
index 0000000..2714904
--- /dev/null
@@ -0,0 +1,261 @@
+/******************************************************************
+ *
+ * Copyright 2017 Samsung Electronics All Rights Reserved.
+ *
+ * Author: Jaroslaw Pelczar <j.pelczar@samsung.com>
+ *
+ * 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 "cert_utils.h"
+#include <vector>
+#include <map>
+#include <algorithm>
+#include <cstring>
+#include <cstdlib>
+#include <mbedtls/pem.h>
+#include <stdexcept>
+#include <cassert>
+#include <set>
+#include <list>
+#include "logging.h"
+
+int x509_crt_rewriter::parse(const unsigned char * buffer, size_t length)
+{
+       BOOST_LOG_FUNCTION();
+       return mbedtls_x509_crt_parse(fChain, buffer, length);
+}
+
+/*
+ * Like memcmp, but case-insensitive and always returns -1 if different
+ */
+static int x509_memcasecmp( const void *s1, const void *s2, size_t len )
+{
+    size_t i;
+    unsigned char diff;
+    const unsigned char *n1 = (const unsigned char *)s1, *n2 = (const unsigned char *)s2;
+
+    for( i = 0; i < len; i++ )
+    {
+        diff = n1[i] ^ n2[i];
+
+        if( diff == 0 )
+            continue;
+
+        if( diff == 32 &&
+            ( ( n1[i] >= 'a' && n1[i] <= 'z' ) ||
+              ( n1[i] >= 'A' && n1[i] <= 'Z' ) ) )
+        {
+            continue;
+        }
+
+        return( -1 );
+    }
+
+    return( 0 );
+}
+
+/*
+ * Compare two X.509 strings, case-insensitive, and allowing for some encoding
+ * variations (but not all).
+ *
+ * Return 0 if equal, -1 otherwise.
+ */
+static int x509_string_cmp( const mbedtls_x509_buf *a, const mbedtls_x509_buf *b )
+{
+    if( a->tag == b->tag &&
+        a->len == b->len &&
+        memcmp( a->p, b->p, b->len ) == 0 )
+    {
+        return( 0 );
+    }
+
+    if( ( a->tag == MBEDTLS_ASN1_UTF8_STRING || a->tag == MBEDTLS_ASN1_PRINTABLE_STRING ) &&
+        ( b->tag == MBEDTLS_ASN1_UTF8_STRING || b->tag == MBEDTLS_ASN1_PRINTABLE_STRING ) &&
+        a->len == b->len &&
+        x509_memcasecmp( a->p, b->p, b->len ) == 0 )
+    {
+        return( 0 );
+    }
+
+    return( -1 );
+}
+
+/*
+ * Compare two X.509 Names (aka rdnSequence).
+ *
+ * See RFC 5280 section 7.1, though we don't implement the whole algorithm:
+ * we sometimes return unequal when the full algorithm would return equal,
+ * but never the other way. (In particular, we don't do Unicode normalisation
+ * or space folding.)
+ *
+ * Return 0 if equal, -1 otherwise.
+ */
+static int x509_name_cmp( const mbedtls_x509_name *a, const mbedtls_x509_name *b )
+{
+    /* Avoid recursion, it might not be optimised by the compiler */
+    while( a != NULL || b != NULL )
+    {
+        if( a == NULL || b == NULL )
+            return( -1 );
+
+        /* type */
+        if( a->oid.tag != b->oid.tag ||
+            a->oid.len != b->oid.len ||
+            memcmp( a->oid.p, b->oid.p, b->oid.len ) != 0 )
+        {
+            return( -1 );
+        }
+
+        /* value */
+        if( x509_string_cmp( &a->val, &b->val ) != 0 )
+            return( -1 );
+
+        /* structure of the list of sets */
+        if( a->next_merged != b->next_merged )
+            return( -1 );
+
+        a = a->next;
+        b = b->next;
+    }
+
+    /* a == NULL == b */
+    return( 0 );
+}
+
+void x509_crt_rewriter::sort_chain()
+{
+       BOOST_LOG_FUNCTION();
+
+       // Only 1 certificate - don't bother
+       if(!fChain->next) {
+               fChainSize = fChain->raw.len;
+               fNumCerts = 1;
+               return;
+       }
+
+       std::vector<mbedtls_x509_crt *> list;
+       std::multimap<mbedtls_x509_crt *, mbedtls_x509_crt *> subject_of;
+       std::set<mbedtls_x509_crt *> visited;
+
+       // Build list of all certificates
+       for(auto * cert = fChain ; cert ; cert = cert->next) {
+               list.push_back(cert);
+       }
+
+       // Create graph vertices to map issuer to subject
+       for(size_t i = 0 ; i < list.size() ; ++i) {
+               mbedtls_x509_crt * issuer = nullptr;
+               // Find issuer - ignore ourselves as we always want root CA issuer to be nullptr
+               for(size_t j = 0 ; j < list.size() ; ++j) {
+                       if(i != j && x509_name_cmp(&list[i]->issuer, &list[j]->subject) == 0) {
+                               issuer = list[j];
+                               break;
+                       }
+               }
+
+               // In case there are multiple subjects for one issuer, the chain
+               // must be horribly broken
+               subject_of.emplace(issuer, list[i]);
+       }
+
+       // BFS algorithm queue
+       std::list<mbedtls_x509_crt *> queue;
+       // Final output chain
+       std::list<mbedtls_x509_crt *> final_chain;
+
+       // Find root certificates - they will not have any issuer
+       auto root_range = subject_of.equal_range(nullptr);
+
+       // Perform BFS for each root
+       for(auto it = root_range.first ; it != root_range.second ; ++it) {
+               mbedtls_x509_crt * root_cert = it->second;
+
+               if(visited.find(root_cert) == visited.end()) {
+                       visited.insert(root_cert);
+                       queue.push_back(root_cert);
+
+                       while(!queue.empty()) {
+                               auto s = queue.front();
+                               // Write out chain from root back to leaf
+                               final_chain.push_back(s);
+                               queue.pop_front();
+
+                               auto range = subject_of.equal_range(s);
+                               for(auto it = range.first ; it != range.second ; ++it) {
+                                       mbedtls_x509_crt * cert = it->second;
+                                       if(visited.find(cert) == visited.end()) {
+                                               visited.insert(cert);
+                                               queue.push_back(cert);
+                                       }
+                               }
+                       }
+               }
+       }
+
+       fChain = nullptr;
+       fChainSize = 0;
+       fNumCerts = list.size();
+
+       // Rebuild certificate linked list
+       for(auto it = final_chain.begin() ; it != final_chain.end() ; ++it) {
+               auto cert(*it);
+               cert->next = fChain;
+               fChain = cert;
+               fChainSize += cert->raw.len;
+       }
+}
+
+#define PEM_BEGIN_CRT           "-----BEGIN CERTIFICATE-----\n"
+#define PEM_END_CRT             "-----END CERTIFICATE-----\n"
+
+std::string x509_crt_rewriter::emit_pem()
+{
+       BOOST_LOG_FUNCTION();
+       std::string buffer;
+
+       if(fChainSize == 0)
+               throw std::runtime_error("State failure");
+
+       // Always rewrite the chain as PEM
+
+       buffer.resize(fChainSize * 4 + fNumCerts * (sizeof(PEM_BEGIN_CRT) + sizeof(PEM_END_CRT)));
+
+       unsigned char * out_buffer = (unsigned char *)buffer.c_str();
+       size_t out_capacity = buffer.size();
+       size_t total_size = 0;
+       size_t this_len;
+
+       for(auto cert = fChain ; cert ; cert = cert->next) {
+               int error = mbedtls_pem_write_buffer(PEM_BEGIN_CRT,
+                               PEM_END_CRT,
+                               cert->raw.p,
+                               cert->raw.len,
+                               out_buffer + total_size,
+                               out_capacity - total_size,
+                               &this_len);
+
+               if(error != 0) {
+                       throw std::runtime_error("Certificate write failure");
+               }
+
+               // Account for final 0 byte
+               total_size += this_len - 1;
+       }
+
+       buffer.resize(total_size);
+       buffer.push_back(0);
+
+       return buffer;
+}
diff --git a/dcm-daemon/cert_utils.h b/dcm-daemon/cert_utils.h
new file mode 100644 (file)
index 0000000..7a5a64f
--- /dev/null
@@ -0,0 +1,50 @@
+/******************************************************************
+ *
+ * Copyright 2017 Samsung Electronics All Rights Reserved.
+ *
+ * Author: Jaroslaw Pelczar <j.pelczar@samsung.com>
+ *
+ * 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 <mbedtls/x509_crt.h>
+#include <string>
+
+struct x509_crt_rewriter {
+private:
+       mbedtls_x509_crt * fChain;
+       size_t fChainSize = 0;
+       size_t fNumCerts = 0;
+
+public:
+       x509_crt_rewriter() :
+               fChain(new mbedtls_x509_crt())
+       {
+               mbedtls_x509_crt_init(fChain);
+       }
+
+       ~x509_crt_rewriter() {
+               mbedtls_x509_crt_free(fChain);
+               delete fChain;
+       }
+
+       int parse(const unsigned char * buffer, size_t length);
+       void sort_chain();
+       std::string emit_pem();
+};
+
+#endif /* DCM_DAEMON_CERT_UTILS_H_ */