fizz/protocol/DefaultCertificateVerifier.cpp (137 lines of code) (raw):

/* * Copyright (c) 2018-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ #include <fizz/protocol/DefaultCertificateVerifier.h> #include <folly/FileUtil.h> #include <folly/ssl/OpenSSLCertUtils.h> namespace fizz { struct STACK_OF_X509_deleter { void operator()(STACK_OF(X509) * sk) { sk_X509_free(sk); } }; /* static */ std::unique_ptr<DefaultCertificateVerifier> DefaultCertificateVerifier::createFromCAFile( VerificationContext context, const std::string& caFile) { auto store = folly::ssl::OpenSSLCertUtils::readStoreFromFile(caFile); return std::make_unique<DefaultCertificateVerifier>( context, std::move(store)); } /* static */ std::unique_ptr<DefaultCertificateVerifier> DefaultCertificateVerifier::createFromCAFiles( VerificationContext context, const std::vector<std::string>& caFiles) { std::string certBuffer; for (const auto& caFile : caFiles) { std::string readBuffer; if (!folly::readFile(caFile.c_str(), readBuffer)) { throw std::runtime_error( folly::to<std::string>("Could not read store file: ", caFile)); } folly::toAppend(readBuffer, &certBuffer); } return std::make_unique<DefaultCertificateVerifier>( context, folly::ssl::OpenSSLCertUtils::readStoreFromBuffer( folly::StringPiece(certBuffer))); } void DefaultCertificateVerifier::verify( const std::vector<std::shared_ptr<const fizz::PeerCert>>& certs) const { if (certs.empty()) { throw std::runtime_error("no certificates to verify"); } auto leafCert = certs.front()->getX509(); auto certChainStack = std::unique_ptr<STACK_OF(X509), STACK_OF_X509_deleter>( sk_X509_new_null()); if (!certChainStack) { throw std::bad_alloc(); } for (size_t i = 1; i < certs.size(); i++) { sk_X509_push(certChainStack.get(), certs[i]->getX509().get()); } auto ctx = folly::ssl::X509StoreCtxUniquePtr(X509_STORE_CTX_new()); if (!ctx) { throw std::bad_alloc(); } if (X509_STORE_CTX_init( ctx.get(), x509Store_ ? x509Store_.get() : getDefaultX509Store(), leafCert.get(), certChainStack.get()) != 1) { throw std::runtime_error("failed to initialize store context"); } if (X509_STORE_CTX_set_default( ctx.get(), context_ == VerificationContext::Server ? "ssl_client" : "ssl_server") != 1) { throw std::runtime_error("failed to set default verification method"); } if (customVerifyCallback_) { X509_STORE_CTX_set_verify_cb(ctx.get(), customVerifyCallback_); } folly::ssl::X509VerifyParam param(X509_VERIFY_PARAM_new()); if (!param) { throw std::bad_alloc(); } if (X509_VERIFY_PARAM_set_flags(param.get(), X509_V_FLAG_X509_STRICT) != 1) { throw std::runtime_error("failed to set strict certificate checking"); } if (X509_VERIFY_PARAM_set1( X509_STORE_CTX_get0_param(ctx.get()), param.get()) != 1) { throw std::runtime_error("failed to apply verification parameters"); } int ret = 0; // if openssl is not built with TSAN then we can get a TSAN false positive // when calling X509_verify_cert from multiple threads { folly::annotate_ignore_thread_sanitizer_guard g(__FILE__, __LINE__); ret = X509_verify_cert(ctx.get()); } if (ret != 1) { const auto errorInt = X509_STORE_CTX_get_error(ctx.get()); std::string errorText = std::string(X509_verify_cert_error_string(errorInt)); throw std::runtime_error("certificate verification failed: " + errorText); } } void DefaultCertificateVerifier::createAuthorities() { CertificateAuthorities auth; X509_STORE* store = x509Store_ ? x509Store_.get() : getDefaultX509Store(); // X509_STORE stores CA certs as objects in this stack. STACK_OF(X509_OBJECT)* entries = X509_STORE_get0_objects(store); for (int i = 0; i < sk_X509_OBJECT_num(entries); i++) { X509_OBJECT* obj = sk_X509_OBJECT_value(entries, i); if (X509_OBJECT_get_type(obj) == X509_LU_X509) { auto certIssuer = X509_get_subject_name(X509_OBJECT_get0_X509(obj)); int dnLength = i2d_X509_NAME(certIssuer, nullptr); if (dnLength < 0) { throw std::runtime_error("Error computing DN length"); } DistinguishedName dn; dn.encoded_name = folly::IOBuf::create(dnLength); auto dnData = dn.encoded_name->writableData(); dnLength = i2d_X509_NAME(certIssuer, &dnData); if (dnLength < 0) { throw std::runtime_error("Error encoding DN in DER format"); } dn.encoded_name->append(dnLength); auth.authorities.push_back(std::move(dn)); } } authorities_ = std::move(auth); } X509_STORE* DefaultCertificateVerifier::getDefaultX509Store() { static folly::ssl::X509StoreUniquePtr defaultStore([]() { X509_STORE* store = X509_STORE_new(); if (!store) { throw std::bad_alloc(); } if (X509_STORE_set_default_paths(store) != 1) { throw std::runtime_error("failed to set default paths"); } return store; }()); return defaultStore.get(); } std::vector<Extension> DefaultCertificateVerifier::getCertificateRequestExtensions() const { std::vector<Extension> exts; exts.push_back(encodeExtension(authorities_)); return exts; } } // namespace fizz