| // 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/tls_handshake.h" |
| |
| #include <openssl/crypto.h> |
| #include <openssl/ssl.h> |
| #include <openssl/x509.h> |
| |
| #include <cstdint> |
| #include <memory> |
| #include <string> |
| #include <ostream> |
| #include <utility> |
| |
| #include "kudu/gutil/strings/strip.h" |
| #include "kudu/gutil/strings/substitute.h" |
| #include "kudu/security/cert.h" |
| #include "kudu/security/tls_socket.h" |
| #include "kudu/util/net/socket.h" |
| #include "kudu/util/openssl_util.h" |
| #include "kudu/util/status.h" |
| #include "kudu/util/trace.h" |
| |
| #if OPENSSL_VERSION_NUMBER < 0x10002000L |
| #include "kudu/security/x509_check_host.h" |
| #endif // OPENSSL_VERSION_NUMBER |
| |
| #ifndef TLS1_3_VERSION |
| #define TLS1_3_VERSION 0x0304 |
| #endif |
| |
| using std::string; |
| using std::unique_ptr; |
| using strings::Substitute; |
| |
| namespace kudu { |
| namespace security { |
| |
| void TlsHandshake::SetSSLVerify() { |
| SCOPED_OPENSSL_NO_PENDING_ERRORS; |
| CHECK(ssl_); |
| CHECK(!has_started_); |
| int ssl_mode = 0; |
| switch (verification_mode_) { |
| case TlsVerificationMode::VERIFY_NONE: |
| ssl_mode = SSL_VERIFY_NONE; |
| break; |
| case TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST: |
| // Server mode: the server sends a client certificate request to the client. The |
| // certificate returned (if any) is checked. If the verification process fails, the TLS/SSL |
| // handshake is immediately terminated with an alert message containing the reason for the |
| // verification failure. The behaviour can be controlled by the additional |
| // SSL_VERIFY_FAIL_IF_NO_PEER_CERT and SSL_VERIFY_CLIENT_ONCE flags. |
| |
| // Client mode: the server certificate is verified. If the verification process fails, the |
| // TLS/SSL handshake is immediately terminated with an alert message containing the reason |
| // for the verification failure. If no server certificate is sent, because an anonymous |
| // cipher is used, SSL_VERIFY_PEER is ignored. |
| ssl_mode |= SSL_VERIFY_PEER; |
| |
| // Server mode: if the client did not return a certificate, the TLS/SSL handshake is |
| // immediately terminated with a "handshake failure" alert. This flag must be used |
| // together with SSL_VERIFY_PEER. |
| ssl_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; |
| // Server mode: only request a client certificate on the initial TLS/SSL handshake. Do |
| // not ask for a client certificate again in case of a renegotiation. This flag must be |
| // used together with SSL_VERIFY_PEER. |
| ssl_mode |= SSL_VERIFY_CLIENT_ONCE; |
| break; |
| } |
| |
| SSL_set_verify(ssl_.get(), ssl_mode, /* callback = */nullptr); |
| } |
| |
| TlsHandshake::TlsHandshake(TlsHandshakeType type) |
| : type_(type) { |
| } |
| |
| Status TlsHandshake::Init(c_unique_ptr<SSL> s) { |
| SCOPED_OPENSSL_NO_PENDING_ERRORS; |
| DCHECK(s); |
| |
| if (ssl_) { |
| return Status::IllegalState("TlsHandshake is already initialized"); |
| } |
| |
| auto rbio = ssl_make_unique(BIO_new(BIO_s_mem())); |
| if (!rbio) { |
| return Status::RuntimeError( |
| "failed to create memory-based read BIO", GetOpenSSLErrors()); |
| } |
| auto wbio = ssl_make_unique(BIO_new(BIO_s_mem())); |
| if (!wbio) { |
| return Status::RuntimeError( |
| "failed to create memory-based write BIO", GetOpenSSLErrors()); |
| } |
| ssl_ = std::move(s); |
| auto* ssl = ssl_.get(); |
| SSL_set_bio(ssl, rbio.release(), wbio.release()); |
| |
| switch (type_) { |
| case TlsHandshakeType::SERVER: |
| SSL_set_accept_state(ssl); |
| break; |
| case TlsHandshakeType::CLIENT: |
| SSL_set_connect_state(ssl); |
| break; |
| } |
| return Status::OK(); |
| } |
| |
| Status TlsHandshake::Continue(const string& recv, string* send) { |
| SCOPED_OPENSSL_NO_PENDING_ERRORS; |
| if (!has_started_) { |
| SetSSLVerify(); |
| has_started_ = true; |
| } |
| DCHECK(ssl_); |
| auto* ssl = ssl_.get(); |
| |
| // +------+ +-----+ |
| // | |--> BIO_write(rbio) -->| |--> SSL_read(ssl) --> IN |
| // | SASL | | SSL | |
| // | |<-- BIO_read(wbio) <--| |<-- SSL_write(ssl) <-- OUT |
| // +------+ +-----+ |
| BIO* rbio = SSL_get_rbio(ssl); |
| int n = BIO_write(rbio, recv.data(), recv.size()); |
| DCHECK(n == recv.size() || (n == -1 && recv.empty())); |
| DCHECK_EQ(BIO_ctrl_pending(rbio), recv.size()); |
| |
| int rc = SSL_do_handshake(ssl); |
| if (rc != 1) { |
| int ssl_err = SSL_get_error(ssl, rc); |
| // WANT_READ and WANT_WRITE indicate that the handshake is not yet complete. |
| if (ssl_err != SSL_ERROR_WANT_READ && ssl_err != SSL_ERROR_WANT_WRITE) { |
| return Status::RuntimeError("TLS Handshake error", GetSSLErrorDescription(ssl_err)); |
| } |
| // In the case that we got SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE, |
| // the OpenSSL implementation guarantees that there is no error entered into |
| // the ERR error queue, so no need to ERR_clear_error() here. |
| } |
| |
| BIO* wbio = SSL_get_wbio(ssl); |
| int pending = BIO_ctrl_pending(wbio); |
| DCHECK_GE(pending, 0); |
| |
| send->resize(pending); |
| if (pending > 0) { |
| int bytes_read = BIO_read(wbio, &(*send)[0], send->size()); |
| DCHECK_EQ(bytes_read, send->size()); |
| DCHECK_EQ(BIO_ctrl_pending(wbio), 0); |
| } |
| |
| if (rc == 1) { |
| // SSL_do_handshake() must have read all the pending data. |
| DCHECK_EQ(0, BIO_ctrl_pending(rbio)); |
| VLOG(2) << Substitute("TSL Handshake complete"); |
| return Status::OK(); |
| } |
| return Status::Incomplete("TLS Handshake incomplete"); |
| } |
| |
| bool TlsHandshake::NeedsExtraStep(const Status& continue_status, |
| const string& token) const { |
| DCHECK(has_started_); |
| DCHECK(ssl_); |
| DCHECK(continue_status.ok() || continue_status.IsIncomplete()); |
| |
| if (continue_status.IsIncomplete()) { |
| return true; |
| } |
| if (continue_status.ok()) { |
| switch (type_) { |
| case TlsHandshakeType::CLIENT: |
| return !token.empty(); |
| case TlsHandshakeType::SERVER: |
| if (SSL_version(ssl_.get()) == TLS1_3_VERSION) { |
| return false; |
| } |
| return !token.empty(); |
| } |
| } |
| return false; |
| } |
| |
| void TlsHandshake::StorePendingData(string data) { |
| DCHECK(!data.empty()); |
| // This is used only for the TLSv1.3 protocol. |
| DCHECK_EQ(TLS1_3_VERSION, SSL_version(ssl_.get())); |
| rbio_pending_data_ = std::move(data); |
| } |
| |
| Status TlsHandshake::Verify(const Socket& /*socket*/) const { |
| SCOPED_OPENSSL_NO_PENDING_ERRORS; |
| DCHECK(SSL_is_init_finished(ssl_.get())); |
| CHECK(ssl_); |
| |
| if (verification_mode_ == TlsVerificationMode::VERIFY_NONE) { |
| return Status::OK(); |
| } |
| DCHECK(verification_mode_ == TlsVerificationMode::VERIFY_REMOTE_CERT_AND_HOST); |
| |
| int rc = SSL_get_verify_result(ssl_.get()); |
| if (rc != X509_V_OK) { |
| return Status::NotAuthorized(Substitute("SSL cert verification failed: $0", |
| X509_verify_cert_error_string(rc)), |
| GetOpenSSLErrors()); |
| } |
| |
| // Get the peer certificate. |
| X509* cert = remote_cert_.GetTopOfChainX509(); |
| if (!cert) { |
| if (SSL_get_verify_mode(ssl_.get()) & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) { |
| return Status::NotAuthorized("Handshake failed: unable to retreive peer certificate"); |
| } |
| // No cert, but we weren't requiring one. |
| TRACE("Got no cert from peer, but not required"); |
| return Status::OK(); |
| } |
| |
| // TODO(KUDU-1886): Do hostname verification. |
| /* |
| TRACE("Verifying peer cert"); |
| |
| // Get the peer's hostname |
| Sockaddr peer_addr; |
| if (!socket.GetPeerAddress(&peer_addr).ok()) { |
| return Status::NotAuthorized( |
| "TLS certificate hostname verification failed: unable to get peer address"); |
| } |
| string peer_hostname; |
| RETURN_NOT_OK_PREPEND(peer_addr.LookupHostname(&peer_hostname), |
| "TLS certificate hostname verification failed: unable to lookup peer hostname"); |
| |
| // Check if the hostname matches with either the Common Name or any of the Subject Alternative |
| // Names of the certificate. |
| int match = X509_check_host(cert, |
| peer_hostname.c_str(), |
| peer_hostname.length(), |
| 0, |
| nullptr); |
| if (match == 0) { |
| return Status::NotAuthorized("TLS certificate hostname verification failed"); |
| } |
| if (match < 0) { |
| return Status::RuntimeError("TLS certificate hostname verification error", GetOpenSSLErrors()); |
| } |
| DCHECK_EQ(match, 1); |
| */ |
| return Status::OK(); |
| } |
| |
| Status TlsHandshake::GetCerts() { |
| SCOPED_OPENSSL_NO_PENDING_ERRORS; |
| X509* cert = SSL_get_certificate(ssl_.get()); |
| if (cert) { |
| // For whatever reason, SSL_get_certificate (unlike SSL_get_peer_certificate) |
| // does not increment the X509's reference count. |
| local_cert_.AdoptAndAddRefX509(cert); |
| } |
| |
| cert = SSL_get_peer_certificate(ssl_.get()); |
| if (cert) { |
| remote_cert_.AdoptX509(cert); |
| } |
| return Status::OK(); |
| } |
| |
| Status TlsHandshake::Finish(unique_ptr<Socket>* socket) { |
| SCOPED_OPENSSL_NO_PENDING_ERRORS; |
| RETURN_NOT_OK(GetCerts()); |
| RETURN_NOT_OK(Verify(**socket)); |
| |
| int fd = (*socket)->Release(); |
| auto* ssl = ssl_.get(); |
| |
| // Nothing should left in the memory-based BIOs upon Finish() is called. |
| // Otherwise, the buffered data would be lost because those BIOs are destroyed |
| // when SSL_set_fd() is called below. |
| DCHECK_EQ(0, SSL_pending(ssl)); |
| |
| BIO* wbio = SSL_get_wbio(ssl); |
| DCHECK_EQ(0, BIO_ctrl_pending(wbio)); |
| DCHECK_EQ(0, BIO_ctrl_wpending(wbio)); |
| |
| BIO* rbio = SSL_get_rbio(ssl); |
| DCHECK_EQ(0, BIO_ctrl_pending(rbio)); |
| DCHECK_EQ(0, BIO_ctrl_wpending(rbio)); |
| |
| // Give the socket to the SSL instance. This will automatically free the |
| // read and write memory BIO instances. |
| int ret = SSL_set_fd(ssl, fd); |
| if (ret != 1) { |
| return Status::RuntimeError("TLS handshake error", GetOpenSSLErrors()); |
| } |
| |
| const auto data_size = rbio_pending_data_.size(); |
| if (data_size != 0) { |
| int fd = SSL_get_fd(ssl); |
| Socket sock(fd); |
| const uint8_t* data = reinterpret_cast<const uint8_t*>(rbio_pending_data_.data()); |
| int32_t written = 0; |
| RETURN_NOT_OK(sock.Write(data, data_size, &written)); |
| if (written != data_size) { |
| // The socket should be in blocking mode, so Write() should return with |
| // success only if all the data is written. |
| return Status::IllegalState( |
| Substitute("wrote only $0 out of $1 bytes", written, data_size)); |
| } |
| sock.Release(); // do not close the descriptor when Socket goes out of scope |
| rbio_pending_data_.clear(); |
| } |
| |
| // Transfer the SSL instance to the socket. |
| socket->reset(new TlsSocket(fd, std::move(ssl_))); |
| |
| return Status::OK(); |
| } |
| |
| Status TlsHandshake::FinishNoWrap(const Socket& socket) { |
| SCOPED_OPENSSL_NO_PENDING_ERRORS; |
| RETURN_NOT_OK(GetCerts()); |
| return Verify(socket); |
| } |
| |
| Status TlsHandshake::GetLocalCert(Cert* cert) const { |
| SCOPED_OPENSSL_NO_PENDING_ERRORS; |
| if (!local_cert_.GetRawData()) { |
| return Status::RuntimeError("no local certificate"); |
| } |
| cert->AdoptAndAddRefRawData(local_cert_.GetRawData()); |
| return Status::OK(); |
| } |
| |
| Status TlsHandshake::GetRemoteCert(Cert* cert) const { |
| SCOPED_OPENSSL_NO_PENDING_ERRORS; |
| if (!remote_cert_.GetRawData()) { |
| return Status::RuntimeError("no remote certificate"); |
| } |
| cert->AdoptAndAddRefRawData(remote_cert_.GetRawData()); |
| return Status::OK(); |
| } |
| |
| string TlsHandshake::GetCipherSuite() const { |
| SCOPED_OPENSSL_NO_PENDING_ERRORS; |
| CHECK(has_started_); |
| return SSL_get_cipher_name(ssl_.get()); |
| } |
| |
| string TlsHandshake::GetProtocol() const { |
| SCOPED_OPENSSL_NO_PENDING_ERRORS; |
| CHECK(has_started_); |
| return SSL_get_version(ssl_.get()); |
| } |
| |
| string TlsHandshake::GetCipherDescription() const { |
| SCOPED_OPENSSL_NO_PENDING_ERRORS; |
| CHECK(has_started_); |
| const SSL_CIPHER* cipher = SSL_get_current_cipher(ssl_.get()); |
| if (!cipher) { |
| return "NONE"; |
| } |
| char buf[512]; |
| const char* description = SSL_CIPHER_description(cipher, buf, sizeof(buf)); |
| if (!description) { |
| return "NONE"; |
| } |
| string ret(description); |
| StripTrailingNewline(&ret); |
| StripDupCharacters(&ret, ' ', 0); |
| return ret; |
| } |
| |
| } // namespace security |
| } // namespace kudu |