]> source.dussan.org Git - jgit.git/commitdiff
Handle SSL handshake failures in TransportHttp 22/104222/7
authorThomas Wolf <thomas.wolf@paranor.ch>
Sat, 2 Sep 2017 11:20:48 +0000 (13:20 +0200)
committerMatthias Sohn <matthias.sohn@sap.com>
Wed, 13 Sep 2017 21:23:08 +0000 (23:23 +0200)
When a https connection could not be established because the SSL
handshake was unsuccessful, TransportHttp would unconditionally
throw a TransportException.

Other https clients like web browsers or also some SVN clients
handle this more gracefully. If there's a problem with the server
certificate, they inform the user and give him a possibility to
connect to the server all the same.

In git, this would correspond to dynamically setting http.sslVerify
to false for the server.

Implement this using the CredentialsProvider to inform and ask the
user. We offer three choices:

1. skip SSL verification for the current git operation, or
2. skip SSL verification for the server always from now on for
   requests originating from the current repository, or
3. always skip SSL verification for the server from now on.

For (1), we just suppress SSL verification for the current instance of
TransportHttp.

For (2), we store a http.<uri>.sslVerify = false setting for the
original URI in the repo config.

For (3), we store the http.<uri>.sslVerify setting in the git user
config.

Adapt the SmartClientSmartServerSslTest such that it uses this
mechanism instead of setting http.sslVerify up front.

Improve SimpleHttpServer to enable setting it up also with HTTPS
support in anticipation of an EGit SWTbot UI test verifying that
cloning via HTTPS from a server that has a certificate that doesn't
validate pops up the correct dialog, and that cloning subsequently
proceeds successfully if the user decides to skip SSL verification.

Bug: 374703
Change-Id: Ie1abada9a3d389ad4d8d52c2d5265d2764e3fb0e
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerSslTest.java
org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/SimpleHttpServer.java
org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java

index 47a84354fb3863c299884617b3fa14503318d769..7deb0d85a0ce2a5d807a95392a4b308bce03e731 100644 (file)
@@ -68,6 +68,7 @@ import org.eclipse.jetty.servlet.FilterHolder;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
 import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.errors.UnsupportedCredentialItem;
 import org.eclipse.jgit.http.server.GitServlet;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.junit.http.AccessEvent;
@@ -78,16 +79,16 @@ import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevBlob;
 import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.transport.CredentialItem;
+import org.eclipse.jgit.transport.CredentialsProvider;
 import org.eclipse.jgit.transport.HttpTransport;
 import org.eclipse.jgit.transport.Transport;
 import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
 import org.eclipse.jgit.transport.http.HttpConnectionFactory;
 import org.eclipse.jgit.transport.http.JDKHttpConnectionFactory;
 import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory;
-import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.HttpSupport;
-import org.eclipse.jgit.util.SystemReader;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -97,6 +98,52 @@ import org.junit.runners.Parameterized.Parameters;
 @RunWith(Parameterized.class)
 public class SmartClientSmartServerSslTest extends HttpTestCase {
 
+       // We run these tests with a server on localhost with a self-signed
+       // certificate. We don't do authentication tests here, so there's no need
+       // for username and password.
+       //
+       // But the server certificate will not validate. We know that Transport will
+       // ask whether we trust the server all the same. This credentials provider
+       // blindly trusts the self-signed certificate by answering "Yes" to all
+       // questions.
+       private CredentialsProvider testCredentials = new CredentialsProvider() {
+
+               @Override
+               public boolean isInteractive() {
+                       return false;
+               }
+
+               @Override
+               public boolean supports(CredentialItem... items) {
+                       for (CredentialItem item : items) {
+                               if (item instanceof CredentialItem.InformationalMessage) {
+                                       continue;
+                               }
+                               if (item instanceof CredentialItem.YesNoType) {
+                                       continue;
+                               }
+                               return false;
+                       }
+                       return true;
+               }
+
+               @Override
+               public boolean get(URIish uri, CredentialItem... items)
+                               throws UnsupportedCredentialItem {
+                       for (CredentialItem item : items) {
+                               if (item instanceof CredentialItem.InformationalMessage) {
+                                       continue;
+                               }
+                               if (item instanceof CredentialItem.YesNoType) {
+                                       ((CredentialItem.YesNoType) item).setValue(true);
+                                       continue;
+                               }
+                               return false;
+                       }
+                       return true;
+               }
+       };
+
        private URIish remoteURI;
 
        private URIish secureURI;
@@ -150,16 +197,6 @@ public class SmartClientSmartServerSslTest extends HttpTestCase {
                src.update(master, B);
 
                src.update("refs/garbage/a/very/long/ref/name/to/compress", B);
-
-               FileBasedConfig userConfig = SystemReader.getInstance()
-                               .openUserConfig(null, FS.DETECTED);
-               userConfig.setBoolean("http",
-                               "https://" + secureURI.getHost() + ':' + server.getSecurePort(),
-                               "sslVerify", false);
-               userConfig.setBoolean("http",
-                               "http://" + remoteURI.getHost() + ':' + server.getPort(),
-                               "sslVerify", false);
-               userConfig.save();
        }
 
        private ServletContextHandler addNormalContext(GitServlet gs, TestRepository<Repository> src, String srcName) {
@@ -241,6 +278,7 @@ public class SmartClientSmartServerSslTest extends HttpTestCase {
                assertFalse(dst.hasObject(A_txt));
 
                try (Transport t = Transport.open(dst, secureURI)) {
+                       t.setCredentialsProvider(testCredentials);
                        t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
                }
                assertTrue(dst.hasObject(A_txt));
@@ -258,6 +296,7 @@ public class SmartClientSmartServerSslTest extends HttpTestCase {
 
                URIish cloneFrom = extendPath(remoteURI, "/https");
                try (Transport t = Transport.open(dst, cloneFrom)) {
+                       t.setCredentialsProvider(testCredentials);
                        t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
                }
                assertTrue(dst.hasObject(A_txt));
@@ -275,6 +314,7 @@ public class SmartClientSmartServerSslTest extends HttpTestCase {
 
                URIish cloneFrom = extendPath(secureURI, "/back");
                try (Transport t = Transport.open(dst, cloneFrom)) {
+                       t.setCredentialsProvider(testCredentials);
                        t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
                        fail("Should have failed (redirect from https to http)");
                } catch (TransportException e) {
@@ -282,4 +322,20 @@ public class SmartClientSmartServerSslTest extends HttpTestCase {
                }
        }
 
+       @Test
+       public void testInitialClone_SslFailure() throws Exception {
+               Repository dst = createBareRepository();
+               assertFalse(dst.hasObject(A_txt));
+
+               try (Transport t = Transport.open(dst, secureURI)) {
+                       // Set a credentials provider that doesn't handle questions
+                       t.setCredentialsProvider(
+                                       new UsernamePasswordCredentialsProvider("any", "anypwd"));
+                       t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
+                       fail("Should have failed (SSL certificate not trusted)");
+               } catch (TransportException e) {
+                       assertTrue(e.getMessage().contains("Secure connection"));
+               }
+       }
+
 }
index 605c69a844f82fe15ba52d90b275d51765f3f9e9..0ea0721286fa8f83d19fb7e8cc0eb8e362a8d043 100644 (file)
@@ -69,9 +69,15 @@ public class SimpleHttpServer {
 
        private URIish uri;
 
+       private URIish secureUri;
+
        public SimpleHttpServer(Repository repository) {
+               this(repository, false);
+       }
+
+       public SimpleHttpServer(Repository repository, boolean withSsl) {
                this.db = repository;
-               server = new AppServer();
+               server = new AppServer(0, withSsl ? 0 : -1);
        }
 
        public void start() throws Exception {
@@ -79,6 +85,10 @@ public class SimpleHttpServer {
                server.setUp();
                final String srcName = db.getDirectory().getName();
                uri = toURIish(sBasic, srcName);
+               int sslPort = server.getSecurePort();
+               if (sslPort > 0) {
+                       secureUri = uri.setPort(sslPort).setScheme("https");
+               }
        }
 
        public void stop() throws Exception {
@@ -89,6 +99,10 @@ public class SimpleHttpServer {
                return uri;
        }
 
+       public URIish getSecureUri() {
+               return secureUri;
+       }
+
        private ServletContextHandler smart(final String path) {
                GitServlet gs = new GitServlet();
                gs.setRepositoryResolver(new RepositoryResolver<HttpServletRequest>() {
index 66699be359eb9177db1d7c930c3ae91a4c618ee3..92bd1473e84c641a1db4ce83b99219e36bbca6b8 100644 (file)
@@ -607,6 +607,14 @@ sourceIsNotAWildcard=Source is not a wildcard.
 sourceRefDoesntResolveToAnyObject=Source ref {0} doesn''t resolve to any object.
 sourceRefNotSpecifiedForRefspec=Source ref not specified for refspec: {0}
 squashCommitNotUpdatingHEAD=Squash commit -- not updating HEAD
+sslFailureExceptionMessage=Secure connection to {0} could not be stablished because of SSL problems
+sslFailureInfo=A secure connection to {0}\ncould not be established because the server''s certificate could not be validated.
+sslFailureCause=SSL reported: {0}
+sslFailureTrustExplanation=Do you want to skip SSL verification for this server?
+sslTrustAlways=Always skip SSL verification for this server from now on
+sslTrustForRepo=Skip SSL verification for git operations for repository {0}
+sslTrustNow=Skip SSL verification for this single git operation
+sslVerifyCannotSave=Could not save setting for http.sslVerify
 staleRevFlagsOn=Stale RevFlags on {0}
 startingReadStageWithoutWrittenRequestDataPendingIsNotSupported=Starting read stage without written request data pending is not supported
 stashApplyConflict=Applying stashed changes resulted in a conflict
index 0f1c8e984ebc883abdddb99fe00c623d2abc0f32..36e09680d6b7d7794d2a0e5322bbbe8a9aff6c86 100644 (file)
@@ -666,6 +666,14 @@ public class JGitText extends TranslationBundle {
        /***/ public String sourceRefDoesntResolveToAnyObject;
        /***/ public String sourceRefNotSpecifiedForRefspec;
        /***/ public String squashCommitNotUpdatingHEAD;
+       /***/ public String sslFailureExceptionMessage;
+       /***/ public String sslFailureInfo;
+       /***/ public String sslFailureCause;
+       /***/ public String sslFailureTrustExplanation;
+       /***/ public String sslTrustAlways;
+       /***/ public String sslTrustForRepo;
+       /***/ public String sslTrustNow;
+       /***/ public String sslVerifyCannotSave;
        /***/ public String staleRevFlagsOn;
        /***/ public String startingReadStageWithoutWrittenRequestDataPendingIsNotSupported;
        /***/ public String stashApplyConflict;
index 73f596db750bd6e450572eb550eadbc2abc1b17a..1435e99195a2624742804ba7c360496d8e292a91 100644 (file)
@@ -71,15 +71,20 @@ public class HttpConfig {
 
        private static final String FTP = "ftp"; //$NON-NLS-1$
 
-       private static final String HTTP = "http"; //$NON-NLS-1$
+       /** git config section key for http settings. */
+       public static final String HTTP = "http"; //$NON-NLS-1$
 
-       private static final String FOLLOW_REDIRECTS_KEY = "followRedirects"; //$NON-NLS-1$
+       /** git config key for the "followRedirects" setting. */
+       public static final String FOLLOW_REDIRECTS_KEY = "followRedirects"; //$NON-NLS-1$
 
-       private static final String MAX_REDIRECTS_KEY = "maxRedirects"; //$NON-NLS-1$
+       /** git config key for the "maxRedirects" setting. */
+       public static final String MAX_REDIRECTS_KEY = "maxRedirects"; //$NON-NLS-1$
 
-       private static final String POST_BUFFER_KEY = "postBuffer"; //$NON-NLS-1$
+       /** git config key for the "postBuffer" setting. */
+       public static final String POST_BUFFER_KEY = "postBuffer"; //$NON-NLS-1$
 
-       private static final String SSL_VERIFY_KEY = "sslVerify"; //$NON-NLS-1$
+       /** git config key for the "sslVerify" setting. */
+       public static final String SSL_VERIFY_KEY = "sslVerify"; //$NON-NLS-1$
 
        private static final String MAX_REDIRECT_SYSTEM_PROPERTY = "http.maxRedirects"; //$NON-NLS-1$
 
index 937c3688573246f410f90387e48dc2991f0a3dd6..ab5fc5f5c76e07d53ea782b0f14e5a1ebf8eb256 100644 (file)
@@ -72,6 +72,9 @@ import java.net.Proxy;
 import java.net.ProxySelector;
 import java.net.URISyntaxException;
 import java.net.URL;
+import java.security.cert.CertPathBuilderException;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.CertificateException;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -87,6 +90,8 @@ import java.util.TreeMap;
 import java.util.zip.GZIPInputStream;
 import java.util.zip.GZIPOutputStream;
 
+import javax.net.ssl.SSLHandshakeException;
+
 import org.eclipse.jgit.errors.NoRemoteRepositoryException;
 import org.eclipse.jgit.errors.NotSupportedException;
 import org.eclipse.jgit.errors.PackProtocolException;
@@ -99,13 +104,16 @@ import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
 import org.eclipse.jgit.lib.SymbolicRef;
 import org.eclipse.jgit.transport.HttpAuthMethod.Type;
 import org.eclipse.jgit.transport.HttpConfig.HttpRedirectMode;
 import org.eclipse.jgit.transport.http.HttpConnection;
+import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.HttpSupport;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.SystemReader;
 import org.eclipse.jgit.util.TemporaryBuffer;
 import org.eclipse.jgit.util.io.DisabledOutputStream;
 import org.eclipse.jgit.util.io.UnionInputStream;
@@ -249,7 +257,7 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
 
        private URL objectsUrl;
 
-       final HttpConfig http;
+       private final HttpConfig http;
 
        private final ProxySelector proxySelector;
 
@@ -259,12 +267,17 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
 
        private Map<String, String> headers;
 
+       private boolean sslVerify;
+
+       private boolean sslFailure = false;
+
        TransportHttp(final Repository local, final URIish uri)
                        throws NotSupportedException {
                super(local, uri);
                setURI(uri);
                http = new HttpConfig(local.getConfig(), uri);
                proxySelector = ProxySelector.getDefault();
+               sslVerify = http.isSslVerify();
        }
 
        private URL toURL(URIish urish) throws MalformedURLException {
@@ -301,6 +314,7 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
                setURI(uri);
                http = new HttpConfig(uri);
                proxySelector = ProxySelector.getDefault();
+               sslVerify = http.isSslVerify();
        }
 
        /**
@@ -549,6 +563,9 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
                                throw e;
                        } catch (TransportException e) {
                                throw e;
+                       } catch (SSLHandshakeException e) {
+                               handleSslFailure(e);
+                               continue; // Re-try
                        } catch (IOException e) {
                                if (authMethod.getType() != HttpAuthMethod.Type.NONE) {
                                        if (ignoreTypes == null) {
@@ -569,6 +586,120 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
                }
        }
 
+       private static class CredentialItems {
+               CredentialItem.InformationalMessage message;
+
+               /** Trust the server for this git operation */
+               CredentialItem.YesNoType now;
+
+               /**
+                * Trust the server for all git operations from this repository; may be
+                * {@code null} if the transport was created via
+                * {@link #TransportHttp(URIish)}.
+                */
+               CredentialItem.YesNoType forRepo;
+
+               /** Always trust the server from now on. */
+               CredentialItem.YesNoType always;
+
+               public CredentialItem[] items() {
+                       if (forRepo == null) {
+                               return new CredentialItem[] { message, now, always };
+                       } else {
+                               return new CredentialItem[] { message, now, forRepo, always };
+                       }
+               }
+       }
+
+       private void handleSslFailure(Throwable e) throws TransportException {
+               if (sslFailure || !trustInsecureSslConnection(e.getCause())) {
+                       throw new TransportException(uri,
+                                       MessageFormat.format(
+                                                       JGitText.get().sslFailureExceptionMessage,
+                                                       currentUri.setPass(null)),
+                                       e);
+               }
+               sslFailure = true;
+       }
+
+       private boolean trustInsecureSslConnection(Throwable cause) {
+               if (cause instanceof CertificateException
+                               || cause instanceof CertPathBuilderException
+                               || cause instanceof CertPathValidatorException) {
+                       // Certificate expired or revoked, PKIX path building not
+                       // possible, self-signed certificate, host does not match ...
+                       CredentialsProvider provider = getCredentialsProvider();
+                       if (provider != null) {
+                               CredentialItems trust = constructSslTrustItems(cause);
+                               CredentialItem[] items = trust.items();
+                               if (provider.supports(items)) {
+                                       boolean answered = provider.get(uri, items);
+                                       if (answered) {
+                                               // Not canceled
+                                               boolean trustNow = trust.now.getValue();
+                                               boolean trustLocal = trust.forRepo != null
+                                                               && trust.forRepo.getValue();
+                                               boolean trustAlways = trust.always.getValue();
+                                               if (trustNow || trustLocal || trustAlways) {
+                                                       sslVerify = false;
+                                                       if (trustAlways) {
+                                                               updateSslVerify(SystemReader.getInstance()
+                                                                               .openUserConfig(null, FS.DETECTED),
+                                                                               false);
+                                                       } else if (trustLocal) {
+                                                               updateSslVerify(local.getConfig(), false);
+                                                       }
+                                                       return true;
+                                               }
+                                       }
+                               }
+                       }
+               }
+               return false;
+       }
+
+       private CredentialItems constructSslTrustItems(Throwable cause) {
+               CredentialItems items = new CredentialItems();
+               String info = MessageFormat.format(JGitText.get().sslFailureInfo,
+                               currentUri.setPass(null));
+               String sslMessage = cause.getLocalizedMessage();
+               if (sslMessage == null) {
+                       sslMessage = cause.toString();
+               }
+               sslMessage = MessageFormat.format(JGitText.get().sslFailureCause,
+                               sslMessage);
+               items.message = new CredentialItem.InformationalMessage(info + '\n'
+                               + sslMessage + '\n'
+                               + JGitText.get().sslFailureTrustExplanation);
+               items.now = new CredentialItem.YesNoType(JGitText.get().sslTrustNow);
+               if (local != null) {
+                       items.forRepo = new CredentialItem.YesNoType(
+                                       MessageFormat.format(JGitText.get().sslTrustForRepo,
+                                       local.getDirectory()));
+               }
+               items.always = new CredentialItem.YesNoType(
+                               JGitText.get().sslTrustAlways);
+               return items;
+       }
+
+       private void updateSslVerify(StoredConfig config, boolean value) {
+               // Since git uses the original URI for matching, we must also use the
+               // original URI and cannot use the current URI (which might be different
+               // after redirects)
+               String uriPattern = uri.getScheme() + "://" + uri.getHost(); //$NON-NLS-1$
+               int port = uri.getPort();
+               if (port > 0) {
+                       uriPattern += ":" + port; //$NON-NLS-1$
+               }
+               config.setBoolean(HttpConfig.HTTP, uriPattern,
+                               HttpConfig.SSL_VERIFY_KEY, value);
+               try {
+                       config.save();
+               } catch (IOException e) {
+                       LOG.error(JGitText.get().sslVerifyCannotSave, e);
+               }
+       }
+
        private URIish redirect(String location, String checkFor, int redirects)
                        throws TransportException {
                if (location == null || location.isEmpty()) {
@@ -687,7 +818,7 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
                final Proxy proxy = HttpSupport.proxyFor(proxySelector, u);
                HttpConnection conn = connectionFactory.create(u, proxy);
 
-               if (!http.isSslVerify() && "https".equals(u.getProtocol())) { //$NON-NLS-1$
+               if (!sslVerify && "https".equals(u.getProtocol())) { //$NON-NLS-1$
                        HttpSupport.disableSslVerify(conn);
                }
 
@@ -1034,123 +1165,133 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
                        int authAttempts = 1;
                        int redirects = 0;
                        for (;;) {
-                               // The very first time we will try with the authentication
-                               // method used on the initial GET request. This is a hint only;
-                               // it may fail. If so, we'll then re-try with proper 401
-                               // handling, going through the available authentication schemes.
-                               openStream();
-                               if (buf != out) {
-                                       conn.setRequestProperty(HDR_CONTENT_ENCODING, ENCODING_GZIP);
-                               }
-                               conn.setFixedLengthStreamingMode((int) buf.length());
-                               try (OutputStream httpOut = conn.getOutputStream()) {
-                                       buf.writeTo(httpOut, null);
-                               }
-
-                               final int status = HttpSupport.response(conn);
-                               switch (status) {
-                               case HttpConnection.HTTP_OK:
-                                       // We're done.
-                                       return;
-
-                               case HttpConnection.HTTP_NOT_FOUND:
-                                       throw new NoRemoteRepositoryException(uri, MessageFormat
-                                                       .format(JGitText.get().uriNotFound, conn.getURL()));
-
-                               case HttpConnection.HTTP_FORBIDDEN:
-                                       throw new TransportException(uri,
-                                                       MessageFormat.format(
-                                                                       JGitText.get().serviceNotPermitted,
-                                                                       baseUrl, serviceName));
-
-                               case HttpConnection.HTTP_MOVED_PERM:
-                               case HttpConnection.HTTP_MOVED_TEMP:
-                               case HttpConnection.HTTP_11_MOVED_TEMP:
-                                       // SEE_OTHER after a POST doesn't make sense for a git
-                                       // server, so we don't handle it here and thus we'll
-                                       // report an error in openResponse() later on.
-                                       if (http.getFollowRedirects() != HttpRedirectMode.TRUE) {
-                                               // Let openResponse() issue an error
-                                               return;
+                               try {
+                                       // The very first time we will try with the authentication
+                                       // method used on the initial GET request. This is a hint
+                                       // only; it may fail. If so, we'll then re-try with proper
+                                       // 401 handling, going through the available authentication
+                                       // schemes.
+                                       openStream();
+                                       if (buf != out) {
+                                               conn.setRequestProperty(HDR_CONTENT_ENCODING,
+                                                               ENCODING_GZIP);
                                        }
-                                       currentUri = redirect(
-                                                       conn.getHeaderField(HDR_LOCATION),
-                                                       '/' + serviceName, redirects++);
-                                       try {
-                                               baseUrl = toURL(currentUri);
-                                       } catch (MalformedURLException e) {
-                                               throw new TransportException(uri, MessageFormat.format(
-                                                               JGitText.get().invalidRedirectLocation,
-                                                               baseUrl, currentUri), e);
+                                       conn.setFixedLengthStreamingMode((int) buf.length());
+                                       try (OutputStream httpOut = conn.getOutputStream()) {
+                                               buf.writeTo(httpOut, null);
                                        }
-                                       continue;
 
-                               case HttpConnection.HTTP_UNAUTHORIZED:
-                                       HttpAuthMethod nextMethod = HttpAuthMethod
-                                                       .scanResponse(conn, ignoreTypes);
-                                       switch (nextMethod.getType()) {
-                                       case NONE:
+                                       final int status = HttpSupport.response(conn);
+                                       switch (status) {
+                                       case HttpConnection.HTTP_OK:
+                                               // We're done.
+                                               return;
+
+                                       case HttpConnection.HTTP_NOT_FOUND:
+                                               throw new NoRemoteRepositoryException(uri,
+                                                               MessageFormat.format(JGitText.get().uriNotFound,
+                                                                               conn.getURL()));
+
+                                       case HttpConnection.HTTP_FORBIDDEN:
                                                throw new TransportException(uri,
                                                                MessageFormat.format(
-                                                                               JGitText.get().authenticationNotSupported,
-                                                                               conn.getURL()));
-                                       case NEGOTIATE:
-                                               // RFC 4559 states "When using the SPNEGO [...] with
-                                               // [...] POST, the authentication should be complete
-                                               // [...] before sending the user data." So in theory
-                                               // the initial GET should have been authenticated
-                                               // already. (Unless there was a redirect?)
-                                               //
-                                               // We try this only once:
-                                               ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE);
-                                               if (authenticator != null) {
-                                                       ignoreTypes.add(authenticator.getType());
+                                                                               JGitText.get().serviceNotPermitted,
+                                                                               baseUrl, serviceName));
+
+                                       case HttpConnection.HTTP_MOVED_PERM:
+                                       case HttpConnection.HTTP_MOVED_TEMP:
+                                       case HttpConnection.HTTP_11_MOVED_TEMP:
+                                               // SEE_OTHER after a POST doesn't make sense for a git
+                                               // server, so we don't handle it here and thus we'll
+                                               // report an error in openResponse() later on.
+                                               if (http.getFollowRedirects() != HttpRedirectMode.TRUE) {
+                                                       // Let openResponse() issue an error
+                                                       return;
                                                }
-                                               authAttempts = 1;
-                                               // We only do the Kerberos part of SPNEGO, which
-                                               // requires only one attempt. We do *not* to the
-                                               // NTLM part of SPNEGO; it's a multi-round
-                                               // negotiation and among other problems it would
-                                               // be unclear when to stop if no HTTP_OK is
-                                               // forthcoming. In theory a malicious server
-                                               // could keep sending requests for another NTLM
-                                               // round, keeping a client stuck here.
-                                               break;
-                                       default:
-                                               // DIGEST or BASIC. Let's be sure we ignore NEGOTIATE;
-                                               // if it was available, we have tried it before.
-                                               ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE);
-                                               if (authenticator == null || authenticator
-                                                               .getType() != nextMethod.getType()) {
+                                               currentUri = redirect(conn.getHeaderField(HDR_LOCATION),
+                                                               '/' + serviceName, redirects++);
+                                               try {
+                                                       baseUrl = toURL(currentUri);
+                                               } catch (MalformedURLException e) {
+                                                       throw new TransportException(uri,
+                                                                       MessageFormat.format(
+                                                                                       JGitText.get().invalidRedirectLocation,
+                                                                                       baseUrl, currentUri),
+                                                                       e);
+                                               }
+                                               continue;
+
+                                       case HttpConnection.HTTP_UNAUTHORIZED:
+                                               HttpAuthMethod nextMethod = HttpAuthMethod
+                                                               .scanResponse(conn, ignoreTypes);
+                                               switch (nextMethod.getType()) {
+                                               case NONE:
+                                                       throw new TransportException(uri,
+                                                                       MessageFormat.format(
+                                                                                       JGitText.get().authenticationNotSupported,
+                                                                                       conn.getURL()));
+                                               case NEGOTIATE:
+                                                       // RFC 4559 states "When using the SPNEGO [...] with
+                                                       // [...] POST, the authentication should be complete
+                                                       // [...] before sending the user data." So in theory
+                                                       // the initial GET should have been authenticated
+                                                       // already. (Unless there was a redirect?)
+                                                       //
+                                                       // We try this only once:
+                                                       ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE);
                                                        if (authenticator != null) {
                                                                ignoreTypes.add(authenticator.getType());
                                                        }
                                                        authAttempts = 1;
+                                                       // We only do the Kerberos part of SPNEGO, which
+                                                       // requires only one attempt. We do *not* to the
+                                                       // NTLM part of SPNEGO; it's a multi-round
+                                                       // negotiation and among other problems it would
+                                                       // be unclear when to stop if no HTTP_OK is
+                                                       // forthcoming. In theory a malicious server
+                                                       // could keep sending requests for another NTLM
+                                                       // round, keeping a client stuck here.
+                                                       break;
+                                               default:
+                                                       // DIGEST or BASIC. Let's be sure we ignore
+                                                       // NEGOTIATE; if it was available, we have tried it
+                                                       // before.
+                                                       ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE);
+                                                       if (authenticator == null || authenticator
+                                                                       .getType() != nextMethod.getType()) {
+                                                               if (authenticator != null) {
+                                                                       ignoreTypes.add(authenticator.getType());
+                                                               }
+                                                               authAttempts = 1;
+                                                       }
+                                                       break;
                                                }
-                                               break;
-                                       }
-                                       authMethod = nextMethod;
-                                       authenticator = nextMethod;
-                                       CredentialsProvider credentialsProvider = getCredentialsProvider();
-                                       if (credentialsProvider == null) {
-                                               throw new TransportException(uri,
-                                                               JGitText.get().noCredentialsProvider);
-                                       }
-                                       if (authAttempts > 1) {
-                                               credentialsProvider.reset(currentUri);
-                                       }
-                                       if (3 < authAttempts || !authMethod.authorize(currentUri,
-                                                       credentialsProvider)) {
-                                               throw new TransportException(uri,
-                                                               JGitText.get().notAuthorized);
-                                       }
-                                       authAttempts++;
-                                       continue;
+                                               authMethod = nextMethod;
+                                               authenticator = nextMethod;
+                                               CredentialsProvider credentialsProvider = getCredentialsProvider();
+                                               if (credentialsProvider == null) {
+                                                       throw new TransportException(uri,
+                                                                       JGitText.get().noCredentialsProvider);
+                                               }
+                                               if (authAttempts > 1) {
+                                                       credentialsProvider.reset(currentUri);
+                                               }
+                                               if (3 < authAttempts || !authMethod
+                                                               .authorize(currentUri, credentialsProvider)) {
+                                                       throw new TransportException(uri,
+                                                                       JGitText.get().notAuthorized);
+                                               }
+                                               authAttempts++;
+                                               continue;
 
-                               default:
-                                       // Just return here; openResponse() will report an appropriate
-                                       // error.
-                                       return;
+                                       default:
+                                               // Just return here; openResponse() will report an
+                                               // appropriate error.
+                                               return;
+                                       }
+                               } catch (SSLHandshakeException e) {
+                                       handleSslFailure(e);
+                                       continue; // Re-try
                                }
                        }
                }