Skip to content

Commit 73e4cc3

Browse files
committed
Fix certificate chain handling for RTMPS urls
1 parent eac0b81 commit 73e4cc3

4 files changed

Lines changed: 61 additions & 18 deletions

File tree

client/src/main/java/org/red5/client/net/rtmps/CertificateGrabber.java

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,21 @@ public class CertificateGrabber {
3030
private static Logger log = LoggerFactory.getLogger(CertificateGrabber.class);
3131

3232
/**
33-
* Retrieves the server certificate from the specified host and port.
33+
* Retrieves the full certificate chain from the specified host and port.
34+
* This includes the server certificate and all intermediate CA certificates,
35+
* which are required for proper TLS validation.
3436
*
35-
* @param host
36-
* @param port
37-
* @throws Exception
37+
* @param host the hostname to connect to
38+
* @param port the port to connect to
39+
* @throws Exception if an error occurs while retrieving certificates
3840
*/
3941
public static void retrieveCertificate(String host, int port) throws Exception {
40-
// Create a trust manager that captures certificates
41-
final X509Certificate[] serverCert = new X509Certificate[1];
42+
// Create a trust manager that accepts all certificates (for retrieval only)
4243
TrustManager[] trustManagers = new TrustManager[] { new X509TrustManager() {
4344
public void checkClientTrusted(X509Certificate[] chain, String authType) {
4445
}
4546

4647
public void checkServerTrusted(X509Certificate[] chain, String authType) {
47-
// Capture the server certificate
48-
if (chain != null && chain.length > 0) {
49-
serverCert[0] = chain[0];
50-
}
5148
}
5249

5350
public X509Certificate[] getAcceptedIssuers() {
@@ -61,23 +58,52 @@ public X509Certificate[] getAcceptedIssuers() {
6158
SSLSocketFactory factory = sslContext.getSocketFactory();
6259
try (SSLSocket socket = (SSLSocket) factory.createSocket(host, port)) {
6360
socket.startHandshake();
64-
// Get the certificate chain
61+
// Get the full certificate chain
6562
SSLSession session = socket.getSession();
6663
Certificate[] certs = session.getPeerCertificates();
67-
// Save the certificate
64+
// Save the full certificate chain
6865
if (certs != null && certs.length > 0) {
69-
X509Certificate cert = (X509Certificate) certs[0];
7066
// check for path to store the certificate
7167
String truststorePath = System.getProperty("javax.net.ssl.trustStore");
7268
if (truststorePath == null || truststorePath.isEmpty()) {
7369
throw new IllegalStateException("Truststore path is not set. Please set 'javax.net.ssl.trustStore' system property.");
7470
}
7571
String pemPath = truststorePath.substring(0, truststorePath.lastIndexOf('/'));
76-
log.info("CertificateGrabber - pemPath: {}", pemPath);
77-
saveCertificate(cert, String.format("%s/%s.pem", pemPath, host));
78-
log.debug("Certificate subject: {} issuer: {}\n serial number: {}\n valid from: {} to: {}", cert.getSubjectX500Principal(), cert.getIssuerX500Principal(), cert.getSerialNumber(), cert.getNotBefore(), cert.getNotAfter());
72+
log.info("CertificateGrabber - pemPath: {}, chain length: {}", pemPath, certs.length);
73+
// Save all certificates in the chain to a single PEM file
74+
saveCertificateChain(certs, String.format("%s/%s.pem", pemPath, host));
75+
// Log info about each certificate in the chain
76+
for (int i = 0; i < certs.length; i++) {
77+
X509Certificate cert = (X509Certificate) certs[i];
78+
log.debug("Certificate[{}] subject: {} issuer: {} serial: {} valid: {} to {}", i, cert.getSubjectX500Principal(), cert.getIssuerX500Principal(), cert.getSerialNumber(), cert.getNotBefore(), cert.getNotAfter());
79+
}
80+
}
81+
}
82+
}
83+
84+
/**
85+
* Saves the full certificate chain to a file in PEM format.
86+
* All certificates are written to a single file, which can then be
87+
* imported into a truststore.
88+
*
89+
* @param certs the certificate chain to save
90+
* @param fileName name of the file to save the certificates to
91+
* @throws Exception if an error occurs while saving the certificates
92+
*/
93+
private static void saveCertificateChain(Certificate[] certs, String fileName) throws Exception {
94+
// Save all certificates as PEM format in a single file
95+
try (FileWriter fw = new FileWriter(fileName); PrintWriter pw = new PrintWriter(fw)) {
96+
for (int i = 0; i < certs.length; i++) {
97+
X509Certificate cert = (X509Certificate) certs[i];
98+
pw.println("-----BEGIN CERTIFICATE-----");
99+
pw.println(Base64.encodeBase64String(cert.getEncoded()));
100+
pw.println("-----END CERTIFICATE-----");
101+
if (i < certs.length - 1) {
102+
pw.println(); // blank line between certificates
103+
}
79104
}
80105
}
106+
log.info("Certificate chain ({} certs) saved to: {}", certs.length, fileName);
81107
}
82108

83109
/**
@@ -87,6 +113,7 @@ public X509Certificate[] getAcceptedIssuers() {
87113
* @param fileName name of the file to save the certificate to
88114
* @throws Exception if an error occurs while saving the certificate
89115
*/
116+
@SuppressWarnings("unused")
90117
private static void saveCertificate(X509Certificate cert, String fileName) throws Exception {
91118
// Save as PEM format
92119
try (FileWriter fw = new FileWriter(fileName); PrintWriter pw = new PrintWriter(fw)) {

client/src/main/java/org/red5/client/net/rtmps/RTMPSClient.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,24 @@ protected void startConnector(String server, int port) {
170170
keyStoreType = keyStoreType == null ? "PKCS12" : truststorePath.lastIndexOf(".p12") > 0 ? "PKCS12" : "JKS";
171171
log.debug("RTMPSClient - keystoreType: {}, truststorePath: {}", keyStoreType, truststorePath);
172172
}
173-
// strip the end of the truststore path to use it for saving the pem file
174-
String pemPath = truststorePath.substring(0, truststorePath.lastIndexOf('/'));
173+
// ensure the truststore parent directory exists
174+
java.nio.file.Path truststoreFile = Paths.get(truststorePath);
175+
java.nio.file.Path parentDir = truststoreFile.getParent();
176+
String pemPath;
177+
if (parentDir != null) {
178+
try {
179+
if (!Files.exists(parentDir)) {
180+
Files.createDirectories(parentDir);
181+
log.info("Created truststore directory: {}", parentDir);
182+
}
183+
} catch (IOException e) {
184+
log.warn("Failed to create truststore directory: {}", parentDir, e);
185+
}
186+
pemPath = parentDir.toString();
187+
} else {
188+
// truststore is in current directory
189+
pemPath = ".";
190+
}
175191
log.info("RTMPSClient - pemPath: {}", pemPath);
176192
// ensure the truststore has a certificate for the server we are connecting to
177193
try {

tests/conf/server.p12

-3.46 KB
Binary file not shown.

tests/conf/truststore.p12

-982 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)