使用 mbedTLS 从服务器中提取 public 证书链并存储为字符串

Use mbedTLS to pull public certificate chain from a server and store as a string

您好,提前致谢。

我是 mbedtls 的新手。以下使用 openssl 会非常简单,但是我不知道从哪里开始。

我需要从 https://example.com 这样的服务器捕获证书链作为类似于下面字符串示例的字符串。格式化应该是最简单的部分,我只是不知道到底要用什么。我假设 x509 模块。

"-----BEGIN CERTIFICATE-----\n"
    "MIIGrTCCBZWgAwIBAgIQBFkU5B02DI8ZdB9c2V/1CzANBgkqhkiG9w0BAQsFADBc\n"
    "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"
    "d3cuZGlnaWNlcnQuY29tMRswGQYDVQQDExJUaGF3dGUgUlNBIENBIDIwMTgwHhcN\n"
    "MTgwNjAxMDAwMDAwWhcNMjAwNTMxMTIwMDAwWjCBnDELMAkGA1UEBhMCVVMxEDAO\n"
    "BgNVBAgTB0Zsb3JpZGExGjAYBgNVBAcTEUFsdGFtb250ZSBTcHJpbmdzMSkwJwYD\n"
    "VQQKEyBCcmlkZ2VwYXkgTmV0d29yayBTb2x1dGlvbnMsIExMQzELMAkGA1UECxMC\n"
    "SVQxJzAlBgNVBAMTHnBnYy5icmlkZ2VwYXluZXRzZWN1cmV0ZXN0LmNvbTCCASIw\n"
    "DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOgeLoYsgYB2EVkox4YKBaeiUg93\n"
    "05S+BV1mj52hY4SA4a+Uv2bGPA34uryUyTzBBzs9V40ugmybgpRiEe7ImaVBRRvK\n"
    "VEftTC2qi5o9y/HySIgHlSyruVFUGGweCz6v32A/4WRXrYMubTcxDPb8eZSz1QKm\n"
    "pTSjHoAeCftlclwAHSvATz78whhEhbpQudYGBsRjqZVdeEcClGP3ukQuDAdZwjqh\n"
    "OAUKH2THEXtvtJYWyyWZSGJm4/FMZnhNRqQKFaf5Pz4rxvM3bNOqzTqj4BmA9def\n"
    "5tcDwblTUBpZ37M0rNJfSebnSc/XrR9Urc1vkJugFAykYptwNoYWhQJ+x2sCAwEA\n"
    "AaOCAygwggMkMB8GA1UdIwQYMBaAFKPIXmVU5TB4wQXqBwpqWcy5/t5aMB0GA1Ud\n"
    "DgQWBBQGXyzHHolmD/seY+LpqVf3ozFmITApBgNVHREEIjAggh5wZ2MuYnJpZGdl\n"
    "cGF5bmV0c2VjdXJldGVzdC5jb20wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQG\n"
    "CCsGAQUFBwMBBggrBgEFBQcDAjA6BgNVHR8EMzAxMC+gLaArhilodHRwOi8vY2Rw\n"
    "LnRoYXd0ZS5jb20vVGhhd3RlUlNBQ0EyMDE4LmNybDBMBgNVHSAERTBDMDcGCWCG\n"
    "SAGG/WwBATAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20v\n"
    "Q1BTMAgGBmeBDAECAjBvBggrBgEFBQcBAQRjMGEwJAYIKwYBBQUHMAGGGGh0dHA6\n"
    "Ly9zdGF0dXMudGhhd3RlLmNvbTA5BggrBgEFBQcwAoYtaHR0cDovL2NhY2VydHMu\n"
    "dGhhd3RlLmNvbS9UaGF3dGVSU0FDQTIwMTguY3J0MAkGA1UdEwQCMAAwggGABgor\n"
    "BgEEAdZ5AgQCBIIBcASCAWwBagB3AKS5CZC0GFgUh7sTosxncAo8NZgE+RvfuON3\n"
    "zQ7IDdwQAAABY7v2qJEAAAQDAEgwRgIhAM5Tsrb1lqzE4D4LtQqmWO9lCH7XzyEb\n"
    "2qvqldqmhxoEAiEA2tBlvXiMI0WysQqhF7RUipBF4YMdsyjMVZ9tLv9uyTsAdwCH\n"
    "db/nWXz4jEOZX73zbv9WjUdWNv9KtWDBtOr/XqCDDwAAAWO79qk2AAAEAwBIMEYC\n"
    "IQDB8ZyF+GuzBzihgkJgUxVcZ4YjntDmyVURrqLp8aycwQIhAPPOCZG8ZwIfY2up\n"
    "Y4DjH2QhlRIjE0rsEPUhMEi+EtbeAHYAu9nfvB+KcbWTlCOXqpJ7RzhXlQqrUuga\n"
    "kJZkNo4e0YUAAAFju/apgQAABAMARzBFAiEA8Z6N8cb1D6XPsu9fcPIvfjuLDyYJ\n"
    "KlpY3GRvVxq+jsMCIE+zdbBSEcc4SkIWTx/vvj8THcaMVX/OKWrqCKVuVHWbMA0G\n"
    "CSqGSIb3DQEBCwUAA4IBAQBmQt8QfGKW8/c+o6fZvBAWtwgPnitKgiWvBwIvMlYr\n"
    "6teFYkRR0qe+vQBWcHF/ax5VyDFHH/MjZLqCzoR0VJKBz1uNTXDYYgfwrwy9EFPt\n"
    "s9bFiZerIZwBHO55HmpWpvmrT6V178gJOFTGppUbwxuwHWan8075Q2MfVZpuP/kw\n"
    "0BYJxeFC09tdgz5CiWRJMsAVvYbqr2Dkdrc1IAERQ782qTMbwujCvojpKmIt5w16\n"
    "UfUY02ICqQ3XgXU/iwMSb3XpnEvP6BIliMgdyW8wW493dEpbZs1igWSct8U1f5bH\n"
    "YVluRgq/O02MQZgmu6tDXFVd9X9NY/TBwtjfiQ35Vmn1\n"
    "-----END CERTIFICATE-----\n"
    "-----BEGIN CERTIFICATE-----\n"
    "MIIEiTCCA3GgAwIBAgIQAlqK7xlvfg1sIQSyGuZwKzANBgkqhkiG9w0BAQsFADBh\n"
    "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"
    "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n"
    "QTAeFw0xNzExMDYxMjIzNTJaFw0yNzExMDYxMjIzNTJaMFwxCzAJBgNVBAYTAlVT\n"
    "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n"
    "b20xGzAZBgNVBAMTElRoYXd0ZSBSU0EgQ0EgMjAxODCCASIwDQYJKoZIhvcNAQEB\n"
    "BQADggEPADCCAQoCggEBAMoIXuVTipccHkMvtoqnVumLhEOorJ16VYJ6FEuGty+P\n"
    "Up8cyrEgW2+6It2mnC142ukGCE6+E6bry7s+uQUMPkrh8DIfE071BsVHc4k+gKOL\n"
    "8QEkm6OZZpJraK0NLbTNcqL0+ThaZaa0jFPBCBqE+P0u8xF1btxqMSmsDYfMk2B4\n"
    "3yW6JlmRxoNSNabKnLgoGs7XHO4Uv3ZcZas4HnnpfMxJIyaiUlBm0Flh/6D+mkwM\n"
    "n/nojt4Ji7gVwaQITCacewbb/Yp0W1h+zWOkkS9F8Ho8lAuKfLIFqWeTn2jllWNg\n"
    "2FiVX+BV75OnETt85pLYZkTgq72nj82khXhBJFTn2AMCAwEAAaOCAUAwggE8MB0G\n"
    "A1UdDgQWBBSjyF5lVOUweMEF6gcKalnMuf7eWjAfBgNVHSMEGDAWgBQD3lA1VtFM\n"
    "u2bwo+IbG8OXsj3RVTAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUH\n"
    "AwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8CAQAwNAYIKwYBBQUHAQEEKDAm\n"
    "MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQgYDVR0fBDsw\n"
    "OTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFs\n"
    "Um9vdENBLmNybDA9BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYcaHR0\n"
    "cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzANBgkqhkiG9w0BAQsFAAOCAQEARE2F\n"
    "5d0cgozhZNWokCLfdhhl6mXSOyU3SoPamYcWfLH1CzMwD8a1+pFvwHIQfvlwXFH8\n"
    "MrjB3C+jVobNbVWRrgqS3Jsa0ltRH/Ffs6ZTgP4WJYm1SNpUbgR7LWUD2F+PTvKB\n"
    "M/gf9eSyqP4OiJslYaa38NU1aVAxZI15o+4xX4RZMqKXIIBTG2V+oPBjQ1oPmHGA\n"
    "C/yWt2eThvb8/re7OpSpUdJyfGf97XeM4PiJAl6+4HQXhjwN7ZPZKrQv9Ay33Mgm\n"
    "YLVQA+x9HONZXx9vvy8pl9bu+NVYWKGxzGxBK0CBozmVUCeXQPJKPTZleYuNM18p\n"
    "U1P8Xh1CDguM+ZEoew==\n"
    "-----END CERTIFICATE-----\n"
    "-----BEGIN CERTIFICATE-----\n"
    "MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\n"
    "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"
    "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n"
    "QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\n"
    "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n"
    "b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n"
    "9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\n"
    "CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\n"
    "nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n"
    "43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\n"
    "T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\n"
    "gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\n"
    "BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\n"
    "TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\n"
    "DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\n"
    "hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n"
    "06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\n"
    "PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\n"
    "YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\n"
    "CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n"
    "-----END CERTIFICATE-----";

简短版本:mbedtls 库表示具有 mbedtls_x509_crt 结构 (reference). This struct has a next member. Certificate chains are represented as a linked list of mbedtls_x509_crt entries, where next points to the next entry in the chain or is nullptr for the last entry in the chain. For an active SSL session, you can query the first entry in the certificate chain of the peer with mbedtls_ssl_get_peer_cert (reference). You can then iterate over the certificate chain and print each certificate with mbedtls_pem_write_buffer (reference) 的证书。

作为参考,我将首先举例说明如何使用 openssl 打印证书链(主要是为了确保我们都同意“简单”的解决方案是什么):

  STACK_OF(X509) *certs = SSL_get_peer_cert_chain(ssl);
  for (int i = 0; i < sk_X509_num(certs); i++) {
    PEM_write_X509(stdout, sk_X509_value(certs, i));
  }

mbedtls 的等效代码为:

const mbedtls_x509_crt *chain;

chain = mbedtls_ssl_get_peer_cert(&ssl);

while (chain != nullptr) {
  std::cout << cert_to_pem(chain) << std::endl;
  chain = chain->next;
}

(其中 cert_to_pem 是用于序列化证书的自定义辅助函数,请参阅下面的完整代码以了解如何实现它。)

这是一个完整的工作示例(基于给定的 SSL 客户端示例 here):

#include "mbedtls/certs.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/debug.h"
#include "mbedtls/entropy.h"
#include "mbedtls/error.h"
#include "mbedtls/net_sockets.h"
#include "mbedtls/pem.h"
#include "mbedtls/platform.h"
#include "mbedtls/ssl.h"

#include <array>
#include <iostream>
#include <vector>

/**
 * Exception for printing mbedtls error codes.
 */
struct MbedtlsError : public std::runtime_error {
  MbedtlsError(int err)
      : std::runtime_error{[=]() {
          constexpr size_t buffer_size{1024};
          std::string buffer{};
          buffer.resize(buffer_size);
          mbedtls_strerror(err, buffer.data(), buffer.size());
          return buffer;
        }()} {}
};

/**
 * Helper function for serializing certificates into PEM.
 *
 * @param crt the certificate to be encoded as PEM
 * @returns the pem encoding of the certificate
 */
std::string cert_to_pem(const mbedtls_x509_crt *crt) {
  constexpr auto pem_begin_crt{"-----BEGIN CERTIFICATE-----\n"};
  constexpr auto pem_end_crt{"-----END CERTIFICATE-----"};
  constexpr size_t buffer_size{4096};

  std::string buffer{};
  size_t written{};

  buffer.resize(buffer_size);
  if (int ret = mbedtls_pem_write_buffer(
          pem_begin_crt, pem_end_crt, crt->raw.p, crt->raw.len,
          (unsigned char *)buffer.data(), buffer.size(), &written);
      ret != 0) {
    throw MbedtlsError{ret};
  }
  buffer.resize(written);

  return buffer;
}

/**
 * RAII helper for the mbedtls CTR_DRBG random-number generator.
 */
struct Random {

  Random(const std::vector<uint8_t> &seed) {
    mbedtls_entropy_init(&entropy);
    mbedtls_ctr_drbg_init(&ctr_drbg);

    if (int ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func,
                                        &entropy, seed.data(), seed.size());
        ret != 0) {
      throw MbedtlsError{ret};
    }
  }

  ~Random() {
    mbedtls_ctr_drbg_free(&ctr_drbg);
    mbedtls_entropy_free(&entropy);
  }

  mbedtls_entropy_context entropy{};
  mbedtls_ctr_drbg_context ctr_drbg{};
};

/**
 * RAII helper for the mbedtls socket connection.
 */
struct Connection {

  Connection(const std::string &hostname, uint16_t port) : hostname_{hostname} {
    mbedtls_net_init(&fd_);

    if (int ret = mbedtls_net_connect(&fd_, hostname_.c_str(),
                                      std::to_string(port).c_str(),
                                      MBEDTLS_NET_PROTO_TCP);
        ret != 0) {
      throw MbedtlsError{ret};
    }
  }

  ~Connection() { mbedtls_net_free(&fd_); }

  const std::string hostname_{};
  mbedtls_net_context fd_{};
};

/**
 * RAII helper for the mbedtls ssl layer.
 */
struct Ssl {

  Ssl(Random &random, Connection &connection) {
    mbedtls_ssl_config_init(&conf_);
    mbedtls_ssl_init(&ssl_);

    if (int ret = mbedtls_ssl_config_defaults(&conf_, MBEDTLS_SSL_IS_CLIENT,
                                              MBEDTLS_SSL_TRANSPORT_STREAM,
                                              MBEDTLS_SSL_PRESET_DEFAULT);
        ret != 0) {
      throw MbedtlsError{ret};
    }

    // XXX Disable certificate verification for this example.
    mbedtls_ssl_conf_authmode(&conf_, MBEDTLS_SSL_VERIFY_OPTIONAL);

    mbedtls_ssl_conf_rng(&conf_, mbedtls_ctr_drbg_random, &random.ctr_drbg);

    if (int ret = mbedtls_ssl_setup(&ssl_, &conf_); ret != 0) {
      throw MbedtlsError{ret};
    }

    if (int ret = mbedtls_ssl_set_hostname(&ssl_, connection.hostname_.c_str());
        ret != 0) {
      throw MbedtlsError{ret};
    }

    mbedtls_ssl_set_bio(&ssl_, &connection.fd_, mbedtls_net_send,
                        mbedtls_net_recv, NULL);

    if (int ret = mbedtls_ssl_handshake(&ssl_); ret != 0) {
      throw MbedtlsError{ret};
    }
  }

  ~Ssl() {
    mbedtls_ssl_close_notify(&ssl_);
    mbedtls_ssl_free(&ssl_);
    mbedtls_ssl_config_free(&conf_);
  }

  mbedtls_ssl_config conf_{};
  mbedtls_ssl_context ssl_{};
};

int main(void) {

  // TODO Initialize seed from /dev/random or another secure source.
  std::vector<uint8_t> seed{};

  // Initialize SSL connection.
  Random random{seed};
  Connection connection{"example.com", 443};
  Ssl ssl{random, connection};

  // Dump certificate chain.
  const mbedtls_x509_crt *chain;

  chain = mbedtls_ssl_get_peer_cert(&ssl.ssl_);

  while (chain != nullptr) {
    std::cout << cert_to_pem(chain) << std::endl;
    chain = chain->next;
  }

  return EXIT_SUCCESS;
}

用法:

$ g++ -std=c++17 -Wall -Wextra -pedantic -lmbedcrypto -lmbedx509 -lmbedtls test.cpp
$ ./a.out 
-----BEGIN CERTIFICATE-----
MIIHQDCCBiigAwIBAgIQD9B43Ujxor1NDyupa2A4/jANBgkqhkiG9w0BAQsFADBN
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E
...