Java эквивалентный команде OpenSSL s_client - PullRequest
11 голосов
/ 06 мая 2019

У меня есть требование преобразовать некоторые bash сценарии в java, и один такой сценарий подключается к серверу, используя openssl с vanity-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 и проверить подключение.Любые идеи о том, как сделать соединение open-ssl с использованием Java .. Это то, что мне нужно использовать внешнюю библиотеку?

Ответы [ 3 ]

7 голосов
/ 17 мая 2019

Я смог добиться этого, сославшись на документ здесь

По сути, необходимо создать 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");
            }
2 голосов
/ 16 мая 2019

вызов 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);
        }
    }
0 голосов
/ 16 мая 2019

Не зная, что такое SNI, я попытался понять с помощью тестовой программы, показанной ниже.

Я не знаю вывод команды openssl s_client, но тестовая программа может оказаться отправной точкой. Когда вывод javax.net.debug включен, выводится много выходных данных, из которых релевантны только несколько строк (см. Также комментарии). Это немного раздражает, и у меня нет простого решения для этого. Класс TrustAllServers может быть переработан для проверки сертификатов, которые вы ожидаете получить от сервера (a.ka. host) для определенного домена. Могут быть и другие варианты (например, методы рукопожатия сокета), но это насколько я понял.

<code>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;

// /8451691/java-ekvivalentnyi-komande-openssl-sclient
// 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: 
     * ...
     * 
* Обратите внимание, что если сервер не предоставляет значение для имя_сервера, * это на самом деле не означает, что сервер не поддерживает SNI / имя_сервера (см. https://serverfault.com/a/506303) * / void test (String host, String domain) выдает исключение { SSLParameters sslParams = новые SSLParameters (); if (domain! = null &&! domain.isEmpty ()) { sslParams.setServerNames (Arrays.asList (новое SNIHostName (домен))); } // Только для веб-серверов: установить алгоритм конечной точки на HTTPS sslParams.setEndpointIdentificationAlgorithm ( "HTTPS"); SSLSocketFactory sslsf = serverTrustingSSLFactory (); try (SSLSocket socket = (SSLSocket) sslsf.createSocket ()) { socket.setSSLParameters (sslParams); socket.setSoTimeout (3_000); System.out.println («Подключение к« + хост + »для домена» + домен); socket.connect (новый InetSocketAddress (хост, 443), 3_000); // Запускаем фактическое соединение, получая сеанс. socket.getSession (); System.out.println («Подключен к удаленному» + socket.getRemoteSocketAddress ()); try (BufferedReader input = new BufferedReader (new InputStreamReader (socket.getInputStream (), StandardCharsets.UTF_8))) { try (OutputStream out = socket.getOutputStream ()) { System.out.println (">> ОПЦИИ"); out.write ("ОПЦИИ * HTTP / 1.1 \ r \ n \ r \ n" .getBytes (StandardCharsets.UTF_8)); System.out.println ("<<" + input.readLine ()); } } catch (исключение e) { System.err.println ("Нет прочитанной строки:" + e); } } } SSLSocketFactory serverTrustingSSLFactory () создает исключение { SSLContext ctx = SSLContext.getInstance ("TLS"); ctx.init (null, trustManager (), null); return ctx.getSocketFactory (); } TrustManager [] trustManager () создает исключение { TrustManagerFactory tmf = TrustManagerFactory.getInstance (TrustManagerFactory.getDefaultAlgorithm ()); tmf.init ((KeyStore) null); // Должен использовать расширенный тип по сравнению с javax.net.ssl.X509TrustManager по умолчанию, // в противном случае ошибка «Нет сопоставления альтернативного имени субъекта DNS» продолжает появляться. X509ExtendedTrustManager defaultManager = null; для (TrustManager trustManager: tmf.getTrustManagers ()) { if (trustManager instanceof X509ExtendedTrustManager) { defaultManager = (X509ExtendedTrustManager) trustManager; перерыв; } } if (defaultManager == null) { генерировать новое RuntimeException («Не удается найти X509ExtendedTrustManager по умолчанию»); } вернуть новый TrustManager [] {new TrustAllServers (defaultManager)}; } static void printChain (X509Certificate [] цепочка) { пытаться { for (int i = 0; i <chain.length; i ++) { X509Сертификат сертификата = цепь [i]; System.out.println ("Cert [" + i + "]" + cert.getSubjectX500Principal () + ": alt:" + cert.getSubjectAlternativeNames ()); } } catch (исключение e) { e.printStackTrace (); } } статический класс TrustAllServers extends X509ExtendedTrustManager { окончательный X509ExtendedTrustManager defaultManager;public TrustAllServers (X509ExtendedTrustManager defaultManager) {this.defaultManager = defaultManager;} public void checkServerTrusted (цепочка X509Certificate [], String authType) выбрасывает CertificateException {try {defaultManager.checkServerTrusted (chain, authType);} catch (Exception e) {System.err.println ("Ненадежный сервер:" + e);} printChain (цепочка);} public void checkServerTrusted (цепочка X509Certificate [], String authType, сокет Socket) выдает CertificateException {try {defaultManager.checkServerTrusted (chain, authType, socket);} catch (Exception e) {System.err.println ("Ненадежный сервер для сокета:" + e);} printChain (цепочка);} public void checkServerTrusted (цепочка X509Certificate [], String authType, движок SSLEngine) выдает CertificateException {try {defaultManager.checkServerTrusted (chain, authType, engine);} catch (Exception e) {System.err.println ("Ненадежный сервер для движка:" + e);} printChain (цепочка);} public void checkClientTrusted (цепочка X509Certificate [], String authType) выбрасывает CertificateException {defaultManager.checkClientTrusted (chain, authType);} public void checkClientTrusted (цепочка X509Certificate [], String authType, сокет Socket) выдает CertificateException {defaultManager.checkClientTrusted (chain, authType, socket);} public void checkClientTrusted (цепочка X509Certificate [], String authType, движок SSLEngine) выдает CertificateException {defaultManager.checkClientTrusted (chain, authType, engine);} public X509Certificate [] getAcceptedIssuers () {return defaultManager.getAcceptedIssuers ();}}} </code>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...