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

问题描述

这篇文章是交叉发布的,所以一定要检查更新in coderanch

我在我的客户端/服务器应用程序上实施 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。没有成功。

它应该如何工作:

How it should work

Wireshark 的外观:

How Wireshark looks

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

Correctly configured ports for wireshark shown messages as 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

正如所见,客户端 hello 包含状态请求,但是服务器(如果客户端这样做,则应该包含一个)没有编写扩展,就像它没有意识到 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",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,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,InetAddress localAddress,int localPort) throws IOException {
        Socket sock = sslc.getSocketFactory().createSocket(host,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 localPort)
            throws IOException {
        Socket sock =  sslc.getSocketFactory().createSocket(host,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[] 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;
}

}

解决方法

问题已解决:

就我而言,服务器 Stapling 无法正常工作导致服务器证书配置错误。

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

所以:

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

[ 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 上的帖子。