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