blob: 82e22e07353800bef43e66a6488f3f1f4afbb431 [file] [log] [blame]
// 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