启用 Java SSL 调试日志记录

Java 安全套接字层 (SSL)调试对于开发人员和管理员诊断和解决与在应用程序中建立安全连接相关的问题至关重要。启用 SSL 调试可以深入了解握手过程、密码套件协商和其他安全相关活动。 

在本教程中,我们将通过一系列实际示例探索启用 Java SSL 调试的各种方法。

为什么启用 SSL 调试日志记录?
SSL /TLS 协议是确保互联网数据传输安全的基础。

在应用程序中使用这些协议时,我们可以使用 SSL 调试来增强系统中受 SSL 保护的通信的安全性和效率。它可以帮助我们的一些方式包括:

  • 识别证书不匹配和连接失败等异常情况
  • 监控恶意活动
  • 确保我们使用加密算法的正确实现
  • 优化性能

输出片段可能如下所示:

%% Initialized: [Session-1, SSL_RSA_WITH_AES_256_CBC_SHA]
Cipher Suite: SSL_RSA_WITH_AES_256_CBC_SHA
ServerKeyExchange: EC Diffie-Hellman Server Params: [...]
*** ServerHelloDone
Cert chain length: 1
*** Certificate chain
[...]
Application data: "Hello, World!"

在此示例中,输出从会话初始化开始,然后是有关所选密码套件、密钥交换参数和握手完成的详细信息。它还包括有关安全传输的证书链和应用程序数据的信息。

使用命令行选项
启用 SSL 调试日志记录的一种直接方法是通过命令行选项。Java 允许我们通过javax.net.debug系统属性来配置它。该属性接受各种调试选项,允许用户自定义调试输出的详细程度:

java -Djavax.net.debug=ssl -jar MyApp.jar
此命令激活所有 SSL 相关活动的调试。对于更详细的调试,其他一些有用的选项是:

握手以获取 SSL 握手期间的详细信息
keygen用于获取密钥生成详细信息
记录有关记录层处理的信息
完整的选项列表可以在官方文档中找到。

让我们利用握手选项来生成与握手过程相关的 SSL 日志:

java -Djavax.net.debug=ssl:handshake -jar MyApp.jar
执行上述命令时,输出包含握手的详细信息。在解决客户端和服务器之间建立 SSL/TLS 连接的初始阶段的问题时,它非常有用。以下是日志的片段:

Allow unsafe renegotiation: false
Allow legacy hello messages: true
Is initial handshake: true
Is secure renegotiation: false
...
main, READ: TLSv1.2 Handshake, length = 232
...
*** ClientHello, TLSv1.2
...

输出包括有关协商过程的信息、正在使用的协议版本(在本例中为 TLSv1.2)以及有关客户端和服务器之间交换的初始握手消息的详细信息。

使用系统属性
在某些情况下,我们可能希望在运行时动态启用 SSL 调试日志记录。我们可以通过以编程方式更改javax.net.debug系统属性的值来做到这一点:

static void enableSSLDebugUsingSystemProperties() {
    System.setProperty("javax.net.debug", "ssl");
}

让我们尝试发起一个虚拟 HTTPS 请求来检索日志:

static void makeHttpsRequest() throws Exception {
    String url = "https://github.com/eugenp/tutorials";
    URL httpsUrl = new URL(url);
    HttpsURLConnection connection = (HttpsURLConnection) httpsUrl.openConnection();
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
        String line;
        logger.info(
"Response from " + url + ":");
        while ((line = reader.readLine()) != null) {
            logger.info(line);
        }
    }
}

这种方法允许我们根据应用程序中的特定事件或条件切换登录和关闭:

@Test
void givenSSLDebuggingEnabled_whenUsingSystemProperties_thenEnableSSLDebugLogging() {
    ByteArrayOutputStream outContent = new ByteArrayOutputStream();
    System.setErr(new PrintStream(outContent));
    SSLDebugLogger.enableSSLDebugUsingSystemProperties();
    assertEquals("ssl", System.getProperty("javax.net.debug"));
    
    SSLDebugLogger.makeHttpsRequest();
    assertTrue(outContent.toString().contains(
"javax.net.ssl|DEBUG|"));
    outContent.reset();
    System.clearProperty(
"javax.net.debug");
    assertNull(System.getProperty(
"javax.net.debug"));
    
    SSLDebugLogger.makeHttpsRequest();
    assertEquals(outContent.toString(),
"");
}

使用系统属性启用 SSL 调试可提供快速设置,不需要任何配置文件并允许即时调试。

使用日志配置文件
我们还可以配置Java Logging来捕获SSL调试信息。通过创建logging.properties文件并将其放置在类路径中,我们可以自定义日志记录行为。

让我们将以下行添加到logging.properties文件以启用日志记录:

java.util.logging.ConsoleHandler.level=ALL
java.net.ssl.handlers=java.util.logging.ConsoleHandler
javax.net.ssl.level=ALL

这些属性告诉控制台处理程序捕获各个级别的消息。

让我们通过单元测试来测试新行为:

@Test
void givenSSLDebuggingEnabled_whenUsingConfigurationFile_thenEnableSSLDebugLogging() throws IOException {
    InputStream configFile = SSLDebugLoggerUnitTest.class.getClassLoader().getResourceAsStream("logging.properties");
    LogManager.getLogManager().readConfiguration(configFile);
    Logger sslLogger = Logger.getLogger(
"javax.net.ssl");
    ConsoleHandler consoleHandler = (ConsoleHandler) sslLogger.getHandlers()[0];
    Level consoleHandlerLevel = consoleHandler.getLevel();
    assertEquals(Level.ALL, consoleHandlerLevel,
"SSL ConsoleHandler level should be ALL");
}

此方法提供对 SSL 调试设置的精细控制,但任何更改通常都需要重新启动应用程序。