jdk16 - java bouncy castle
¿Cómo hago TLS con BouncyCastle? (3)
Escenario: Nuestro servidor de producción está utilizando JDK1.6. Sin embargo, el servidor del cliente se ha actualizado 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 forma predeterminada) porque esto causará otros problemas de compatibilidad con las bibliotecas.
El siguiente código de ejemplo utiliza jdk1.6.0_45 y bcprov-jdk15on-153.jar (Bouncy Castle SIGNED JAR FILES) para conectarse a cualquier servidor mediante 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 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
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>302 Moved</TITLE></HEAD><BODY>
<H1>302 Moved</H1>
The document has moved
<A HREF="https://www.google.com.sg/?gfe_rd=cr&ei=WRgeVovGEOTH8Afcx4XYAw">here</A>.
</BODY></HTML>
¿Alguien sabe acerca de 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 solo de 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 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();
Debe usar el flujo de entrada y salida de tlsClient / ServerProtocol para leer y escribir datos encriptados (por ejemplo, tlsClientProtocol.getInputStream ()). De lo contrario, si usara, por ejemplo, socket.getOutputStream (), simplemente escribiría datos sin cifrar.
¿Cómo implementar validatecertificate? Estoy usando certificados autofirmados Esto significa que simplemente los busco en el almacén 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.
Un ejemplo más, construido sobre la respuesta de autenticación solo del servidor: TLS con certificados autofirmados con autenticación de cliente (solo muestro 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<Object>());
}
});
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);
}
};
}
});