From: Jaroslaw Pelczar Date: Thu, 14 Dec 2017 06:30:47 +0000 (+0100) Subject: Implement x509 certificate rewriter X-Git-Tag: submit/tizen_4.0/20171227.060648^2~3^2 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=126cdd48eec3612156064d68126cbb7bb5b191ea;p=platform%2Fcore%2Fsecurity%2Fdevice-certificate-manager.git Implement x509 certificate rewriter This class will rewrite broken x509 chains into correct order. Change-Id: I58b7a312f39443d7740fcda2bef94b089ca24090 Signed-off-by: Jaroslaw Pelczar --- diff --git a/dcm-daemon/CMakeLists.txt b/dcm-daemon/CMakeLists.txt index 2c50e1c..2d8a157 100644 --- a/dcm-daemon/CMakeLists.txt +++ b/dcm-daemon/CMakeLists.txt @@ -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 index 0000000..2714904 --- /dev/null +++ b/dcm-daemon/cert_utils.cpp @@ -0,0 +1,261 @@ +/****************************************************************** + * + * Copyright 2017 Samsung Electronics All Rights Reserved. + * + * Author: Jaroslaw Pelczar + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 list; + std::multimap subject_of; + std::set 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 queue; + // Final output chain + std::list 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 index 0000000..7a5a64f --- /dev/null +++ b/dcm-daemon/cert_utils.h @@ -0,0 +1,50 @@ +/****************************************************************** + * + * Copyright 2017 Samsung Electronics All Rights Reserved. + * + * Author: Jaroslaw Pelczar + * + * 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 +#include + +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_ */