SSL errors with custom Java and C# applications

Document ID : KB000057598
Last Modified Date : 14/02/2018
Show Technical Document Details

Issue

In the following cases SSL error is returned:
  • custom application tries to connect to On-premises instance of CA Agile Central, which is always shipped with self-signed certificate
  • custom application has to pass through an SSL proxy.
Example:
A user runs a java application which has to pass through the user's SSL proxy prior to connecting to CA Agile Central and receives the following exception:
?
Exception in thread "main" javax.net.ssl.
SSLPeerUnverifiedException: peer not authenticated
       at com.sun.net.ssl.internal.ssl.SSLSessionImpl.getPeerCertificates(Unknown Source)
       at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:126)
       at org.apache.http.conn.ssl.SSLSocketFactory.createLayeredSocket(SSLSocketFactory.java:493)
       at org.apache.http.impl.conn.DefaultClientConnectionOperator.updateSecureConnection(DefaultClientConnectionOperator.java:232)
       at org.apache.http.impl.conn.ManagedClientConnectionImpl.layerProtocol(ManagedClientConnectionImpl.java:401)
       at org.apache.http.impl.client.DefaultRequestDirector.establishRoute(DefaultRequestDirector.java:840)
       at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:647)
       at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:479)
       at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:906)
       at org.apache.http.impl.client.DecompressingHttpClient.execute(DecompressingHttpClient.java:137)
       at org.apache.http.impl.client.DecompressingHttpClient.execute(DecompressingHttpClient.java:108)
       at com.rallydev.rest.client.HttpClient.executeRequest(HttpClient.java:157)
       at com.rallydev.rest.client.HttpClient.doRequest(HttpClient.java:145)
       at com.rallydev.rest.client.BasicAuthClient.doRequest(BasicAuthClient.java:56)
       at com.rallydev.rest.client.HttpClient.doGet(HttpClient.java:221)
       at com.rallydev.rest.RallyRestApi.query(RallyRestApi.java:168)
       at rest.authenticate(rest.java:64)
       at rest.main(rest.java:905)

?

Resolution

Generally? invalid or expired certificates cause "javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated".

SSL certificates use a chain of trust, where each certificate is signed by a higher, more credible certificate. At the top of the chain of trust are the root certificates, owned by Verisign or others certifcate authorities.
When using a self-signed certificate, there is no chain of trust. A web browser will issue a warning when a web site certificate cannot be verified, but the user can dismiss the warning and it will not appear again. But a java app is more stringent than a browser and it needs to be explicitly told to tolerate invalid certificate chains.

CA Agile Central REST Toolkit for Java does not support that scenario out of box, and custom code is needed for that use case.
As mentioned here, CA Agile Central Software does not actively maintain or support the toolkit.
The reason for this caveat is that it is a community effort, and not an enterprise effort. The source is available on github, and customization is possible.
As of rally-rest-api-2.1.1.jar the toolkit is current, and works with latest features of WS API v2.0.

Starting with 2.1 version of the jar the toolkit allows access to the HTTPClient under it.
It means that when we instantiate RallyRestApi:
?
String host = "https://rally1.rallydev.com";
String apiKey = "_abc123";
RallyRestApi restApi = new RallyRestApi(new URI(host),apiKey);
restApi.setProxy(new URI("http://myproxy.mycompany.com"), "MyProxyUsername", "MyProxyPassword");

we may access? HttpClient object with getClient() method:
?
HttpClient client = restApi.getClient();

From there we may customize client to ignore invalid certificate chains and workaround SSLPeerUnverifiedException: peer not authenticated
exception. Here is a full code example:
?
import com.rallydev.rest.RallyRestApi;
import com.rallydev.rest.client.HttpClient;
import com.rallydev.rest.request.GetRequest;
import com.rallydev.rest.response.GetResponse;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.conn.scheme.Scheme;


public class ConnnectionTestWithHTTPClient {

	public static void main(String[] args) throws URISyntaxException, IOException {
		

	    String host = "https://rally1.rallydev.com";
	    String apiKey = "_abc123";
	    String applicationName = "Connnection Test With HTTPClient";
	    RallyRestApi restApi = new RallyRestApi(new URI(host),apiKey);
        restApi.setApplicationName(applicationName); 
        //restApi.setProxy(new URI("http://myproxy.mycompany.com"), "MyProxyUsername", "MyProxyPassword");  //YOUR PROXY SETTINGS HERE
        HttpClient client = restApi.getClient();
        try {
        	SSLSocketFactory sf = new SSLSocketFactory(new TrustStrategy() {
                public boolean isTrusted(X509Certificate[] certificate, String authType)
                    throws CertificateException {
                    //trust all certs
                    return true;
                }
            }, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            client.getConnectionManager().getSchemeRegistry().register(new Scheme("https", 443, sf));
            
        	String workspaceRef = "/workspace/12345"; //YOUR VALID WORKSPACE OID HERE
        	GetRequest getRequest = new GetRequest(workspaceRef);
        	GetResponse getResponse = restApi.get(getRequest);
        	System.out.println(getResponse.getObject());
        } catch (Exception e) {
        	System.out.println(e);
        } finally {
            restApi.close();
        }   
	} 
}
The same java code can be used to ignore self-signed certificate when connecting to On-premises instance. The only difference is that the host will not point to rall1.rallydev.com but to a specific on-premise instance,e.g.
String host = "https://10.xx.xx.xxx";

Here is a C# code example for On-premises case:
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using Rally.RestApi;
using Rally.RestApi.Response;

// The following are SSL Cert related libraries
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
using System.Net;

namespace ConnectToOnPremises
{
    class Program
    {
        static void Main(string[] args)
        {

            // Trust the un-trusted connection
            ValidateCertificate();

            RallyRestApi restApi;
            restApi = new RallyRestApi("user@co.com", "secret", "https://10.xx.xx.xxx", "v2.0"); //USER YOUR OWN URL

            DynamicJsonObject sub = restApi.GetSubscription("Workspaces");

            Request wRequest = new Request(sub["Workspaces"]);
            wRequest.Limit = 1000;
            QueryResult queryResult = restApi.Query(wRequest);
            int allProjects = 0;
            foreach (var result in queryResult.Results)
            {
                var workspaceReference = result["_ref"];
                var workspaceName = result["Name"];
                Console.WriteLine("Workspace: " + workspaceName);
                Request projectsRequest = new Request(result["Projects"]);
                projectsRequest.Fetch = new List<string>()
                {"Name"};
                projectsRequest.Limit = 1000; //project requests are made per workspace
                QueryResult queryProjectResult = restApi.Query(projectsRequest);
                int projectsPerWorkspace = 0;
                foreach (var p in queryProjectResult.Results)
                {
                    allProjects++;
                    projectsPerWorkspace++;
                    Console.WriteLine(projectsPerWorkspace + " Project: " + p["Name"] + " State: " + p["State"]);
                }
                Console.WriteLine("----------------------------");
            }
        }

        private static void ValidateCertificate()
        {
            // The following code is required to connect to an on-premise Rally server without a trusted cert uploaded
            // If we remove this code it throws the following error
            // Could not establish trust relationship for the SSL/TLS secure channel".
            // This code has to placed before starting any calls to the webservise.

            TrustAllCertificatePolicy trustAll = new TrustAllCertificatePolicy();
            System.Net.ServicePointManager.ServerCertificateValidationCallback += new System.Net.Security.RemoteCertificateValidationCallback(TrustAllCertificatePolicy.AcceptAllCerts);
        }
    }

    public class TrustAllCertificatePolicy : ICertificatePolicy
    {
        //Default policy for certificate validation.
        public static bool DefaultValidate = false;
        public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate cert, WebRequest request, int problem)
        {
            return true;
        }

        public static bool AcceptAllCerts(object sender, X509Certificate cert, X509Chain chain, System.Net.Security.SslPolicyErrors SslPolicyErrors)
        {
            return true;
        }
    }
}

?