Client-Zertifikat kann nicht an den Nginx-Server gesendet werden – BouncyCastleJava

Java-Forum
Anonymous
 Client-Zertifikat kann nicht an den Nginx-Server gesendet werden – BouncyCastle

Post by Anonymous »

Hallo, ich versuche, ein clientseitiges Zertifikat mit mTls 1.3 an den Nginx-Server zu senden.
Ich verwende mein selbstsigniertes Zertifikat aus meinem Java-Keystore. Ich habe Firefoxnginx bereits manuell mit dem exportierten Schlüsselpaar aus meinem Keystore getestet. Nginx akzeptiert das.
Aber bei meiner Java-Hüpfburg-Implementierung schlägt es fehl. Ich habe mehrere Tage damit verbracht, konnte aber keine Lösung finden, um das Client-Zertifikat ordnungsgemäß zu senden. Wenn ich die Client-Überprüfung auf Nginx deaktiviere, funktioniert es und dies zeigt, dass mein Zertifikat nicht ordnungsgemäß gesendet wird.
Bitte helfen Sie mir! Vielen Dank im Voraus!

Code: Select all

2026/01/15 12:26:31 [debug] 4549#4549: epoll: fd:5 ev:0001 d:00007F47D4B16010
2026/01/15 12:26:31 [debug] 4549#4549: accept on 0.0.0.0:443, ready: 0
2026/01/15 12:26:31 [debug] 4549#4549: posix_memalign: 000055D246C321F0:512 @16
2026/01/15 12:26:31 [debug] 4549#4549: *2 accept: 192.168.178.57:45070 fd:8
2026/01/15 12:26:31 [debug] 4549#4549: *2 event timer add: 8: 60000:19293951
2026/01/15 12:26:31 [debug] 4549#4549: *2 reusable connection: 1
2026/01/15 12:26:31 [debug] 4549#4549: *2 epoll add event: fd:8 op:1 ev:80002001
2026/01/15 12:26:31 [debug] 4549#4549: epoll del event: fd:5 op:2 ev:00000000
2026/01/15 12:26:31 [debug] 4549#4549: epoll add event: fd:5 op:1 ev:10000001
2026/01/15 12:26:31 [debug] 4549#4549: timer delta: 53104
2026/01/15 12:26:31 [debug] 4549#4549: worker cycle
2026/01/15 12:26:31 [debug] 4549#4549: epoll timer: 60000
2026/01/15 12:26:31 [debug] 4549#4549: epoll: fd:8 ev:0001 d:00007F47D4B162F8
2026/01/15 12:26:31 [debug] 4549#4549: *2 http check ssl handshake
2026/01/15 12:26:31 [debug] 4549#4549: *2 http recv(): 1
2026/01/15 12:26:31 [debug] 4549#4549: *2 https ssl handshake: 0x16
2026/01/15 12:26:31 [debug] 4549#4549: *2 tcp_nodelay
2026/01/15 12:26:31 [debug] 4549#4549: *2 reusable connection: 0
2026/01/15 12:26:31 [debug] 4549#4549: *2 SSL server name: null
2026/01/15 12:26:31 [debug] 4549#4549: *2 SSL_do_handshake: -1
2026/01/15 12:26:31 [debug] 4549#4549: *2 SSL_get_error: 2
2026/01/15 12:26:31 [debug] 4549#4549: timer delta: 65
2026/01/15 12:26:31 [debug] 4549#4549: worker cycle
2026/01/15 12:26:31 [debug] 4549#4549: epoll timer: 59935
2026/01/15 12:26:31 [debug] 4549#4549: epoll: fd:8 ev:0001 d:00007F47D4B162F8
2026/01/15 12:26:31 [debug] 4549#4549: *2 SSL handshake handler: 0
2026/01/15 12:26:31 [debug] 4549#4549: *2 SSL_do_handshake: -1
2026/01/15 12:26:31 [debug] 4549#4549: *2 SSL_get_error: 2
2026/01/15 12:26:31 [debug] 4549#4549: timer delta: 68
2026/01/15 12:26:31 [debug] 4549#4549: worker cycle
2026/01/15 12:26:31 [debug] 4549#4549: epoll timer: 59867
2026/01/15 12:26:32 [debug] 4549#4549: epoll: fd:8 ev:0001 d:00007F47D4B162F8
2026/01/15 12:26:32 [debug] 4549#4549: *2 SSL handshake handler: 0
2026/01/15 12:26:32 [debug] 4549#4549: *2 SSL_do_handshake: -1
2026/01/15 12:26:32 [debug] 4549#4549: *2 SSL_get_error: 1
2026/01/15 12:26:32 [info] 4549#4549: *2 SSL_do_handshake() failed (SSL: error:0A000438:SSL routines::tlsv1 alert internal error:SSL alert number 80) while SSL handshaking, client: 192.168.178.57, server: 0.0.0.0:443
2026/01/15 12:26:32 [debug] 4549#4549: *2 close http connection: 8
2026/01/15 12:26:32 [debug] 4549#4549: *2 event timer del: 8: 19293951
2026/01/15 12:26:32 [debug] 4549#4549: *2 reusable connection: 0
2026/01/15 12:26:32 [debug] 4549#4549: *2 free: 000055D246C321F0, unused: 136
2026/01/15 12:26:32 [debug] 4549#4549: timer delta: 223
Hier ist meine TLSSocketConnectionFactory

Code: Select all

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import javax.net.ssl.HandshakeCompletedEvent;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.tls.TlsClientProtocol;

@Slf4j
public class TLSSocketConnectionFactory extends SSLSocketFactory {
// ******************Adding Custom BouncyCastleProvider*********************//
private final KeyStoreManager keyStoreManager;
private CustomSSLSocket sslSocket;

public TLSSocketConnectionFactory(KeyStoreManager keyStoreManager) {
System.out.println("TLSSocketConnectionFactory:  Initializing with KeyStoreManager");
this.keyStoreManager = keyStoreManager;
}

// ******************HANDSHAKE LISTENER*********************//
public class TLSHandshakeListener implements HandshakeCompletedListener {
@Override
public void handshakeCompleted(HandshakeCompletedEvent event) {
// intentionally empty
}
}

// ******************Adding Custom BouncyCastleProvider*********************//
@Override
public Socket createSocket(Socket socket, final String host, int port, boolean arg3)
throws IOException {
System.out.println("TLSSocketConnectionFactory: Creating socket to host " + host + " on port " + port);
if (socket == null) {
socket = new Socket(); // NOSONAR -- this is closed in the SSLSocket
}

if (!socket.isConnected()) {
socket.connect(new InetSocketAddress(host, port));
}

final TlsClientProtocol tlsClientProtocol =
new TlsClientProtocol(socket.getInputStream(), socket.getOutputStream());
return createSSLSocket(host, tlsClientProtocol);
}

// ******************SOCKET FACTORY  METHODS*********************//
@Override
public String[] getDefaultCipherSuites() {
return new String[]{};
}

@Override
public String[] getSupportedCipherSuites() {
return new String[]{};
}

@Override
public Socket createSocket(String host, int port) {
return null;
}

@Override
public Socket createSocket(InetAddress host, int port) {
return null;
}

@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) {
return null;
}

@Override
public Socket createSocket(
InetAddress address, int port, InetAddress localAddress, int localPort) {
return null;
}

private SSLSocket createSSLSocket(final String host, final TlsClientProtocol tlsClientProtocol) {
sslSocket = new CustomSSLSocket(tlsClientProtocol, host, keyStoreManager);
return sslSocket;
}

public CustomSSLSocket getSSLSocket(final String host, final TlsClientProtocol tlsClientProtocol) {
if (sslSocket == null) {
sslSocket = new CustomSSLSocket(tlsClientProtocol, host, keyStoreManager);
}
return sslSocket;
}
}
und CustomSSLSocket-Klasse

Code: Select all

import TlsFatalAlertExtendedException;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.tls.*;
import org.bouncycastle.tls.crypto.TlsCertificate;
import org.bouncycastle.tls.crypto.TlsCryptoParameters;
import org.bouncycastle.tls.crypto.impl.bc.BcDefaultTlsCredentialedSigner;
import org.bouncycastle.tls.crypto.impl.bc.BcTlsCrypto;
import org.bouncycastle.crypto.util.PrivateKeyFactory;

import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import java.io.*;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Hashtable;
import java.util.Vector;

@Slf4j
public class CustomSSLSocket extends SSLSocket {
private static final int INT_3 = 3; // Unused, consider removing if not needed elsewhere
private final TlsClientProtocol tlsClientProtocol;
private final KeyStoreManager keyStoreManager;
private final String host;
private Certificate[] peertCerts;  // Initialized to null, will be set after validation
private final BcTlsCrypto crypto;

public CustomSSLSocket(TlsClientProtocol tlsClientProtocol, String host, KeyStoreManager keyStoreManager) {
System.out.println("Creating CustomSSLSocket for mTLS...");
// peertCerts is now initialized to null, will be set after server cert validation
this.host = host;
this.keyStoreManager = keyStoreManager;
this.tlsClientProtocol = tlsClientProtocol;
this.crypto = new BcTlsCrypto(new SecureRandom());
}

// Setter for peer certificates, called after successful validation
private void setPeerCertificates(Certificate[] certs) {
this.peertCerts = certs;
}

@Override
public InputStream getInputStream() {
return tlsClientProtocol.getInputStream();
}

@Override
public OutputStream getOutputStream() {
return tlsClientProtocol.getOutputStream();
}

@Override
public synchronized void close() throws IOException {
tlsClientProtocol.close();
}

@Override
public void addHandshakeCompletedListener(HandshakeCompletedListener arg0) {
// intentionally empty
}

@Override
public boolean getEnableSessionCreation() {
return false;
}

@Override
public String[] getEnabledCipherSuites() {
return new String[]{};
}

@Override
public String[] getEnabledProtocols() {
return new String[]{};
}

@Override
public boolean getNeedClientAuth() {
return false;
}

@Override
public SSLSession getSession() {
// Ensure peertCerts is not null, though it should be set by now if handshake succeeded
return new CustomSSLSession(peertCerts != null ? peertCerts : new Certificate[0]);
}

@Override
public String[] getSupportedProtocols() {
return new String[]{};
}

@Override
public boolean getUseClientMode() {
return false;
}

@Override
public boolean getWantClientAuth() {
return false;
}

@Override
public void removeHandshakeCompletedListener(HandshakeCompletedListener arg0) {
// intentionally empty
}

@Override
public void setEnableSessionCreation(boolean arg0) {
// intentionally empty
}

@Override
public void setEnabledCipherSuites(String[] arg0) {
// intentionally empty
}

@Override
public void setEnabledProtocols(String[] arg0) {
// intentionally empty
}

@Override
public void setNeedClientAuth(boolean arg0) {
// intentionally empty
}

@Override
public void setUseClientMode(boolean arg0) {
// intentionally empty
}

@Override
public void setWantClientAuth(boolean arg0) {
// intentionally empty
}

@Override
public String[] getSupportedCipherSuites() {
return new String[]{};
}

@Override
public void startHandshake() throws IOException {
System.out.println("Starting handshake for mTLS...");
// Always use this.crypto instance, not a new one!
tlsClientProtocol.connect(new MyDefaultTlsClient(this.crypto, this.keyStoreManager, this.host));
}

// This method now correctly throws TlsFatalAlertExtendedException if the cert is not trusted.
public void validateCertificate(TlsServerCertificate cert)
throws IOException, CertificateException, KeyStoreException, TlsFatalAlertExtendedException {
System.out.println("Validating server certificate for mTLS...");
byte[] encoded = cert.getCertificate().getCertificateList()[0].getEncoded();
Certificate jsCert =
CertificateFactory.getInstance("X.509")
.generateCertificate(new ByteArrayInputStream(encoded));
String alias = keyStoreManager.getCertificateAlias(jsCert);

if (alias == null) {
System.out.println("Server certificate validation failed: Certificate not trusted.");
// CRITICAL FIX:  UNCOMMENT THIS LINE to properly fail the handshake
throw new TlsFatalAlertExtendedException(AlertDescription.bad_certificate, jsCert);
// Removed the problematic dynamic storing of untrusted certificates.
// If you need to trust a certificate, it should be added to the KeyStoreManager's trust store beforehand.
} else {
System.out.println("Server certificate trusted via alias: " + alias);
}
}

private class MyDefaultTlsClient extends DefaultTlsClient {
private final KeyStoreManager ksm;
private final String hostName;
private TlsContext tlsContext;

public MyDefaultTlsClient(BcTlsCrypto crypto, KeyStoreManager ksm, String hostName) {
super(crypto);
this.ksm = ksm;
this.hostName = hostName;
}

@Override
public void init(TlsClientContext context) {
this.tlsContext = context;
super.init(context);
}

@Override
public Hashtable getClientExtensions() throws IOException {
Hashtable clientExtensions = TlsExtensionsUtils.ensureExtensionsInitialised(super.getClientExtensions());
{
/*
* NOTE: If you are copying test code, do not blindly set these extensions in your own client.
* These are often for specific testing scenarios or legacy compatibility.
* For a general client, you might not need all of them.
*/
TlsExtensionsUtils.addMaxFragmentLengthExtension(clientExtensions, MaxFragmentLength.pow2_9);
TlsExtensionsUtils.addPaddingExtension(clientExtensions, context.getCrypto().getSecureRandom().nextInt(16));
TlsExtensionsUtils.addTruncatedHMacExtension(clientExtensions);
}
return clientExtensions;
}

@Override
public TlsAuthentication getAuthentication() {
System.out.println("Getting TLS authentication for mTLS...");
// IMPORTANT: Pass the BC handshake context, BC crypto, and KeyStoreManager
return new MyTlsAuthentication(tlsContext, (BcTlsCrypto) getCrypto(), ksm);
}

private class MyTlsAuthentication implements TlsAuthentication {
private final TlsContext tlsContext;
private final BcTlsCrypto crypto;
private final KeyStoreManager keyStoreManager;

public MyTlsAuthentication(TlsContext ctx, BcTlsCrypto crypto, KeyStoreManager ksm) {
this.tlsContext = ctx;
this.crypto = crypto;
this.keyStoreManager = ksm;
}

@Override
public void notifyServerCertificate(TlsServerCertificate serverCertificate) throws IOException {
System.out.println("Notifying server certificate for mTLS...");
try {
boolean isEmpty =
serverCertificate == null
|| serverCertificate.getCertificate() == null
|| serverCertificate.getCertificate().isEmpty();
if (isEmpty) {
throw new TlsFatalAlert(AlertDescription.bad_certificate);
}

// Validate the server certificate
validateCertificate(serverCertificate);

// If validation succeeds, store the peer certificates for the SSLSession
TlsCertificate[] bcCertList = serverCertificate.getCertificate().getCertificateList();
Certificate[] javaCertList = new Certificate[bcCertList.length];
for (int i = 0; i < bcCertList.length; i++) {
javaCertList[i] = CertificateFactory.getInstance("X.509")
.generateCertificate(new ByteArrayInputStream(bcCertList[i].getEncoded()));
}
CustomSSLSocket.this.setPeerCertificates(javaCertList);  // Update the outer class's peertCerts

} catch (TlsFatalAlert e) {
// CRITICAL FIX: Re-throw TlsFatalAlert directly to ensure Bouncy Castle handles it
log.error("TLS Fatal Alert during server certificate notification: {}", e.getMessage(), e);
throw e;
} catch (Exception e) {
// For other unexpected exceptions, wrap in IOException
log.error("Error during server certificate notification: {}", e.getMessage(), e);
throw new IOException("Error during server certificate notification: " + e.getMessage(), e);
}
}

@Override
public TlsCredentials getClientCredentials(CertificateRequest certificateRequest) {
System.out.println("Getting client credentials for mTLS...");
try {
// 1. Retrieve keystore credentials
String alias = "CN-ClientCertificate"; // Ensure this alias exists in your KeyStoreManager
KeyStore keyStore = keyStoreManager.getKeyStore();
char[] password = keyStoreManager.getKeystorePass().toCharArray();

// 2. Extract chain and key
java.security.cert.Certificate[] certChain = keyStore.getCertificateChain(alias);
if (certChain == null || certChain.length == 0) {
log.warn("Client certificate chain not found for alias: {}. Returning null credentials.", alias);
// If client cert is required by server, this will cause handshake failure.
// Consider throwing TlsFatalAlert if client cert is mandatory for your use case.
return null;
}
for (Certificate cert : certChain) {
System.out.println("Chain cert subject: " + ((X509Certificate) cert).getSubjectX500Principal());
}

PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, password);
if (privateKey == null) {
log.warn("Client private key not found for alias: {}. Returning null credentials.", alias);
// Similar to above, consider throwing TlsFatalAlert if private key is mandatory.
return null;
}

// 3. Convert certificates to BC format
TlsCertificate[] bcTlsCertChain = new TlsCertificate[certChain.length];
for (int i = 0; i < certChain.length; i++) {
bcTlsCertChain[i] = crypto.createCertificate(certChain[i].getEncoded());
}

org.bouncycastle.tls.Certificate bcTlsCertificate = new org.bouncycastle.tls.Certificate(bcTlsCertChain);
AsymmetricKeyParameter bcKeyParam = PrivateKeyFactory.createKey(privateKey.getEncoded());

// 4.  Choose signature algorithm
SignatureAndHashAlgorithm sigAlg = null;
Vector algs = certificateRequest.getSupportedSignatureAlgorithms();
if (algs != null) {
for (Object a : algs) {
SignatureAndHashAlgorithm alg = (SignatureAndHashAlgorithm) a;

// Check for RSA-PSS (Case 4 / SignatureAlgorithm.rsa_pss_rsae_sha256)
// Prioritize modern, secure algorithms
if (alg.getSignature() == SignatureAlgorithm.rsa_pss_rsae_sha256 && alg.getHash() == HashAlgorithm.sha256) {
sigAlg = alg;
System.out.println("Selected modern signature algorithm: RSA-PSS with SHA256");
break;
}
// Add other preferred algorithms if RSA-PSS is not available
if (alg.getSignature() == SignatureAlgorithm.rsa && alg.getHash() == HashAlgorithm.sha256) {
sigAlg = alg; // Fallback to RSA/SHA256
}
}
}
// If no suitable algorithm was found in the request, default to a common one
if (sigAlg == null) {
sigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.sha256, SignatureAlgorithm.rsa);
System.out.println("Defaulting to standard RSA/SHA256 as no preferred algorithm was found in request.");
}

System.out.println("Client will use signature algorithm: " + SignatureAlgorithm.getName(sigAlg.getSignature()) + " with hash " + HashAlgorithm.getName(sigAlg.getHash()));
// 5. Always pass the handshake's BC context!
TlsCryptoParameters cryptoParams = new TlsCryptoParameters(tlsContext);

System.out.println("Successfully obtained client credentials for mTLS.");
return new BcDefaultTlsCredentialedSigner(
cryptoParams,
crypto,
bcKeyParam,
bcTlsCertificate,
sigAlg
);
} catch (Exception e) {
log.error("Error obtaining client credentials for mTLS: {}", e.getMessage(), e);
// Consider throwing TlsFatalAlert here if client credentials are mandatory
return null;
}
}
}
}
}
Java-Ausgabe:

Code: Select all

TLSSocketConnectionFactory: Creating socket to host 192.168.178.88 on port 443
Creating CustomSSLSocket for mTLS...
Starting handshake for mTLS...
Getting TLS authentication for mTLS...
Notifying server certificate for mTLS...
Validating server certificate for mTLS...
Server certificate trusted via alias: cEbm.L3*
Getting client credentials for mTLS...
Chain cert subject: CN=MY SUBJECT CERTIFICATE
Chain cert subject: CN=MY ISSUER CERTIFICATE
Chain cert subject: CN=MY ROOT CA
Client will use signature algorithm: rsa with hash sha256
Successfully obtained client credentials for mTLS.
2026-01-15T13:26:33.899 [pool-6-thread-1] ERROR c.s.a.c.c.c.o.f.c.Cn4100ClientExceptionHandler - Request timeout when calling Cn4100Client service.: : (-1)  feign.RetryableException: internal_error(80) executing GET https://192.168.178.88:443/config/restapi_version

Quick Reply

Change Text Case: 
   
  • Similar Topics
    Replies
    Views
    Last post