summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Zhivich <mzhivich@akamai.com>2019-05-20 15:07:04 -0400
committerJulian Andres Klode <julian.klode@canonical.com>2019-05-21 14:53:01 +0200
commitf3e109d40937dbf90994bcf74b76837ec670205c (patch)
tree5c1139fbe0300e8768a84fa1502d19ff0ce94f90
parentb758eea96f89c929f14ca558e9ec2b2e82bf485a (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.cc79
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;
}