| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you 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 "kudu/security/cert.h" |
| |
| #include <openssl/asn1.h> |
| #include <openssl/crypto.h> |
| #include <openssl/obj_mac.h> |
| #include <openssl/objects.h> |
| #include <openssl/ssl.h> |
| #include <openssl/x509.h> |
| #include <openssl/x509v3.h> |
| |
| #include <functional> |
| #include <memory> |
| #include <mutex> |
| #include <ostream> |
| #include <string> |
| |
| #include <boost/optional/optional.hpp> |
| #include <glog/logging.h> |
| |
| #include "kudu/gutil/macros.h" |
| #include "kudu/security/crypto.h" |
| #include "kudu/util/openssl_util.h" |
| #include "kudu/util/openssl_util_bio.h" |
| #include "kudu/util/status.h" |
| |
| using std::string; |
| using std::vector; |
| |
| namespace kudu { |
| namespace security { |
| |
| template<> struct SslTypeTraits<GENERAL_NAMES> { |
| static constexpr auto kFreeFunc = &GENERAL_NAMES_free; |
| }; |
| |
| // This OID is generated via the UUID method. |
| static const char* kKuduKerberosPrincipalOidStr = "2.25.243346677289068076843480765133256509912"; |
| |
| string X509NameToString(X509_NAME* name) { |
| SCOPED_OPENSSL_NO_PENDING_ERRORS; |
| CHECK(name); |
| auto bio = ssl_make_unique(BIO_new(BIO_s_mem())); |
| OPENSSL_CHECK_OK(X509_NAME_print_ex(bio.get(), name, 0, XN_FLAG_ONELINE)); |
| |
| BUF_MEM* membuf; |
| OPENSSL_CHECK_OK(BIO_get_mem_ptr(bio.get(), &membuf)); |
| return string(membuf->data, membuf->length); |
| } |
| |
| int GetKuduKerberosPrincipalOidNid() { |
| InitializeOpenSSL(); |
| static std::once_flag flag; |
| static int nid; |
| std::call_once(flag, [&] () { |
| nid = OBJ_create(kKuduKerberosPrincipalOidStr, "kuduPrinc", "kuduKerberosPrincipal"); |
| CHECK_NE(nid, NID_undef) << "failed to create kuduPrinc oid: " << GetOpenSSLErrors(); |
| }); |
| return nid; |
| } |
| |
| X509* Cert::GetTopOfChainX509() const { |
| CHECK_GT(chain_len(), 0); |
| return sk_X509_value(data_.get(), 0); |
| } |
| |
| Status Cert::FromString(const std::string& data, DataFormat format) { |
| RETURN_NOT_OK(::kudu::security::FromString(data, format, &data_)); |
| if (sk_X509_num(data_.get()) < 1) { |
| return Status::RuntimeError("Certificate chain is empty. Expected at least one certificate."); |
| } |
| return Status::OK(); |
| } |
| |
| Status Cert::ToString(std::string* data, DataFormat format) const { |
| return ::kudu::security::ToString(data, format, data_.get()); |
| } |
| |
| Status Cert::FromFile(const std::string& fpath, DataFormat format) { |
| RETURN_NOT_OK(::kudu::security::FromFile(fpath, format, &data_)); |
| if (sk_X509_num(data_.get()) < 1) { |
| return Status::RuntimeError("Certificate chain is empty. Expected at least one certificate."); |
| } |
| return Status::OK(); |
| } |
| |
| string Cert::SubjectName() const { |
| return X509NameToString(X509_get_subject_name(GetTopOfChainX509())); |
| } |
| |
| string Cert::IssuerName() const { |
| return X509NameToString(X509_get_issuer_name(GetTopOfChainX509())); |
| } |
| |
| boost::optional<string> Cert::UserId() const { |
| SCOPED_OPENSSL_NO_PENDING_ERRORS; |
| X509_NAME* name = X509_get_subject_name(GetTopOfChainX509()); |
| char buf[1024]; |
| int len = X509_NAME_get_text_by_NID(name, NID_userId, buf, arraysize(buf)); |
| if (len < 0) return boost::none; |
| return string(buf, len); |
| } |
| |
| vector<string> Cert::Hostnames() const { |
| SCOPED_OPENSSL_NO_PENDING_ERRORS; |
| vector<string> result; |
| auto gens = ssl_make_unique(reinterpret_cast<GENERAL_NAMES*>(X509_get_ext_d2i( |
| GetTopOfChainX509(), NID_subject_alt_name, nullptr, nullptr))); |
| if (gens) { |
| for (int i = 0; i < sk_GENERAL_NAME_num(gens.get()); ++i) { |
| GENERAL_NAME* gen = sk_GENERAL_NAME_value(gens.get(), i); |
| if (gen->type != GEN_DNS) { |
| continue; |
| } |
| const ASN1_STRING* cstr = gen->d.dNSName; |
| if (cstr->type != V_ASN1_IA5STRING || cstr->data == nullptr) { |
| LOG(DFATAL) << "invalid DNS name in the SAN field"; |
| return {}; |
| } |
| result.emplace_back(reinterpret_cast<char*>(cstr->data), cstr->length); |
| } |
| } |
| return result; |
| } |
| |
| boost::optional<string> Cert::KuduKerberosPrincipal() const { |
| SCOPED_OPENSSL_NO_PENDING_ERRORS; |
| int idx = X509_get_ext_by_NID(GetTopOfChainX509(), GetKuduKerberosPrincipalOidNid(), -1); |
| if (idx < 0) return boost::none; |
| X509_EXTENSION* ext = X509_get_ext(GetTopOfChainX509(), idx); |
| ASN1_OCTET_STRING* octet_str = X509_EXTENSION_get_data(ext); |
| const unsigned char* octet_str_data = octet_str->data; |
| long len; // NOLINT |
| int tag, xclass; |
| if (ASN1_get_object(&octet_str_data, &len, &tag, &xclass, octet_str->length) != 0 || |
| tag != V_ASN1_UTF8STRING) { |
| LOG(DFATAL) << "invalid extension value in cert " << SubjectName(); |
| return boost::none; |
| } |
| |
| return string(reinterpret_cast<const char*>(octet_str_data), len); |
| } |
| |
| Status Cert::CheckKeyMatch(const PrivateKey& key) const { |
| SCOPED_OPENSSL_NO_PENDING_ERRORS; |
| OPENSSL_RET_NOT_OK(X509_check_private_key(GetTopOfChainX509(), key.GetRawData()), |
| "certificate does not match private key"); |
| return Status::OK(); |
| } |
| |
| Status Cert::GetServerEndPointChannelBindings(string* channel_bindings) const { |
| SCOPED_OPENSSL_NO_PENDING_ERRORS; |
| // Find the signature type of the certificate. This corresponds to the digest |
| // (hash) algorithm, and the public key type which signed the cert. |
| |
| #if OPENSSL_VERSION_NUMBER >= 0x10002000L |
| int signature_nid = X509_get_signature_nid(GetTopOfChainX509()); |
| #else |
| // Older version of OpenSSL appear not to have a public way to get the |
| // signature digest method from a certificate. Instead, we reach into the |
| // 'private' internals. |
| int signature_nid = OBJ_obj2nid(GetTopOfChainX509()->sig_alg->algorithm); |
| #endif |
| |
| // Retrieve the digest algorithm type. |
| int digest_nid; |
| int public_key_nid; |
| OBJ_find_sigid_algs(signature_nid, &digest_nid, &public_key_nid); |
| |
| // RFC 5929: if the certificate's signatureAlgorithm uses no hash functions or |
| // uses multiple hash functions, then this channel binding type's channel |
| // bindings are undefined at this time (updates to is channel binding type may |
| // occur to address this issue if it ever arises). |
| // |
| // TODO(dan): can the multiple hash function scenario actually happen? What |
| // does OBJ_find_sigid_algs do in that scenario? |
| if (digest_nid == NID_undef) { |
| return Status::NotSupported("server certificate has no signature digest (hash) algorithm"); |
| } |
| |
| // RFC 5929: if the certificate's signatureAlgorithm uses a single hash |
| // function, and that hash function is either MD5 [RFC1321] or SHA-1 |
| // [RFC3174], then use SHA-256 [FIPS-180-3]; |
| if (digest_nid == NID_md5 || digest_nid == NID_sha1) { |
| digest_nid = NID_sha256; |
| } |
| |
| const EVP_MD* md = EVP_get_digestbynid(digest_nid); |
| OPENSSL_RET_IF_NULL(md, "digest for nid not found"); |
| |
| // Create a digest BIO. All data written to the BIO will be sent through the |
| // digest (hash) function. The digest BIO requires a null BIO to writethrough to. |
| auto null_bio = ssl_make_unique(BIO_new(BIO_s_null())); |
| auto md_bio = ssl_make_unique(BIO_new(BIO_f_md())); |
| OPENSSL_RET_NOT_OK(BIO_set_md(md_bio.get(), md), "failed to set digest for BIO"); |
| BIO_push(md_bio.get(), null_bio.get()); |
| |
| // Write the cert to the digest BIO. |
| RETURN_NOT_OK(ToBIO(md_bio.get(), DataFormat::DER, data_.get())); |
| |
| // Read the digest from the BIO and append it to 'channel_bindings'. |
| char buf[EVP_MAX_MD_SIZE]; |
| int digest_len = BIO_gets(md_bio.get(), buf, sizeof(buf)); |
| OPENSSL_RET_NOT_OK(digest_len, "failed to get cert digest from BIO"); |
| channel_bindings->assign(buf, digest_len); |
| return Status::OK(); |
| } |
| |
| void Cert::AdoptAndAddRefRawData(RawDataType* data) { |
| DCHECK_EQ(sk_X509_num(data), 1); |
| X509* cert = sk_X509_value(data, sk_X509_num(data) - 1); |
| |
| DCHECK(cert); |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L |
| CHECK_GT(CRYPTO_add(&cert->references, 1, CRYPTO_LOCK_X509), 1) << "X509 use-after-free detected"; |
| #else |
| OPENSSL_CHECK_OK(X509_up_ref(cert)) << "X509 use-after-free detected: " << GetOpenSSLErrors(); |
| #endif |
| // We copy the STACK_OF() object, but the copy and the original both internally point to the |
| // same elements. |
| AdoptRawData(sk_X509_dup(data)); |
| } |
| |
| void Cert::AdoptX509(X509* cert) { |
| // Free current STACK_OF(X509). |
| sk_X509_pop_free(data_.get(), X509_free); |
| // Allocate new STACK_OF(X509) and populate with 'cert'. |
| STACK_OF(X509)* sk = sk_X509_new_null(); |
| DCHECK(sk); |
| sk_X509_push(sk, cert); |
| AdoptRawData(sk); |
| } |
| |
| void Cert::AdoptAndAddRefX509(X509* cert) { |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L |
| CHECK_GT(CRYPTO_add(&cert->references, 1, CRYPTO_LOCK_X509), 1) << "X509 use-after-free detected"; |
| #else |
| OPENSSL_CHECK_OK(X509_up_ref(cert)) << "X509 use-after-free detected: " << GetOpenSSLErrors(); |
| #endif |
| AdoptX509(cert); |
| } |
| |
| Status Cert::GetPublicKey(PublicKey* key) const { |
| SCOPED_OPENSSL_NO_PENDING_ERRORS; |
| EVP_PKEY* raw_key = X509_get_pubkey(GetTopOfChainX509()); |
| OPENSSL_RET_IF_NULL(raw_key, "unable to get certificate public key"); |
| key->AdoptRawData(raw_key); |
| return Status::OK(); |
| } |
| |
| Status CertSignRequest::FromString(const std::string& data, DataFormat format) { |
| return ::kudu::security::FromString(data, format, &data_); |
| } |
| |
| Status CertSignRequest::ToString(std::string* data, DataFormat format) const { |
| return ::kudu::security::ToString(data, format, data_.get()); |
| } |
| |
| Status CertSignRequest::FromFile(const std::string& fpath, DataFormat format) { |
| return ::kudu::security::FromFile(fpath, format, &data_); |
| } |
| |
| CertSignRequest CertSignRequest::Clone() const { |
| X509_REQ* cloned_req; |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L |
| CHECK_GT(CRYPTO_add(&data_->references, 1, CRYPTO_LOCK_X509_REQ), 1) |
| << "X509_REQ use-after-free detected"; |
| cloned_req = GetRawData(); |
| #else |
| // With OpenSSL 1.1, data structure internals are hidden, and there doesn't |
| // seem to be a public method that increments data_'s refcount. |
| cloned_req = X509_REQ_dup(GetRawData()); |
| CHECK(cloned_req != nullptr) |
| << "X509 allocation failure detected: " << GetOpenSSLErrors(); |
| #endif |
| |
| CertSignRequest clone; |
| clone.AdoptRawData(cloned_req); |
| return clone; |
| } |
| |
| Status CertSignRequest::GetPublicKey(PublicKey* key) const { |
| SCOPED_OPENSSL_NO_PENDING_ERRORS; |
| EVP_PKEY* raw_key = X509_REQ_get_pubkey(data_.get()); |
| OPENSSL_RET_IF_NULL(raw_key, "unable to get CSR public key"); |
| key->AdoptRawData(raw_key); |
| return Status::OK(); |
| } |
| |
| } // namespace security |
| } // namespace kudu |