├── .gitignore ├── Jamfile ├── .editorconfig ├── README.md ├── test ├── gnutls │ ├── error.cpp │ ├── stream_base.cpp │ ├── context_base.cpp │ ├── rfc2818_verification.cpp │ ├── host_name_verification.cpp │ ├── Jamfile.v2 │ ├── context.cpp │ └── stream.cpp └── unit_test.hpp ├── include └── boost │ └── asio │ ├── gnutls.hpp │ └── gnutls │ ├── rfc2818_verification.hpp │ ├── verify_context.hpp │ ├── host_name_verification.hpp │ ├── stream_base.hpp │ ├── error.hpp │ ├── context_base.hpp │ ├── context.hpp │ └── stream.hpp ├── .clang-format └── LICENSE_1_0.txt /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | bin/ 3 | *.d 4 | *.o 5 | *.a 6 | *.so 7 | compile_commands.json 8 | 9 | -------------------------------------------------------------------------------- /Jamfile: -------------------------------------------------------------------------------- 1 | alias asio-gnutls 2 | : # no sources 3 | : # no build requirements 4 | : # no default build 5 | : ./include ; 6 | 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | indent_style = space 10 | indent_size = 4 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # boost-asio-gnutls 2 | GnuTLS wrapper for Boost.Asio 3 | 4 | ## Usage 5 | 6 | Add `include` as include directory for your project, then include the header `boost/asio/gnutls.hpp`. 7 | Don't forget to link against GnuTLS instead of OpenSSL. 8 | 9 | The two classes `context` and `stream` in `boost::asio::gnutls` mimic the ones in `boost::asio::ssl`. 10 | 11 | ## Test 12 | 13 | From the boost root directory, run: 14 | ``` 15 | b2 [PATH_TO_THIS_REPOSITORY]/test/gnutls 16 | ``` 17 | 18 | -------------------------------------------------------------------------------- /test/gnutls/error.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // error.cpp 3 | // ~~~~~~~~~ 4 | // 5 | // Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | 11 | // Disable autolinking for unit tests. 12 | #if !defined(BOOST_ALL_NO_LIB) 13 | #define BOOST_ALL_NO_LIB 1 14 | #endif // !defined(BOOST_ALL_NO_LIB) 15 | 16 | // Test that header file is self-contained. 17 | #include 18 | 19 | #include "../unit_test.hpp" 20 | 21 | BOOST_ASIO_TEST_SUITE("gnutls/error", BOOST_ASIO_TEST_CASE(null_test)) 22 | -------------------------------------------------------------------------------- /test/gnutls/stream_base.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // stream_base.cpp 3 | // ~~~~~~~~~~~~~~~ 4 | // 5 | // Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | 11 | // Disable autolinking for unit tests. 12 | #if !defined(BOOST_ALL_NO_LIB) 13 | #define BOOST_ALL_NO_LIB 1 14 | #endif // !defined(BOOST_ALL_NO_LIB) 15 | 16 | // Test that header file is self-contained. 17 | #include 18 | 19 | #include "../unit_test.hpp" 20 | 21 | BOOST_ASIO_TEST_SUITE("gnutls/stream_base", BOOST_ASIO_TEST_CASE(null_test)) 22 | -------------------------------------------------------------------------------- /test/gnutls/context_base.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // context_base.cpp 3 | // ~~~~~~~~~~~~~~~~ 4 | // 5 | // Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | 11 | // Disable autolinking for unit tests. 12 | #if !defined(BOOST_ALL_NO_LIB) 13 | #define BOOST_ALL_NO_LIB 1 14 | #endif // !defined(BOOST_ALL_NO_LIB) 15 | 16 | // Test that header file is self-contained. 17 | #include 18 | 19 | #include "../unit_test.hpp" 20 | 21 | BOOST_ASIO_TEST_SUITE("gnutls/context_base", BOOST_ASIO_TEST_CASE(null_test)) 22 | -------------------------------------------------------------------------------- /test/gnutls/rfc2818_verification.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // rfc2818_verification.cpp 3 | // ~~~~~~~~~~~~~~~~~~~~~~~~ 4 | // 5 | // Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | 11 | // Disable autolinking for unit tests. 12 | #if !defined(BOOST_ALL_NO_LIB) 13 | #define BOOST_ALL_NO_LIB 1 14 | #endif // !defined(BOOST_ALL_NO_LIB) 15 | 16 | // Test that header file is self-contained. 17 | #include 18 | 19 | #include "../unit_test.hpp" 20 | 21 | BOOST_ASIO_TEST_SUITE("gnutls/rfc2818_verification", BOOST_ASIO_TEST_CASE(null_test)) 22 | -------------------------------------------------------------------------------- /test/gnutls/host_name_verification.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // host_name_verification.cpp 3 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | // 5 | // Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | 11 | // Disable autolinking for unit tests. 12 | #if !defined(BOOST_ALL_NO_LIB) 13 | #define BOOST_ALL_NO_LIB 1 14 | #endif // !defined(BOOST_ALL_NO_LIB) 15 | 16 | // Test that header file is self-contained. 17 | #include 18 | 19 | #include "../unit_test.hpp" 20 | 21 | BOOST_ASIO_TEST_SUITE("gnutls/host_name_verification", BOOST_ASIO_TEST_CASE(null_test)) 22 | -------------------------------------------------------------------------------- /include/boost/asio/gnutls.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // gnutls.hpp 3 | // ~~~~~~~ 4 | // 5 | // Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | 11 | #ifndef BOOST_ASIO_GNUTLS_HPP 12 | #define BOOST_ASIO_GNUTLS_HPP 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #endif // BOOST_ASIO_GNUTLS_HPP 24 | -------------------------------------------------------------------------------- /include/boost/asio/gnutls/rfc2818_verification.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // gnutls/rfc2818_verification.hpp 3 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | // 5 | // Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | 11 | #ifndef BOOST_ASIO_GNUTLS_RFC2818_VERIFICATION_HPP 12 | #define BOOST_ASIO_GNUTLS_RFC2818_VERIFICATION_HPP 13 | 14 | #include 15 | 16 | namespace boost { 17 | namespace asio { 18 | namespace gnutls { 19 | 20 | // Verifies a certificate against a hostname according to the rules described in RFC 2818. 21 | // Deprecated, use host_name_verification instead. 22 | using rfc2818_verification = host_name_verification; 23 | 24 | } // namespace gnutls 25 | } // namespace asio 26 | } // namespace boost 27 | 28 | #endif // BOOST_ASIO_GNUTLS_RFC2818_VERIFICATION_HPP 29 | -------------------------------------------------------------------------------- /include/boost/asio/gnutls/verify_context.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // gnutls/context.hpp 3 | // ~~~~~~~~~~~~~~~~~~ 4 | // 5 | // Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | 11 | #ifndef BOOST_ASIO_GNUTLS_VERIFY_CONTEXT_HPP 12 | #define BOOST_ASIO_GNUTLS_VERIFY_CONTEXT_HPP 13 | 14 | #include 15 | 16 | namespace boost { 17 | namespace asio { 18 | namespace gnutls { 19 | 20 | class verify_context 21 | { 22 | public: 23 | using native_handle_type = gnutls_x509_crt_t; 24 | 25 | explicit verify_context(native_handle_type cert) 26 | : m_cert(cert) 27 | {} 28 | 29 | native_handle_type native_handle() { return m_cert; } 30 | 31 | private: 32 | gnutls_x509_crt_t m_cert; 33 | }; 34 | 35 | } // namespace gnutls 36 | } // namespace asio 37 | } // namespace boost 38 | 39 | #endif 40 | 41 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | IndentWidth: 4 4 | UseTab: Never 5 | --- 6 | Language: Cpp 7 | Standard: Cpp11 8 | ColumnLimit: 100 9 | PointerAlignment: Left 10 | AccessModifierOffset: -4 11 | BreakBeforeBraces: Custom 12 | BraceWrapping: 13 | AfterEnum: true 14 | AfterUnion: true 15 | AfterClass: true 16 | AfterStruct: true 17 | AfterFunction: true 18 | AfterCaseLabel: true 19 | AfterNamespace: false 20 | AfterExternBlock: false 21 | AfterControlStatement: Always 22 | BeforeCatch: true 23 | BeforeElse: true 24 | SplitEmptyFunction: false 25 | SplitEmptyRecord: false 26 | SplitEmptyNamespace: false 27 | AlignAfterOpenBracket: true 28 | AlignEscapedNewlines: Left 29 | AlignOperands: true 30 | AlignTrailingComments: true 31 | AllowAllArgumentsOnNextLine: true 32 | AllowAllConstructorInitializersOnNextLine: true 33 | AllowAllParametersOfDeclarationOnNextLine: true 34 | AllowShortBlocksOnASingleLine: Always 35 | AllowShortCaseLabelsOnASingleLine: true 36 | AllowShortFunctionsOnASingleLine: true 37 | AllowShortIfStatementsOnASingleLine: WithoutElse 38 | AllowShortLambdasOnASingleLine: true 39 | AllowShortLoopsOnASingleLine: true 40 | AlwaysBreakAfterReturnType: None 41 | BinPackArguments: false 42 | BinPackParameters: false 43 | BreakBeforeTernaryOperators: true 44 | BreakConstructorInitializers: BeforeComma 45 | BreakInheritanceList: BeforeComma 46 | AlwaysBreakTemplateDeclarations: MultiLine 47 | 48 | -------------------------------------------------------------------------------- /LICENSE_1_0.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /include/boost/asio/gnutls/host_name_verification.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // gnutls/host_name_verification.hpp 3 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | // 5 | // Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | 11 | #ifndef BOOST_ASIO_GNUTLS_HOST_NAME_VERIFICATION_HPP 12 | #define BOOST_ASIO_GNUTLS_HOST_NAME_VERIFICATION_HPP 13 | 14 | #include 15 | 16 | #include 17 | 18 | #include 19 | 20 | namespace boost { 21 | namespace asio { 22 | namespace gnutls { 23 | 24 | // Verifies a certificate against a host_name according to the rules described in RFC 6125. 25 | class host_name_verification 26 | { 27 | public: 28 | typedef bool result_type; 29 | 30 | explicit host_name_verification(std::string host) 31 | : m_host(std::move(host)) 32 | {} 33 | 34 | // Perform certificate verification. 35 | bool operator()(bool preverified, verify_context& ctx) const 36 | { 37 | // Don't bother looking at certificates that have failed pre-verification. 38 | if (!preverified) return false; 39 | 40 | // Return non-zero for a successful match 41 | return gnutls_x509_crt_check_hostname(ctx.native_handle(), m_host.c_str()) != 0; 42 | } 43 | 44 | private: 45 | // The host name to be checked. 46 | std::string m_host; 47 | }; 48 | 49 | } // namespace gnutls 50 | } // namespace asio 51 | } // namespace boost 52 | 53 | #endif // BOOST_ASIO_GNUTLS_HOST_NAME_VERIFICATION_HPP 54 | -------------------------------------------------------------------------------- /test/gnutls/Jamfile.v2: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) 3 | # 4 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | # 7 | 8 | import os ; 9 | import feature ; 10 | 11 | lib gnutls ; 12 | lib crypto ; 13 | 14 | lib socket ; # SOLARIS 15 | lib nsl ; # SOLARIS 16 | lib ws2_32 ; # NT 17 | lib mswsock ; # NT 18 | lib ipv6 ; # HPUX 19 | lib network ; # HAIKU 20 | 21 | local USE_SELECT = 22 | BOOST_ASIO_DISABLE_EPOLL 23 | BOOST_ASIO_DISABLE_KQUEUE 24 | BOOST_ASIO_DISABLE_IOCP 25 | ; 26 | 27 | project 28 | : requirements 29 | ../../include 30 | /boost/system//boost_system 31 | BOOST_ALL_NO_LIB=1 32 | multi 33 | solaris:socket 34 | solaris:nsl 35 | windows:_WIN32_WINNT=0x0501 36 | windows,gcc:ws2_32 37 | windows,gcc:mswsock 38 | windows,gcc-cygwin:__USE_W32_SOCKETS 39 | hpux,gcc:_XOPEN_SOURCE_EXTENDED 40 | hpux:ipv6 41 | haiku:network 42 | ; 43 | 44 | test-suite "asio-gnutls" : 45 | [ compile context_base.cpp ] 46 | [ compile context_base.cpp : $(USE_SELECT) : context_base_select ] 47 | [ compile context.cpp ] 48 | [ compile context.cpp : $(USE_SELECT) : context_select ] 49 | [ compile error.cpp ] 50 | [ compile error.cpp : $(USE_SELECT) : error_select ] 51 | [ compile stream_base.cpp ] 52 | [ compile stream_base.cpp : $(USE_SELECT) : stream_base_select ] 53 | [ compile stream.cpp ] 54 | [ compile stream.cpp : $(USE_SELECT) : stream_select ] 55 | ; 56 | -------------------------------------------------------------------------------- /include/boost/asio/gnutls/stream_base.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // gnutls/stream_base.hpp 3 | // ~~~~~~~~~~~~~~~~~~~~~~ 4 | // 5 | // Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | 11 | #ifndef BOOST_ASIO_GNUTLS_STREAM_BASE_HPP 12 | #define BOOST_ASIO_GNUTLS_STREAM_BASE_HPP 13 | 14 | #include "context.hpp" 15 | 16 | #include 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | namespace boost { 25 | namespace asio { 26 | namespace gnutls { 27 | 28 | class stream_base 29 | { 30 | public: 31 | using error_code = boost::system::error_code; 32 | using native_handle_type = gnutls_session_t; 33 | 34 | enum handshake_type 35 | { 36 | client, 37 | server 38 | }; 39 | 40 | stream_base(context& ctx) { set_context(ctx); } 41 | stream_base(stream_base&& other) 42 | : m_context_impl(std::move(other.m_context_impl)) 43 | {} 44 | stream_base(stream_base const& other) = delete; 45 | virtual ~stream_base() = default; 46 | 47 | context& get_context() const 48 | { 49 | if (!m_context_impl->parent) throw std::logic_error("access to destroyed ssl context"); 50 | 51 | return *m_context_impl->parent; 52 | } 53 | 54 | void set_context(context& ctx) { m_context_impl = ctx.m_impl; } 55 | 56 | virtual native_handle_type native_handle() = 0; 57 | 58 | #ifndef BOOST_NO_EXCEPTIONS 59 | virtual void set_host_name(std::string const& name) = 0; 60 | #endif 61 | virtual error_code set_host_name(std::string const& name, error_code& ec) = 0; 62 | 63 | protected: 64 | std::shared_ptr m_context_impl; 65 | }; 66 | 67 | } // namespace gnutls 68 | } // namespace asio 69 | } // namespace boost 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /test/gnutls/context.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // context.cpp 3 | // ~~~~~~~~~~~ 4 | // 5 | // Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | 11 | // Disable autolinking for unit tests. 12 | #if !defined(BOOST_ALL_NO_LIB) 13 | #define BOOST_ALL_NO_LIB 1 14 | #endif // !defined(BOOST_ALL_NO_LIB) 15 | 16 | // Test that header file is self-contained. 17 | #include 18 | 19 | #include "../unit_test.hpp" 20 | 21 | //------------------------------------------------------------------------------ 22 | 23 | // gnutls_context_compile test 24 | // ~~~~~~~~~~~~~~~~~~~~~~~ 25 | // The following test checks that all public member functions on the class 26 | // gnutls::context compile and link correctly. Runtime failures are ignored. 27 | 28 | namespace gnutls_context_compile { 29 | 30 | bool verify_callback(bool, boost::asio::gnutls::verify_context&) { return false; } 31 | 32 | bool server_name_callback(boost::asio::gnutls::stream_base& s, std::string name) { return false; } 33 | 34 | void test() 35 | { 36 | using namespace boost::asio; 37 | 38 | try 39 | { 40 | boost::asio::gnutls::context context(boost::asio::gnutls::context::tls); 41 | boost::system::error_code ec; 42 | 43 | context.set_verify_mode(gnutls::context::verify_none); 44 | context.set_verify_mode(gnutls::context::verify_none, ec); 45 | 46 | context.set_verify_depth(1); 47 | context.set_verify_depth(1, ec); 48 | 49 | context.set_verify_callback(verify_callback); 50 | context.set_verify_callback(verify_callback, ec); 51 | 52 | // SNI extension 53 | 54 | context.set_server_name_callback(server_name_callback); 55 | context.set_server_name_callback(server_name_callback, ec); 56 | } 57 | catch (std::exception&) 58 | {} 59 | } 60 | 61 | } // namespace gnutls_context_compile 62 | 63 | //------------------------------------------------------------------------------ 64 | 65 | BOOST_ASIO_TEST_SUITE("gnutls/context", BOOST_ASIO_TEST_CASE(gnutls_context_compile::test)) 66 | -------------------------------------------------------------------------------- /include/boost/asio/gnutls/error.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // gnutls/error.hpp 3 | // ~~~~~~~~~~~~~~~ 4 | // 5 | // Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | 11 | #ifndef BOOST_ASIO_GNUTLS_ERROR_HPP 12 | #define BOOST_ASIO_GNUTLS_ERROR_HPP 13 | 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include 20 | 21 | namespace boost { 22 | namespace asio { 23 | namespace gnutls { 24 | 25 | class error_category : public boost::system::error_category 26 | { 27 | public: 28 | char const* name() const BOOST_ASIO_ERROR_CATEGORY_NOEXCEPT { return "GnuTLS"; } 29 | 30 | std::string message(int value) const 31 | { 32 | char const* s = gnutls_strerror(value); 33 | return s ? s : "GnuTLS error"; 34 | } 35 | }; 36 | 37 | namespace error { 38 | 39 | enum stream_errors 40 | { 41 | stream_truncated = 1, 42 | unspecified_system_error = 2, 43 | unexpected_result = 3 44 | }; 45 | 46 | static const boost::system::error_category& get_ssl_category() 47 | { 48 | static error_category instance; 49 | return instance; 50 | } 51 | 52 | static const boost::system::error_category& get_stream_category() { return get_ssl_category(); } 53 | 54 | static const auto& ssl_category BOOST_ASIO_UNUSED_VARIABLE = get_ssl_category(); 55 | static const auto& stream_category BOOST_ASIO_UNUSED_VARIABLE = get_stream_category(); 56 | 57 | } // namespace error 58 | } // namespace gnutls 59 | } // namespace asio 60 | } // namespace boost 61 | 62 | namespace boost { 63 | namespace system { 64 | 65 | template<> struct is_error_code_enum 66 | { 67 | static const bool value = true; 68 | }; 69 | 70 | } // namespace system 71 | } // namespace boost 72 | 73 | namespace boost { 74 | namespace asio { 75 | namespace gnutls { 76 | namespace error { 77 | inline boost::system::error_code make_error_code(stream_errors e) 78 | { 79 | return boost::system::error_code( 80 | static_cast(e), get_stream_category()); 81 | } 82 | 83 | } // namespace error 84 | } // namespace gnutls 85 | } // namespace asio 86 | } // namespace boost 87 | 88 | #endif 89 | -------------------------------------------------------------------------------- /include/boost/asio/gnutls/context_base.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // gnutls/context_base.hpp 3 | // ~~~~~~~~~~~~~~~~~~~~~~~ 4 | // 5 | // Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | 11 | #ifndef BOOST_ASIO_GNUTLS_CONTEXT_BASE_HPP 12 | #define BOOST_ASIO_GNUTLS_CONTEXT_BASE_HPP 13 | 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | namespace boost { 20 | namespace asio { 21 | namespace gnutls { 22 | 23 | class stream_base; 24 | template class stream; 25 | 26 | typedef int verify_mode; 27 | 28 | BOOST_ASIO_STATIC_CONSTANT(int, verify_none = 0x00); 29 | BOOST_ASIO_STATIC_CONSTANT(int, verify_peer = 0x01); 30 | BOOST_ASIO_STATIC_CONSTANT(int, verify_fail_if_no_peer_cert = 0x02); 31 | BOOST_ASIO_STATIC_CONSTANT(int, verify_client_once = 0x04); // Ignored 32 | 33 | class context_base 34 | { 35 | public: 36 | using error_code = boost::system::error_code; 37 | using native_handle_type = gnutls_certificate_credentials_t; 38 | 39 | enum method : int 40 | { 41 | // Any TLS version 42 | tls = 0x0000, 43 | tls_client = 0x0001, 44 | tls_server = 0x0002, 45 | 46 | // Force specific TLS version 47 | tlsv1 = 0x1000, // 0xXY.. => TLS X.Y 48 | tlsv1_client = 0x1001, 49 | tlsv1_server = 0x1002, 50 | tlsv11 = 0x1100, 51 | tlsv11_client = 0x1101, 52 | tlsv11_server = 0x1102, 53 | tlsv12 = 0x1200, 54 | tlsv12_client = 0x1201, 55 | tlsv12_server = 0x1202, 56 | tlsv13 = 0x1300, 57 | tlsv13_client = 0x1301, 58 | tlsv13_server = 0x1302, 59 | 60 | // SSLv3 + TLS (for compatibility only) 61 | sslv23 = 0x0300, 62 | sslv23_client = 0x0301, 63 | sslv23_server = 0x0302, 64 | }; 65 | 66 | enum file_format 67 | { 68 | pem, 69 | der 70 | }; 71 | 72 | typedef long options; 73 | 74 | BOOST_ASIO_STATIC_CONSTANT(long, default_workarounds = 0x01); 75 | BOOST_ASIO_STATIC_CONSTANT(long, single_dh_use = 0x02); // Ignored 76 | BOOST_ASIO_STATIC_CONSTANT(long, no_sslv2 = 0x04); // Ignored, always disabled 77 | BOOST_ASIO_STATIC_CONSTANT(long, no_sslv3 = 0x08); 78 | 79 | using verify_mode = gnutls::verify_mode; 80 | 81 | BOOST_ASIO_STATIC_CONSTANT(int, verify_none = gnutls::verify_none); 82 | BOOST_ASIO_STATIC_CONSTANT(int, verify_peer = gnutls::verify_peer); 83 | BOOST_ASIO_STATIC_CONSTANT(int, 84 | verify_fail_if_no_peer_cert = gnutls::verify_fail_if_no_peer_cert); 85 | BOOST_ASIO_STATIC_CONSTANT(int, verify_client_once = gnutls::verify_client_once); 86 | }; 87 | 88 | } // namespace gnutls 89 | } // namespace asio 90 | } // namespace boost 91 | 92 | #endif 93 | -------------------------------------------------------------------------------- /test/unit_test.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // unit_test.hpp 3 | // ~~~~~~~~~~~~~ 4 | // 5 | // Copyright (c) 2003-2020 Christopher M. Kohlhoff (chris at kohlhoff dot com) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | 11 | #ifndef UNIT_TEST_HPP 12 | #define UNIT_TEST_HPP 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #if defined(__sun) 19 | # include // Needed for lrand48. 20 | #endif // defined(__sun) 21 | 22 | #if defined(__BORLANDC__) 23 | 24 | // Prevent use of intrinsic for strcmp. 25 | # include 26 | # undef strcmp 27 | 28 | // Suppress error about condition always being true. 29 | # pragma option -w-ccc 30 | 31 | #endif // defined(__BORLANDC__) 32 | 33 | #if defined(BOOST_ASIO_MSVC) 34 | # pragma warning (disable:4127) 35 | # pragma warning (push) 36 | # pragma warning (disable:4244) 37 | # pragma warning (disable:4702) 38 | #endif // defined(BOOST_ASIO_MSVC) 39 | 40 | #if !defined(BOOST_ASIO_TEST_IOSTREAM) 41 | # define BOOST_ASIO_TEST_IOSTREAM std::cerr 42 | #endif // !defined(BOOST_ASIO_TEST_IOSTREAM) 43 | 44 | namespace boost { 45 | namespace asio { 46 | namespace detail { 47 | 48 | inline const char*& test_name() 49 | { 50 | static const char* name = 0; 51 | return name; 52 | } 53 | 54 | inline atomic_count& test_errors() 55 | { 56 | static atomic_count errors(0); 57 | return errors; 58 | } 59 | 60 | inline void begin_test_suite(const char* name) 61 | { 62 | boost::asio::detail::test_name(); 63 | boost::asio::detail::test_errors(); 64 | BOOST_ASIO_TEST_IOSTREAM << name << " test suite begins" << std::endl; 65 | } 66 | 67 | inline int end_test_suite(const char* name) 68 | { 69 | BOOST_ASIO_TEST_IOSTREAM << name << " test suite ends" << std::endl; 70 | BOOST_ASIO_TEST_IOSTREAM << "\n*** "; 71 | long errors = boost::asio::detail::test_errors(); 72 | if (errors == 0) 73 | BOOST_ASIO_TEST_IOSTREAM << "No errors detected."; 74 | else if (errors == 1) 75 | BOOST_ASIO_TEST_IOSTREAM << "1 error detected."; 76 | else 77 | BOOST_ASIO_TEST_IOSTREAM << errors << " errors detected." << std::endl; 78 | BOOST_ASIO_TEST_IOSTREAM << std::endl; 79 | return errors == 0 ? 0 : 1; 80 | } 81 | 82 | template 83 | inline void run_test(const char* name) 84 | { 85 | test_name() = name; 86 | long errors_before = boost::asio::detail::test_errors(); 87 | Test(); 88 | if (test_errors() == errors_before) 89 | BOOST_ASIO_TEST_IOSTREAM << name << " passed" << std::endl; 90 | else 91 | BOOST_ASIO_TEST_IOSTREAM << name << " failed" << std::endl; 92 | } 93 | 94 | template 95 | inline void compile_test(const char* name) 96 | { 97 | BOOST_ASIO_TEST_IOSTREAM << name << " passed" << std::endl; 98 | } 99 | 100 | #if defined(BOOST_ASIO_NO_EXCEPTIONS) 101 | 102 | template 103 | void throw_exception(const T& t) 104 | { 105 | BOOST_ASIO_TEST_IOSTREAM << "Exception: " << t.what() << std::endl; 106 | std::abort(); 107 | } 108 | 109 | #endif // defined(BOOST_ASIO_NO_EXCEPTIONS) 110 | 111 | } // namespace detail 112 | } // namespace asio 113 | } // namespace boost 114 | 115 | #define BOOST_ASIO_CHECK(expr) \ 116 | do { if (!(expr)) { \ 117 | BOOST_ASIO_TEST_IOSTREAM << __FILE__ << "(" << __LINE__ << "): " \ 118 | << boost::asio::detail::test_name() << ": " \ 119 | << "check '" << #expr << "' failed" << std::endl; \ 120 | ++boost::asio::detail::test_errors(); \ 121 | } } while (0) 122 | 123 | #define BOOST_ASIO_CHECK_MESSAGE(expr, msg) \ 124 | do { if (!(expr)) { \ 125 | BOOST_ASIO_TEST_IOSTREAM << __FILE__ << "(" << __LINE__ << "): " \ 126 | << boost::asio::detail::test_name() << ": " \ 127 | << msg << std::endl; \ 128 | ++boost::asio::detail::test_errors(); \ 129 | } } while (0) 130 | 131 | #define BOOST_ASIO_WARN_MESSAGE(expr, msg) \ 132 | do { if (!(expr)) { \ 133 | BOOST_ASIO_TEST_IOSTREAM << __FILE__ << "(" << __LINE__ << "): " \ 134 | << boost::asio::detail::test_name() << ": " \ 135 | << msg << std::endl; \ 136 | } } while (0) 137 | 138 | #define BOOST_ASIO_ERROR(msg) \ 139 | do { \ 140 | BOOST_ASIO_TEST_IOSTREAM << __FILE__ << "(" << __LINE__ << "): " \ 141 | << boost::asio::detail::test_name() << ": " \ 142 | << msg << std::endl; \ 143 | ++boost::asio::detail::test_errors(); \ 144 | } while (0) 145 | 146 | #define BOOST_ASIO_TEST_SUITE(name, tests) \ 147 | int main() \ 148 | { \ 149 | boost::asio::detail::begin_test_suite(name); \ 150 | tests \ 151 | return boost::asio::detail::end_test_suite(name); \ 152 | } 153 | 154 | #define BOOST_ASIO_TEST_CASE(test) \ 155 | boost::asio::detail::run_test<&test>(#test); 156 | 157 | #define BOOST_ASIO_COMPILE_TEST_CASE(test) \ 158 | boost::asio::detail::compile_test<&test>(#test); 159 | 160 | inline void null_test() 161 | { 162 | } 163 | 164 | #if defined(__GNUC__) && defined(_AIX) 165 | 166 | // AIX needs this symbol defined in asio, even if it doesn't do anything. 167 | int test_main(int, char**) 168 | { 169 | } 170 | 171 | #endif // defined(__GNUC__) && defined(_AIX) 172 | 173 | #if defined(BOOST_ASIO_MSVC) 174 | # pragma warning (pop) 175 | #endif // defined(BOOST_ASIO_MSVC) 176 | 177 | #endif // UNIT_TEST_HPP 178 | -------------------------------------------------------------------------------- /test/gnutls/stream.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // stream.cpp 3 | // ~~~~~~~~~~ 4 | // 5 | // Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | 11 | // Disable autolinking for unit tests. 12 | #if !defined(BOOST_ALL_NO_LIB) 13 | #define BOOST_ALL_NO_LIB 1 14 | #endif // !defined(BOOST_ALL_NO_LIB) 15 | 16 | // Test that header file is self-contained. 17 | #include 18 | 19 | #include "../unit_test.hpp" 20 | #include 21 | #include 22 | 23 | //------------------------------------------------------------------------------ 24 | 25 | // gnutls_stream_compile test 26 | // ~~~~~~~~~~~~~~~~~~~~~~~ 27 | // The following test checks that all public member functions on the class 28 | // gnutls::stream::socket compile and link correctly. Runtime failures are ignored. 29 | 30 | namespace gnutls_stream_compile { 31 | 32 | bool verify_callback(bool, boost::asio::gnutls::verify_context&) { return false; } 33 | 34 | void handshake_handler(const boost::system::error_code&) {} 35 | 36 | void buffered_handshake_handler(const boost::system::error_code&, std::size_t) {} 37 | 38 | void shutdown_handler(const boost::system::error_code&) {} 39 | 40 | void write_some_handler(const boost::system::error_code&, std::size_t) {} 41 | 42 | void read_some_handler(const boost::system::error_code&, std::size_t) {} 43 | 44 | void test() 45 | { 46 | using namespace boost::asio; 47 | namespace ip = boost::asio::ip; 48 | 49 | try 50 | { 51 | io_context ioc; 52 | char mutable_char_buffer[128] = ""; 53 | const char const_char_buffer[128] = ""; 54 | std::string hostname = "hostname"; 55 | boost::asio::gnutls::context context(boost::asio::gnutls::context::tls); 56 | boost::system::error_code ec; 57 | 58 | // gnutls::stream constructors. 59 | 60 | gnutls::stream stream1(ioc, context); 61 | ip::tcp::socket socket1(ioc, ip::tcp::v4()); 62 | gnutls::stream stream2(socket1, context); 63 | 64 | // basic_io_object functions. 65 | 66 | gnutls::stream::executor_type ex = stream1.get_executor(); 67 | (void)ex; 68 | 69 | // gnutls::stream functions. 70 | 71 | stream1.set_verify_mode(gnutls::verify_none); 72 | stream1.set_verify_mode(gnutls::verify_none, ec); 73 | 74 | stream1.set_verify_depth(1); 75 | stream1.set_verify_depth(1, ec); 76 | 77 | stream1.set_verify_callback(verify_callback); 78 | stream1.set_verify_callback(verify_callback, ec); 79 | 80 | gnutls_session_t session1 = stream1.native_handle(); 81 | (void)session1; 82 | 83 | gnutls::stream::lowest_layer_type& lowest_layer = stream1.lowest_layer(); 84 | (void)lowest_layer; 85 | 86 | const gnutls::stream& stream3 = stream1; 87 | const gnutls::stream::lowest_layer_type& lowest_layer2 = 88 | stream3.lowest_layer(); 89 | (void)lowest_layer2; 90 | 91 | stream1.handshake(gnutls::stream_base::client); 92 | stream1.handshake(gnutls::stream_base::server); 93 | stream1.handshake(gnutls::stream_base::client, ec); 94 | stream1.handshake(gnutls::stream_base::server, ec); 95 | 96 | stream1.handshake(gnutls::stream_base::client, buffer(mutable_char_buffer)); 97 | stream1.handshake(gnutls::stream_base::server, buffer(mutable_char_buffer)); 98 | stream1.handshake(gnutls::stream_base::client, buffer(const_char_buffer)); 99 | stream1.handshake(gnutls::stream_base::server, buffer(const_char_buffer)); 100 | stream1.handshake(gnutls::stream_base::client, buffer(mutable_char_buffer), ec); 101 | stream1.handshake(gnutls::stream_base::server, buffer(mutable_char_buffer), ec); 102 | stream1.handshake(gnutls::stream_base::client, buffer(const_char_buffer), ec); 103 | stream1.handshake(gnutls::stream_base::server, buffer(const_char_buffer), ec); 104 | 105 | stream1.async_handshake(gnutls::stream_base::client, handshake_handler); 106 | stream1.async_handshake(gnutls::stream_base::server, handshake_handler); 107 | 108 | stream1.async_handshake( 109 | gnutls::stream_base::client, buffer(mutable_char_buffer), buffered_handshake_handler); 110 | stream1.async_handshake( 111 | gnutls::stream_base::server, buffer(mutable_char_buffer), buffered_handshake_handler); 112 | stream1.async_handshake( 113 | gnutls::stream_base::client, buffer(const_char_buffer), buffered_handshake_handler); 114 | stream1.async_handshake( 115 | gnutls::stream_base::server, buffer(const_char_buffer), buffered_handshake_handler); 116 | 117 | stream1.shutdown(); 118 | stream1.shutdown(ec); 119 | 120 | stream1.async_shutdown(shutdown_handler); 121 | 122 | stream1.write_some(buffer(mutable_char_buffer)); 123 | stream1.write_some(buffer(const_char_buffer)); 124 | stream1.write_some(buffer(mutable_char_buffer), ec); 125 | stream1.write_some(buffer(const_char_buffer), ec); 126 | 127 | stream1.async_write_some(buffer(mutable_char_buffer), write_some_handler); 128 | stream1.async_write_some(buffer(const_char_buffer), write_some_handler); 129 | 130 | stream1.read_some(buffer(mutable_char_buffer)); 131 | stream1.read_some(buffer(mutable_char_buffer), ec); 132 | 133 | stream1.async_read_some(buffer(mutable_char_buffer), read_some_handler); 134 | 135 | // SNI extension 136 | 137 | stream1.set_host_name(hostname); 138 | stream1.set_host_name(hostname, ec); 139 | } 140 | catch (std::exception&) 141 | { 142 | } 143 | } 144 | 145 | } // namespace gnutls_stream_compile 146 | 147 | //------------------------------------------------------------------------------ 148 | 149 | BOOST_ASIO_TEST_SUITE("gnutls/stream", BOOST_ASIO_TEST_CASE(gnutls_stream_compile::test)) 150 | -------------------------------------------------------------------------------- /include/boost/asio/gnutls/context.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // gnutls/context.hpp 3 | // ~~~~~~~~~~~~~~~~~~ 4 | // 5 | // Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | 11 | #ifndef BOOST_ASIO_GNUTLS_CONTEXT_HPP 12 | #define BOOST_ASIO_GNUTLS_CONTEXT_HPP 13 | 14 | #include "context_base.hpp" 15 | #include "error.hpp" 16 | #include "verify_context.hpp" 17 | 18 | #include 19 | #include 20 | 21 | #ifndef BOOST_NO_EXCEPTIONS 22 | #include 23 | #endif 24 | 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | namespace boost { 34 | namespace asio { 35 | namespace gnutls { 36 | 37 | class stream_base; 38 | template class stream; 39 | 40 | using const_buffer = boost::asio::const_buffer; 41 | 42 | class context : public context_base 43 | { 44 | public: 45 | enum password_purpose 46 | { 47 | for_reading, 48 | for_writing 49 | }; 50 | 51 | explicit context(method m) 52 | : m_impl(std::make_shared(this, m)) 53 | {} 54 | context(context&& other) { *this = std::move(other); } 55 | context(const context& other) = delete; 56 | ~context() 57 | { 58 | if (m_impl) m_impl->parent = nullptr; 59 | } 60 | 61 | context& operator=(context&& other) 62 | { 63 | m_impl = std::move(other.m_impl); 64 | m_impl->parent = this; 65 | return *this; 66 | } 67 | 68 | native_handle_type native_handle() { return m_impl->cred; } 69 | 70 | #ifndef BOOST_NO_EXCEPTIONS 71 | void set_options(options opts) 72 | { 73 | error_code ec; 74 | set_options(opts, ec); 75 | } 76 | #endif 77 | 78 | error_code set_options(options opts, error_code& ec) 79 | { 80 | m_impl->opts |= opts; 81 | return ec; 82 | } 83 | 84 | #ifndef BOOST_NO_EXCEPTIONS 85 | void clear_options() 86 | { 87 | error_code ec; 88 | clear_options(ec); 89 | } 90 | #endif 91 | 92 | void clear_options(error_code& ec) { m_impl->opts = 0; } 93 | 94 | #ifndef BOOST_NO_EXCEPTIONS 95 | void set_default_verify_paths() 96 | { 97 | error_code ec; 98 | set_default_verify_paths(ec); 99 | if (ec) boost::throw_exception(boost::system::system_error(ec)); 100 | } 101 | #endif 102 | 103 | error_code set_default_verify_paths(error_code& ec) 104 | { 105 | // Returns the number of certificates processed 106 | int ret = gnutls_certificate_set_x509_system_trust(m_impl->cred); 107 | if (ret < 0) ec = error_code(ret, error::get_ssl_category()); 108 | return ec; 109 | } 110 | 111 | #ifndef BOOST_NO_EXCEPTIONS 112 | void set_verify_mode(verify_mode v) 113 | { 114 | error_code ec; 115 | set_verify_mode(v, ec); 116 | } 117 | #endif 118 | 119 | error_code set_verify_mode(verify_mode v, error_code& ec) 120 | { 121 | m_impl->verify = v; 122 | return ec; 123 | } 124 | 125 | #ifndef BOOST_NO_EXCEPTIONS 126 | void set_verify_depth(int depth) 127 | { 128 | error_code ec; 129 | set_verify_depth(depth, ec); 130 | } 131 | #endif 132 | 133 | error_code set_verify_depth(int depth, error_code& ec) 134 | { 135 | unsigned int const max_bits = 8200; // default 136 | unsigned int const max_depth = static_cast(depth); 137 | gnutls_certificate_set_verify_limits(m_impl->cred, max_bits, max_depth); 138 | return ec; 139 | } 140 | 141 | #ifndef BOOST_NO_EXCEPTIONS 142 | template void set_verify_callback(VerifyCallback callback) 143 | { 144 | error_code ec; 145 | set_verify_callback(callback, ec); 146 | } 147 | #endif 148 | 149 | template 150 | error_code set_verify_callback(VerifyCallback callback, error_code& ec) 151 | { 152 | m_impl->verify_callback = callback; 153 | return ec; 154 | } 155 | 156 | #ifndef BOOST_NO_EXCEPTIONS 157 | template void set_password_callback(PasswordCallback callback) 158 | { 159 | error_code ec; 160 | set_password_callback(callback, ec); 161 | if (ec) boost::throw_exception(boost::system::system_error(ec)); 162 | } 163 | #endif 164 | 165 | template 166 | error_code set_password_callback(PasswordCallback callback, error_code& ec) 167 | { 168 | m_impl->password_callback = callback; 169 | return ec; 170 | } 171 | 172 | #ifndef BOOST_NO_EXCEPTIONS 173 | void use_certificate_file(std::string const& filename, file_format format) 174 | { 175 | error_code ec; 176 | use_certificate_file(filename, format, ec); 177 | if (ec) boost::throw_exception(boost::system::system_error(ec)); 178 | } 179 | #endif 180 | 181 | error_code use_certificate_file(std::string const& filename, file_format, error_code& ec) 182 | { 183 | m_impl->certificate_file = filename; 184 | return ec; 185 | } 186 | 187 | #ifndef BOOST_NO_EXCEPTIONS 188 | void use_private_key_file(std::string const& filename, file_format format) 189 | { 190 | error_code ec; 191 | use_private_key_file(filename, format, ec); 192 | if (ec) boost::throw_exception(boost::system::system_error(ec)); 193 | } 194 | #endif 195 | 196 | error_code use_private_key_file(std::string const& filename, file_format format, error_code& ec) 197 | { 198 | if (m_impl->certificate_file.empty()) 199 | return ec = boost::asio::error::operation_not_supported; 200 | 201 | std::size_t const max_len = 256; 202 | std::string pass; 203 | if (m_impl->password_callback) pass = m_impl->password_callback(max_len, for_reading); 204 | 205 | m_impl->private_key_file = filename; 206 | int ret = gnutls_certificate_set_x509_key_file2(m_impl->cred, 207 | m_impl->certificate_file.c_str(), 208 | m_impl->private_key_file.c_str(), 209 | format == pem ? GNUTLS_X509_FMT_PEM 210 | : GNUTLS_X509_FMT_DER, 211 | pass.c_str(), 212 | 0); 213 | if (ret != GNUTLS_E_SUCCESS) ec = error_code(ret, error::get_ssl_category()); 214 | return ec; 215 | } 216 | 217 | #ifndef BOOST_NO_EXCEPTIONS 218 | void use_tmp_dh_file(std::string const& filename) 219 | { 220 | error_code ec; 221 | use_tmp_dh_file(filename, ec); 222 | if (ec) boost::throw_exception(boost::system::system_error(ec)); 223 | } 224 | #endif 225 | 226 | error_code use_tmp_dh_file(std::string const&, error_code& ec) 227 | { 228 | // Unnecessary and discouraged on GnuTLS 3.6.0 or later. 229 | // Since 3.6.0, DH parameters are negotiated following RFC7919. 230 | return ec; 231 | } 232 | 233 | #ifndef BOOST_NO_EXCEPTIONS 234 | void use_certificate(const_buffer const& certificate, file_format format) 235 | { 236 | error_code ec; 237 | use_certificate(certificate, format, ec); 238 | if (ec) boost::throw_exception(boost::system::system_error(ec)); 239 | } 240 | #endif 241 | 242 | error_code use_certificate(const_buffer const& certificate, file_format, error_code& ec) 243 | { 244 | m_impl->certificate.assign(static_cast(certificate.data()), 245 | certificate.size()); 246 | return ec; 247 | } 248 | 249 | #ifndef BOOST_NO_EXCEPTIONS 250 | void use_private_key(const_buffer const& private_key, file_format format) 251 | { 252 | error_code ec; 253 | use_private_key(private_key, format, ec); 254 | if (ec) boost::throw_exception(boost::system::system_error(ec)); 255 | } 256 | #endif 257 | 258 | error_code use_private_key(const_buffer const& private_key, file_format format, error_code& ec) 259 | { 260 | if (m_impl->certificate.empty()) return ec = boost::asio::error::operation_not_supported; 261 | 262 | m_impl->private_key.assign(reinterpret_cast(private_key.data()), 263 | private_key.size()); 264 | 265 | gnutls_datum_t cert; 266 | cert.data = reinterpret_cast( 267 | const_cast(m_impl->certificate.c_str())); // must be null terminated 268 | cert.size = m_impl->certificate.size(); 269 | 270 | gnutls_datum_t key; 271 | key.data = reinterpret_cast( 272 | const_cast(m_impl->private_key.c_str())); // must be null terminated 273 | key.size = m_impl->private_key.size(); 274 | 275 | int ret = gnutls_certificate_set_x509_key_mem2(m_impl->cred, 276 | &cert, 277 | &key, 278 | format == pem ? GNUTLS_X509_FMT_PEM 279 | : GNUTLS_X509_FMT_DER, 280 | m_impl->passphrase.c_str(), 281 | 0); 282 | if (ret != GNUTLS_E_SUCCESS) ec = error_code(ret, error::get_ssl_category()); 283 | return ec; 284 | } 285 | 286 | #ifndef BOOST_NO_EXCEPTIONS 287 | void load_verify_file(std::string const& ca_file) 288 | { 289 | error_code ec; 290 | load_verify_file(ca_file, ec); 291 | if (ec) boost::throw_exception(boost::system::system_error(ec)); 292 | } 293 | #endif 294 | 295 | void load_verify_file(std::string const& ca_file, error_code& ec) 296 | { 297 | int ret = gnutls_certificate_set_x509_trust_file(m_impl->cred, 298 | ca_file.c_str(), 299 | GNUTLS_X509_FMT_PEM); 300 | if (ret < 0) ec = error_code(ret, error::get_ssl_category()); 301 | } 302 | 303 | #ifndef BOOST_NO_EXCEPTIONS 304 | void add_verify_path(std::string const& dir) 305 | { 306 | error_code ec; 307 | add_verify_path(dir, ec); 308 | if (ec) boost::throw_exception(boost::system::system_error(ec)); 309 | } 310 | #endif 311 | 312 | void add_verify_path(std::string const& dir, error_code& ec) 313 | { 314 | int ret = gnutls_certificate_set_x509_trust_dir(m_impl->cred, 315 | dir.c_str(), 316 | GNUTLS_X509_FMT_PEM); 317 | if (ret < 0) ec = error_code(ret, error::get_ssl_category()); 318 | } 319 | 320 | #ifndef BOOST_NO_EXCEPTIONS 321 | void use_tmp_dh(const_buffer const& dh) 322 | { 323 | error_code ec; 324 | use_tmp_dh(dh, ec); 325 | if (ec) boost::throw_exception(boost::system::system_error(ec)); 326 | } 327 | #endif 328 | 329 | error_code use_tmp_dh(const_buffer const&, error_code& ec) 330 | { 331 | // Unnecessary and discouraged on GnuTLS 3.6.0 or later. 332 | // Since 3.6.0, DH parameters are negotiated following RFC7919. 333 | return ec; 334 | } 335 | 336 | // ---------- SNI extension ---------- 337 | 338 | #ifndef BOOST_NO_EXCEPTIONS 339 | void set_server_name_callback(std::function cb) 340 | { 341 | error_code ec; 342 | set_server_name_callback(cb, ec); 343 | if (ec) boost::throw_exception(boost::system::system_error(ec)); 344 | } 345 | 346 | #endif 347 | 348 | error_code set_server_name_callback(std::function cb, 349 | error_code& ec) 350 | { 351 | m_impl->server_name_callback = std::move(cb); 352 | return ec; 353 | } 354 | 355 | // ----------------------------------- 356 | 357 | private: 358 | struct impl 359 | { 360 | impl(context* p_, method m_) 361 | : m(m_) 362 | , parent(p_) 363 | { 364 | int ret = gnutls_certificate_allocate_credentials(&cred); 365 | if (ret != GNUTLS_E_SUCCESS) 366 | throw std::runtime_error("gnutls_certificate_allocate_credentials failed: " + 367 | std::string(gnutls_strerror(ret))); 368 | 369 | gnutls_certificate_set_known_dh_params(cred, GNUTLS_SEC_PARAM_MEDIUM); 370 | } 371 | ~impl() { gnutls_certificate_free_credentials(cred); } 372 | 373 | bool is_server() const { return (static_cast(m) & 0x2) != 0; } 374 | unsigned int tls_version() const { return static_cast(m) >> 16; } 375 | 376 | const method m; 377 | context* parent; 378 | 379 | gnutls_certificate_credentials_t cred; 380 | verify_mode verify = 0; 381 | options opts = 0; 382 | 383 | std::string certificate_file, private_key_file; 384 | std::string certificate, private_key; 385 | std::string passphrase; 386 | 387 | std::function verify_callback; 388 | std::function password_callback; 389 | std::function server_name_callback; 390 | }; 391 | 392 | std::shared_ptr m_impl; 393 | 394 | friend class stream_base; 395 | template friend class stream; 396 | }; 397 | 398 | } // namespace gnutls 399 | } // namespace asio 400 | } // namespace boost 401 | 402 | #include 403 | 404 | #endif 405 | -------------------------------------------------------------------------------- /include/boost/asio/gnutls/stream.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // gnutls/stream.hpp 3 | // ~~~~~~~~~~~~~~~~~ 4 | // 5 | // Copyright (c) 2020 Paul-Louis Ageneau (paul-louis at ageneau dot org) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | 11 | #ifndef BOOST_ASIO_GNUTLS_STREAM_HPP 12 | #define BOOST_ASIO_GNUTLS_STREAM_HPP 13 | 14 | #include "context.hpp" 15 | #include "stream_base.hpp" 16 | 17 | #include 18 | 19 | #ifndef BOOST_NO_EXCEPTIONS 20 | #include 21 | #endif 22 | 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | namespace boost { 34 | namespace asio { 35 | namespace gnutls { 36 | 37 | template class stream : public stream_base 38 | { 39 | public: 40 | using next_layer_type = NextLayer; 41 | using lowest_layer_type = 42 | typename std::remove_reference::type::lowest_layer_type; 43 | using executor_type = typename std::remove_reference::type::executor_type; 44 | using io_context = boost::asio::io_context; 45 | 46 | template 47 | stream(Arg&& arg, context& ctx) 48 | : stream_base(ctx) 49 | , m_next_layer(std::forward(arg)) 50 | , m_tls_version(ctx.m_impl->tls_version()) 51 | { 52 | ensure_impl(ctx.m_impl->is_server() ? server : client); 53 | } 54 | 55 | stream(stream&& other) 56 | : stream_base(std::move(other)) 57 | , m_next_layer(std::move(other.m_next_layer)) 58 | , m_verify(std::move(other.m_verify)) 59 | , m_verify_callback(std::move(other.m_verify_callback)) 60 | , m_tls_version(other.m_tls_version) 61 | , m_impl(std::move(other.m_impl)) 62 | { 63 | m_impl->parent = this; 64 | } 65 | 66 | stream(stream const& other) = delete; 67 | ~stream() 68 | { 69 | if (m_impl) 70 | { 71 | m_impl->abort(); 72 | m_impl->parent = nullptr; 73 | } 74 | } 75 | 76 | executor_type get_executor() { return m_next_layer.get_executor(); } 77 | io_context& get_io_context() { return m_next_layer.lowest_layer().get_io_context(); } 78 | const lowest_layer_type& lowest_layer() const { return m_next_layer.lowest_layer(); } 79 | lowest_layer_type& lowest_layer() { return m_next_layer.lowest_layer(); } 80 | const next_layer_type& next_layer() const { return m_next_layer; } 81 | next_layer_type& next_layer() { return m_next_layer; } 82 | 83 | native_handle_type native_handle() { return m_impl->session; } 84 | 85 | #ifndef BOOST_NO_EXCEPTIONS 86 | void set_verify_mode(verify_mode v) 87 | { 88 | error_code ec; 89 | set_verify_mode(v, ec); 90 | } 91 | #endif 92 | 93 | // Warning: for clients only (verify_none or verify_peer) 94 | error_code set_verify_mode(verify_mode v, error_code& ec) 95 | { 96 | m_verify = v; 97 | return ec; 98 | } 99 | 100 | #ifndef BOOST_NO_EXCEPTIONS 101 | void set_verify_depth(int depth) 102 | { 103 | error_code ec; 104 | set_verify_depth(depth, ec); 105 | } 106 | #endif 107 | 108 | // Warning: ignored 109 | error_code set_verify_depth(int, error_code& ec) { return ec; } 110 | 111 | #ifndef BOOST_NO_EXCEPTIONS 112 | template void set_verify_callback(VerifyCallback callback) 113 | { 114 | error_code ec; 115 | set_verify_callback(callback, ec); 116 | } 117 | #endif 118 | 119 | template 120 | error_code set_verify_callback(VerifyCallback callback, error_code& ec) 121 | { 122 | m_verify_callback = callback; 123 | return ec; 124 | } 125 | 126 | template 127 | BOOST_ASIO_INITFN_RESULT_TYPE(HandshakeHandler, void(error_code)) 128 | async_handshake(handshake_type type, HandshakeHandler&& handler) 129 | { 130 | // If you get an error on the following line it means that your handler does 131 | // not meet the documented type requirements for a HandshakeHandler. 132 | BOOST_ASIO_HANDSHAKE_HANDLER_CHECK(HandshakeHandler, handler) type_check; 133 | 134 | async_callable callable(std::move(handler)); 135 | 136 | if (m_impl->handshake_handler || m_impl->is_handshake_done) 137 | { 138 | post(get_executor(), std::bind(callable, boost::asio::error::operation_not_supported)); 139 | return; 140 | } 141 | 142 | error_code ec; 143 | m_next_layer.non_blocking(true, ec); 144 | if (ec) 145 | { 146 | post(get_executor(), std::bind(callable, ec)); 147 | return; 148 | } 149 | 150 | ensure_impl(type); 151 | m_impl->handshake_handler = std::bind(callable, std::placeholders::_1); 152 | m_impl->handle_handshake(); 153 | return callable.get_completion_result(); 154 | } 155 | 156 | template 157 | BOOST_ASIO_INITFN_RESULT_TYPE(BufferedHandshakeHandler, void(error_code, std::size_t)) 158 | async_handshake(handshake_type type, 159 | const ConstBufferSequence& buffers, 160 | BufferedHandshakeHandler&& handler) 161 | { 162 | // If you get an error on the following line it means that your handler does 163 | // not meet the documented type requirements for a BufferedHandshakeHandler. 164 | BOOST_ASIO_BUFFERED_HANDSHAKE_HANDLER_CHECK(BufferedHandshakeHandler, handler) type_check; 165 | 166 | async_callable callable( 167 | std::move(handler)); 168 | 169 | async_handshake(type, std::bind(callable, std::placeholders::_1, std::size_t(0))); 170 | return callable.get_completion_result(); 171 | } 172 | 173 | template 174 | BOOST_ASIO_INITFN_RESULT_TYPE(ShutdownHandler, void(error_code)) 175 | async_shutdown(ShutdownHandler&& handler) 176 | { 177 | // If you get an error on the following line it means that your handler does 178 | // not meet the documented type requirements for a ShutdownHandler. 179 | BOOST_ASIO_SHUTDOWN_HANDLER_CHECK(ShutdownHandler, handler) type_check; 180 | 181 | async_callable callable(std::move(handler)); 182 | 183 | if (m_impl->shutdown_handler || !m_impl->is_handshake_done) 184 | { 185 | post(get_executor(), std::bind(callable, boost::asio::error::operation_not_supported)); 186 | return; 187 | } 188 | 189 | error_code ec; 190 | m_next_layer.non_blocking(true, ec); 191 | if (ec) 192 | { 193 | post(get_executor(), std::bind(callable, ec)); 194 | return; 195 | } 196 | 197 | m_impl->abort(); 198 | m_impl->shutdown_handler = std::bind(callable, std::placeholders::_1); 199 | m_impl->handle_shutdown(); 200 | return callable.get_completion_result(); 201 | } 202 | 203 | template 204 | BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler, void(error_code, std::size_t)) 205 | async_read_some(const MutableBufferSequence& buffers, ReadHandler&& handler) 206 | { 207 | // If you get an error on the following line it means that your handler does 208 | // not meet the documented type requirements for a ReadHandler. 209 | BOOST_ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check; 210 | 211 | async_callable callable(std::move(handler)); 212 | 213 | if (m_impl->read_handler) 214 | { 215 | post(get_executor(), 216 | std::bind(callable, boost::asio::error::operation_not_supported, std::size_t(0))); 217 | return; 218 | } 219 | 220 | error_code ec; 221 | m_next_layer.non_blocking(true, ec); 222 | if (ec) 223 | { 224 | post(get_executor(), std::bind(callable, ec, std::size_t(0))); 225 | return; 226 | } 227 | 228 | std::size_t bytes_added = 0; 229 | for (auto b = buffer_sequence_begin(buffers), end(buffer_sequence_end(buffers)); b != end; 230 | ++b) 231 | { 232 | auto r = *b; // operator -> might be deleted 233 | if (r.size() == 0) continue; 234 | m_impl->read_buffers.push_back(r); 235 | bytes_added += r.size(); 236 | } 237 | 238 | if (bytes_added == 0) 239 | { 240 | // if we're reading 0 bytes, post handler immediately 241 | post(get_executor(), std::bind(callable, error_code(), std::size_t(0))); 242 | return; 243 | } 244 | 245 | m_impl->read_handler = std::bind(callable, std::placeholders::_1, std::placeholders::_2); 246 | m_impl->bytes_read = 0; 247 | m_impl->async_schedule(); 248 | return callable.get_completion_result(); 249 | } 250 | 251 | template 252 | BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler, void(error_code, std::size_t)) 253 | async_write_some(const ConstBufferSequence& buffers, WriteHandler&& handler) 254 | { 255 | // If you get an error on the following line it means that your handler does 256 | // not meet the documented type requirements for a WriteHandler. 257 | BOOST_ASIO_WRITE_HANDLER_CHECK(WriteHandler, handler) type_check; 258 | 259 | async_callable callable(std::move(handler)); 260 | 261 | if (m_impl->write_handler) 262 | { 263 | post(get_executor(), 264 | std::bind(callable, boost::asio::error::operation_not_supported, std::size_t(0))); 265 | return; 266 | } 267 | 268 | error_code ec; 269 | m_next_layer.non_blocking(true, ec); 270 | if (ec) 271 | { 272 | post(get_executor(), std::bind(callable, ec, std::size_t(0))); 273 | return; 274 | } 275 | 276 | size_t bytes_added = 0; 277 | for (auto b = buffer_sequence_begin(buffers), end(buffer_sequence_end(buffers)); b != end; 278 | ++b) 279 | { 280 | auto r = *b; // operator -> might be deleted 281 | if (r.size() == 0) continue; 282 | m_impl->write_buffers.push_back(r); 283 | bytes_added += r.size(); 284 | } 285 | 286 | if (bytes_added == 0) 287 | { 288 | // if we're writing 0 bytes, post handler immediately 289 | post(get_executor(), std::bind(callable, error_code(), std::size_t(0))); 290 | return; 291 | } 292 | 293 | m_impl->write_handler = std::bind(callable, std::placeholders::_1, std::placeholders::_2); 294 | m_impl->bytes_written = 0; 295 | m_impl->async_schedule(); 296 | return callable.get_completion_result(); 297 | } 298 | 299 | void handshake(handshake_type type) 300 | { 301 | error_code ec; 302 | handshake(type, ec); 303 | if (ec) boost::throw_exception(boost::system::system_error(ec)); 304 | } 305 | 306 | error_code handshake(handshake_type type, error_code& ec) 307 | { 308 | if (m_impl->is_handshake_done) return ec = boost::asio::error::operation_not_supported; 309 | 310 | ensure_impl(type); 311 | int ret; 312 | do { 313 | ret = gnutls_handshake(m_impl->session); 314 | } while (ret != GNUTLS_E_SUCCESS && !gnutls_error_is_fatal(ret)); 315 | 316 | if (ret == GNUTLS_E_PREMATURE_TERMINATION) 317 | return ec = error::stream_truncated; 318 | else if (ret != GNUTLS_E_SUCCESS) 319 | return ec = error_code(ret, error::get_ssl_category()); 320 | 321 | m_impl->is_handshake_done = true; 322 | return ec; 323 | } 324 | 325 | #ifndef BOOST_NO_EXCEPTIONS 326 | template 327 | void handshake(handshake_type type, const ConstBufferSequence& buffers) 328 | { 329 | error_code ec; 330 | handshake(type, ec); 331 | if (ec) boost::throw_exception(boost::system::system_error(ec)); 332 | } 333 | #endif 334 | 335 | template 336 | error_code handshake(handshake_type type, const ConstBufferSequence& buffers, error_code& ec) 337 | { 338 | handshake(type, ec); 339 | return ec; 340 | } 341 | 342 | #ifndef BOOST_NO_EXCEPTIONS 343 | void shutdown() 344 | { 345 | error_code ec; 346 | shutdown(ec); 347 | if (ec) boost::throw_exception(boost::system::system_error(ec)); 348 | } 349 | #endif 350 | 351 | error_code shutdown(error_code& ec) 352 | { 353 | int ret; 354 | do { 355 | ret = gnutls_bye(m_impl->session, GNUTLS_SHUT_RDWR); 356 | } while (ret != GNUTLS_E_SUCCESS && !gnutls_error_is_fatal(ret)); 357 | 358 | if (ret == GNUTLS_E_PREMATURE_TERMINATION) 359 | return ec = error::stream_truncated; 360 | else if (ret != GNUTLS_E_SUCCESS) 361 | return ec = error_code(ret, error::get_ssl_category()); 362 | 363 | m_impl->is_handshake_done = false; 364 | return ec; 365 | } 366 | 367 | #ifndef BOOST_NO_EXCEPTIONS 368 | template size_t read_some(const MutableBufferSequence& buffers) 369 | { 370 | error_code ec; 371 | std::size_t bytes_read = read_some(buffers, ec); 372 | if (ec) boost::throw_exception(boost::system::system_error(ec)); 373 | return bytes_read; 374 | } 375 | #endif 376 | 377 | template 378 | size_t read_some(const MutableBufferSequence& buffers, error_code& ec) 379 | { 380 | if (m_impl->read_handler || !m_impl->is_handshake_done) 381 | { 382 | ec = boost::asio::error::operation_not_supported; 383 | return 0; 384 | } 385 | 386 | std::copy(buffer_sequence_begin(buffers), 387 | buffer_sequence_end(buffers), 388 | std::back_inserter(m_impl->read_buffers)); 389 | 390 | std::size_t bytes_read = m_impl->recv_some(ec); 391 | m_impl->read_buffers.clear(); 392 | return bytes_read; 393 | } 394 | 395 | #ifndef BOOST_NO_EXCEPTIONS 396 | template 397 | std::size_t write_some(const ConstBufferSequence& buffers) 398 | { 399 | error_code ec; 400 | std::size_t bytes_written = write_some(buffers, ec); 401 | if (ec) boost::throw_exception(boost::system::system_error(ec)); 402 | return bytes_written; 403 | } 404 | #endif 405 | 406 | template 407 | std::size_t write_some(const ConstBufferSequence& buffers, error_code& ec) 408 | { 409 | if (m_impl->write_handler || !m_impl->is_handshake_done) 410 | { 411 | ec = boost::asio::error::operation_not_supported; 412 | return 0; 413 | } 414 | 415 | std::copy(buffer_sequence_begin(buffers), 416 | buffer_sequence_end(buffers), 417 | std::back_inserter(m_impl->write_buffers)); 418 | 419 | std::size_t bytes_written = m_impl->send_some(ec); 420 | m_impl->write_buffers.clear(); 421 | return bytes_written; 422 | } 423 | 424 | // ---------- SNI extension ---------- 425 | 426 | #ifndef BOOST_NO_EXCEPTIONS 427 | void set_host_name(std::string const& name) 428 | { 429 | error_code ec; 430 | set_host_name(name, ec); 431 | if (ec) boost::throw_exception(boost::system::system_error(ec)); 432 | } 433 | #endif 434 | 435 | error_code set_host_name(std::string const& name, error_code& ec) 436 | { 437 | int ret = 438 | gnutls_server_name_set(m_impl->session, GNUTLS_NAME_DNS, name.c_str(), name.size()); 439 | return ec = ret == GNUTLS_E_SUCCESS ? error_code() 440 | : error_code(ret, error::get_ssl_category()); 441 | } 442 | 443 | // ----------------------------------- 444 | 445 | private: 446 | template class async_callable 447 | { 448 | public: 449 | async_callable(Handler&& h) 450 | : m_impl(std::make_shared(std::move(h))) 451 | {} 452 | 453 | void operator()(Args... args) const 454 | { 455 | m_impl->completion.completion_handler(std::forward(args)...); 456 | } 457 | 458 | auto get_completion_result() { return m_impl->completion.result.get(); } 459 | 460 | private: 461 | struct impl 462 | { 463 | impl(Handler&& h) 464 | : handler(std::move(h)) 465 | , completion(handler) 466 | {} 467 | 468 | Handler handler; 469 | boost::asio::async_completion completion; 470 | }; 471 | 472 | std::shared_ptr m_impl; 473 | }; 474 | 475 | enum class direction 476 | { 477 | none, 478 | read, 479 | write 480 | }; 481 | 482 | next_layer_type m_next_layer; 483 | verify_mode m_verify = -1; 484 | std::function m_verify_callback; 485 | unsigned int m_tls_version; // X*10 + Y => TLS X.Y, 0*10 + Z => SSL Z 486 | 487 | struct impl : public std::enable_shared_from_this 488 | { 489 | impl(stream* p, handshake_type t) 490 | : type(t) 491 | , parent(p) 492 | { 493 | int ret = gnutls_init( 494 | &session, (type == client ? GNUTLS_CLIENT : GNUTLS_SERVER) | GNUTLS_NONBLOCK); 495 | if (ret != GNUTLS_E_SUCCESS) 496 | throw std::runtime_error("gnutls_init failed: " + 497 | std::string(gnutls_strerror(ret))); 498 | 499 | gnutls_session_set_ptr(session, this); 500 | gnutls_handshake_set_post_client_hello_function(session, post_client_hello_func); 501 | 502 | gnutls_transport_set_ptr(session, this); 503 | gnutls_transport_set_push_function(session, push_func); 504 | gnutls_transport_set_pull_function(session, pull_func); 505 | 506 | auto context_impl = parent->m_context_impl; 507 | auto const opts = context_impl->opts; 508 | auto const tls_version = parent->m_tls_version; 509 | 510 | std::ostringstream priority; 511 | priority << "NORMAL"; 512 | if (opts & context::default_workarounds) priority << ":%COMPAT"; 513 | if (tls_version > 0 && tls_version < 10 && !(opts & context::no_sslv3)) 514 | priority << ":+VERS-SSL3.0"; 515 | if (tls_version >= 10) 516 | priority << ":-VERS-TLS-ALL:+VERS-TLS" << (tls_version / 10) << '.' 517 | << (tls_version % 10); 518 | 519 | char const* err_pos = nullptr; 520 | ret = gnutls_priority_set_direct(session, priority.str().c_str(), &err_pos); 521 | if (ret != GNUTLS_E_SUCCESS) 522 | throw std::runtime_error("gnutls_priority_set_direct failed for \"" + 523 | priority.str() + 524 | "\": " + std::string(gnutls_strerror(ret))); 525 | 526 | gnutls_certificate_set_verify_function(context_impl->cred, verify_func); 527 | ret = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, context_impl->cred); 528 | if (ret != GNUTLS_E_SUCCESS) 529 | throw std::runtime_error("gnutls_credentials_set failed: " + 530 | std::string(gnutls_strerror(ret))); 531 | } 532 | 533 | ~impl() { gnutls_deinit(session); } 534 | 535 | template void post(Function&& function) const 536 | { 537 | if (parent) boost::asio::post(parent->get_executor(), std::forward(function)); 538 | } 539 | 540 | void abort() 541 | { 542 | if (auto handler = std::exchange(handshake_handler, nullptr)) 543 | handler(boost::asio::error::operation_aborted); 544 | if (auto handler = std::exchange(shutdown_handler, nullptr)) 545 | handler(boost::asio::error::operation_aborted); 546 | if (auto handler = std::exchange(read_handler, nullptr)) 547 | handler(boost::asio::error::operation_aborted, std::size_t(0)); 548 | if (auto handler = std::exchange(write_handler, nullptr)) 549 | handler(boost::asio::error::operation_aborted, std::size_t(0)); 550 | } 551 | 552 | std::string get_server_name() const 553 | { 554 | char buf[256]; 555 | size_t len = sizeof(buf); 556 | unsigned int type = GNUTLS_NAME_DNS; 557 | int ret = gnutls_server_name_get(session, buf, &len, &type, 0); 558 | return ret == GNUTLS_E_SUCCESS ? std::string(buf, len) : ""; 559 | } 560 | 561 | bool want_read() const { return want_direction == direction::read || read_handler; } 562 | bool want_write() const { return want_direction == direction::write || write_handler; } 563 | 564 | void async_schedule() 565 | { 566 | constexpr auto wait_read = std::remove_reference::type::wait_read; 567 | constexpr auto wait_write = std::remove_reference::type::wait_write; 568 | 569 | if (!parent) return; 570 | auto& next_layer = parent->m_next_layer; 571 | 572 | // Start a read operation if GnuTLS wants one 573 | if (want_read() && !std::exchange(is_reading, true)) 574 | { 575 | if (gnutls_record_check_pending(session) > 0 && read_handler) 576 | handle_read(); 577 | else 578 | next_layer.async_wait(wait_read, 579 | std::bind(&impl::handle_read, 580 | this->shared_from_this(), 581 | std::placeholders::_1)); 582 | } 583 | 584 | // Start a write operation if GnuTLS wants one 585 | if (want_write() && !std::exchange(is_writing, true)) 586 | { 587 | next_layer.async_wait(wait_write, 588 | std::bind(&impl::handle_write, 589 | this->shared_from_this(), 590 | std::placeholders::_1)); 591 | } 592 | } 593 | 594 | void handle_read(error_code ec = {}) 595 | { 596 | namespace error = boost::asio::error; 597 | 598 | is_reading = false; 599 | if (read_handler) 600 | { 601 | if (!ec) bytes_read += recv_some(ec); 602 | 603 | if (ec == error::try_again || ec == error::would_block) return async_schedule(); 604 | 605 | read_buffers.clear(); 606 | auto handler = std::exchange(read_handler, nullptr); 607 | post(std::bind(std::move(handler), ec, std::exchange(bytes_read, std::size_t(0)))); 608 | return; 609 | } 610 | 611 | if (handshake_handler) return handle_handshake(ec); 612 | if (shutdown_handler) return handle_shutdown(ec); 613 | } 614 | 615 | void handle_write(error_code ec = {}) 616 | { 617 | namespace error = boost::asio::error; 618 | 619 | is_writing = false; 620 | if (write_handler) 621 | { 622 | if (!ec) bytes_written += send_some(ec); 623 | 624 | if (ec == error::try_again || ec == error::would_block) return async_schedule(); 625 | 626 | write_buffers.clear(); 627 | auto handler = std::exchange(write_handler, nullptr); 628 | post(std::bind( 629 | std::move(handler), ec, std::exchange(bytes_written, std::size_t(0)))); 630 | } 631 | 632 | if (handshake_handler) return handle_handshake(ec); 633 | if (shutdown_handler) return handle_shutdown(ec); 634 | } 635 | 636 | void handle_handshake(error_code ec = {}) 637 | { 638 | if (!ec) 639 | { 640 | int ret = gnutls_handshake(session); 641 | if (ret == GNUTLS_E_AGAIN) 642 | { 643 | want_direction = gnutls_record_get_direction(session) == 0 ? direction::read 644 | : direction::write; 645 | return async_schedule(); 646 | } 647 | 648 | want_direction = direction::none; 649 | if (ret == GNUTLS_E_SUCCESS) 650 | is_handshake_done = true; 651 | else if (ret == GNUTLS_E_PREMATURE_TERMINATION) 652 | ec = error::stream_truncated; 653 | else 654 | ec = error_code(ret, error::get_ssl_category()); 655 | } 656 | 657 | if (handshake_handler != nullptr) 658 | { 659 | auto handler = std::exchange(handshake_handler, nullptr); 660 | post(std::bind(std::move(handler), ec)); 661 | } 662 | } 663 | 664 | bool is_safe_renegotiation_enabled() 665 | { 666 | return gnutls_safe_renegotiation_status(session) != 0; 667 | } 668 | 669 | void handle_shutdown(error_code ec = {}) 670 | { 671 | if (!ec) 672 | { 673 | int ret = gnutls_bye(session, GNUTLS_SHUT_RDWR); 674 | if (ret == GNUTLS_E_AGAIN) 675 | { 676 | want_direction = gnutls_record_get_direction(session) == 0 ? direction::read 677 | : direction::write; 678 | return async_schedule(); 679 | } 680 | 681 | want_direction = direction::none; 682 | if (ret == GNUTLS_E_SUCCESS) 683 | is_handshake_done = false; 684 | else 685 | ec = error_code(ret, error::get_ssl_category()); 686 | } 687 | 688 | auto handler = std::exchange(shutdown_handler, nullptr); 689 | post(std::bind(std::move(handler), ec)); 690 | } 691 | 692 | std::size_t recv_some(error_code& ec) 693 | { 694 | std::size_t bytes_read = 0; 695 | while (!read_buffers.empty()) 696 | { 697 | auto& front = read_buffers.front(); 698 | int ret = gnutls_record_recv(session, front.data(), front.size()); 699 | if (ret < 0) 700 | { 701 | if (ret == GNUTLS_E_AGAIN) 702 | ec = boost::asio::error::would_block; 703 | else if (ret == GNUTLS_E_PREMATURE_TERMINATION) 704 | ec = error::stream_truncated; 705 | else if (ret == GNUTLS_E_REHANDSHAKE && is_safe_renegotiation_enabled()) 706 | handle_handshake(ec); 707 | else if (gnutls_error_is_fatal(ret)) 708 | ec = error_code(ret, error::get_ssl_category()); 709 | else 710 | continue; 711 | 712 | break; 713 | } 714 | 715 | if (front.size() > 0 && ret == 0) 716 | { 717 | ec = boost::asio::error::eof; 718 | break; 719 | } 720 | 721 | front += ret; 722 | bytes_read += ret; 723 | if (front.size() == 0) read_buffers.pop_front(); 724 | 725 | if (gnutls_record_check_pending(session) == 0) break; 726 | } 727 | 728 | if (bytes_read > 0) ec.clear(); 729 | 730 | return bytes_read; 731 | } 732 | 733 | std::size_t send_some(error_code& ec) 734 | { 735 | gnutls_record_cork(session); 736 | while (!write_buffers.empty()) 737 | { 738 | auto& front = write_buffers.front(); 739 | int ret = gnutls_record_send(session, front.data(), front.size()); 740 | if (ret < 0) break; 741 | 742 | front += ret; 743 | if (front.size() == 0) write_buffers.pop_front(); 744 | } 745 | 746 | std::size_t bytes_written = 0; 747 | do { 748 | int ret = gnutls_record_uncork(session, 0); 749 | if (ret < 0) 750 | { 751 | if (ret == GNUTLS_E_AGAIN) 752 | ec = boost::asio::error::would_block; 753 | else if (ret == GNUTLS_E_PREMATURE_TERMINATION) 754 | ec = error::stream_truncated; 755 | else if (gnutls_error_is_fatal(ret)) 756 | ec = error_code(ret, error::get_ssl_category()); 757 | else 758 | continue; 759 | 760 | break; 761 | } 762 | 763 | bytes_written += ret; 764 | } while (gnutls_record_check_corked(session) > 0); 765 | 766 | if (bytes_written > 0) ec.clear(); 767 | 768 | return bytes_written; 769 | } 770 | 771 | static ssize_t pull_func(void* ptr, void* buffer, std::size_t size) 772 | { 773 | namespace error = boost::asio::error; 774 | 775 | auto* im = static_cast(ptr); 776 | if (!im->parent) 777 | { 778 | gnutls_transport_set_errno(im->session, ECONNRESET); 779 | return -1; 780 | } 781 | 782 | auto& next_layer = im->parent->m_next_layer; 783 | error_code ec; 784 | std::size_t bytes_read = next_layer.read_some(boost::asio::buffer(buffer, size), ec); 785 | if (ec && ec != error::eof && ec != error::connection_reset) // consider reset as close 786 | { 787 | gnutls_transport_set_errno( 788 | im->session, 789 | (ec == error::try_again || ec == error::would_block) ? EAGAIN : ECONNRESET); 790 | return -1; 791 | } 792 | 793 | gnutls_transport_set_errno(im->session, 0); 794 | return ssize_t(bytes_read); 795 | } 796 | 797 | static ssize_t push_func(void* ptr, const void* data, std::size_t len) 798 | { 799 | namespace error = boost::asio::error; 800 | 801 | auto* im = static_cast(ptr); 802 | if (!im->parent) 803 | { 804 | gnutls_transport_set_errno(im->session, ECONNRESET); 805 | return -1; 806 | } 807 | 808 | auto& next_layer = im->parent->m_next_layer; 809 | error_code ec; 810 | std::size_t bytes_written = 811 | next_layer.write_some(boost::asio::const_buffer(data, len), ec); 812 | if (ec) 813 | { 814 | gnutls_transport_set_errno( 815 | im->session, 816 | (ec == error::try_again || ec == error::would_block) ? EAGAIN : ECONNRESET); 817 | return -1; 818 | } 819 | 820 | gnutls_transport_set_errno(im->session, 0); 821 | return ssize_t(bytes_written); 822 | } 823 | 824 | static int verify_func(gnutls_session_t session) 825 | { 826 | auto* im = static_cast(gnutls_session_get_ptr(session)); 827 | if (!im->parent) return GNUTLS_E_INVALID_SESSION; 828 | auto context_impl = im->parent->m_context_impl; 829 | 830 | auto verify = im->parent->m_verify >= 0 ? im->parent->m_verify : context_impl->verify; 831 | auto verify_callback = im->parent->m_verify_callback ? im->parent->m_verify_callback 832 | : context_impl->verify_callback; 833 | 834 | if (!(verify & context::verify_peer)) 835 | return GNUTLS_E_SUCCESS; // no verification requested 836 | 837 | if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) 838 | return GNUTLS_E_NO_CERTIFICATE_FOUND; 839 | 840 | unsigned int count = 0; 841 | gnutls_datum_t const* array = gnutls_certificate_get_peers(session, &count); 842 | if (!array || count == 0) return GNUTLS_E_NO_CERTIFICATE_FOUND; 843 | 844 | gnutls_x509_crt_t cert; 845 | gnutls_x509_crt_init(&cert); 846 | int ret = gnutls_x509_crt_import(cert, &array[0], GNUTLS_X509_FMT_DER); 847 | if (ret != GNUTLS_E_SUCCESS) 848 | { 849 | gnutls_x509_crt_deinit(cert); 850 | return ret; 851 | } 852 | 853 | bool verified = false; 854 | unsigned int status = 0; 855 | ret = gnutls_certificate_verify_peers2(session, &status); 856 | if (ret == GNUTLS_E_SUCCESS && !(status & GNUTLS_CERT_INVALID)) verified = true; 857 | 858 | if (verify_callback) 859 | { 860 | verify_context ctx(cert); 861 | verified = verify_callback(verified, ctx); 862 | } 863 | 864 | gnutls_x509_crt_deinit(cert); 865 | return verified ? GNUTLS_E_SUCCESS : GNUTLS_E_CERTIFICATE_ERROR; 866 | } 867 | 868 | static int post_client_hello_func(gnutls_session_t session) 869 | { 870 | auto* im = static_cast(gnutls_session_get_ptr(session)); 871 | if (!im->parent) return GNUTLS_E_INVALID_SESSION; 872 | auto context_impl = im->parent->m_context_impl; 873 | 874 | auto& callback = context_impl->server_name_callback; 875 | if (!callback) return GNUTLS_E_SUCCESS; 876 | 877 | if (!callback(*im->parent, im->get_server_name())) return GNUTLS_E_UNRECOGNIZED_NAME; 878 | 879 | // context may have been switched 880 | context_impl = im->parent->m_context_impl; 881 | 882 | // set credentials now 883 | gnutls_certificate_set_verify_function(context_impl->cred, verify_func); 884 | int ret = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, context_impl->cred); 885 | if (ret != GNUTLS_E_SUCCESS) return ret; 886 | 887 | // set certificate request 888 | auto const verify = context_impl->verify; 889 | gnutls_certificate_request_t req = GNUTLS_CERT_IGNORE; 890 | if (verify & context::verify_peer) 891 | { 892 | if (verify & context::verify_fail_if_no_peer_cert) 893 | req = GNUTLS_CERT_REQUIRE; 894 | else 895 | req = GNUTLS_CERT_REQUEST; 896 | } 897 | gnutls_certificate_server_set_request(session, req); 898 | 899 | return GNUTLS_E_SUCCESS; 900 | } 901 | 902 | const handshake_type type; 903 | stream* parent; 904 | 905 | gnutls_session_t session; 906 | 907 | direction want_direction = direction::none; 908 | bool is_handshake_done = false; 909 | bool is_reading = false; 910 | bool is_writing = false; 911 | 912 | std::function handshake_handler; 913 | std::function shutdown_handler; 914 | std::function read_handler; 915 | std::function write_handler; 916 | 917 | std::list read_buffers; 918 | std::list write_buffers; 919 | 920 | std::size_t bytes_read = 0; 921 | std::size_t bytes_written = 0; 922 | }; 923 | 924 | std::shared_ptr ensure_impl(handshake_type type) 925 | { 926 | if (!m_impl || m_impl->type != type) 927 | if (auto old = std::exchange(m_impl, std::make_shared(this, type))) 928 | { 929 | old->abort(); 930 | old->parent = nullptr; 931 | } 932 | return m_impl; 933 | } 934 | 935 | std::shared_ptr m_impl; 936 | }; 937 | 938 | } // namespace gnutls 939 | } // namespace asio 940 | } // namespace boost 941 | 942 | #endif 943 | --------------------------------------------------------------------------------