Java and SSL certs


  • mod

    So, fair warning, I have 0 influence into the architecture of this product, and as it's already been deployed and we have several clients actively using it, it's not likely to change any time soon.

    In order to authenticate with our API, you have to have a cert we provide. I have one of those. You also have to have installed the root certificate that signed it as a trusted authority or whatever. I have that too. I'm trying to write a Java client (for testing purposes, obviously) that can authenticate with our API.

    My code:

    		//Connect to URL using custom certificate auth
    		HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
    		try {
    			
    			connection.setSSLSocketFactory(getFactory());
    		} catch (GeneralSecurityException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		connection.connect();
    

    And the factory method:

    /**
    	 * Get the SSL factory associated with a specific client certificate and password. Lifted from http://vafer.org/blog/20061010073725/
    	 * @return The factory for generating the SSL socket
    	 * @throws GeneralSecurityException If there is a security failure
    	 * @throws IOException If there is a problem reading the certificate
    	 */
    	private SSLSocketFactory getFactory() throws GeneralSecurityException, IOException {
    		  //Client cert
    		  InputStream certInput = this.getClass().getClassLoader().getResourceAsStream(this.certFile);
    		  if (certInput == null) throw new FileNotFoundException("File not found: " + this.certFile);
    		
    		  KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
    		  KeyStore keyStore = KeyStore.getInstance("PKCS12");
    
    		  keyStore.load(certInput, this.certPass.toCharArray());
    		  certInput.close();
    		  
    		  keyManagerFactory.init(keyStore, this.certPass.toCharArray());
    
    		  SSLContext context = SSLContext.getInstance("TLS");
    		  context.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());
    
    		  return context.getSocketFactory();
    		}
    

    And this works! Except. I get this error:

    javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

    Obviously it's because I haven't installed the root CA. Except that I did, to Windows' store. I apparently need to install it to Java's separate store, I guess. But the thing is, I'm going to have to do that on every machine that I want this code to run from, right? Is there some way I can bundle the root cert with my code like I did the client cert so it can be installed on demand?



  • I use a little script to get the cert file:

    #!/bin/sh
    #
    # usage: retrieve-cert.sh remote.host.name [port]
    #
    REMHOST=$1
    REMPORT=${2:-443}
    echo |\
    openssl s_client -connect ${REMHOST}:${REMPORT} 2>&1 |\
    sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'
    

    You need to use keytool to create a local keystore:

    keytool -import -alias your.domain -file your-cert.cer -keystore ./your_local_keystore -storepass somepassword
    

    Then, in Java:

    URI myServerUri;
    System.setProperty( "javax.net.ssl.trustStore", "path/to/your_local_keystore" );
    System.setProperty( "javax.net.ssl.trustStorePassword", "somepassword" );
    myServerUri = new URI( "https://your/domain/whatever");
    

    I use this along with Jira's REST API:

    JerseyJiraRestClientFactory factory = new JerseyJiraRestClientFactory();
    jira = factory.createWithBasicHttpAuthentication( myServerUri, "jira-user", "jira-password" );
    

    I have no idea what you're doing, so that last bit is probably irrelevant, but that's what I do with the result.

    EDIT: Oh! And my local keystore gets compiled into the jar. The path is basically the path of the package / dir that it ends up in.


  • mod

    @boomzilla said:

    And my local keystore gets compiled into the jar.

    THAT sounds like the useful part: instead of (in addition to?) putting the cert itself in the resources directory, I can put the keystore in the resources and load it.

    Oh, should I be replacing this line?

    KeyStore keyStore = KeyStore.getInstance("PKCS12");

    or does the environment variables make that line refer to the local one?


  • mod

    What is Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty about?



  • The keystore stores the cert for you in a "java friendly" way. I've never explicitly created a KeyStore object. I just do exactly what I put up there.

    TBH, I only put all that together by googling stuff and trial and error. I don't claim to really understand WTF java does with all that stuff, and I haven't had to touch it since last September (according to file timestamps) so I'm remembering it as I go here. :smile:

    @Yamikuronue said:

    What is Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty about?

    Indeed. NFC.


  • mod

    Apparently it means the way I had retrieved the path didn't lead to the file, because it was a URL so it had "file://" in front.

    This works!

    		//CA
    		URL storeURL = this.getClass().getClassLoader().getResource("local_keystore");
    		File storeFile = Paths.get(storeURL.toURI()).toFile();
    		String store = storeFile.getAbsolutePath();
    		
    		System.out.println("Store: " + store);
    		System.setProperty( "javax.net.ssl.trustStore", store);
    		System.setProperty( "javax.net.ssl.trustStorePassword", "[redacted]" );
    

    This is my most successful coding help thread yet. Thanks, @boomzilla :)

    I now have a 401 forbidden to contend with, but that I can probably figure out on my own, since I didn't give my username and pass yet. But I got past the cert challenge!


Log in to reply
 

Looks like your connection to What the Daily WTF? was lost, please wait while we try to reconnect.