【问题标题】:Java equivalent to OpenSSL s_client commandJava 等效于 OpenSSL s_client 命令
【发布时间】:2019-09-24 03:06:51
【问题描述】:

我需要将某些bash 脚本转换为java,并且一个这样的脚本使用opensslvanity-url 作为参数连接到服务器,以检查是否可以使用vanity-url 进行连接。请参阅下面的命令

/usr/bin/openssl s_client -connect api.sys.found1.cf.company.com:443 -servername www.app.company.com 2>/dev/null

我想在java 中进行类似的活动并测试连接性。关于如何使用 Java 建立open-ssl 连接的任何想法。这是我需要使用外部库的东西吗?

【问题讨论】:

  • 为什么?这里的好处在哪里?
  • 好处是我以编程方式检查那里是否存在证书,如果不存在将触发另一个生成证书的作业
  • 你也可以使用 Bash 脚本来做到这一点。 s_client 满足您的需求。我在 java 中看到很多样板代码,可以通过 shell 和 openssl(1) 中的几行代码来完成。
  • 不...我不想在 Bash-script 中做...我希望用 Java 编写并通过 Springboot 将其作为 API 公开
  • 好吧,你应该在你的问题中提到这一点,因为你争取的解决方案看起来有点矫枉过正。

标签: java java-8 openssl


【解决方案1】:

用你的值调用 isAliasExists ,

isAliasExists("api.sys.found1.cf.company.com","www.app.company.com");

如果您的别名(服务器名)是证书的一部分,则返回 true,

private static boolean isAliasExists(String hostName, String alias) throws Exception  {
        String host;
        int port;
        String[] parts = hostName.split(":");
        host = parts[0];
        port = (parts.length == 1) ? 443 : Integer.parseInt(parts[1]);
        // key store password
        char[] passphrase = "changeit".toCharArray();
        File file = new File("jssecacerts");
        if (file.isFile() == false) {
            char SEP = File.separatorChar;
            File dir = new File(System.getProperty("java.home") + SEP + "lib" + SEP + "security");
            file = new File(dir, "jssecacerts");
            if (file.isFile() == false) {
                file = new File(dir, "cacerts");
            }
        }
        InputStream in = new FileInputStream(file);
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(in, passphrase);
        in.close();
        SSLContext context = SSLContext.getInstance("TLS");
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(ks);
        X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
        SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
        context.init(null, new TrustManager[] { tm }, null);
        SSLSocketFactory factory = context.getSocketFactory();
        SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
        socket.setSoTimeout(10000);
        try {
            System.out.println("Starting SSL handshake...");
            socket.startHandshake();
            socket.close();
            System.out.println("Certificate is already trusted");
        } catch (SSLException e) {
            e.printStackTrace();
        }

        X509Certificate[] chain = tm.chain;

        List<String> altNames=new ArrayList<String>();

        for (X509Certificate cert: chain)
        {
            altNames.addAll(getSubjectAltNames(cert));
        }

        for(String altName: altNames) {
            if(altName.trim().contains(alias))
               return true;
        }

        if (chain == null) {
            System.out.println("Could not obtain server certificate chain");
            return false;
        }



        return false;
    }

从证书返回替代名称列表,

private static List<String> getSubjectAltNames(X509Certificate certificate) throws CertificateParsingException {
         List<String> result = new ArrayList<>();
         try {
          Collection<?> subjectAltNames = certificate.getSubjectAlternativeNames();
          if (subjectAltNames == null) {
           return Collections.emptyList();
          }
          for (Object subjectAltName : subjectAltNames) {
           List<?> entry = (List<?>) subjectAltName;
           if (entry == null || entry.size() < 2) {
            continue;
           }
           Integer altNameType = (Integer) entry.get(0);
           if (altNameType == null) {
            continue;
           }
            String altName = (String) entry.get(1);
            if (altName != null) {
             result.add(altName);
            }
          }
          return result;
         } catch (CertificateParsingException e) {
          return Collections.emptyList();
         }
        }

自定义信任管理器,

private static class SavingTrustManager implements X509TrustManager {

        private final X509TrustManager tm;
        private X509Certificate[] chain;

        SavingTrustManager(X509TrustManager tm) {
            this.tm = tm;
        }

        public X509Certificate[] getAcceptedIssuers() {

            return new X509Certificate[0];
            // throw new UnsupportedOperationException();
        }

        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            throw new UnsupportedOperationException();
        }

        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            this.chain = chain;
            tm.checkServerTrusted(chain, authType);
        }
    }

【讨论】:

  • 这里如何设置SNI?根据这个例子,正在连接的服务器名称是www.app.company.com
  • 不用,java会处理的。
  • 不,我不想那样做。我的要求是用户将在哪里给出 url 在这个例子中是 www.app.company.com 然后我必须验证它是否存在于 api.sys.found1.cf.company.com:443 中。这个例子似乎加载了api.sys.found1.cf.company.com 下所有可用的cert,我不喜欢这些SNI Host,只想检查那个特定的SNI Host
【解决方案2】:

在不知道 SNI 是什么的情况下,我试图通过下面显示的测试程序获得一些见解。

我不知道openssl s_client 命令的输出,但测试程序可能证明是一个起点。当javax.net.debug 输出打开时,大量输出被转储,其中只有几行相关(另见 cmets)。这有点烦人,我没有一个简单的解决方案。 TrustAllServers 类可以重新设计以检查您希望从特定域的服务器(又名主机)接收的证书。可能还有其他选项(例如套接字的握手方法),但这是我所得到的。

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;

import javax.net.ssl.SNIHostName;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedTrustManager;

// https://stackoverflow.com/questions/56005883/java-equivalent-to-openssl-s-client-command
// Please use latest Java 8 version, bugs are around in earlier versions.
public class ServerNameTest {

    public static void main(String[] args) {

        // SSL debug options, see https://stackoverflow.com/q/23659564/3080094 and https://access.redhat.com/solutions/973783
        // System.setProperty("javax.net.debug", "all");
        // System.setProperty("javax.net.debug", "ssl:handshake");
        // System.setProperty("jsse.enableSNIExtension", "true"); // "true" is the default
        try {
            ServerNameTest sn = new ServerNameTest();
            // This will show 2 different server certificate chains.
            // Note this is a random server - please pick your own one.
            sn.test("major.io", "rackerhacker.com");
            sn.test("major.io", "major.io");
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Done");
    }

    /*
     * With javax.net.debug output you should see something like:
     * <pre>
     * *** ClientHello
     * ...
     * Extension server_name, server_name: [type=host_name (0), value=DOMAIN;]
     * ...
     * *** ServerHello
     * ...
     * Extension server_name, server_name: 
     * ...
     * </pre>
     * Note that if the server does not provide a value for server_name, 
     * it does not actually mean the server does not support SNI/server_name (see https://serverfault.com/a/506303)
     */
    void test(String host, String domain) throws Exception {

        SSLParameters sslParams = new SSLParameters();
        if (domain != null && !domain.isEmpty()) {
            sslParams.setServerNames(Arrays.asList(new SNIHostName(domain)));
        }
        // Only for webservers: set endpoint algorithm to HTTPS 
        sslParams.setEndpointIdentificationAlgorithm("HTTPS");

        SSLSocketFactory sslsf = serverTrustingSSLFactory();
        try (SSLSocket socket = (SSLSocket) sslsf.createSocket()) {
            socket.setSSLParameters(sslParams);
            socket.setSoTimeout(3_000);
            System.out.println("Connecting to " + host + " for domain " + domain);
            socket.connect(new InetSocketAddress(host, 443), 3_000);
            // Trigger actual connection by getting the session.
            socket.getSession();
            System.out.println("Connected to remote " + socket.getRemoteSocketAddress());
            try (BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))) {
                try (OutputStream out = socket.getOutputStream()) {
                    System.out.println(">> OPTIONS");
                    out.write("OPTIONS * HTTP/1.1\r\n\r\n".getBytes(StandardCharsets.UTF_8));
                    System.out.println("<< " + input.readLine());
                }
            } catch (Exception e) {
                System.err.println("No line read: " + e);
            }
        }
    }

    SSLSocketFactory serverTrustingSSLFactory() throws Exception {

        SSLContext ctx = SSLContext.getInstance("TLS");
        ctx.init(null, trustManager(), null);
        return ctx.getSocketFactory();
    }

    TrustManager[] trustManager() throws Exception {

        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init( (KeyStore) null);
        // Must use "extended" type versus the default javax.net.ssl.X509TrustManager, 
        // otherwise the error "No subject alternative DNS name matching" keeps showing up.
        X509ExtendedTrustManager defaultManager = null;
        for (TrustManager trustManager : tmf.getTrustManagers()) {
            if (trustManager instanceof X509ExtendedTrustManager) {
                defaultManager = (X509ExtendedTrustManager) trustManager;
                break;
            }
        }
        if (defaultManager == null) {
            throw new RuntimeException("Cannot find default X509ExtendedTrustManager");
        }
        return new TrustManager[] { new TrustAllServers(defaultManager) };
    }

    static void printChain(X509Certificate[] chain) {

        try {
            for (int i = 0; i < chain.length; i++) {
                X509Certificate cert = chain[i];
                System.out.println("Cert[" + i + "] " + cert.getSubjectX500Principal() + " :alt: " + cert.getSubjectAlternativeNames());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static class TrustAllServers extends X509ExtendedTrustManager {

        final X509ExtendedTrustManager defaultManager;

        public TrustAllServers(X509ExtendedTrustManager defaultManager) {
            this.defaultManager = defaultManager;
        }

        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            try {
                defaultManager.checkServerTrusted(chain, authType);
            } catch (Exception e) {
                System.err.println("Untrusted server: " + e);
            }
            printChain(chain);
        }

        public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
            try {
                defaultManager.checkServerTrusted(chain, authType, socket);
            } catch (Exception e) {
                System.err.println("Untrusted server for socket: " + e);
            }
            printChain(chain);
        }

        public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {
            try {
                defaultManager.checkServerTrusted(chain, authType, engine);
            } catch (Exception e) {
                System.err.println("Untrusted server for engine: " + e);
            }
            printChain(chain);
        }

        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            defaultManager.checkClientTrusted(chain, authType);
        }

        public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
            defaultManager.checkClientTrusted(chain, authType, socket);
        }

        public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {
            defaultManager.checkClientTrusted(chain, authType, engine);
        }

        public X509Certificate[] getAcceptedIssuers() {
            return defaultManager.getAcceptedIssuers();
        }

    }

}

【讨论】:

    【解决方案3】:

    我可以通过参考文档over here来实现这一点

    基本上,需要创建一个SSLEngine 并与SNI 成功握手

     private SocketChannel createSocketChannel() throws IOException {
            InetSocketAddress socketAddress = new InetSocketAddress(PROXY_ADDRESS, PROXY_PORT);
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.connect(socketAddress);
            socketChannel.configureBlocking(false);
            return socketChannel;
    
        }
    
    private SSLContext createSSLContext() throws KeyManagementException, NoSuchAlgorithmException {
            SSLContext sslContext = SSLContext.getInstance(TLS_VERSION);
            sslContext.init(null,null,null);
            return sslContext;
        }
    
    
    
    
    private SSLEngine createSSLEngine() throws KeyManagementException, NoSuchAlgorithmException {
            SSLContext sslContext = createSSLContext();
            SSLEngine sslEngine = sslContext.createSSLEngine(PROXY_ADDRESS, PROXY_PORT);
            sslEngine.setUseClientMode(true);
    
            List<SNIServerName> serverNameList = new ArrayList<>();
            serverNameList.add(new SNIHostName(SNI_HOST_NAME));
            SSLParameters sslParameters = sslEngine.getSSLParameters();
            sslParameters.setServerNames(serverNameList);
    
            sslEngine.setSSLParameters(sslParameters);
    
            return sslEngine;
        }
    

    创建 SSLEngine 后,handShake 必须开始

    SocketChannel channel = createSocketChannel();
    SSLEngine sslEngine = createSSLEngine();
    doHandShake(sslEngine,channel);
    
    
     private void doHandShake(SSLEngine sslEngine, SocketChannel socketChannel) throws Exception {
            System.out.println("Going to do Handshake");
    
            SSLSession session = sslEngine.getSession();
    
            ByteBuffer myAppData = ByteBuffer.allocate(session.getApplicationBufferSize());
            ByteBuffer peerAppData = ByteBuffer.allocate(session.getApplicationBufferSize());
    
            ByteBuffer myNetData = ByteBuffer.allocate(session.getPacketBufferSize());
            ByteBuffer peerNetData = ByteBuffer.allocate(session.getPacketBufferSize());
    
            sslEngine.beginHandshake();
            SSLEngineResult result;
    
            handshakeStatus = sslEngine.getHandshakeStatus();
    
            while (handshakeStatus != SSLEngineResult.HandshakeStatus.FINISHED &&
                    handshakeStatus != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
    
    
                switch (handshakeStatus) {
                    case NEED_UNWRAP:
                        if (! (socketChannel.read(peerNetData) <0)) {
                            peerNetData.flip();
                            result = sslEngine.unwrap(peerNetData,peerAppData);
                            peerNetData.compact();
    
                            handshakeStatus = result.getHandshakeStatus();
    
                            switch (result.getStatus()) {
                                case OK:
                                    break;
                            }
    
                        }
    
                        break;
                    case NEED_WRAP:
                        myNetData.clear() ;// Empty the local network packet buffer
                        result = sslEngine.wrap(myAppData,myNetData);
                        handshakeStatus = result.getHandshakeStatus();
                        switch (result.getStatus()) {
                            case OK:
                                myNetData.flip();
                                while (myNetData.hasRemaining()) {
                                    socketChannel.write(myNetData);
                                }
                        }
                        break;
    
                    case NEED_TASK:
                        Runnable task  = sslEngine.getDelegatedTask();
                        if (null!=task) {
                            task.run();
                        }
                        handshakeStatus = sslEngine.getHandshakeStatus();
                        break;
                }
    
    
            }
    

    handShake 完成后。你可以得到Principal 对象

    Principal principal = sslEngine.getSession().getPeerPrincipal();
    
                if (principal.getName().contains(SNI_HOST_NAME)) {
                    System.out.println("available ... ");
                }else {
                    System.out.println("Not available");
                }
    

    【讨论】:

      猜你喜欢
      • 2016-12-29
      • 2012-12-03
      • 1970-01-01
      • 1970-01-01
      • 2015-12-29
      • 1970-01-01
      • 2023-04-06
      • 2015-12-07
      • 2013-07-14
      相关资源
      最近更新 更多