diff options
author | Michael Zhivich <mzhivich@akamai.com> | 2019-05-20 15:07:04 -0400 |
---|---|---|
committer | Julian Andres Klode <julian.klode@canonical.com> | 2019-05-21 14:53:01 +0200 |
commit | f3e109d40937dbf90994bcf74b76837ec670205c (patch) | |
tree | 5c1139fbe0300e8768a84fa1502d19ff0ce94f90 | |
parent | b758eea96f89c929f14ca558e9ec2b2e82bf485a (diff) |
methods: https: handle requests for TLS re-handshake
When accessing repository protected by TLS mutual auth, apt may receive
a "re-handshake" request from the server, which must be handled
in order for download to proceed.
This situation arises when the server requests a client certificate
based on the resource path provided in the GET request, after the inital
handshake in UnwrapTLS() has already occurred, and a secure connection
has been established.
This issue has been observed with Artifactory-backed Debian repository.
To address the issue, split TLS handshake code out into its own method
in TlsFd, and call it when GNUTLS_E_REHANDSHAKE error is received.
Signed-off-by: Michael Zhivich <mzhivich@akamai.com>
(merged from Debian/apt#93)
LP: #1829861
-rw-r--r-- | methods/connect.cc | 79 |
1 files changed, 51 insertions, 28 deletions
diff --git a/methods/connect.cc b/methods/connect.cc index e5d17a2f5..1d6f8919d 100644 --- a/methods/connect.cc +++ b/methods/connect.cc @@ -808,6 +808,7 @@ struct TlsFd : public MethodFd gnutls_session_t session; gnutls_certificate_credentials_t credentials; std::string hostname; + unsigned long Timeout; int Fd() APT_OVERRIDE { return UnderlyingFd->Fd(); } @@ -820,9 +821,56 @@ struct TlsFd : public MethodFd return HandleError(gnutls_record_send(session, buf, count)); } + ssize_t DoTLSHandshake() + { + int err; + // Do the handshake. Our socket is non-blocking, so we need to call WaitFd() + // accordingly. + do + { + err = gnutls_handshake(session); + if ((err == GNUTLS_E_INTERRUPTED || err == GNUTLS_E_AGAIN) && + WaitFd(this->Fd(), gnutls_record_get_direction(session) == 1, Timeout) == false) + { + _error->Errno("select", "Could not wait for server fd"); + return err; + } + } while (err < 0 && gnutls_error_is_fatal(err) == 0); + + if (err < 0) + { + // Print reason why validation failed. + if (err == GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR) + { + gnutls_datum_t txt; + auto type = gnutls_certificate_type_get(session); + auto status = gnutls_session_get_verify_cert_status(session); + if (gnutls_certificate_verification_status_print(status, type, &txt, 0) == 0) + { + _error->Error("Certificate verification failed: %s", txt.data); + } + gnutls_free(txt.data); + } + _error->Error("Could not handshake: %s", gnutls_strerror(err)); + } + return err; + } + template <typename T> T HandleError(T err) { + // Server may request re-handshake if client certificates need to be provided + // based on resource requested + if (err == GNUTLS_E_REHANDSHAKE) + { + int rc = DoTLSHandshake(); + // Only reset err if DoTLSHandshake() fails. + // Otherwise, we want to follow the original error path and set errno to EAGAIN + // so that the request is retried. + if (rc < 0) + err = rc; + } + if (err < 0 && gnutls_error_is_fatal(err)) errno = EIO; else if (err < 0) @@ -859,6 +907,7 @@ ResultState UnwrapTLS(std::string Host, std::unique_ptr<MethodFd> &Fd, tlsFd->hostname = Host; tlsFd->UnderlyingFd = MethodFd::FromFd(-1); // For now + tlsFd->Timeout = Timeout; if ((err = gnutls_init(&tlsFd->session, GNUTLS_CLIENT | GNUTLS_NONBLOCK)) < 0) { @@ -992,37 +1041,11 @@ ResultState UnwrapTLS(std::string Host, std::unique_ptr<MethodFd> &Fd, tlsFd->UnderlyingFd = std::move(Fd); Fd.reset(tlsFd); - // Do the handshake. Our socket is non-blocking, so we need to call WaitFd() - // accordingly. - do - { - err = gnutls_handshake(tlsFd->session); - if ((err == GNUTLS_E_INTERRUPTED || err == GNUTLS_E_AGAIN) && - WaitFd(Fd->Fd(), gnutls_record_get_direction(tlsFd->session) == 1, Timeout) == false) - { - _error->Errno("select", "Could not wait for server fd"); - return ResultState::TRANSIENT_ERROR; - } - } while (err < 0 && gnutls_error_is_fatal(err) == 0); + // Do the handshake. + err = tlsFd->DoTLSHandshake(); if (err < 0) - { - // Print reason why validation failed. - if (err == GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR) - { - gnutls_datum_t txt; - auto type = gnutls_certificate_type_get(tlsFd->session); - auto status = gnutls_session_get_verify_cert_status(tlsFd->session); - if (gnutls_certificate_verification_status_print(status, - type, &txt, 0) == 0) - { - _error->Error("Certificate verification failed: %s", txt.data); - } - gnutls_free(txt.data); - } - _error->Error("Could not handshake: %s", gnutls_strerror(err)); return ResultState::FATAL_ERROR; - } return ResultState::SUCCESSFUL; } |