diff options
author | Håvard Pettersen <havardpe@oath.com> | 2018-09-07 10:23:36 +0000 |
---|---|---|
committer | Håvard Pettersen <havardpe@oath.com> | 2018-09-07 10:58:30 +0000 |
commit | 77b923eefca22f32a0843ed618d53e6e7a0744ab (patch) | |
tree | 4ed12bdd67012f157ad64c075007a721ef2565b4 /vespalib | |
parent | c329ceedde1aeb8b2bbe1fb6f9a164ad8130a6ad (diff) |
tls crypto adapter
Diffstat (limited to 'vespalib')
7 files changed, 251 insertions, 8 deletions
diff --git a/vespalib/src/vespa/vespalib/net/crypto_socket.h b/vespalib/src/vespa/vespalib/net/crypto_socket.h index 7fe7871960f..f78f7fc0ce7 100644 --- a/vespalib/src/vespa/vespalib/net/crypto_socket.h +++ b/vespalib/src/vespa/vespalib/net/crypto_socket.h @@ -74,13 +74,16 @@ struct CryptoSocket { virtual ssize_t write(const char *buf, size_t len) = 0; /** - * Try to flush data in the write pipeline that is not depenedent + * Try to flush data in the write pipeline that is not dependent * on data not yet written by the application into the underlying * socket. This is to enable the application to identify pending * work that may not be completed until the underlying socket is * ready for writing more data. The semantics are the same as with * a normal socket write (errno, etc.) with the exception that 0 - * will be returned when there is no more data to flush. + * will be returned when there is no more data to flush and any + * positive number indicates that we were able to flush something + * (it does not need to reflect the actual number of bytes written + * to the underlying socket). **/ virtual ssize_t flush() = 0; diff --git a/vespalib/src/vespa/vespalib/net/tls/CMakeLists.txt b/vespalib/src/vespa/vespalib/net/tls/CMakeLists.txt index 938ae0896a2..02941460e40 100644 --- a/vespalib/src/vespa/vespalib/net/tls/CMakeLists.txt +++ b/vespalib/src/vespa/vespalib/net/tls/CMakeLists.txt @@ -2,8 +2,10 @@ vespa_add_library(vespalib_vespalib_net_tls OBJECT SOURCES crypto_codec.cpp + crypto_codec_adapter.cpp crypto_exception.cpp tls_context.cpp + tls_crypto_engine.cpp transport_security_options.cpp DEPENDS ) diff --git a/vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.cpp b/vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.cpp new file mode 100644 index 00000000000..435f16cc340 --- /dev/null +++ b/vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.cpp @@ -0,0 +1,146 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "crypto_codec_adapter.h" +#include <assert.h> + +namespace vespalib::net::tls { + +CryptoSocket::HandshakeResult +CryptoCodecAdapter::hs_try_flush() +{ + auto flush_res = flush_all(); + if (flush_res == 0) { + return HandshakeResult::DONE; + } else if (is_blocked(flush_res, errno)) { + return HandshakeResult::NEED_WRITE; + } else { + return HandshakeResult::FAIL; + } +} + +CryptoSocket::HandshakeResult +CryptoCodecAdapter::hs_try_fill() +{ + auto fill_res = fill_input(); + if (fill_res > 0) { + return HandshakeResult::DONE; + } else if (is_blocked(fill_res, errno)) { + return HandshakeResult::NEED_READ; + } else { // eof included here + return HandshakeResult::FAIL; + } +} + +ssize_t +CryptoCodecAdapter::fill_input() +{ + if (_input.get().size < _codec->min_encode_buffer_size()) { + auto dst = _input.reserve(_codec->min_encode_buffer_size()); + ssize_t res = _socket.read(dst.data, dst.size); + if (res > 0) { + _input.commit(res); + } else { + return res; // eof/error + } + } + return 1; // progress +} + +ssize_t +CryptoCodecAdapter::flush_all() +{ + ssize_t res = flush(); + while (res > 0) { + res = flush(); + } + return res; +} + +CryptoSocket::HandshakeResult +CryptoCodecAdapter::handshake() +{ + for (;;) { + auto in = _input.obtain(); + auto out = _output.reserve(_codec->min_encode_buffer_size()); + auto hs_res = _codec->handshake(in.data, in.size, out.data, out.size); + _input.evict(hs_res.bytes_consumed); + _output.commit(hs_res.bytes_produced); + switch (hs_res.state) { + case ::vespalib::net::tls::HandshakeResult::State::Failed: return HandshakeResult::FAIL; + case ::vespalib::net::tls::HandshakeResult::State::Done: return hs_try_flush(); + case ::vespalib::net::tls::HandshakeResult::State::NeedsMorePeerData: + auto flush_res = hs_try_flush(); + if (flush_res != HandshakeResult::DONE) { + return flush_res; + } + auto fill_res = hs_try_fill(); + if (fill_res != HandshakeResult::DONE) { + return fill_res; + } + } + } + return HandshakeResult::DONE; +} + +ssize_t +CryptoCodecAdapter::read(char *buf, size_t len) +{ + auto fill_res = fill_input(); + if (fill_res <= 0) { + return fill_res; + } + ssize_t res = drain(buf, len); + if (res != 0) { + return res; + } + errno = EWOULDBLOCK; + return -1; +} + +ssize_t +CryptoCodecAdapter::drain(char *buf, size_t len) +{ + auto src = _input.obtain(); + auto res = _codec->decode(src.data, src.size, buf, len); + if (res.failed()) { + errno = EIO; + return -1; + } + _input.evict(res.bytes_consumed); + return res.bytes_produced; +} + +ssize_t +CryptoCodecAdapter::write(const char *buf, size_t len) +{ + if (flush_all() < 0) { + return -1; + } + auto dst = _output.reserve(_codec->min_encode_buffer_size()); + auto res = _codec->encode(buf, len, dst.data, dst.size); + if (res.failed) { + errno = EIO; + return -1; + } + _output.commit(res.bytes_produced); + return res.bytes_consumed; +} + +ssize_t +CryptoCodecAdapter::flush() +{ + auto pending = _output.obtain(); + if (pending.size > 0) { + ssize_t res = _socket.write(pending.data, pending.size); + if (res > 0) { + _output.evict(res); + return 1; // progress + } else { + assert(res < 0); + return -1; // error + } + } + return 0; // done +} + +} // namespace vespalib::net::tls diff --git a/vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.h b/vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.h new file mode 100644 index 00000000000..6a624ca44f7 --- /dev/null +++ b/vespalib/src/vespa/vespalib/net/tls/crypto_codec_adapter.h @@ -0,0 +1,46 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include <vespa/vespalib/net/crypto_socket.h> +#include <vespa/vespalib/net/socket_handle.h> +#include <vespa/vespalib/data/simple_buffer.h> +#include "crypto_codec.h" + +namespace vespalib::net::tls { + +/** + * Component adapting an underlying CryptoCodec to the CryptoSocket + * interface by performing buffer and socket management. + * + * NOTE: initial implementation is for functionality/proof-of-concept + * purposes, not performance. + **/ +class CryptoCodecAdapter : public CryptoSocket +{ +private: + SimpleBuffer _input; + SimpleBuffer _output; + SocketHandle _socket; + std::unique_ptr<CryptoCodec> _codec; + + bool is_blocked(ssize_t res, int error) const { + return ((res < 0) && ((error == EWOULDBLOCK) || (error == EAGAIN))); + } + HandshakeResult hs_try_flush(); + HandshakeResult hs_try_fill(); + ssize_t fill_input(); // -1/0/1 -> error/eof/ok + ssize_t flush_all(); // -1/0 -> error/ok +public: + CryptoCodecAdapter(SocketHandle socket, std::unique_ptr<CryptoCodec> codec) + : _socket(std::move(socket)), _codec(std::move(codec)) {} + int get_fd() const override { return _socket.get(); } + HandshakeResult handshake() override; + size_t min_read_buffer_size() const override { return _codec->min_decode_buffer_size(); } + ssize_t read(char *buf, size_t len) override; + ssize_t drain(char *, size_t) override; + ssize_t write(const char *buf, size_t len) override; + ssize_t flush() override; +}; + +} // namespace vespalib::net::tls diff --git a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.cpp b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.cpp index 13e1be2ce34..a563a43baac 100644 --- a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.cpp +++ b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.cpp @@ -35,6 +35,10 @@ namespace vespalib::net::tls::impl { namespace { +bool verify_buf(const char *buf, size_t len) { + return ((len < INT32_MAX) && ((len == 0) || (buf != nullptr))); +} + const char* ssl_error_to_str(int ssl_error) noexcept { // From https://www.openssl.org/docs/manmaster/man3/SSL_get_error.html // Our code paths shouldn't trigger most of these, but included for completeness @@ -200,8 +204,7 @@ int OpenSslCryptoCodecImpl::drain_outgoing_network_bytes_if_any( HandshakeResult OpenSslCryptoCodecImpl::handshake(const char* from_peer, size_t from_peer_buf_size, char* to_peer, size_t to_peer_buf_size) noexcept { - LOG_ASSERT(from_peer != nullptr && to_peer != nullptr - && from_peer_buf_size < INT32_MAX && to_peer_buf_size < INT32_MAX); + LOG_ASSERT(verify_buf(from_peer, from_peer_buf_size) && verify_buf(to_peer, to_peer_buf_size)); if (SSL_is_init_finished(_ssl.get())) { return handshake_completed(); @@ -279,8 +282,7 @@ HandshakeResult OpenSslCryptoCodecImpl::do_handshake_and_consume_peer_input_byte EncodeResult OpenSslCryptoCodecImpl::encode(const char* plaintext, size_t plaintext_size, char* ciphertext, size_t ciphertext_size) noexcept { - LOG_ASSERT(plaintext != nullptr && ciphertext != nullptr - && plaintext_size < INT32_MAX && ciphertext_size < INT32_MAX); + LOG_ASSERT(verify_buf(plaintext, plaintext_size) && verify_buf(ciphertext, ciphertext_size)); if (!SSL_is_init_finished(_ssl.get())) { LOG(error, "OpenSslCryptoCodecImpl::encode() called before handshake completed"); @@ -317,8 +319,7 @@ EncodeResult OpenSslCryptoCodecImpl::encode(const char* plaintext, size_t plaint } DecodeResult OpenSslCryptoCodecImpl::decode(const char* ciphertext, size_t ciphertext_size, char* plaintext, size_t plaintext_size) noexcept { - LOG_ASSERT(ciphertext != nullptr && plaintext != nullptr - && ciphertext_size < INT32_MAX && plaintext_size < INT32_MAX); + LOG_ASSERT(verify_buf(ciphertext, ciphertext_size) && verify_buf(plaintext, plaintext_size)); if (!SSL_is_init_finished(_ssl.get())) { LOG(error, "OpenSslCryptoCodecImpl::decode() called before handshake completed"); diff --git a/vespalib/src/vespa/vespalib/net/tls/tls_crypto_engine.cpp b/vespalib/src/vespa/vespalib/net/tls/tls_crypto_engine.cpp new file mode 100644 index 00000000000..72d9eacf37c --- /dev/null +++ b/vespalib/src/vespa/vespalib/net/tls/tls_crypto_engine.cpp @@ -0,0 +1,22 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "tls_crypto_engine.h" +#include "crypto_codec.h" +#include "crypto_codec_adapter.h" + +namespace vespalib { + +TlsCryptoEngine::TlsCryptoEngine(net::tls::TransportSecurityOptions tls_opts) + : _tls_ctx(net::tls::TlsContext::create_default_context(tls_opts)) +{ +} + +CryptoSocket::UP +TlsCryptoEngine::create_crypto_socket(SocketHandle socket, bool is_server) +{ + auto mode = is_server ? net::tls::CryptoCodec::Mode::Server : net::tls::CryptoCodec::Mode::Client; + auto codec = net::tls::CryptoCodec::create_default_codec(*_tls_ctx, mode); + return std::make_unique<net::tls::CryptoCodecAdapter>(std::move(socket), std::move(codec)); +} + +} // namespace vespalib diff --git a/vespalib/src/vespa/vespalib/net/tls/tls_crypto_engine.h b/vespalib/src/vespa/vespalib/net/tls/tls_crypto_engine.h new file mode 100644 index 00000000000..58fda2b3b21 --- /dev/null +++ b/vespalib/src/vespa/vespalib/net/tls/tls_crypto_engine.h @@ -0,0 +1,23 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include <vespa/vespalib/net/crypto_engine.h> +#include "transport_security_options.h" +#include "tls_context.h" + +namespace vespalib { + +/** + * Crypto engine implementing TLS. + **/ +class TlsCryptoEngine : public CryptoEngine +{ +private: + std::unique_ptr<net::tls::TlsContext> _tls_ctx; +public: + TlsCryptoEngine(net::tls::TransportSecurityOptions tls_opts); + CryptoSocket::UP create_crypto_socket(SocketHandle socket, bool is_server) override; +}; + +} // namespace vespalib |