11

I am trying to a clone of a Git Repository via the CloneCommand. With this piece of code

`Git.cloneRepository().setDirectory(new File(path)).setURI(url).call();`

The remote repository is on a GitBlit Instance which uses self signed certificates. Because of these self signed certificates I get the below exception when the Fetch Part of the Clone is executing:

Caused by: java.security.cert.CertificateException: No name matching <hostName> found
    at sun.security.util.HostnameChecker.matchDNS(HostnameChecker.java:221)
    at sun.security.util.HostnameChecker.match(HostnameChecker.java:95)

While I could create a new TrustManager, register a dummy HostnameVerifier and create and init a SSLContext that uses this dummy TrustManager. And after the clone is done revert all of this.

However this would mean that any other SSL connection that is initiated during the same time would expose them to unsecured connections.

On a already cloned repo you can set the http.sslVerify to false and JGit works perfectly fine.

Is there a cleaner way in which I could tell JGit to set this http.sslVerify to false for Clone action, like I can do for a already cloned repo.

Rüdiger Herrmann
  • 20,512
  • 11
  • 62
  • 79
Yogesh_D
  • 17,656
  • 10
  • 41
  • 55

5 Answers5

8

With version 4.9, JGit will handle SSL verification more gracefully. If the SSL handshake was unsuccessful, JGit will ask the CredentialsProvider whether SSL verification should be skipped or not.

In this process, the CredentialsProvider is given an InformationalMessage describing the issue textually and up to three YesNoType CredentialItems to decide whether to skip SSL verification for this operation, for the current repository, and/or always.

It seems that the change was made with an interactive UI in mind and it might be hard to answer these 'credential requests' programmatically. The commit message of this change describes the behavior in more detail.

If you are certain that SSL verification is the only InformationalMessage that will be sent, you can apply the technique used in this test that accompanies the change and blindly answer 'yes' to all such questions.

For earlier versions of JGit, or if the CredentialsProvider model does not fit your needs, there are two workarounds described below.


To work around this limitation, you can execute the specific clone steps manually as suggested in the comments below:

  • init a repository using the InitCommand
  • set ssl verify to false
    StoredConfig config = git.getRepository().getConfig();
    config.setBoolean( "http", null, "sslVerify", false );
    config.save();
  • fetch (see FetchCommand)
  • checkout (see CheckoutCommand)

Another way to work around the issue is to provide an HttpConnectionFactory that returns HttpConnections with dummy host name and certificate verifiers. For example:

class InsecureHttpConnectionFactory implements HttpConnectionFactory {

  @Override
  public HttpConnection create( URL url ) throws IOException {
    return create( url, null );
  }

  @Override
  public HttpConnection create( URL url, Proxy proxy ) throws IOException {
    HttpConnection connection = new JDKHttpConnectionFactory().create( url, proxy );
    HttpSupport.disableSslVerify( connection );
    return connection;
  }
}

HttpConnection is in package org.eclipse.jgit.transport.http and is a JGit abstraction for HTTP connections. While the example uses the default implementation (backed by JDK http code), you are free to use your own implementation or the one provided by the org.eclipse.jgit.transport.http.apache package that uses Apache http components.

The currently used connection factory can be changed with HttpTransport::setConnectionFactory():

HttpConnectionFactory preservedConnectionFactory = HttpTransport.getConnectionFactory();
HttpTransport.setConnectionFactory( new InsecureHttpConnectionFactory() );
// clone repository
HttpTransport.setConnectionFactory( preservedConnectionFactory );

Unfortunately, the connection factory is a singleton so that this trick needs extra work (e.g. a thread local variable to control if sslVerify is on or off) when JGit commands are executed concurrently.

Rüdiger Herrmann
  • 20,512
  • 11
  • 62
  • 79
  • Thanks! Exactly our findings, after I posted the question found out that we have a `TransportConfigCallback` that can be used but even that has a Final version of the HttpConfig that cannot be changed. The only other way I see is to have do a Init, and then update the remote and then pull. Any other inputs that you might have? – Yogesh_D Nov 30 '15 at 14:18
  • Yes, that looks like the best workaround at hand: init, configure sslVerify and upstream repo, fetch, update submodules (if necessary) and checkout. – Rüdiger Herrmann Nov 30 '15 at 16:27
  • Accepting this as the current correct answer, as with the current state of things, the only workaround is what was described as aboved, Init a repo, set ssl verify to false and then pull or fetch and checkout. – Yogesh_D Dec 02 '15 at 07:13
  • Thanks for accepting the answer, I've listed the workaround in the answer so that it can be discovered more easily. – Rüdiger Herrmann Dec 02 '15 at 09:00
  • I have a really hard time to use that workaround #1 with a CloneCommand. As soon as I start to Clone a Repository which lies in SSL I receive the error. After I init via InitCommand and set the config values for "sslVerify" I only can use fetch on my Git instance. Using `Git.cloneRepository.call()` will create something new without that *sslVerify* configuration... I'm pretty lost here. Would you please show an example to do this with CloneCommand? – xetra11 Jan 31 '17 at 16:34
  • 1
    @xetra11 please carefully re-read the answer: workaround #1 suggests to **not** use the `CloneCommand` but call init + fetch instead. – Rüdiger Herrmann Jan 31 '17 at 17:02
  • @RüdigerHerrmann I already tried to use fetch and init but I did not founda way to define the Repo URL through these builder methods. What am I missing? Thanks for the reply – xetra11 Jan 31 '17 at 17:05
  • What package is that `HttpClientConnection` from? In org.apache.http, it's an interface not an instantiable class. – allquixotic Sep 02 '17 at 05:08
  • @RüdigerHerrmann If you plan to use this hack into a shared Git connector, guard `HttpSupport.disableSslVerify(connection);` line with `if ("https".equals(url.getProtocol()))` to prevent `ClassCastException`. This way this connector can be used with `http` and `https` schemes at the same time and will be thread safe. – fdaugan Mar 15 '18 at 17:56
  • @fabdouglas Could you explain where the ClassCastException happens what makes this change thread safe? – Rüdiger Herrmann Mar 15 '18 at 19:09
  • @RüdigerHerrmann The treadsafe issue is the lack of context of `create` functions. So, you can safely use this hack in a multi-threaded usage if `setConnectionFactory` is only called once, and if the `sslVerify` decision is done at `create` functions level. The `ClassCastException` is thrown when the `sslVerify` is changed for a non https connection. This way I can use the JGit either for both `http` and `https`. Threadsafe usage there: [GitPluginResource.java](https://github.com/ligoj/plugin-scm-git/blob/master/src/main/java/org/ligoj/app/plugin/scm/git/GitPluginResource.java) – fdaugan Mar 17 '18 at 13:37
  • I tried that first method as well, but still, get this error "TransportException occur while cloning the project, cannot open git-upload-pack", any suggestions? – Milinda Kasun Aug 27 '21 at 03:19
8

Another workaround is to create a .gitconfig file in the home of the current user before calling Git.cloneRepository():

File file = new File(System.getProperty("user.home")+"/.gitconfig");
if(!file.exists()) {
    PrintWriter writer = new PrintWriter(file);
    writer.println("[http]");
    writer.println("sslverify = false");
    writer.close();
}

This will make JGit skip SSL certificate verification.

Rüdiger Herrmann
  • 20,512
  • 11
  • 62
  • 79
Jadson Santos
  • 199
  • 1
  • 2
  • 11
  • 3
    I recommend using the JGit API to manipulate Git user settings, Use `SystemReader.getInstance().openUserConfig( null, FS.DETECTED )` to obtain a `FileBasedConfig` that can be used to manipulate and save configuration settings. – Rüdiger Herrmann Apr 04 '18 at 16:45
1

I have inferred from all answers above for the snippet below;

private void disableSSLVerify(URI gitServer) throws Exception {
    if (gitServer.getScheme().equals("https")) {
        FileBasedConfig config = SystemReader.getInstance().openUserConfig(null, FS.DETECTED);
        synchronized (config) {
            config.load();
            config.setBoolean(
                "http",
                "https://" + gitServer.getHost() + ':' + (gitServer.getPort() == -1 ? 443 : gitServer.getPort()),
                "sslVerify", false);
            config.save();
        }
    }
}

This option is safer because it allows sslVerify to false for the gitServer alone.

Please take a look at this link which shares other options.

Sameer Khan
  • 637
  • 6
  • 15
1

I have come across with the same problem and I used ChainingCredentialsProvider class to create a credential provider, I did my implementation as bellow,

Please note that this is an implementation of a previously given answer.

CredentialsProvider token = new UsernamePasswordCredentialsProvider("PRIVATE-TOKEN", token);
    CredentialsProvider ssl = new CredentialsProvider() {
        @Override
        public boolean supports(CredentialItem... items) {
            for ( CredentialItem item : items ) {
                if ( (item instanceof CredentialItem.YesNoType) ) {
                    return true;
                }
            }
            return false;
        }

        @Override
        public boolean get(URIish uri, CredentialItem... items) throws UnsupportedCredentialItem {
            for ( CredentialItem item : items ) {
                if ( item instanceof CredentialItem.YesNoType ) {
                    (( CredentialItem.YesNoType ) item).setValue(true);
                    return true;
                }
            }
            return false;
        }

        @Override
        public boolean isInteractive() {
            return false;
        }
    };

    CredentialsProvider cp = new ChainingCredentialsProvider(ssl, token);

        Git result = Git.cloneRepository()
                .setURI(gitProjectUrl)
                .setDirectory(localPath)
                .setCredentialsProvider(cp)
                .call();
0

This is an example of implementing manual steps to clone repository from this answer by Rüdiger Herrmann

    Git git = Git.init()
        .setDirectory(new File(localRepoFullPath))
        .setBare(false)
        .call();
    git.remoteAdd()
        .setUri(new URIish(repoRemotePath)).setName("origin")
        .call();
    StoredConfig config = git.getRepository().getConfig();
    // here set sslVerify = false
    config.setBoolean( "http", null, "sslVerify", false );
    // http.sslCAInfo,sslCAPath,sslCert,sslKey sections are not parsed by JGit
    // more details here - https://stackoverflow.com/questions/75951655/mtls-mutual-tls-with-jgit
    config.setString("http",null,"sslCAInfo" , gitMTLSProperties.getServerCertificatePath());
    config.setString("http",null,"sslCAPath" , gitMTLSProperties.getServerCertificatePath());
    config.setString("http",null,"sslCert" , gitMTLSProperties.getClientCertificatePath());
    config.setString("http",null,"sslKey" , gitMTLSProperties.getClientKeyPath());
    config.save();
    git.checkout()
        .setCredentialsProvider(new UsernamePasswordCredentialsProvider("token", pat))
        .setCreateBranch(true)
        .setName("master")
        .setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.TRACK)
        .setStartPoint("origin/master")
        .call();

There is another way.

This is an alternative implementation example of disabling SSL verification through own CredentialsProvider from this answer by Milinda Kasun

public class MyOwnCredentialsProvider extends CredentialsProvider {
    
    @SneakyThrows
    public String getLogin() {
        // getting your login
        return myLogin;
    }

    @SneakyThrows
    private String getPass() {
        // getting your pass
        return myPass;
    }
    
    @Override
    public boolean isInteractive() {
        return false;
    }

    @Override
    public boolean supports(CredentialItem... items) {
        JGitText jGitText = JGitText.get();
        for (CredentialItem i : items) {
            if (i instanceof CredentialItem.Username)
                continue;
            else if (i instanceof CredentialItem.Password)
                continue;
            else {
                if (i instanceof CredentialItem.InformationalMessage &&
                        i.getPromptText().contains(jGitText.sslFailureTrustExplanation)) {
                    continue;
                } else if (i instanceof CredentialItem.YesNoType
                        && (i.getPromptText().contains(jGitText.sslTrustNow)
                        || i.getPromptText().contains(jGitText.sslTrustForRepo.substring(0, jGitText.sslTrustForRepo.length()-3))
                        || i.getPromptText().contains(jGitText.sslTrustAlways)))
                    continue;
                else
                    return false;
            }
        }
        return true;
    }

    
    @Override
    public boolean get(URIish uri, CredentialItem... items)
            throws UnsupportedCredentialItem {
        JGitText jGitText = JGitText.get();
        for (CredentialItem i : items) {
            if (i instanceof CredentialItem.Username) {
                ((CredentialItem.Username) i).setValue(getLogin());
                continue;
            }
            if (i instanceof CredentialItem.Password) {
                ((CredentialItem.Password) i).setValue(getPass().toCharArray());
                continue;
            }
            if (i instanceof CredentialItem.StringType) {
                if (i.getPromptText().equals("Password: ")) { 
                    ((CredentialItem.StringType) i).setValue(getPass());
                    continue;
                }
            }
            if (i instanceof CredentialItem.InformationalMessage &&
                    i.getPromptText().contains(jGitText.sslFailureTrustExplanation))
                continue;

            if (i instanceof CredentialItem.YesNoType) {
                if (i.getPromptText().contains(jGitText.sslTrustNow)) {
                    //set "true" for "Skip SSL verification for this single git operation".
                    //You can set "true" to any of the questions:
                    //      * "Do you want to skip SSL verification for this server?"
                    //      * "Skip SSL verification for git operations for repository {0}"
                    //      * "Always skip SSL verification for this server from now on"
                    //see file JGitText.properties
                    ((CredentialItem.YesNoType) i).setValue(true);
                    continue;
                } else if (i.getPromptText().contains(jGitText.sslTrustForRepo.substring(0, jGitText.sslTrustForRepo.length()-3))
                        || i.getPromptText().contains(jGitText.sslTrustAlways))
                    continue;
            }

            throw new UnsupportedCredentialItem(uri, i.getClass().getName()
                    + ":" + i.getPromptText()); 
        }
        return true;
    }
}

And in the step of cloning the repository, add your provider MyOwnCredentialsProvider

   Git git = Git.cloneRepository()
                .setURI(repoRemotePath)
                .setDirectory(new File(localRepoFullPath))
                .setCloneAllBranches(true)
                .setBranch("master")
                .setCredentialsProvider(new MyOwnCredentialsProvider())
                .call();
gearbase
  • 21
  • 3