¿Cómo hago TLS con BouncyCastle?

¿Alguien sabe sobre ejemplos de TLS con BouncyCastle? Me sorprendió la falta de ellos en Internet. Si realmente no hay ninguno, vamos a recogerlos como respuestas.

Este es un ejemplo muy básico, con autenticación de solo servidor y certificado autofirmado. El código se basa en BC 1.49, en su mayoría API de peso ligero:

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"); } 

dónde

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

Este es el código del cliente:

 Socket socket = new Socket(, 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(); 

Debe usar la secuencia de entrada y salida de tlsClient / ServerProtocol para leer y escribir datos encriptados (por ejemplo, tlsClientProtocol.getInputStream ()). De lo contrario, si utilizó, por ejemplo, socket.getOutputStream (), simplemente escribiría datos no encriptados.

¿Cómo implementar validateCertificate? Estoy usando certificados autofirmados. Esto significa que simplemente los busqué en la tienda de claves sin ninguna cadena de certificados. Así es como creo el almacén de claves:

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

Y esta es la validación:

 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); } } 

Lo que es bastante confuso, son las tres clases de certificados diferentes. Tienes que convertir entre ellos como se muestra arriba.

Escenario: Nuestro servidor de producción está utilizando JDK1.6. Sin embargo, el servidor del cliente se actualiza para comunicarse solo en TLS 1.2. La comunicación SSL entre ambos servidores está rota. Pero no podemos simplemente actualizar JDK6 a 8 (que es compatible con TLS 1.2 de manera predeterminada) porque esto causará problemas de compatibilidad de otras bibliotecas.

El siguiente código de ejemplo utiliza jdk1.6.0_45 y bcprov-jdk15on-153.jar (ARCHIVOS DE ARCHIVO FIRMADOS POR EL CASTILLO SACUDIDARIO) para conectarse a cualquier servidor que use TLS.

 import java.io.IOException; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.Socket; import org.bouncycastle.crypto.tls.CertificateRequest; import org.bouncycastle.crypto.tls.DefaultTlsClient; import org.bouncycastle.crypto.tls.TlsAuthentication; import org.bouncycastle.crypto.tls.TlsClientProtocol; import org.bouncycastle.crypto.tls.TlsCredentials; public class TestHttpClient { // Reference: http://boredwookie.net/index.php/blog/how-to-use-bouncy-castle-lightweight-api-s-tlsclient/ // bcprov-jdk15on-153.tar\src\org\bouncycastle\crypto\tls\test\TlsClientTest.java public static void main(String[] args) throws Exception { java.security.SecureRandom secureRandom = new java.security.SecureRandom(); Socket socket = new Socket(java.net.InetAddress.getByName("www.google.com"), 443); TlsClientProtocol protocol = new TlsClientProtocol(socket.getInputStream(), socket.getOutputStream(),secureRandom); DefaultTlsClient client = new DefaultTlsClient() { public TlsAuthentication getAuthentication() throws IOException { TlsAuthentication auth = new TlsAuthentication() { // Capture the server certificate information! public void notifyServerCertificate(org.bouncycastle.crypto.tls.Certificate serverCertificate) throws IOException { } public TlsCredentials getClientCredentials(CertificateRequest certificateRequest) throws IOException { return null; } }; return auth; } }; protocol.connect(client); java.io.OutputStream output = protocol.getOutputStream(); output.write("GET / HTTP/1.1\r\n".getBytes("UTF-8")); output.write("Host: www.google.com\r\n".getBytes("UTF-8")); output.write("Connection: close\r\n".getBytes("UTF-8")); // So the server will close socket immediately. output.write("\r\n".getBytes("UTF-8")); // HTTP1.1 requirement: last line must be empty line. output.flush(); java.io.InputStream input = protocol.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(input)); String line; while ((line = reader.readLine()) != null) { System.out.println(line); } } } 

La salida de muestra muestra que JDK 6 puede obtener la página del servidor en TLS, en lugar de alguna Excepción de SSL:

 HTTP/1.1 302 Found Cache-Control: private Content-Type: text/html; charset=UTF-8 Location: https://www.google.com.sg/?gfe_rd=cr&ei=WRgeVovGEOTH8Afcx4XYAw Content-Length: 263 Date: Wed, 14 Oct 2015 08:54:49 GMT Server: GFE/2.0 Alternate-Protocol: 443:quic,p=1 Alt-Svc: quic="www.google.com:443"; p="1"; ma=600,quic=":443"; p="1"; ma=600 Connection: close  302 Moved 

302 Moved

The document has moved here.

Un ejemplo más, construido sobre la respuesta de autenticación solo de servidor: TLS con certificados autofirmados con autenticación de cliente (estoy mostrando solo las partes modificadas). Esta es la parte del servidor:

 tlsServerProtocol.accept(new DefaultTlsServer() { protected TlsSignerCredentials getRSASignerCredentials() throws IOException { return tlsSignerCredentials(context); } public void notifyClientCertificate(Certificate clientCertificate) throws IOException { validateCertificate(clientCertificate); } public CertificateRequest getCertificateRequest() { return new CertificateRequest(new short[] { ClientCertificateType.rsa_sign }, new Vector()); } }); 

Y esta es la parte del cliente:

 tlsClientProtocol.connect(new DefaultTlsClient() { public TlsAuthentication getAuthentication() throws IOException { return new TlsAuthentication() { public void notifyServerCertificate(Certificate serverCertificate) throws IOException { validateCertificate(serverCertificate); } public TlsCredentials getClientCredentials(CertificateRequest certificateRequest) throws IOException { return tlsSignerCredentials(context); } }; } });