0

I've already saw this question: Need to do a GET&POST HTTPS Request using a .cer certificate

Mine is quite different:

It is possible to make an HTTPS request using Java (vanilla, or using any library), trusting a server certificate and providing a client certificate, without using a keystore but using plain certificates?

I have both certs in X.509 format, and I don't want to have every certificate in a keystore.

lilezek
  • 6,976
  • 1
  • 27
  • 45
  • 2
    no problem using java for example `HttpsUrlConnection`. just make your own `KeyManager` for the `SSLContext`. – mr mcwolf Oct 11 '17 at 10:24
  • @mrmcwolf May you elaborate? I'm looking for KeyManager documentation right now. – lilezek Oct 11 '17 at 10:32
  • @mrmcwolf Following Java docs, I found out this: https://docs.oracle.com/javase/7/docs/api/javax/net/ssl/X509ExtendedKeyManager.html which has a protected constructor. Is there any subclass I can use? – lilezek Oct 11 '17 at 10:37
  • Use `X509KeyManager`. After a while I will make a rough example. – mr mcwolf Oct 11 '17 at 10:47

2 Answers2

1

This is a rough example. Represents the X509KeyManager decorator.

KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(null, null);

X509KeyManager manager = (X509KeyManager) kmf.getKeyManagers()[0];
KeyManager km = new X509KeyManager() {
    @Override
    public String[] getClientAliases(String s, Principal[] principals) {
        return manager.getServerAliases(s, principals);
    }

    @Override
    public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) {
        return manager.chooseClientAlias(strings, principals, socket);
    }

    @Override
    public String[] getServerAliases(String s, Principal[] principals) {
        return manager.getServerAliases(s, principals);
    }

    @Override
    public String chooseServerAlias(String s, Principal[] principals, Socket socket) {
        return manager.chooseServerAlias(s, principals, socket);
    }

    @Override
    public X509Certificate[] getCertificateChain(String s) {
        // You can use `s` to select the appropriate file

        try {
            File file = new File("path to certificate");

            try(InputStream is = new FileInputStream(file)) {
                CertificateFactory factory = CertificateFactory.getInstance("X.509");
                return new X509Certificate[] {
                        (X509Certificate) factory.generateCertificate(is)
                };
            }
        }
        catch (CertificateException| IOException  e) {
            e.printStackTrace();
        }

        return null;
    }

    @Override
    public PrivateKey getPrivateKey(String s) {
        // You can use `s` to select the appropriate file

        // load and private key from selected certificate
        // this use for certificate authorisation

        try {
            File file = new File("private key file");
            byte buffer[] = Files.readAllBytes(file.toPath());

            KeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
            KeyFactory factory = KeyFactory.getInstance("RSA");

            return factory.generatePrivate(keySpec);
        }
        catch (NoSuchAlgorithmException | IOException | InvalidKeySpecException e) {
            e.printStackTrace();
        }

        return null;
    }
};

TrustManager tm = new X509TrustManager() {
    @Override
    public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

    }

    @Override
    public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        try {
            File file = new File("path to certificate");

            try(InputStream is = new FileInputStream(file)) {
                CertificateFactory factory = CertificateFactory.getInstance("X.509");
                return new X509Certificate[] {
                        (X509Certificate) factory.generateCertificate(is)
                };
            }
        }
        catch (CertificateException| IOException  e) {
            e.printStackTrace();
        }

        return null;
    }
};

TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore)null); //use java system trust certificates

TrustManager managers[] = new TrustManager[tmf.getTrustManagers().length + 1];
System.arraycopy(tmf.getTrustManagers(), 0, managers, 0, tmf.getTrustManagers().length);
managers[managers.length - 1] = tm;

SSLContext context = SSLContext.getInstance("TLS");
context.init(new KeyManager[]{ km }, managers, new SecureRandom());

URL url = new URL("https://............/");
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setSSLSocketFactory(connection.getSSLSocketFactory());

connection.connect();
mr mcwolf
  • 2,574
  • 2
  • 14
  • 27
0

If you really don't want to create a new keystore file, then can use KeyStore API to create in memory and load certificate directly.

InputStream is = new FileInputStream("somecert.cer");
// You could get a resource as a stream instead.

CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate caCert = (X509Certificate)cf.generateCertificate(is);

TrustManagerFactory tmf = TrustManagerFactory
    .getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null); // You don't need the KeyStore instance to come from a file.
ks.setCertificateEntry("caCert", caCert);

tmf.init(ks);

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);

Alternatively, if you want to avoid modifying your default cacerts file, then you'll need to implement your own TrustManager. However a TrustManager needs a keystore to load, so you can either create a new keystore file importing just your certificate.

keytool -import -alias ca -file somecert.cer -keystore truststore.jks -storepass changeit

And use something like following snippet to load the keystore file.

TrustManagerFactory tmf = TrustManagerFactory
    .getInstance(TrustManagerFactory.getDefaultAlgorithm());
// Using null here initialises the TMF with the default trust store.
tmf.init((KeyStore) null);

// Get hold of the default trust manager
X509TrustManager defaultTm = null;
for (TrustManager tm : tmf.getTrustManagers()) {
    if (tm instanceof X509TrustManager) {
        defaultTm = (X509TrustManager) tm;
        break;
    }
}

FileInputStream myKeys = new FileInputStream("truststore.jks");

// Do the same with your trust store this time
// Adapt how you load the keystore to your needs
KeyStore myTrustStore = KeyStore.getInstance(KeyStore.getDefaultType());
myTrustStore.load(myKeys, "password".toCharArray());

myKeys.close();

tmf = TrustManagerFactory
    .getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(myTrustStore);

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
lilezek
  • 6,976
  • 1
  • 27
  • 45
Shashwat Kumar
  • 5,159
  • 2
  • 30
  • 66
  • @Code.IT after the edit he provided an snippet to read directly a X.509 cert. Idk if the downvote is yours, but I'm going to upvote this as soon as I test the solution. – lilezek Oct 11 '17 at 11:12