diff options
| author | Tim Costen <tim.costen@isode.com> | 2019-08-01 11:05:53 (GMT) |
|---|---|---|
| committer | Tim Costen <timcosten64@gmail.com> | 2019-09-03 10:14:51 (GMT) |
| commit | 415870c04a7e6cabf13e6acf3a94bb0f68732907 (patch) | |
| tree | c1d2a509cdfa341efbc9e1d575ad3bb2383100c0 /Swiften/TLS/OpenSSL/OpenSSLContext.cpp | |
| parent | c62b7b6ce35a77d0a8236ef48155187fe5c30d12 (diff) | |
| download | swift-415870c04a7e6cabf13e6acf3a94bb0f68732907.zip swift-415870c04a7e6cabf13e6acf3a94bb0f68732907.tar.bz2 | |
Add enhanced OpenSSL configuration
Adds TLSOptions to the OpenSSLContext, which invokes a new private
'configure' method which allows various OpenSSL options to be set.
Also add standard verification callbacks and external (via a
std::function field in TLSOptions) to allow the user
to specify their own method which will perform client certificate
checking when a new TLS connection is accepted. Only set up
the internal verifyCertCallback if the user-supplied hook is set.
All callback hooks are set up in the 'configure' method, and only
then if TLSOptions.verifyMode is present (i.e. not defaulted
to boost::none), to preserve compatibility for users of
this class (e.g. Swift) which want to use OpenSSL's own
internal validation functions rather than setting the
callbacks.
Test-information:
Used new code under development in M-Link when setting up a TLSContext,
setting verify-mode=require, and set up verifyCertCallback with a local
method. Making a client TLS connection which includes a client
certificate results in the local verify callback being invoked.
Change-Id: Idbb7279e1711fca8123f430bfca0dcfb65bc8da6
Diffstat (limited to 'Swiften/TLS/OpenSSL/OpenSSLContext.cpp')
| -rw-r--r-- | Swiften/TLS/OpenSSL/OpenSSLContext.cpp | 233 |
1 files changed, 229 insertions, 4 deletions
diff --git a/Swiften/TLS/OpenSSL/OpenSSLContext.cpp b/Swiften/TLS/OpenSSL/OpenSSLContext.cpp index 5692e74..e585766 100644 --- a/Swiften/TLS/OpenSSL/OpenSSLContext.cpp +++ b/Swiften/TLS/OpenSSL/OpenSSLContext.cpp | |||
| @@ -42,6 +42,14 @@ namespace Swift { | |||
| 42 | static const int MAX_FINISHED_SIZE = 4096; | 42 | static const int MAX_FINISHED_SIZE = 4096; |
| 43 | static const int SSL_READ_BUFFERSIZE = 8192; | 43 | static const int SSL_READ_BUFFERSIZE = 8192; |
| 44 | 44 | ||
| 45 | #define SSL_DEFAULT_VERIFY_DEPTH 5 | ||
| 46 | |||
| 47 | // Callback function declarations for certificate verification | ||
| 48 | extern "C" { | ||
| 49 | static int certVerifyCallback(X509_STORE_CTX *store_ctx, void*); | ||
| 50 | static int verifyCallback(int preverify_ok, X509_STORE_CTX *ctx); | ||
| 51 | } | ||
| 52 | |||
| 45 | static void freeX509Stack(STACK_OF(X509)* stack) { | 53 | static void freeX509Stack(STACK_OF(X509)* stack) { |
| 46 | sk_X509_free(stack); | 54 | sk_X509_free(stack); |
| 47 | } | 55 | } |
| @@ -90,7 +98,7 @@ namespace { | |||
| 90 | } | 98 | } |
| 91 | } | 99 | } |
| 92 | 100 | ||
| 93 | OpenSSLContext::OpenSSLContext(Mode mode) : mode_(mode), state_(State::Start) { | 101 | OpenSSLContext::OpenSSLContext(const TLSOptions& options, Mode mode) : mode_(mode), state_(State::Start) { |
| 94 | ensureLibraryInitialized(); | 102 | ensureLibraryInitialized(); |
| 95 | context_ = createSSL_CTX(mode_); | 103 | context_ = createSSL_CTX(mode_); |
| 96 | SSL_CTX_set_options(context_.get(), SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); | 104 | SSL_CTX_set_options(context_.get(), SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); |
| @@ -118,9 +126,9 @@ OpenSSLContext::OpenSSLContext(Mode mode) : mode_(mode), state_(State::Start) { | |||
| 118 | X509_STORE* store = SSL_CTX_get_cert_store(context_.get()); | 126 | X509_STORE* store = SSL_CTX_get_cert_store(context_.get()); |
| 119 | HCERTSTORE systemStore = CertOpenSystemStore(0, "ROOT"); | 127 | HCERTSTORE systemStore = CertOpenSystemStore(0, "ROOT"); |
| 120 | if (systemStore) { | 128 | if (systemStore) { |
| 121 | PCCERT_CONTEXT certContext = NULL; | 129 | PCCERT_CONTEXT certContext = nullptr; |
| 122 | while (true) { | 130 | while (true) { |
| 123 | certContext = CertFindCertificateInStore(systemStore, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_ANY, NULL, certContext); | 131 | certContext = CertFindCertificateInStore(systemStore, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_ANY, nullptr, certContext); |
| 124 | if (!certContext) { | 132 | if (!certContext) { |
| 125 | break; | 133 | break; |
| 126 | } | 134 | } |
| @@ -159,6 +167,7 @@ OpenSSLContext::OpenSSLContext(Mode mode) : mode_(mode), state_(State::Start) { | |||
| 159 | CFRelease(anchorCertificates); | 167 | CFRelease(anchorCertificates); |
| 160 | } | 168 | } |
| 161 | #endif | 169 | #endif |
| 170 | configure(options); | ||
| 162 | } | 171 | } |
| 163 | 172 | ||
| 164 | OpenSSLContext::~OpenSSLContext() { | 173 | OpenSSLContext::~OpenSSLContext() { |
| @@ -175,6 +184,222 @@ void OpenSSLContext::initAndSetBIOs() { | |||
| 175 | SSL_set_bio(handle_.get(), readBIO_, writeBIO_); | 184 | SSL_set_bio(handle_.get(), readBIO_, writeBIO_); |
| 176 | } | 185 | } |
| 177 | 186 | ||
| 187 | // This callback is called by OpenSSL when a client certificate needs to be verified. | ||
| 188 | // In turn, this calls the verification callback which the user | ||
| 189 | // of this OpenSSLContext has configured (if any). | ||
| 190 | static int certVerifyCallback(X509_STORE_CTX* store_ctx, void* arg) | ||
| 191 | { | ||
| 192 | OpenSSLContext* context = static_cast<OpenSSLContext *>(arg); | ||
| 193 | |||
| 194 | // Need to stash store_ctx pointer for use within verification | ||
| 195 | context->setX509StoreContext(store_ctx); | ||
| 196 | |||
| 197 | int ret; | ||
| 198 | |||
| 199 | // This callback shouldn't have been set up if the context doesn't | ||
| 200 | // have a verifyCertCallback set, but it doesn't hurt to double check | ||
| 201 | std::function<int (const TLSContext *)> cb = context->getVerifyCertCallback(); | ||
| 202 | if (cb != nullptr) { | ||
| 203 | ret = cb(static_cast<const OpenSSLContext*>(context)); | ||
| 204 | } else { | ||
| 205 | SWIFT_LOG(warning) << "certVerifyCallback called but context.verifyCertCallback is unset" << std::endl; | ||
| 206 | ret = 0; | ||
| 207 | } | ||
| 208 | |||
| 209 | context->setX509StoreContext(nullptr); | ||
| 210 | return ret; | ||
| 211 | } | ||
| 212 | |||
| 213 | // Convenience function to generate a text representation | ||
| 214 | // of an X509 Name. This information is only used for logging. | ||
| 215 | static std::string X509_NAME_to_text(X509_NAME* name) | ||
| 216 | { | ||
| 217 | std::string nameString; | ||
| 218 | |||
| 219 | if (!name) { | ||
| 220 | return nameString; | ||
| 221 | } | ||
| 222 | |||
| 223 | std::unique_ptr<BIO, decltype(&BIO_free)> io(BIO_new(BIO_s_mem()), &BIO_free); | ||
| 224 | int r = X509_NAME_print_ex(io.get(), name, 0, XN_FLAG_RFC2253); | ||
| 225 | BIO_write(io.get(), "\0", 1); | ||
| 226 | |||
| 227 | if (r > 0) { | ||
| 228 | BUF_MEM* ptr = nullptr; | ||
| 229 | BIO_get_mem_ptr(io.get(), &ptr); | ||
| 230 | nameString = ptr->data; | ||
| 231 | } | ||
| 232 | |||
| 233 | return nameString; | ||
| 234 | } | ||
| 235 | |||
| 236 | // Check depth of certificate chain | ||
| 237 | static int verifyCallback(int preverifyOk, X509_STORE_CTX* ctx) | ||
| 238 | { | ||
| 239 | // Retrieve the pointer to the SSL of the connection currently treated | ||
| 240 | // and the application specific data stored into the SSL object. | ||
| 241 | |||
| 242 | int err = X509_STORE_CTX_get_error(ctx); | ||
| 243 | int depth = X509_STORE_CTX_get_error_depth(ctx); | ||
| 244 | |||
| 245 | SSL* ssl = static_cast<SSL*>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); | ||
| 246 | SSL_CTX* sslctx = ssl ? SSL_get_SSL_CTX(ssl) : nullptr; | ||
| 247 | if (!sslctx) { | ||
| 248 | SWIFT_LOG(error) << "verifyCallback: internal error" << std::endl; | ||
| 249 | return preverifyOk; | ||
| 250 | } | ||
| 251 | |||
| 252 | if (SSL_CTX_get_verify_mode(sslctx) == SSL_VERIFY_NONE) { | ||
| 253 | SWIFT_LOG(info) << "verifyCallback: no verification required" << std::endl; | ||
| 254 | // No verification requested | ||
| 255 | return 1; | ||
| 256 | } | ||
| 257 | |||
| 258 | X509* errCert = X509_STORE_CTX_get_current_cert(ctx); | ||
| 259 | std::string subjectString; | ||
| 260 | if (errCert) { | ||
| 261 | X509_NAME* subjectName = X509_get_subject_name(errCert); | ||
| 262 | subjectString = X509_NAME_to_text(subjectName); | ||
| 263 | } | ||
| 264 | |||
| 265 | // Catch a too long certificate chain. The depth limit set using | ||
| 266 | // SSL_CTX_set_verify_depth() is by purpose set to "limit+1" so | ||
| 267 | // that whenever the "depth>verify_depth" condition is met, we | ||
| 268 | // have violated the limit and want to log this error condition. | ||
| 269 | // We must do it here, because the CHAIN_TOO_LONG error would not | ||
| 270 | // be found explicitly; only errors introduced by cutting off the | ||
| 271 | // additional certificates would be logged. | ||
| 272 | if (depth >= SSL_CTX_get_verify_depth(sslctx)) { | ||
| 273 | preverifyOk = 0; | ||
| 274 | err = X509_V_ERR_CERT_CHAIN_TOO_LONG; | ||
| 275 | X509_STORE_CTX_set_error(ctx, err); | ||
| 276 | } | ||
| 277 | |||
| 278 | if (!preverifyOk) { | ||
| 279 | std::string issuerString; | ||
| 280 | if (errCert) { | ||
| 281 | X509_NAME* issuerName = X509_get_issuer_name(errCert); | ||
| 282 | issuerString = X509_NAME_to_text(issuerName); | ||
| 283 | } | ||
| 284 | SWIFT_LOG(error) << "verifyCallback: verification error" << | ||
| 285 | X509_verify_cert_error_string(err) << " depth: " << | ||
| 286 | depth << " issuer: " << ((issuerString.length() > 0) ? issuerString : "<unknown>") << std::endl; | ||
| 287 | } else { | ||
| 288 | SWIFT_LOG(info) << "verifyCallback: SSL depth: " << depth << " Subject: " << | ||
| 289 | ((subjectString.length() > 0) ? subjectString : "<>") << std::endl; | ||
| 290 | } | ||
| 291 | return preverifyOk; | ||
| 292 | } | ||
| 293 | |||
| 294 | bool OpenSSLContext::configure(const TLSOptions &options) | ||
| 295 | { | ||
| 296 | if (options.cipherSuites) { | ||
| 297 | std::string cipherSuites = *(options.cipherSuites); | ||
| 298 | if (SSL_CTX_set_cipher_list(context_.get(), cipherSuites.c_str()) != 1 ) { | ||
| 299 | SWIFT_LOG(error) << "Failed to set cipher-suites" << std::endl; | ||
| 300 | return false; | ||
| 301 | } | ||
| 302 | } | ||
| 303 | |||
| 304 | if (options.context) { | ||
| 305 | const auto& contextId = *options.context; | ||
| 306 | |||
| 307 | if (SSL_CTX_set_session_id_context(context_.get(), | ||
| 308 | reinterpret_cast<const unsigned char *>(contextId.c_str()), | ||
| 309 | contextId.length()) != 1) { | ||
| 310 | SWIFT_LOG(error) << "Failed to set context-id" << std::endl; | ||
| 311 | return false; | ||
| 312 | } | ||
| 313 | } | ||
| 314 | |||
| 315 | if (options.sessionCacheTimeout) { | ||
| 316 | int scto = *options.sessionCacheTimeout; | ||
| 317 | if (scto <= 0) { | ||
| 318 | SWIFT_LOG(error) << "Invalid value for session-cache-timeout" << std::endl; | ||
| 319 | return false; | ||
| 320 | } | ||
| 321 | (void)SSL_CTX_set_timeout(context_.get(), scto); | ||
| 322 | if (SSL_CTX_get_timeout(context_.get()) != scto) { | ||
| 323 | SWIFT_LOG(error) << "Failed to set session-cache-timeout" << std::endl; | ||
| 324 | return false; | ||
| 325 | } | ||
| 326 | } | ||
| 327 | |||
| 328 | if (options.verifyCertificateCallback) { | ||
| 329 | verifyCertCallback = *options.verifyCertificateCallback; | ||
| 330 | } else { | ||
| 331 | verifyCertCallback = nullptr; | ||
| 332 | } | ||
| 333 | |||
| 334 | if (options.verifyMode) { | ||
| 335 | TLSOptions::VerifyMode verify_mode = *options.verifyMode; | ||
| 336 | int mode; | ||
| 337 | switch (verify_mode) { | ||
| 338 | case TLSOptions::VerifyMode::NONE: | ||
| 339 | mode = SSL_VERIFY_NONE; | ||
| 340 | break; | ||
| 341 | case TLSOptions::VerifyMode::REQUIRED: | ||
| 342 | mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT | SSL_VERIFY_CLIENT_ONCE; | ||
| 343 | break; | ||
| 344 | case TLSOptions::VerifyMode::OPTIONAL: | ||
| 345 | mode = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE; | ||
| 346 | break; | ||
| 347 | } | ||
| 348 | |||
| 349 | // Set up default certificate chain verification depth - may be overridden below | ||
| 350 | SSL_CTX_set_verify_depth(context_.get(), SSL_DEFAULT_VERIFY_DEPTH + 1); | ||
| 351 | |||
| 352 | // Set callbacks up | ||
| 353 | SSL_CTX_set_verify(context_.get(), mode, verifyCallback); | ||
| 354 | |||
| 355 | // Only set up certificate verification callback if a user callback has | ||
| 356 | // been configured via the TLSOptions | ||
| 357 | if (verifyCertCallback != nullptr) { | ||
| 358 | SSL_CTX_set_cert_verify_callback(context_.get(), certVerifyCallback, this); | ||
| 359 | } | ||
| 360 | } | ||
| 361 | |||
| 362 | if (options.verifyDepth) { | ||
| 363 | int depth = *options.verifyDepth; | ||
| 364 | if (depth <= 0) { | ||
| 365 | SWIFT_LOG(error) << "Invalid value for verify-depth" << std::endl; | ||
| 366 | return false; | ||
| 367 | } | ||
| 368 | |||
| 369 | // Increase depth limit by one, so that verifyCallback() will log it | ||
| 370 | SSL_CTX_set_verify_depth(context_.get(), depth + 1); | ||
| 371 | } | ||
| 372 | |||
| 373 | auto updateOptionIfPresent = [this](boost::optional<bool> option, int flag) { | ||
| 374 | if (option) { | ||
| 375 | if (*option) { | ||
| 376 | SSL_CTX_set_options(context_.get(), flag); | ||
| 377 | } | ||
| 378 | else { | ||
| 379 | SSL_CTX_clear_options(context_.get(), flag); | ||
| 380 | } | ||
| 381 | } | ||
| 382 | }; | ||
| 383 | updateOptionIfPresent(options.workaroundMicrosoftSessID, SSL_OP_MICROSOFT_SESS_ID_BUG); | ||
| 384 | updateOptionIfPresent(options.workaroundNetscapeChallenge, SSL_OP_NETSCAPE_CHALLENGE_BUG); | ||
| 385 | updateOptionIfPresent(options.workaroundNetscapeReuseCipherChange, SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG); | ||
| 386 | updateOptionIfPresent(options.workaroundSSLRef2ReuseCertType, SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG); | ||
| 387 | updateOptionIfPresent(options.workaroundMicrosoftBigSSLv3Buffer, SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER); | ||
| 388 | updateOptionIfPresent(options.workaroundSSLeay080ClientDH, SSL_OP_SSLEAY_080_CLIENT_DH_BUG); | ||
| 389 | updateOptionIfPresent(options.workaroundTLSD5, SSL_OP_TLS_D5_BUG); | ||
| 390 | updateOptionIfPresent(options.workaroundTLSBlockPadding, SSL_OP_TLS_BLOCK_PADDING_BUG); | ||
| 391 | updateOptionIfPresent(options.workaroundDontInsertEmptyFragments, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS); | ||
| 392 | updateOptionIfPresent(options.workaroundAll, SSL_OP_ALL); | ||
| 393 | updateOptionIfPresent(options.suppressSSLv2, SSL_OP_NO_SSLv2); | ||
| 394 | updateOptionIfPresent(options.suppressSSLv3, SSL_OP_NO_SSLv3); | ||
| 395 | updateOptionIfPresent(options.suppressTLSv1, SSL_OP_NO_TLSv1); | ||
| 396 | updateOptionIfPresent(options.disableTLSRollBackBug, SSL_OP_TLS_ROLLBACK_BUG); | ||
| 397 | updateOptionIfPresent(options.singleDHUse, SSL_OP_SINGLE_DH_USE); | ||
| 398 | |||
| 399 | return true; | ||
| 400 | } | ||
| 401 | |||
| 402 | |||
| 178 | void OpenSSLContext::accept() { | 403 | void OpenSSLContext::accept() { |
| 179 | assert(mode_ == Mode::Server); | 404 | assert(mode_ == Mode::Server); |
| 180 | handle_ = std::unique_ptr<SSL>(SSL_new(context_.get())); | 405 | handle_ = std::unique_ptr<SSL>(SSL_new(context_.get())); |
| @@ -486,7 +711,7 @@ bool OpenSSLContext::setDiffieHellmanParameters(const ByteArray& parametersInOpe | |||
| 486 | if (bio) { | 711 | if (bio) { |
| 487 | BIO_write(bio.get(), vecptr(parametersInOpenSslDer), parametersInOpenSslDer.size()); | 712 | BIO_write(bio.get(), vecptr(parametersInOpenSslDer), parametersInOpenSslDer.size()); |
| 488 | auto result = 0L; | 713 | auto result = 0L; |
| 489 | if (auto dhparams = d2i_DHparams_bio(bio.get(), NULL)) { | 714 | if (auto dhparams = d2i_DHparams_bio(bio.get(), nullptr)) { |
| 490 | if (handle_) { | 715 | if (handle_) { |
| 491 | result = SSL_set_tmp_dh(handle_.get(), dhparams); | 716 | result = SSL_set_tmp_dh(handle_.get(), dhparams); |
| 492 | } | 717 | } |
Swift