bcgit / bc-java

Bouncy Castle Java Distribution (Mirror)
https://www.bouncycastle.org/java.html
MIT License
2.31k stars 1.14k forks source link

TLS with brainpoolP256r1 Key #1442

Open vbrandl opened 1 year ago

vbrandl commented 1 year ago

I want to implement a TLS server and client that use a certificate with a brainpoolP256r1 keypair.

First here is how I generated the certificate:

openssl ecparam -name brainpoolP256r1 -genkey -out key.pem
openssl req -new -x509 -sha256 -days 365 -key key.pem -out cert.pem

Attached you can see my server and client code in Server.java and Client.java respectively.

I tried various variations of passing the Provider directly when creating the SSLContext and setting it in java.security like this

security.provider.1=SUN
security.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
security.provider.3=org.bouncycastle.jce.provider.BouncyCastleProvider
security.provider.4=SunRsaSign
security.provider.5=SunEC
security.provider.6=SunJSSE
security.provider.7=SunJCE
security.provider.8=SunJGSS
security.provider.9=SunSASL
security.provider.10=XMLDSig
security.provider.11=SunPCSC
security.provider.12=JdkLDAP
security.provider.13=JdkSASL
security.provider.14=SunPKCS11

I tried both OpenJDK 11.0.19 and 17.0.7.

From the exceptions below, I can see, the bouncycastle provider is actually used.

Server Side

INFO: [server #1 @584f54e6] raised fatal(2) handshake_failure(40) alert: Failed to read record
org.bouncycastle.tls.TlsFatalAlert: handshake_failure(40); [server #1 @584f54e6] found no selectable cipher suite among the 37 offered: [{0x13,0x03}(TLS_CHACHA20_POLY1305_SHA256), {0x13,0x02}(TLS_AES_256_GCM_SHA38
4), {0x13,0x01}(TLS_AES_128_GCM_SHA256), {0xcc,0xa9}(TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256), {0xc0,0x2c}(TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384), {0xc0,0x2b}(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256), {0xc
c,0xa8}(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256), {0xc0,0x30}(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384), {0xc0,0x2f}(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256), {0xcc,0xaa}(TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256),
 {0x00,0x9f}(TLS_DHE_RSA_WITH_AES_256_GCM_SHA384), {0x00,0xa3}(TLS_DHE_DSS_WITH_AES_256_GCM_SHA384), {0x00,0x9e}(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256), {0x00,0xa2}(TLS_DHE_DSS_WITH_AES_128_GCM_SHA256), {0xc0,0x24}(
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384), {0xc0,0x28}(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384), {0xc0,0x23}(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256), {0xc0,0x27}(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256), {0x00,0x6b}(T
LS_DHE_RSA_WITH_AES_256_CBC_SHA256), {0x00,0x6a}(TLS_DHE_DSS_WITH_AES_256_CBC_SHA256), {0x00,0x67}(TLS_DHE_RSA_WITH_AES_128_CBC_SHA256), {0x00,0x40}(TLS_DHE_DSS_WITH_AES_128_CBC_SHA256), {0xc0,0x0a}(TLS_ECDHE_ECDS
A_WITH_AES_256_CBC_SHA), {0xc0,0x14}(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA), {0xc0,0x09}(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA), {0xc0,0x13}(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA), {0x00,0x39}(TLS_DHE_RSA_WITH_AES_256_CB
C_SHA), {0x00,0x38}(TLS_DHE_DSS_WITH_AES_256_CBC_SHA), {0x00,0x33}(TLS_DHE_RSA_WITH_AES_128_CBC_SHA), {0x00,0x32}(TLS_DHE_DSS_WITH_AES_128_CBC_SHA), {0x00,0x9d}(TLS_RSA_WITH_AES_256_GCM_SHA384), {0x00,0x9c}(TLS_RS
A_WITH_AES_128_GCM_SHA256), {0x00,0x3d}(TLS_RSA_WITH_AES_256_CBC_SHA256), {0x00,0x3c}(TLS_RSA_WITH_AES_128_CBC_SHA256), {0x00,0x35}(TLS_RSA_WITH_AES_256_CBC_SHA), {0x00,0x2f}(TLS_RSA_WITH_AES_128_CBC_SHA), {0x00,$
xff}(TLS_EMPTY_RENEGOTIATION_INFO_SCSV)]
        at org.bouncycastle.tls.AbstractTlsServer.getSelectedCipherSuite(Unknown Source)
        at org.bouncycastle.jsse.provider.ProvTlsServer.getSelectedCipherSuite(Unknown Source)
        at org.bouncycastle.tls.TlsServerProtocol.generate13ServerHello(Unknown Source)
        at org.bouncycastle.tls.TlsServerProtocol.generateServerHello(Unknown Source)
        at org.bouncycastle.tls.TlsServerProtocol.handleHandshakeMessage(Unknown Source)
        at org.bouncycastle.tls.TlsProtocol.processHandshakeQueue(Unknown Source)
        at org.bouncycastle.tls.TlsProtocol.processRecord(Unknown Source)
        at org.bouncycastle.tls.RecordStream.readRecord(Unknown Source)
        at org.bouncycastle.tls.TlsProtocol.safeReadRecord(Unknown Source)
        at org.bouncycastle.tls.TlsProtocol.blockForHandshake(Unknown Source)
        at org.bouncycastle.tls.TlsServerProtocol.accept(Unknown Source)
        at org.bouncycastle.jsse.provider.ProvSSLSocketDirect.startHandshake(Unknown Source)
        at org.bouncycastle.jsse.provider.ProvSSLSocketDirect.handshakeIfNecessary(Unknown Source)
        at org.bouncycastle.jsse.provider.ProvSSLSocketDirect$AppDataInput.read(Unknown Source)
        at java.base/sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
        at java.base/sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
        at java.base/sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
        at java.base/java.io.InputStreamReader.read(InputStreamReader.java:181)
        at java.base/java.io.BufferedReader.fill(BufferedReader.java:161)
        at java.base/java.io.BufferedReader.readLine(BufferedReader.java:326)
        at java.base/java.io.BufferedReader.readLine(BufferedReader.java:392)
        at org.example.tlsserver.Main.getHeaderLines(Main.java:109)
        at org.example.tlsserver.Main.startSingleThreaded(Main.java:53)
        at org.example.tlsserver.Main.main(Main.java:28)

Client Side

INFO: [client #2 @1ddae9b5] disconnected from 127.0.0.1:1337
Exception in thread "main" org.bouncycastle.tls.TlsFatalAlertReceived: handshake_failure(40)
        at org.bouncycastle.tls.TlsProtocol.handleAlertMessage(Unknown Source)
        at org.bouncycastle.tls.TlsProtocol.processAlertQueue(Unknown Source)
        at org.bouncycastle.tls.TlsProtocol.processRecord(Unknown Source)
        at org.bouncycastle.tls.RecordStream.readRecord(Unknown Source)
        at org.bouncycastle.tls.TlsProtocol.safeReadRecord(Unknown Source)
        at org.bouncycastle.tls.TlsProtocol.blockForHandshake(Unknown Source)
        at org.bouncycastle.tls.TlsClientProtocol.connect(Unknown Source)
        at org.bouncycastle.jsse.provider.ProvSSLSocketWrap.startHandshake(Unknown Source)
        at org.bouncycastle.jsse.provider.ProvSSLSocketWrap.startHandshake(Unknown Source)
        at org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory.executeHandshake(SSLConnectionSocketFactory.java:303)
        at org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:275)
        at org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:251)
        at org.apache.hc.client5.http.impl.io.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:181)
        at org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:447)
        at org.apache.hc.client5.http.impl.classic.InternalExecRuntime.connectEndpoint(InternalExecRuntime.java:162)
        at org.apache.hc.client5.http.impl.classic.InternalExecRuntime.connectEndpoint(InternalExecRuntime.java:172)
        at org.apache.hc.client5.http.impl.classic.ConnectExec.execute(ConnectExec.java:142)
        at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
        at org.apache.hc.client5.http.impl.classic.ProtocolExec.execute(ProtocolExec.java:192)
        at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
        at org.apache.hc.client5.http.impl.classic.HttpRequestRetryExec.execute(HttpRequestRetryExec.java:96)
        at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
        at org.apache.hc.client5.http.impl.classic.ContentCompressionExec.execute(ContentCompressionExec.java:152)
        at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
        at org.apache.hc.client5.http.impl.classic.RedirectExec.execute(RedirectExec.java:115)
        at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
        at org.apache.hc.client5.http.impl.classic.InternalHttpClient.doExecute(InternalHttpClient.java:170)
        at org.apache.hc.client5.http.impl.classic.CloseableHttpClient.execute(CloseableHttpClient.java:245)
        at org.apache.hc.client5.http.impl.classic.CloseableHttpClient.execute(CloseableHttpClient.java:188)
        at org.apache.hc.client5.http.impl.classic.CloseableHttpClient.execute(CloseableHttpClient.java:162)
        at org.example.mtls.Main.sendRequestApache(Main.java:150)
        at org.example.mtls.Main.main(Main.java:60)

code

Server.java

Server.java ```java package org.example.tlsserver; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.security.KeyStore; import java.util.ArrayList; import java.util.List; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; public class Server { public static void main(String[] args) throws IOException { final short port = Short.parseShort(args[0]); final String keyStorePath = args[1]; final char[] keyStorePass = args.length < 3 ? "123456".toCharArray() : args[2].toCharArray(); InetSocketAddress address = new InetSocketAddress("0.0.0.0", port); startSingleThreaded(address, keyStorePath, keyStorePass); } public static void startSingleThreaded( InetSocketAddress address, final String keyStorePath, char[] keyStorePass) { System.out.println("Start single-threaded server at " + address); try (ServerSocket serverSocket = getServerSocket(address, keyStorePath, keyStorePass)) { java.nio.charset.Charset encoding = StandardCharsets.UTF_8; // This infinite loop is not CPU-intensive since method "accept" blocks // until a client has made a connection to the socket while (true) { try (java.net.Socket socket = serverSocket.accept(); // Use the socket to read the client's request BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), encoding)); // Writing to the output stream and then closing it sends // data to the client BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), encoding))) { getHeaderLines(reader).forEach(System.out::println); writer.write(getResponse()); writer.flush(); } catch (IOException e) { System.err.println("Exception while handling connection"); e.printStackTrace(); } } } catch (Exception e) { System.err.println("Could not create socket at " + address); e.printStackTrace(); } } private static ServerSocket getServerSocket( InetSocketAddress address, final String keyStore, final char[] keyStorePass) throws Exception { // Backlog is the maximum number of pending connections on the socket, // 0 means that an implementation-specific default is used int backlog = 0; Path keyStorePath = new File(keyStore).toPath(); // Bind the socket to the given port and address ServerSocket serverSocket = getSslContext(keyStorePath, keyStorePass) .getServerSocketFactory() .createServerSocket(address.getPort(), backlog, address.getAddress()); return serverSocket; } private static SSLContext getSslContext(Path keyStorePath, char[] keyStorePass) throws Exception { KeyStore keyStore = KeyStore.getInstance("PKCS12"); keyStore.load(new FileInputStream(keyStorePath.toFile()), keyStorePass); KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); keyManagerFactory.init(keyStore, keyStorePass); SSLContext sslContext = SSLContext.getInstance("TLSv1.3", new BouncyCastleJsseProvider(new BouncyCastleProvider())); // Null means using default implementations for TrustManager and SecureRandom sslContext.init(keyManagerFactory.getKeyManagers(), null, null); return sslContext; } private static String getResponse() { return "HTTP/1.1 404 not found\r\n\r\n"; } private static List getHeaderLines(BufferedReader reader) throws IOException { ArrayList lines = new ArrayList(); String line = reader.readLine(); // An empty line marks the end of the request's header while (!line.isEmpty()) { lines.add(line); line = reader.readLine(); } return lines; } } ```

Client.java

Client.java ```java package org.example.mtls; import java.io.DataInputStream; import java.io.IOException; import java.net.URI; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import javax.net.ssl.SSLContext; import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; import org.apache.hc.client5.http.io.HttpClientConnectionManager; import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder; import org.apache.hc.client5.http.ssl.TrustAllStrategy; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.ssl.TLS; import org.apache.hc.core5.ssl.SSLContextBuilder; import org.apache.hc.core5.util.Timeout; public final class Client { private static void usage() { System.err.println("Usage:"); System.err.println("\tjava -jar client.jar "); System.exit(1); } public static void main(final String[] args) throws IOException, NoSuchAlgorithmException, KeyManagementException, KeyStoreException { if (args.length < 1) { usage(); } final URI uri = URI.create(args[0]); sendRequestApache(uri); } private static void sendRequestApache(final URI endpoint) throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException, IOException { final SSLContext context = SSLContextBuilder.create().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build(); final SSLConnectionSocketFactory scsf = SSLConnectionSocketFactoryBuilder.create() .setSslContext(context) .setHostnameVerifier(NoopHostnameVerifier.INSTANCE) .setTlsVersions(TLS.V_1_2, TLS.V_1_3) .build(); final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() .setSSLSocketFactory(scsf) .setDefaultTlsConfig( TlsConfig.custom() .setHandshakeTimeout(Timeout.ofSeconds(30)) .setSupportedProtocols(TLS.V_1_2, TLS.V_1_3) .build()) .build(); try (CloseableHttpClient client = HttpClients.custom().setConnectionManager(cm).build()) { final HttpGet request = new HttpGet(endpoint); client.execute( request, response -> { System.out.println("Status: " + response.getCode()); System.out.println("Headers: "); for (final Header header : request.getHeaders()) { System.out.println("\t" + header.getName() + ": " + header.getValue()); } System.out.println("Body: "); System.out.println(); try (DataInputStream dis = new DataInputStream(response.getEntity().getContent())) { final String body = dis.readUTF(); System.out.println(body); } return 1; }); } } } ```
peterdettman commented 1 year ago

Firstly, use the "PKIX" KeyManager (resp. TrustManager) from "BCJSSE"; you can't mix the SunJSSE ones with the BCJSSE SSLContext.

Secondly, to use brainpool for your server cert with TLS 1.3 you have to enable the relevant signature scheme (e.g. "ecdsa_brainpoolP256r1tls13_sha256"), which is not enabled by default. See the system properties "jdk.tls.client.SignatureSchemes" and "jdk.tls.server.SignatureSchemes", or from JDK 19 you can use SSLParameters.setSignatureSchemes.

Enabling the curve i.e. NamedGroup.brainpoolP256r1tls13 is also a consideration, but that is enabled by default in BCJSSE.

Also try changing your java.util.logging API logging level down to FINEST to get more information from the server (and KeyManager) about the cipher suite selection process (which depends in turn on credentials selection).