1 //
2 // Copyright (C) 2014-2015 Codership Oy <info@codership.com>
3 //
4 
5 #include "gu_config.hpp"
6 #include "gu_asio.hpp"
7 
8 #include <boost/bind.hpp>
9 
ssl_register_params(gu::Config & conf)10 void gu::ssl_register_params(gu::Config& conf)
11 {
12     // register SSL config parameters
13     conf.add(gu::conf::use_ssl);
14     conf.add(gu::conf::ssl_cipher);
15     conf.add(gu::conf::ssl_compression);
16     conf.add(gu::conf::ssl_key);
17     conf.add(gu::conf::ssl_cert);
18     conf.add(gu::conf::ssl_ca);
19     conf.add(gu::conf::ssl_password_file);
20 }
21 
22 /* checks if all mandatory SSL options are set */
ssl_check_conf(const gu::Config & conf)23 static bool ssl_check_conf(const gu::Config& conf)
24 {
25     using namespace gu;
26 
27     bool explicit_ssl(false);
28 
29     if (conf.is_set(conf::use_ssl))
30     {
31         if  (conf.get<bool>(conf::use_ssl) == false)
32         {
33             return false; // SSL is explicitly disabled
34         }
35         else
36         {
37             explicit_ssl = true;
38         }
39     }
40 
41     int count(0);
42 
43     count += conf.is_set(conf::ssl_key);
44     count += conf.is_set(conf::ssl_cert);
45 
46     bool const use_ssl(explicit_ssl || count > 0);
47 
48     if (use_ssl && count < 2)
49     {
50         gu_throw_error(EINVAL) << "To enable SSL at least both of '"
51                                << conf::ssl_key << "' and '" << conf::ssl_cert
52                                << "' must be set";
53     }
54 
55     return use_ssl;
56 }
57 
ssl_init_options(gu::Config & conf)58 void gu::ssl_init_options(gu::Config& conf)
59 {
60     bool use_ssl(ssl_check_conf(conf));
61 
62     if (use_ssl == true)
63     {
64         // set defaults
65 
66         // cipher list
67         const std::string cipher_list(conf.get(conf::ssl_cipher, ""));
68         conf.set(conf::ssl_cipher, cipher_list);
69 
70         // compression
71         bool compression(conf.get(conf::ssl_compression, true));
72         if (compression == false)
73         {
74             log_info << "disabling SSL compression";
75             sk_SSL_COMP_zero(SSL_COMP_get_compression_methods());
76         }
77         conf.set(conf::ssl_compression, compression);
78 
79 
80         // verify that asio::ssl::context can be initialized with provided
81         // values
82         try
83         {
84             asio::io_service io_service;
85             asio::ssl::context ctx(io_service, asio::ssl::context::sslv23);
86             ssl_prepare_context(conf, ctx);
87         }
88         catch (asio::system_error& ec)
89         {
90             gu_throw_error(EINVAL) << "Initializing SSL context failed: "
91                                    << extra_error_info(ec.code());
92         }
93     }
94 }
95 
96 namespace
97 {
98     // Callback for reading SSL key protection password from file
99     class SSLPasswordCallback
100     {
101     public:
SSLPasswordCallback(const gu::Config & conf)102         SSLPasswordCallback(const gu::Config& conf) : conf_(conf) { }
103 
get_password() const104         std::string get_password() const
105         {
106             std::string   file(conf_.get(gu::conf::ssl_password_file));
107             std::ifstream ifs(file.c_str(), std::ios_base::in);
108 
109             if (ifs.good() == false)
110             {
111                 gu_throw_error(errno) <<
112                     "could not open password file '" << file << "'";
113             }
114 
115             std::string ret;
116             std::getline(ifs, ret);
117             return ret;
118         }
119     private:
120         const gu::Config& conf_;
121     };
122 }
123 
throw_last_SSL_error(const std::string & msg)124 static void throw_last_SSL_error(const std::string& msg)
125 {
126     unsigned long const err(ERR_peek_last_error());
127     char errstr[120] = {0, };
128     ERR_error_string_n(err, errstr, sizeof(errstr));
129     gu_throw_error(EINVAL) << msg << ": "
130                            << err << ": '" << errstr << "'";
131 }
132 
ssl_prepare_context(const gu::Config & conf,asio::ssl::context & ctx,bool verify_peer_cert)133 void gu::ssl_prepare_context(const gu::Config& conf, asio::ssl::context& ctx,
134                              bool verify_peer_cert)
135 {
136     ctx.set_verify_mode(asio::ssl::context::verify_peer |
137                         (verify_peer_cert == true ?
138                          asio::ssl::context::verify_fail_if_no_peer_cert : 0));
139     SSLPasswordCallback cb(conf);
140     ctx.set_password_callback(
141         boost::bind(&SSLPasswordCallback::get_password, &cb));
142 
143     std::string param;
144 
145     try
146     {
147         // In some older OpenSSL versions ECDH engines must be enabled
148         // explicitly. Here we use SSL_CTX_set_ecdh_auto() or
149         // SSL_CTX_set_tmp_ecdh() if present.
150 #if defined(OPENSSL_HAS_SET_ECDH_AUTO)
151         if (!SSL_CTX_set_ecdh_auto(ctx.impl(), 1))
152         {
153             throw_last_SSL_error("SSL_CTX_set_ecdh_auto() failed");
154         }
155 #elif defined(OPENSSL_HAS_SET_TMP_ECDH)
156         {
157             EC_KEY* const ecdh(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
158             if (ecdh == NULL)
159             {
160                 throw_last_SSL_error("EC_KEY_new_by_curve_name() failed");
161             }
162             if (!SSL_CTX_set_tmp_ecdh(ctx.impl(),ecdh))
163             {
164                 throw_last_SSL_error("SSL_CTX_set_tmp_ecdh() failed");
165             }
166             EC_KEY_free(ecdh);
167         }
168 #endif /* OPENSSL_HAS_SET_ECDH_AUTO | OPENSSL_HAS_SET_TMP_ECDH */
169         param = conf::ssl_key;
170         ctx.use_private_key_file(conf.get(param), asio::ssl::context::pem);
171         param = conf::ssl_cert;
172         ctx.use_certificate_file(conf.get(param), asio::ssl::context::pem);
173         param = conf::ssl_ca;
174         ctx.load_verify_file(conf.get(param, conf.get(conf::ssl_cert)));
175         param = conf::ssl_cipher;
176         std::string const value(conf.get(param));
177         if (!value.empty())
178         {
179             if (!SSL_CTX_set_cipher_list(ctx.impl(), value.c_str()))
180             {
181                 throw_last_SSL_error("Error setting SSL cipher list to '"
182                                       + value + "'");
183             }
184             else
185             {
186                 log_info << "SSL cipher list set to '" << value << '\'';
187             }
188         }
189         ctx.set_options(asio::ssl::context::no_sslv2 |
190                         asio::ssl::context::no_sslv3 |
191                         asio::ssl::context::no_tlsv1);
192     }
193     catch (asio::system_error& ec)
194     {
195         gu_throw_error(EINVAL) << "Bad value '" << conf.get(param, "")
196                                << "' for SSL parameter '" << param
197                                << "': " << extra_error_info(ec.code());
198     }
199     catch (gu::NotSet& ec)
200     {
201         gu_throw_error(EINVAL) << "Missing required value for SSL parameter '"
202                                << param << "'";
203     }
204 }
205