How do I do TLS with BouncyCastle?

Jakub Adamek picture Jakub Adamek · Aug 5, 2013 · Viewed 33.7k times · Source

Does anybody know about examples of TLS with BouncyCastle? I was surprised by the lack of them on Internet. If there are really none, let's collect them as answers.

Answer

Jakub Adamek picture Jakub Adamek · Aug 9, 2013

This is a very basic example, with server-only authentication and self-signed cert. The code is based on BC 1.49, mostly leightweight API:

ServerSocket serverSocket = new ServerSocket(SERVER_PORT);
final KeyPair keyPair = ...
final Certificate bcCert = new Certificate(new org.spongycastle.asn1.x509.Certificate[] {
    new X509V3CertificateStrategy().selfSignedCertificateHolder(keyPair).toASN1Structure()}); 
while (true) {
    Socket socket = serverSocket.accept();
    TlsServerProtocol tlsServerProtocol = new TlsServerProtocol(
    socket.getInputStream(), socket.getOutputStream(), secureRandom);
    tlsServerProtocol.accept(new DefaultTlsServer() {
        protected TlsSignerCredentials getRSASignerCredentials() throws IOException {
            return tlsSignerCredentials(context);
        }               
    });      
    new PrintStream(tlsServerProtocol.getOutputStream()).println("Hello TLS");
}

where

private TlsSignerCredentials tlsSignerCredentials(TlsContext context) throws IOException {
    return new DefaultTlsSignerCredentials(context, bcCert,
            PrivateKeyFactory.createKey(keyPair.getPrivate().getEncoded()));                
}

This is the client code:

Socket socket = new Socket(<server IP>, SERVER_PORT);
TlsClientProtocol tlsClientProtocol = new TlsClientProtocol(    
    socket.getInputStream(), socket.getOutputStream());
tlsClientProtocol.connect(new DefaultTlsClient() {          
    public TlsAuthentication getAuthentication() throws IOException {
        return new ServerOnlyTlsAuthentication() {                  
            public void notifyServerCertificate(Certificate serverCertificate) throws IOException {
                validateCertificate(serverCertificate);
            }
        };
    }
});
String message = new BufferedReader(
    new InputStreamReader(tlsClientProtocol.getInputStream())).readLine();

You need to use the input and output stream from tlsClient/ServerProtocol to read and write encrypted data (e.g. tlsClientProtocol.getInputStream()). Otherwise, if you used e.g. socket.getOutputStream(), you would just write unencrypted data.

How to implement validateCertificate? I am using self-signed certificates. This means I just look them up in the key-store without any certificate chains. This is how I create the key store:

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, password);
X509Certificate certificate = ...;
keyStore.setCertificateEntry(alias, certificate);

And this is the validation:

private void validateCertificate(org.spongycastle.crypto.tls.Certificate cert) throws IOException, CertificateException, KeyStoreException {
    byte[] encoded = cert.getCertificateList()[0].getEncoded();
    java.security.cert.Certificate jsCert = 
        CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(encoded));
    String alias = keyStore.getCertificateAlias(jsCert);
    if(alias == null) {
        throw new IllegalArgumentException("Unknown cert " + jsCert);
    }
}

What is rather confusing, are the three different Certificate classes. You have to convert between them as shown above.