certificate.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. /**
  2. * Copyright (c) 2019 Paul-Louis Ageneau
  3. *
  4. * This Source Code Form is subject to the terms of the Mozilla Public
  5. * License, v. 2.0. If a copy of the MPL was not distributed with this
  6. * file, You can obtain one at https://mozilla.org/MPL/2.0/.
  7. */
  8. #include "certificate.hpp"
  9. #include "threadpool.hpp"
  10. #include <cassert>
  11. #include <chrono>
  12. #include <iomanip>
  13. #include <mutex>
  14. #include <sstream>
  15. #include <unordered_map>
  16. namespace rtc::impl {
  17. #if USE_GNUTLS
  18. Certificate Certificate::FromString(string crt_pem, string key_pem) {
  19. PLOG_DEBUG << "Importing certificate from PEM string (GnuTLS)";
  20. shared_ptr<gnutls_certificate_credentials_t> creds(gnutls::new_credentials(),
  21. gnutls::free_credentials);
  22. gnutls_datum_t crt_datum = gnutls::make_datum(crt_pem.data(), crt_pem.size());
  23. gnutls_datum_t key_datum = gnutls::make_datum(key_pem.data(), key_pem.size());
  24. gnutls::check(
  25. gnutls_certificate_set_x509_key_mem(*creds, &crt_datum, &key_datum, GNUTLS_X509_FMT_PEM),
  26. "Unable to import PEM certificate and key");
  27. return Certificate(std::move(creds));
  28. }
  29. Certificate Certificate::FromFile(const string &crt_pem_file, const string &key_pem_file,
  30. const string &pass) {
  31. PLOG_DEBUG << "Importing certificate from PEM file (GnuTLS): " << crt_pem_file;
  32. shared_ptr<gnutls_certificate_credentials_t> creds(gnutls::new_credentials(),
  33. gnutls::free_credentials);
  34. gnutls::check(gnutls_certificate_set_x509_key_file2(*creds, crt_pem_file.c_str(),
  35. key_pem_file.c_str(), GNUTLS_X509_FMT_PEM,
  36. pass.c_str(), 0),
  37. "Unable to import PEM certificate and key from file");
  38. return Certificate(std::move(creds));
  39. }
  40. Certificate Certificate::Generate(CertificateType type, const string &commonName) {
  41. PLOG_DEBUG << "Generating certificate (GnuTLS)";
  42. using namespace gnutls;
  43. unique_ptr<gnutls_x509_crt_t, decltype(&free_crt)> crt(new_crt(), free_crt);
  44. unique_ptr<gnutls_x509_privkey_t, decltype(&free_privkey)> privkey(new_privkey(), free_privkey);
  45. switch (type) {
  46. // RFC 8827 WebRTC Security Architecture 6.5. Communications Security
  47. // All implementations MUST support DTLS 1.2 with the TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
  48. // cipher suite and the P-256 curve
  49. // See https://www.rfc-editor.org/rfc/rfc8827.html#section-6.5
  50. case CertificateType::Default:
  51. case CertificateType::Ecdsa: {
  52. gnutls::check(gnutls_x509_privkey_generate(*privkey, GNUTLS_PK_ECDSA,
  53. GNUTLS_CURVE_TO_BITS(GNUTLS_ECC_CURVE_SECP256R1),
  54. 0),
  55. "Unable to generate ECDSA P-256 key pair");
  56. break;
  57. }
  58. case CertificateType::Rsa: {
  59. const unsigned int bits = 2048;
  60. gnutls::check(gnutls_x509_privkey_generate(*privkey, GNUTLS_PK_RSA, bits, 0),
  61. "Unable to generate RSA key pair");
  62. break;
  63. }
  64. default:
  65. throw std::invalid_argument("Unknown certificate type");
  66. }
  67. using namespace std::chrono;
  68. auto now = time_point_cast<seconds>(system_clock::now());
  69. gnutls_x509_crt_set_activation_time(*crt, (now - hours(1)).time_since_epoch().count());
  70. gnutls_x509_crt_set_expiration_time(*crt, (now + hours(24 * 365)).time_since_epoch().count());
  71. gnutls_x509_crt_set_version(*crt, 1);
  72. gnutls_x509_crt_set_key(*crt, *privkey);
  73. gnutls_x509_crt_set_dn_by_oid(*crt, GNUTLS_OID_X520_COMMON_NAME, 0, commonName.data(),
  74. commonName.size());
  75. const size_t serialSize = 16;
  76. char serial[serialSize];
  77. gnutls_rnd(GNUTLS_RND_NONCE, serial, serialSize);
  78. gnutls_x509_crt_set_serial(*crt, serial, serialSize);
  79. gnutls::check(gnutls_x509_crt_sign2(*crt, *crt, *privkey, GNUTLS_DIG_SHA256, 0),
  80. "Unable to auto-sign certificate");
  81. return Certificate(*crt, *privkey);
  82. }
  83. Certificate::Certificate(gnutls_x509_crt_t crt, gnutls_x509_privkey_t privkey)
  84. : mCredentials(gnutls::new_credentials(), gnutls::free_credentials),
  85. mFingerprint(make_fingerprint(crt)) {
  86. gnutls::check(gnutls_certificate_set_x509_key(*mCredentials, &crt, 1, privkey),
  87. "Unable to set certificate and key pair in credentials");
  88. }
  89. Certificate::Certificate(shared_ptr<gnutls_certificate_credentials_t> creds)
  90. : mCredentials(std::move(creds)), mFingerprint(make_fingerprint(*mCredentials)) {}
  91. gnutls_certificate_credentials_t Certificate::credentials() const { return *mCredentials; }
  92. string make_fingerprint(gnutls_certificate_credentials_t credentials) {
  93. auto new_crt_list = [credentials]() -> gnutls_x509_crt_t * {
  94. gnutls_x509_crt_t *crt_list = nullptr;
  95. unsigned int crt_list_size = 0;
  96. gnutls::check(gnutls_certificate_get_x509_crt(credentials, 0, &crt_list, &crt_list_size));
  97. assert(crt_list_size == 1);
  98. return crt_list;
  99. };
  100. auto free_crt_list = [](gnutls_x509_crt_t *crt_list) {
  101. gnutls_x509_crt_deinit(crt_list[0]);
  102. gnutls_free(crt_list);
  103. };
  104. unique_ptr<gnutls_x509_crt_t, decltype(free_crt_list)> crt_list(new_crt_list(), free_crt_list);
  105. return make_fingerprint(*crt_list);
  106. }
  107. string make_fingerprint(gnutls_x509_crt_t crt) {
  108. const size_t size = 32;
  109. unsigned char buffer[size];
  110. size_t len = size;
  111. gnutls::check(gnutls_x509_crt_get_fingerprint(crt, GNUTLS_DIG_SHA256, buffer, &len),
  112. "X509 fingerprint error");
  113. std::ostringstream oss;
  114. oss << std::hex << std::uppercase << std::setfill('0');
  115. for (size_t i = 0; i < len; ++i) {
  116. if (i)
  117. oss << std::setw(1) << ':';
  118. oss << std::setw(2) << unsigned(buffer[i]);
  119. }
  120. return oss.str();
  121. }
  122. #elif USE_MBEDTLS
  123. string make_fingerprint(shared_ptr<mbedtls_x509_crt> crt) {
  124. const int size = 32;
  125. uint8_t buffer[size];
  126. std::stringstream fingerprint;
  127. mbedtls::check(
  128. mbedtls_sha256(crt->raw.p, crt->raw.len, reinterpret_cast<unsigned char *>(buffer), 0),
  129. "Failed to generate certificate fingerprint");
  130. for (auto i = 0; i < size; i++) {
  131. fingerprint << std::setfill('0') << std::setw(2) << std::hex << static_cast<int>(buffer[i]);
  132. if (i != (size - 1)) {
  133. fingerprint << ":";
  134. }
  135. }
  136. return fingerprint.str();
  137. }
  138. Certificate::Certificate(shared_ptr<mbedtls_x509_crt> crt, shared_ptr<mbedtls_pk_context> pk)
  139. : mCrt(crt), mPk(pk), mFingerprint(make_fingerprint(crt)) {}
  140. Certificate Certificate::FromString(string crt_pem, string key_pem) {
  141. PLOG_DEBUG << "Importing certificate from PEM string (MbedTLS)";
  142. auto crt = mbedtls::new_x509_crt();
  143. auto pk = mbedtls::new_pk_context();
  144. mbedtls::check(mbedtls_x509_crt_parse(crt.get(),
  145. reinterpret_cast<const unsigned char *>(crt_pem.c_str()),
  146. crt_pem.length()),
  147. "Failed to parse certificate");
  148. mbedtls::check(mbedtls_pk_parse_key(pk.get(),
  149. reinterpret_cast<const unsigned char *>(key_pem.c_str()),
  150. key_pem.size(), NULL, 0, NULL, 0),
  151. "Failed to parse key");
  152. return Certificate(std::move(crt), std::move(pk));
  153. }
  154. Certificate Certificate::FromFile(const string &crt_pem_file, const string &key_pem_file,
  155. const string &pass) {
  156. PLOG_DEBUG << "Importing certificate from PEM file (MbedTLS): " << crt_pem_file;
  157. auto crt = mbedtls::new_x509_crt();
  158. auto pk = mbedtls::new_pk_context();
  159. mbedtls::check(mbedtls_x509_crt_parse_file(crt.get(), crt_pem_file.c_str()),
  160. "Failed to parse certificate");
  161. mbedtls::check(mbedtls_pk_parse_keyfile(pk.get(), key_pem_file.c_str(), pass.c_str(), 0, NULL),
  162. "Failed to parse key");
  163. return Certificate(std::move(crt), std::move(pk));
  164. }
  165. Certificate Certificate::Generate(CertificateType type, const string &commonName) {
  166. PLOG_DEBUG << "Generating certificate (MbedTLS)";
  167. mbedtls_entropy_context entropy;
  168. mbedtls_ctr_drbg_context drbg;
  169. mbedtls_x509write_cert wcrt;
  170. mbedtls_mpi serial;
  171. auto crt = mbedtls::new_x509_crt();
  172. auto pk = mbedtls::new_pk_context();
  173. mbedtls_entropy_init(&entropy);
  174. mbedtls_ctr_drbg_init(&drbg);
  175. mbedtls_ctr_drbg_set_prediction_resistance(&drbg, MBEDTLS_CTR_DRBG_PR_ON);
  176. mbedtls_x509write_crt_init(&wcrt);
  177. mbedtls_mpi_init(&serial);
  178. try {
  179. mbedtls::check(mbedtls_ctr_drbg_seed(
  180. &drbg, mbedtls_entropy_func, &entropy,
  181. reinterpret_cast<const unsigned char *>(commonName.data()), commonName.size()));
  182. switch (type) {
  183. // RFC 8827 WebRTC Security Architecture 6.5. Communications Security
  184. // All implementations MUST support DTLS 1.2 with the
  185. // TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 cipher suite and the P-256 curve
  186. // See https://www.rfc-editor.org/rfc/rfc8827.html#section-6.5
  187. case CertificateType::Default:
  188. case CertificateType::Ecdsa: {
  189. mbedtls::check(mbedtls_pk_setup(pk.get(), mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY)));
  190. mbedtls::check(mbedtls_ecp_gen_key(MBEDTLS_ECP_DP_SECP256R1, mbedtls_pk_ec(*pk.get()),
  191. mbedtls_ctr_drbg_random, &drbg),
  192. "Unable to generate ECDSA P-256 key pair");
  193. break;
  194. }
  195. case CertificateType::Rsa: {
  196. const unsigned int nbits = 2048;
  197. const int exponent = 65537;
  198. mbedtls::check(mbedtls_pk_setup(pk.get(), mbedtls_pk_info_from_type(MBEDTLS_PK_RSA)));
  199. mbedtls::check(mbedtls_rsa_gen_key(mbedtls_pk_rsa(*pk.get()), mbedtls_ctr_drbg_random,
  200. &drbg, nbits, exponent),
  201. "Unable to generate RSA key pair");
  202. break;
  203. }
  204. default:
  205. throw std::invalid_argument("Unknown certificate type");
  206. }
  207. auto now = std::chrono::system_clock::now();
  208. string notBefore = mbedtls::format_time(now - std::chrono::hours(1));
  209. string notAfter = mbedtls::format_time(now + std::chrono::hours(24 * 365));
  210. const size_t serialBufferSize = 16;
  211. unsigned char serialBuffer[serialBufferSize];
  212. mbedtls::check(mbedtls_ctr_drbg_random(&drbg, serialBuffer, serialBufferSize),
  213. "Failed to generate certificate");
  214. mbedtls::check(mbedtls_mpi_read_binary(&serial, serialBuffer, serialBufferSize),
  215. "Failed to generate certificate");
  216. std::string name = std::string("O=" + commonName + ",CN=" + commonName);
  217. mbedtls::check(mbedtls_x509write_crt_set_serial(&wcrt, &serial),
  218. "Failed to generate certificate");
  219. mbedtls::check(mbedtls_x509write_crt_set_subject_name(&wcrt, name.c_str()),
  220. "Failed to generate certificate");
  221. mbedtls::check(mbedtls_x509write_crt_set_issuer_name(&wcrt, name.c_str()),
  222. "Failed to generate certificate");
  223. mbedtls::check(
  224. mbedtls_x509write_crt_set_validity(&wcrt, notBefore.c_str(), notAfter.c_str()),
  225. "Failed to generate certificate");
  226. mbedtls_x509write_crt_set_version(&wcrt, MBEDTLS_X509_CRT_VERSION_3);
  227. mbedtls_x509write_crt_set_subject_key(&wcrt, pk.get());
  228. mbedtls_x509write_crt_set_issuer_key(&wcrt, pk.get());
  229. mbedtls_x509write_crt_set_md_alg(&wcrt, MBEDTLS_MD_SHA256);
  230. const size_t certificateBufferSize = 4096;
  231. unsigned char certificateBuffer[certificateBufferSize];
  232. std::memset(certificateBuffer, 0, certificateBufferSize);
  233. auto certificateLen = mbedtls_x509write_crt_der(
  234. &wcrt, certificateBuffer, certificateBufferSize, mbedtls_ctr_drbg_random, &drbg);
  235. if (certificateLen <= 0) {
  236. throw std::runtime_error("Certificate generation failed");
  237. }
  238. mbedtls::check(mbedtls_x509_crt_parse_der(
  239. crt.get(), (certificateBuffer + certificateBufferSize - certificateLen),
  240. certificateLen),
  241. "Failed to generate certificate");
  242. } catch (...) {
  243. mbedtls_entropy_free(&entropy);
  244. mbedtls_ctr_drbg_free(&drbg);
  245. mbedtls_x509write_crt_free(&wcrt);
  246. mbedtls_mpi_free(&serial);
  247. throw;
  248. }
  249. mbedtls_entropy_free(&entropy);
  250. mbedtls_ctr_drbg_free(&drbg);
  251. mbedtls_x509write_crt_free(&wcrt);
  252. mbedtls_mpi_free(&serial);
  253. return Certificate(std::move(crt), std::move(pk));
  254. }
  255. std::tuple<shared_ptr<mbedtls_x509_crt>, shared_ptr<mbedtls_pk_context>>
  256. Certificate::credentials() const {
  257. return {mCrt, mPk};
  258. }
  259. #else // OPENSSL
  260. namespace {
  261. // Dummy password callback that copies the password from user data
  262. int dummy_pass_cb(char *buf, int size, int /*rwflag*/, void *u) {
  263. const char *pass = static_cast<char *>(u);
  264. return snprintf(buf, size, "%s", pass);
  265. }
  266. } // namespace
  267. Certificate Certificate::FromString(string crt_pem, string key_pem) {
  268. PLOG_DEBUG << "Importing certificate from PEM string (OpenSSL)";
  269. BIO *bio = BIO_new(BIO_s_mem());
  270. BIO_write(bio, crt_pem.data(), int(crt_pem.size()));
  271. auto x509 = shared_ptr<X509>(PEM_read_bio_X509(bio, nullptr, nullptr, nullptr), X509_free);
  272. BIO_free(bio);
  273. if (!x509)
  274. throw std::invalid_argument("Unable to import PEM certificate");
  275. bio = BIO_new(BIO_s_mem());
  276. BIO_write(bio, key_pem.data(), int(key_pem.size()));
  277. auto pkey = shared_ptr<EVP_PKEY>(PEM_read_bio_PrivateKey(bio, nullptr, nullptr, nullptr),
  278. EVP_PKEY_free);
  279. BIO_free(bio);
  280. if (!pkey)
  281. throw std::invalid_argument("Unable to import PEM key");
  282. return Certificate(x509, pkey);
  283. }
  284. Certificate Certificate::FromFile(const string &crt_pem_file, const string &key_pem_file,
  285. const string &pass) {
  286. PLOG_DEBUG << "Importing certificate from PEM file (OpenSSL): " << crt_pem_file;
  287. BIO *bio = openssl::BIO_new_from_file(crt_pem_file);
  288. if (!bio)
  289. throw std::invalid_argument("Unable to open PEM certificate file");
  290. auto x509 = shared_ptr<X509>(PEM_read_bio_X509(bio, nullptr, nullptr, nullptr), X509_free);
  291. BIO_free(bio);
  292. if (!x509)
  293. throw std::invalid_argument("Unable to import PEM certificate from file");
  294. bio = openssl::BIO_new_from_file(key_pem_file);
  295. if (!bio)
  296. throw std::invalid_argument("Unable to open PEM key file");
  297. auto pkey = shared_ptr<EVP_PKEY>(
  298. PEM_read_bio_PrivateKey(bio, nullptr, dummy_pass_cb, const_cast<char *>(pass.c_str())),
  299. EVP_PKEY_free);
  300. BIO_free(bio);
  301. if (!pkey)
  302. throw std::invalid_argument("Unable to import PEM key from file");
  303. return Certificate(x509, pkey);
  304. }
  305. Certificate Certificate::Generate(CertificateType type, const string &commonName) {
  306. PLOG_DEBUG << "Generating certificate (OpenSSL)";
  307. shared_ptr<X509> x509(X509_new(), X509_free);
  308. shared_ptr<EVP_PKEY> pkey(EVP_PKEY_new(), EVP_PKEY_free);
  309. unique_ptr<BIGNUM, decltype(&BN_free)> serial_number(BN_new(), BN_free);
  310. unique_ptr<X509_NAME, decltype(&X509_NAME_free)> name(X509_NAME_new(), X509_NAME_free);
  311. if (!x509 || !pkey || !serial_number || !name)
  312. throw std::runtime_error("Unable to allocate structures for certificate generation");
  313. switch (type) {
  314. // RFC 8827 WebRTC Security Architecture 6.5. Communications Security
  315. // All implementations MUST support DTLS 1.2 with the TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
  316. // cipher suite and the P-256 curve
  317. // See https://www.rfc-editor.org/rfc/rfc8827.html#section-6.5
  318. case CertificateType::Default:
  319. case CertificateType::Ecdsa: {
  320. PLOG_VERBOSE << "Generating ECDSA P-256 key pair";
  321. unique_ptr<EC_KEY, decltype(&EC_KEY_free)> ecc(
  322. EC_KEY_new_by_curve_name(NID_X9_62_prime256v1), EC_KEY_free);
  323. if (!ecc)
  324. throw std::runtime_error("Unable to allocate structure for ECDSA P-256 key pair");
  325. EC_KEY_set_asn1_flag(ecc.get(), OPENSSL_EC_NAMED_CURVE); // Set ASN1 OID
  326. if (!EC_KEY_generate_key(ecc.get()) ||
  327. !EVP_PKEY_assign_EC_KEY(pkey.get(),
  328. ecc.release())) // the key will be freed when pkey is freed
  329. throw std::runtime_error("Unable to generate ECDSA P-256 key pair");
  330. break;
  331. }
  332. case CertificateType::Rsa: {
  333. PLOG_VERBOSE << "Generating RSA key pair";
  334. const int bits = 2048;
  335. const unsigned int e = 65537; // 2^16 + 1
  336. unique_ptr<RSA, decltype(&RSA_free)> rsa(RSA_new(), RSA_free);
  337. unique_ptr<BIGNUM, decltype(&BN_free)> exponent(BN_new(), BN_free);
  338. if (!rsa || !exponent)
  339. throw std::runtime_error("Unable to allocate structures for RSA key pair");
  340. if (!BN_set_word(exponent.get(), e) ||
  341. !RSA_generate_key_ex(rsa.get(), bits, exponent.get(), NULL) ||
  342. !EVP_PKEY_assign_RSA(pkey.get(),
  343. rsa.release())) // the key will be freed when pkey is freed
  344. throw std::runtime_error("Unable to generate RSA key pair");
  345. break;
  346. }
  347. default:
  348. throw std::invalid_argument("Unknown certificate type");
  349. }
  350. const size_t serialSize = 16;
  351. auto *commonNameBytes =
  352. reinterpret_cast<unsigned char *>(const_cast<char *>(commonName.c_str()));
  353. if (!X509_set_pubkey(x509.get(), pkey.get()))
  354. throw std::runtime_error("Unable to set certificate public key");
  355. if (!X509_gmtime_adj(X509_getm_notBefore(x509.get()), 3600 * -1) ||
  356. !X509_gmtime_adj(X509_getm_notAfter(x509.get()), 3600 * 24 * 365) ||
  357. !X509_set_version(x509.get(), 1) ||
  358. !BN_pseudo_rand(serial_number.get(), serialSize, 0, 0) ||
  359. !BN_to_ASN1_INTEGER(serial_number.get(), X509_get_serialNumber(x509.get())) ||
  360. !X509_NAME_add_entry_by_NID(name.get(), NID_commonName, MBSTRING_UTF8, commonNameBytes, -1,
  361. -1, 0) ||
  362. !X509_set_subject_name(x509.get(), name.get()) ||
  363. !X509_set_issuer_name(x509.get(), name.get()))
  364. throw std::runtime_error("Unable to set certificate properties");
  365. if (!X509_sign(x509.get(), pkey.get(), EVP_sha256()))
  366. throw std::runtime_error("Unable to auto-sign certificate");
  367. return Certificate(x509, pkey);
  368. }
  369. Certificate::Certificate(shared_ptr<X509> x509, shared_ptr<EVP_PKEY> pkey)
  370. : mX509(std::move(x509)), mPKey(std::move(pkey)), mFingerprint(make_fingerprint(mX509.get())) {}
  371. std::tuple<X509 *, EVP_PKEY *> Certificate::credentials() const {
  372. return {mX509.get(), mPKey.get()};
  373. }
  374. string make_fingerprint(X509 *x509) {
  375. const size_t size = 32;
  376. unsigned char buffer[size];
  377. unsigned int len = size;
  378. if (!X509_digest(x509, EVP_sha256(), buffer, &len))
  379. throw std::runtime_error("X509 fingerprint error");
  380. std::ostringstream oss;
  381. oss << std::hex << std::uppercase << std::setfill('0');
  382. for (size_t i = 0; i < len; ++i) {
  383. if (i)
  384. oss << std::setw(1) << ':';
  385. oss << std::setw(2) << unsigned(buffer[i]);
  386. }
  387. return oss.str();
  388. }
  389. #endif
  390. // Common for GnuTLS, Mbed TLS, and OpenSSL
  391. future_certificate_ptr make_certificate(CertificateType type) {
  392. return ThreadPool::Instance().enqueue([type, token = Init::Instance().token()]() {
  393. return std::make_shared<Certificate>(Certificate::Generate(type, "libdatachannel"));
  394. });
  395. }
  396. string Certificate::fingerprint() const { return mFingerprint; }
  397. } // namespace rtc::impl