如何在 java TLS 服务器上启用 OCSP 装订?

How to enable OCSP stapling on java TLS server?

此 post 是跨posted,因此请务必检查更新 in coderanch

我很难在我的 client/server 应用程序上实施 OCSP 吊销检查,我设法让客户端 OCSP 工作,我用 openssl 实施了我自己的 OCSP 响应器,我正在检查我的签名的证书自己的 CA.

尝试从服务器检查它们时出现问题。我按照 Standard Edition Security Developer’s Guide 上的说明操作,更确切地说是这样:

static class ServerParameters {
    boolean enabled = true;
    int cacheSize = 256;
    int cacheLifetime = 3600;
    int respTimeout = 5000;
    String respUri = "http://localhost:9999";
    boolean respOverride = false;
    boolean ignoreExts = false;
    String[] protocols = new String[]{ "TLSv1.2" };
    String[] ciphers = null;

    ServerParameters() { }
}

... 在我调用 SSLContext.getInstance("TSL") 之前:

ServerParameters servParams = new ServerParameters();        
System.setProperty("jdk.tls.server.enableStatusRequestExtension",
                Boolean.toString(servParams.enabled));

    System.setProperty("jdk.tls.stapling.cacheSize",
            Integer.toString(servParams.cacheSize));
    System.setProperty("jdk.tls.stapling.cacheLifetime",
            Integer.toString(servParams.cacheLifetime));
    System.setProperty("jdk.tls.stapling.responseTimeout",
            Integer.toString(servParams.respTimeout));
    System.setProperty("jdk.tls.stapling.responderURI", servParams.respUri);
    System.setProperty("jdk.tls.stapling.responderOverride",
            Boolean.toString(servParams.respOverride));
    System.setProperty("jdk.tls.stapling.ignoreExtensions",
            Boolean.toString(servParams.ignoreExts));

在握手过程中检查数据包,我意识到客户端正确添加了 status_request 扩展,但是服务器没有发送 CertificateStatusMessage,也没有向 OCSP 响应者发送请求。

我也将响应者 URL 添加到证书中。 我尝试使用 TLS 1.2 和 1.3,Java 11 和 15。没有成功。

它应该如何工作:

Wireshark 的外观:

为 wireshark 正确配置的端口将消息显示为 tls:

我不知道我做错了什么,文档看起来很简单,但我做不到。

Wireshark 生成的包含握手数据包信息的文本文件:file.txt

客户问候status_request:

        Extension: status_request (len=5)
            Type: status_request (5)
            Length: 5
            Certificate Status Type: OCSP (1)
            Responder ID list Length: 0
            Request Extensions Length: 0

可以看出,客户端问候包括状态请求,但是服务器(如果客户端有的话应该包括一个)没有写扩展,就像它没有意识到 status_request.

添加:我用于测试的完整代码,这是一个修改,删除了 ocsp 响应程序的启动并创建了 this code 的证书:

    /*
 * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

// SunJSSE does not support dynamic system properties, no way to re-use
// system properties in samevm/agentvm mode.

/*
 * @test
 * @bug 8046321 8153829
 * @summary OCSP Stapling for TLS
 * @library ../../../../java/security/testlibrary
 * @build CertificateBuilder SimpleOCSPServer
 * @run main/othervm SSLSocketWithStapling
 */

import java.io.*;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.Socket;
import java.net.ServerSocket;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import javax.net.ssl.*;
import java.security.KeyStore;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertPathValidatorException.BasicReason;
import java.security.cert.Certificate;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.security.cert.PKIXRevocationChecker;
import java.security.cert.PKIXRevocationChecker.Option;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;

public class SSLSocketWithStapling {

    /*
     * =============================================================
     * Set the various variables needed for the tests, then
     * specify what tests to run on each side.
     */

    // Turn on TLS debugging
    static final boolean debug = false;

    /*
 * Should we run the client or server in a separate thread?
 * Both sides can throw exceptions, but do you have a preference
 * as to which side should be the main thread.
 */
static boolean separateServerThread = true;
Thread clientThread = null;
Thread serverThread = null;

static String passwd = "serverpass";

/*
 * Is the server ready to serve?
 */
volatile static boolean serverReady = false;
volatile int serverPort = 443;

volatile Exception serverException = null;
volatile Exception clientException = null;

// PKI components we will need for this test
static KeyStore serverKeystore;         // SSL Server Keystore
static KeyStore trustStore;             // SSL Client trust store

// Extra configuration parameters and constants
static final String[] TLS13ONLY = new String[] { "TLSv1.3" };
static final String[] TLS12MAX =
        new String[] { "TLSv1.2", "TLSv1.1", "TLSv1" };

/*
 * If the client or server is doing some kind of object creation
 * that the other side depends on, and that thread prematurely
 * exits, you may experience a hang.  The test harness will
 * terminate all hung threads after its timeout has expired,
 * currently 3 minutes by default, but you might try to be
 * smart about it....
 */
public static void main(String[] args) throws Exception {
    if (debug) {
        System.setProperty("javax.net.debug", "ssl:handshake");
    }

    try {
        String keystoreServerPath ="/Users/lexy/Desktop/Clases/Seguridad/almacenes/keystoreServidor.jceks";
        String trustClientPath = "/Users/lexy/Desktop/Clases/Seguridad/almacenes/truststoreClient.jceks";
        
        System.setProperty("jdk.security.allowNonCaAnchor", "true" );

        System.setProperty("javax.net.ssl.trustStore", trustClientPath);
        System.setProperty("javax.net.ssl.trustStoreType",     "JCEKS");
        System.setProperty("javax.net.ssl.trustStorePassword", "clientpass");
        serverKeystore = KeyStore.getInstance("JCEKS");
        //System.out.println(passwd_key);
        serverKeystore.load(new FileInputStream(keystoreServerPath),"serverpass".toCharArray());
        trustStore = KeyStore.getInstance("JCEKS");
        //trustedStore.load(new FileInputStream("C:\Users\usuario\Desktop\alamcenes/clientTrustedCerts.jks"), "clientpass".toCharArray());
        trustStore.load(new FileInputStream(trustClientPath), "clientpass".toCharArray()); //supuestamente no hay que poner contrase�a es la misma pero no se deberia i dont know 
        

        testPKIXParametersRevEnabled(false);
        testPKIXParametersRevEnabled(true);
    } finally {
    }
}

/**
 * Do a basic connection using PKIXParameters with revocation checking
 * enabled and client-side OCSP disabled.  It will only pass if all
 * stapled responses are present, valid and have a GOOD status.
 */

static class ClientParameters {
    boolean enabled = true;
    PKIXBuilderParameters pkixParams = null;
    PKIXRevocationChecker revChecker = null;
    String[] protocols = null;
    String[] ciphers = null;

    ClientParameters() { }
}

static class ServerParameters {
    boolean enabled = true;
    int cacheSize = 256;
    int cacheLifetime = 3600;
    int respTimeout = 5000;
    String respUri = "http://localhost:9999";
    boolean respOverride = false;
    boolean ignoreExts = false;
    String[] protocols = null;
    String[] ciphers = null;

    ServerParameters() { }
}
static void testPKIXParametersRevEnabled(boolean isTls13) throws Exception {
    ClientParameters cliParams = new ClientParameters();
    ServerParameters servParams = new ServerParameters();
    if (isTls13) {
        cliParams.protocols = TLS13ONLY;
        servParams.protocols = TLS13ONLY;
    } else {
        cliParams.protocols = TLS12MAX;
        servParams.protocols = TLS12MAX;
    }
    serverReady = false;

    System.out.println("=====================================");
    System.out.println("Stapling enabled, PKIXParameters with");
    System.out.println("Revocation checking enabled ");
    System.out.println("=====================================");

    cliParams.pkixParams = new PKIXBuilderParameters(trustStore,
            new X509CertSelector());
    cliParams.pkixParams.setRevocationEnabled(true);
    Security.setProperty("ocsp.enable", "false");
    cliParams.enabled=true;
    servParams.enabled=true;
    servParams.respUri="http://localhost:9999";

    SSLSocketWithStapling sslTest = new SSLSocketWithStapling(cliParams,
            servParams);
    TestResult tr = sslTest.getResult();
    if (tr.clientExc != null) {
        throw tr.clientExc;
    } else if (tr.serverExc != null) {
        throw tr.serverExc;
    }

    System.out.println("                PASS");
    System.out.println("=====================================\n");
}

/*
 * Define the server side of the test.
 *
 * If the server prematurely exits, serverReady will be set to true
 * to avoid infinite hangs.
 */
void doServerSide(ServerParameters servParams) throws Exception {

    // Selectively enable or disable the feature
    System.setProperty("jdk.tls.server.enableStatusRequestExtension",
            Boolean.toString(servParams.enabled));

    // Set all the other operating parameters
    System.setProperty("jdk.tls.stapling.cacheSize",
            Integer.toString(servParams.cacheSize));
    System.setProperty("jdk.tls.stapling.cacheLifetime",
            Integer.toString(servParams.cacheLifetime));
    System.setProperty("jdk.tls.stapling.responseTimeout",
            Integer.toString(servParams.respTimeout));
    System.setProperty("jdk.tls.stapling.responderURI", servParams.respUri);
    System.setProperty("jdk.tls.stapling.responderOverride",
            Boolean.toString(servParams.respOverride));
    System.setProperty("jdk.tls.stapling.ignoreExtensions",
            Boolean.toString(servParams.ignoreExts));

    // Set keystores and trust stores for the server
    KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
    kmf.init(serverKeystore, passwd.toCharArray());
    TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
    tmf.init(trustStore);

    SSLContext sslc = SSLContext.getInstance("TLS");
    sslc.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

    SSLServerSocketFactory sslssf = new CustomizedServerSocketFactory(sslc,
            servParams.protocols, servParams.ciphers);

    try (SSLServerSocket sslServerSocket =
            (SSLServerSocket) sslssf.createServerSocket(serverPort)) {

        serverPort = sslServerSocket.getLocalPort();

        /*
         * Signal Client, we're ready for his connect.
         */
        serverReady = true;

        try (SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();
                InputStream sslIS = sslSocket.getInputStream();
                OutputStream sslOS = sslSocket.getOutputStream()) {
            int numberIn = sslIS.read();
            int numberSent = 85;
            log("Server received number: " + numberIn);
            sslOS.write(numberSent);
            sslOS.flush();
            log("Server sent number: " + numberSent);
        }
    }
}

/*
 * Define the client side of the test.
 *
 * If the server prematurely exits, serverReady will be set to true
 * to avoid infinite hangs.
 */
void doClientSide(ClientParameters cliParams) throws Exception {

    // Wait 5 seconds for server ready
    for (int i = 0; (i < 100 && !serverReady); i++) {
        Thread.sleep(50);
    }
    if (!serverReady) {
        throw new RuntimeException("Server not ready yet");
    }

    // Selectively enable or disable the feature
    System.setProperty("jdk.tls.client.enableStatusRequestExtension",
            Boolean.toString(cliParams.enabled));

    // Create the Trust Manager Factory using the PKIX variant
    TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");

    // If we have a customized pkixParameters then use it
    if (cliParams.pkixParams != null) {
        // LIf we have a customized PKIXRevocationChecker, add
        // it to the PKIXBuilderParameters.
        if (cliParams.revChecker != null) {
            cliParams.pkixParams.addCertPathChecker(cliParams.revChecker);
        }

        ManagerFactoryParameters trustParams =
                new CertPathTrustManagerParameters(cliParams.pkixParams);
        tmf.init(trustParams);
    } else {
        tmf.init(trustStore);
    }

    SSLContext sslc = SSLContext.getInstance("TLS");
    sslc.init(null, tmf.getTrustManagers(), null);

    SSLSocketFactory sslsf = new CustomizedSocketFactory(sslc,
            cliParams.protocols, cliParams.ciphers);
    try (SSLSocket sslSocket = (SSLSocket)sslsf.createSocket("localhost",
            serverPort);
            InputStream sslIS = sslSocket.getInputStream();
            OutputStream sslOS = sslSocket.getOutputStream()) {
        int numberSent = 80;
        sslOS.write(numberSent);
        sslOS.flush();
        log("Client sent number: " + numberSent);
        int numberIn = sslIS.read();
        log("Client received number:" + numberIn);
    }
}

/*
 * Primary constructor, used to drive remainder of the test.
 *
 * Fork off the other side, then do your work.
 */
SSLSocketWithStapling(ClientParameters cliParams,
        ServerParameters servParams) throws Exception {
    Exception startException = null;
    try {
        if (separateServerThread) {
            startServer(servParams, true);
            startClient(cliParams, false);
        } else {
            startClient(cliParams, true);
            startServer(servParams, false);
        }
    } catch (Exception e) {
        startException = e;
    }

    /*
     * Wait for other side to close down.
     */
    if (separateServerThread) {
        if (serverThread != null) {
            serverThread.join();
        }
    } else {
        if (clientThread != null) {
            clientThread.join();
        }
    }
}


TestResult getResult() {
    TestResult tr = new TestResult();
    tr.clientExc = clientException;
    tr.serverExc = serverException;
    return tr;
}

void startServer(ServerParameters servParams, boolean newThread)
        throws Exception {
    if (newThread) {
        serverThread = new Thread() {
            public void run() {
                try {
                    doServerSide(servParams);
                } catch (Exception e) {
                    /*
                     * Our server thread just died.
                     *
                     * Release the client, if not active already...
                     */
                    System.err.println("Server died...");
                    e.printStackTrace(System.err);
                    serverReady = true;
                    serverException = e;
                }
            }
        };
        serverThread.start();
    } else {
        try {
            doServerSide(servParams);
        } catch (Exception e) {
            serverException = e;
        } finally {
            serverReady = true;
        }
    }
}

void startClient(ClientParameters cliParams, boolean newThread)
        throws Exception {
    if (newThread) {
        clientThread = new Thread() {
            public void run() {
                try {
                    doClientSide(cliParams);
                } catch (Exception e) {
                    /*
                     * Our client thread just died.
                     */
                    System.err.println("Client died...");
                    clientException = e;
                }
            }
        };
        clientThread.start();
    } else {
        try {
            doClientSide(cliParams);
        } catch (Exception e) {
            clientException = e;
        }
    }
}

/**
 * Log a message on stdout
 *
 * @param message The message to log
 */
private static void log(String message) {
    if (debug) {
        System.out.println(message);
    }
}

// The following two classes are Simple nested class to group a handful
// of configuration parameters used before starting a client or server.
// We'll just access the data members directly for convenience.


static class CustomizedSocketFactory extends SSLSocketFactory {
    final SSLContext sslc;
    final String[] protocols;
    final String[] cipherSuites;

    CustomizedSocketFactory(SSLContext ctx, String[] prots, String[] suites)
            throws GeneralSecurityException {
        super();
        sslc = (ctx != null) ? ctx : SSLContext.getDefault();
        protocols = prots;
        cipherSuites = suites;
    }

    @Override
    public Socket createSocket(Socket s, String host, int port,
            boolean autoClose) throws IOException {
        Socket sock =  sslc.getSocketFactory().createSocket(s, host, port,
                autoClose);
        customizeSocket(sock);
        return sock;
    }

    @Override
    public Socket createSocket(InetAddress host, int port)
            throws IOException {
        Socket sock = sslc.getSocketFactory().createSocket(host, port);
        customizeSocket(sock);
        return sock;
    }

    @Override
    public Socket createSocket(InetAddress host, int port,
            InetAddress localAddress, int localPort) throws IOException {
        Socket sock = sslc.getSocketFactory().createSocket(host, port,
                localAddress, localPort);
        customizeSocket(sock);
        return sock;
    }

    @Override
    public Socket createSocket(String host, int port)
            throws IOException {
        Socket sock =  sslc.getSocketFactory().createSocket(host, port);
        customizeSocket(sock);
        return sock;
    }

    @Override
    public Socket createSocket(String host, int port,
            InetAddress localAddress, int localPort)
            throws IOException {
        Socket sock =  sslc.getSocketFactory().createSocket(host, port,
                localAddress, localPort);
        customizeSocket(sock);
        return sock;
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return sslc.getDefaultSSLParameters().getCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return sslc.getSupportedSSLParameters().getCipherSuites();
    }

    private void customizeSocket(Socket sock) {
        if (sock instanceof SSLSocket) {
            if (protocols != null) {
                ((SSLSocket)sock).setEnabledProtocols(protocols);
            }
            if (cipherSuites != null) {
                ((SSLSocket)sock).setEnabledCipherSuites(cipherSuites);
            }
        }
    }
}

static class CustomizedServerSocketFactory extends SSLServerSocketFactory {
    final SSLContext sslc;
    final String[] protocols;
    final String[] cipherSuites;

    CustomizedServerSocketFactory(SSLContext ctx, String[] prots, String[] suites)
            throws GeneralSecurityException {
        super();
        sslc = (ctx != null) ? ctx : SSLContext.getDefault();
        protocols = prots;
        cipherSuites = suites;

    }

    @Override
    public ServerSocket createServerSocket(int port) throws IOException {
        ServerSocket sock =
                sslc.getServerSocketFactory().createServerSocket(port);
        customizeSocket(sock);
        return sock;
    }

    @Override
    public ServerSocket createServerSocket(int port, int backlog)
            throws IOException {
        ServerSocket sock =
                sslc.getServerSocketFactory().createServerSocket(port,
                        backlog);
        customizeSocket(sock);
        return sock;
    }

    @Override
    public ServerSocket createServerSocket(int port, int backlog,
            InetAddress ifAddress) throws IOException {
        ServerSocket sock =
                sslc.getServerSocketFactory().createServerSocket(port,
                        backlog, ifAddress);
        customizeSocket(sock);
        return sock;
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return sslc.getDefaultSSLParameters().getCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return sslc.getSupportedSSLParameters().getCipherSuites();
    }

    private void customizeSocket(ServerSocket sock) {
        if (sock instanceof SSLServerSocket) {
            if (protocols != null) {
                ((SSLServerSocket)sock).setEnabledProtocols(protocols);
            }
            if (cipherSuites != null) {
                ((SSLServerSocket)sock).setEnabledCipherSuites(cipherSuites);
            }
        }
    }
}


static class TestResult {
    Exception serverExc = null;
    Exception clientExc = null;
}

}

问题已解决:

在我的例子中,服务器装订不工作导致服务器证书配置错误。

服务器证书必须链接到根 CA 证书,而我的是单独的。另外,我在证书上指定了 authorityInfoAccess 扩展。

所以:

我用于签署 ssl 证书的 openssl 配置文件如下所示(请参阅带有 OCSP URI 的 authorityInfoAccess):

[ auth_cert ]
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = critical, serverAuth,clientAuth
authorityInfoAccess = OCSP;URI:http://localhost:9999

还要确保您的 ssl 证书与 CA 或 CA 和中间证书链接在一起,具体取决于您的设置。

如果是per format,就拼接起来,在linux中用cat工具,我用的命令是:

cat ca.crt.pem >> server/serverauth.crt.pem

有关我如何实现的更多信息,请查看我在此 coderanch thread 上的帖子。