Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ log/
temp/
ci/Red5-release-steps.md
/.metadata/

*.crt
*.pem
17 changes: 0 additions & 17 deletions client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,22 +53,5 @@
<version>${mina.version}</version>
<type>bundle</type>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>${ehcache.version}</version>
<scope>runtime</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -1037,17 +1037,17 @@ public void resultReceived(IPendingServiceCall call) {
} else if (callResult instanceof Map) {
Map<?, ?> map = (Map<?, ?>) callResult;
// XXX(paul) log out the map contents
log.warn("CreateStreamCallBack resultReceived - map: {}", map);
log.warn("{} resultReceived - map: {}", call.getClass().getSimpleName(), map);
if (map.containsKey("streamId")) {
Object tmpStreamId = map.get("streamId");
if (tmpStreamId instanceof Number) {
streamId = ((Number) tmpStreamId).intValue();
} else {
log.warn("CreateStreamCallBack resultReceived - stream id is not a number: {}", tmpStreamId);
log.warn("{} resultReceived - stream id is not a number: {}", call.getClass().getName(), tmpStreamId);
}
}
}
log.debug("CreateStreamCallBack resultReceived - stream id: {} call: {} connection: {}", streamId, call, conn);
log.debug("{} resultReceived - stream id: {} call: {} connection: {}", call.getClass().getName(), streamId, call, conn);
if (conn != null && streamId != -1) {
log.debug("Setting new net stream");
NetStream stream = new NetStream(streamEventDispatcher);
Expand All @@ -1062,7 +1062,7 @@ public void resultReceived(IPendingServiceCall call) {
}
wrapped.resultReceived(call);
} else {
log.warn("CreateStreamCallBack resultReceived - call result is null");
log.warn("{} resultReceived - call result is null", call.getClass().getSimpleName());
}
}
}
Expand All @@ -1078,6 +1078,7 @@ public ReleaseStreamCallBack(IPendingServiceCallback wrapped) {

@Override
public void resultReceived(IPendingServiceCall call) {
log.debug("ReleaseStreamCallBack resultReceived - call: {}", call);
wrapped.resultReceived(call);
}
}
Expand All @@ -1093,6 +1094,7 @@ public DeleteStreamCallBack(IPendingServiceCallback wrapped) {

@Override
public void resultReceived(IPendingServiceCall call) {
log.debug("DeleteStreamCallBack resultReceived - call: {}", call);
// get the result as base object
Object callResult = call.getResult();
if (callResult != null) {
Expand Down
19 changes: 19 additions & 0 deletions client/src/main/java/org/red5/client/net/rtmp/RTMPClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,23 @@ public void setProtocol(String protocol) throws Exception {
throw new Exception("Unsupported protocol specified, please use the correct client for the intended protocol");
}
}

/**
* Logs the contents of a map at debug level.
* This is useful for debugging purposes, especially when dealing with connection parameters or other
* configuration settings.
*
* @param map
* @param name
*/
protected void logMap(Map<String, Object> map, String name) {
if (isDebug) {
log.debug("logMap------ {} -------", name);
for (String key : map.keySet()) {
Object obj = map.get(key);
log.debug("{} : {} ({})", key, obj, obj == null ? "--" : obj.getClass().getName());
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,7 @@ public BaseConnection createConnection(Class<?> connCls) {
conn = createConnectionInstance(connCls);
// add to local map
connMap.put(conn.getSessionId(), conn);
log.trace("Connections: {}", conns.incrementAndGet());
log.trace("Connection created: {}", conn);
log.trace("Connections: {} created: {}", conns.incrementAndGet(), conn);
} catch (Exception ex) {
log.warn("Exception creating connection", ex);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package org.red5.client.net.rtmps;

import java.io.FileWriter;
import java.io.PrintWriter;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Utility class to grab and save server certificates from a given host and port; useful for TLS connections to ensure
* a server's certificate is trusted.
*
* Usage:
* Call the `retrieveCertificate` method with the desired host and port to save the server's certificate in PEM format.
*
* @author Paul Gregoire
*/
public class CertificateGrabber {

private static Logger log = LoggerFactory.getLogger(CertificateGrabber.class);

/**
* Retrieves the server certificate from the specified host and port.
*
* @param host
* @param port
* @throws Exception
*/
public static void retrieveCertificate(String host, int port) throws Exception {
// Create a trust manager that captures certificates
final X509Certificate[] serverCert = new X509Certificate[1];
TrustManager[] trustManagers = new TrustManager[] { new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}

public void checkServerTrusted(X509Certificate[] chain, String authType) {
// Capture the server certificate
if (chain != null && chain.length > 0) {
serverCert[0] = chain[0];
}
}

public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
} };
// Create SSL context
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagers, null);
// Connect to the server
SSLSocketFactory factory = sslContext.getSocketFactory();
try (SSLSocket socket = (SSLSocket) factory.createSocket(host, port)) {
socket.startHandshake();
// Get the certificate chain
SSLSession session = socket.getSession();
Certificate[] certs = session.getPeerCertificates();
// Save the certificate
if (certs != null && certs.length > 0) {
X509Certificate cert = (X509Certificate) certs[0];
saveCertificate(cert, String.format("%s.pem", host));
log.debug("Certificate subject: {} issuer: {}\n serial number: {}\n valid from: {} to: {}", cert.getSubjectX500Principal(), cert.getIssuerX500Principal(), cert.getSerialNumber(), cert.getNotBefore(), cert.getNotAfter());
}
}
}

/**
* Saves the given X509 certificate to a file in PEM format.
*
* @param cert the X509 certificate to save
* @param fileName name of the file to save the certificate to
* @throws Exception if an error occurs while saving the certificate
*/
private static void saveCertificate(X509Certificate cert, String fileName) throws Exception {
// Save as PEM format
try (FileWriter fw = new FileWriter(fileName); PrintWriter pw = new PrintWriter(fw)) {
pw.println("-----BEGIN CERTIFICATE-----");
pw.println(Base64.encodeBase64String(cert.getEncoded()));
pw.println("-----END CERTIFICATE-----");
}
log.debug("Certificate saved to: {}", fileName);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package org.red5.client.net.rtmps;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Collection;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Manages a PKCS#12 (P12) truststore for TLS connections.
*/
public class P12StoreManager {

private static Logger log = LoggerFactory.getLogger(P12StoreManager.class);

@SuppressWarnings("unchecked")
public static KeyStore buildTrustStore(String truststorePath, char[] truststorePassword, String certPath) {
KeyStore trustStore = null;
try {
// load / create the store (P12 format)
trustStore = KeyStore.getInstance("PKCS12");
try (InputStream is = new FileInputStream(truststorePath)) {
trustStore.load(is, truststorePassword);
log.info("Truststore loaded successfully from {}", truststorePath);
} catch (FileNotFoundException e) {
// store doesn't exist, create a new one
trustStore.load(null, truststorePassword);
log.info("New truststore created successfully at {}", truststorePath);
}
// load the certs
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
// if we have a single PEM file with the full chain
try (InputStream certIs = new FileInputStream(certPath)) {
// read all certificates from the stream (useful for chain)
for (X509Certificate cert : (Collection<X509Certificate>) certFactory.generateCertificates(certIs)) {
// generate an alias based on the certificate's subject name
// using hashCode to ensure uniqueness
String alias = "cert_" + Integer.toHexString(cert.getSubjectX500Principal().getName().hashCode());
log.info("Processing certificate with alias: {}", alias);
if (trustStore.containsAlias(alias)) {
log.warn("Certificate with alias '{}' already exists, skipping", alias);
} else {
trustStore.setCertificateEntry(alias, cert);
log.info("Imported certificate: {}", cert.getSubjectX500Principal().getName());
}
}
}
// save the KeyStore
try (FileOutputStream fos = new FileOutputStream(truststorePath)) {
trustStore.store(fos, truststorePassword);
log.info("Truststore saved successfully to {}", truststorePath);
}
} catch (Exception e) {
log.error("Error managing truststore", e);
}
return trustStore;
}
}
Loading