Прийміть самопідписаний сертифікат ssl сервера в клієнті Java


215

Це виглядає як стандартне запитання, але я не міг ніде знайти чіткі вказівки.

У мене є Java-код, який намагається підключитися до сервера, ймовірно, самопідписаний (або закінчився термін дії). Код повідомляє про наступну помилку:

[HttpMethodDirector] I/O exception (javax.net.ssl.SSLHandshakeException) caught 
when processing request: sun.security.validator.ValidatorException: PKIX path 
building failed: sun.security.provider.certpath.SunCertPathBuilderException: 
unable to find valid certification path to requested target

Як я це розумію, я повинен використовувати keytool і сказати Java, що це нормально, щоб дозволити це з'єднання.

Всі вказівки для вирішення цієї проблеми передбачають, що я повністю володію клавішними інструментами, такими як

генерувати приватний ключ для сервера та імпортувати його в сховище ключів

Чи є хтось, хто може розмістити детальні інструкції?

Я запускаю unix, тому найкращий сценарій bash.

Не впевнений, чи це важливо, але код, виконаний у jboss.


2
Див. Як я приймаю сертифікат, що підписується самостійно, з підключенням Java HttpsURLConnection? . Очевидно, було б краще, якщо ви зможете отримати сайт, щоб використовувати дійсний cert.
Меттью Флашен

2
Дякуємо за посилання, я не бачив його під час пошуку. Але обидва рішення містять спеціальний код для надсилання запиту, і я використовую існуючий код (amazon ws client for java). Відповідно, я підключаю їх сайт, і я не можу виправити його проблеми із сертифікатом.
Микита Рибак

2
@MatthewFlaschen - "Очевидно, було б краще, якщо ви зможете отримати сайт, щоб використовувати дійсний сертифікат ..." - Сертифікат, який підписує сам, є дійсним сертифікатом, якщо клієнт довіряє йому. Багато хто думає, що надання довіри картелі CA / Browser є вадою безпеки.
jww

3
Пов’язаний, див . Найнебезпечніший код у світі: перевірка SSL-сертифікатів у непрограмному забезпеченні браузера . (Посилання надається, оскільки, здається, ви отримуєте ці спам-відповіді, які вимикають перевірку).
jww

Відповіді:


306

Тут у вас є два варіанти: додати самопідписаний сертифікат до вашого довіреного магазину JVM або налаштувати свого клієнта

Варіант 1

Експортуйте сертифікат зі свого веб-переглядача та імпортуйте його у ваші довірені магазини JVM (щоб створити ланцюжок довіри):

<JAVA_HOME>\bin\keytool -import -v -trustcacerts
-alias server-alias -file server.cer
-keystore cacerts.jks -keypass changeit
-storepass changeit 

Варіант 2

Вимкнути перевірку сертифікату:

// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] { 
    new X509TrustManager() {     
        public java.security.cert.X509Certificate[] getAcceptedIssuers() { 
            return new X509Certificate[0];
        } 
        public void checkClientTrusted( 
            java.security.cert.X509Certificate[] certs, String authType) {
            } 
        public void checkServerTrusted( 
            java.security.cert.X509Certificate[] certs, String authType) {
        }
    } 
}; 

// Install the all-trusting trust manager
try {
    SSLContext sc = SSLContext.getInstance("SSL"); 
    sc.init(null, trustAllCerts, new java.security.SecureRandom()); 
    HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (GeneralSecurityException e) {
} 
// Now you can access an https URL without having the certificate in the truststore
try { 
    URL url = new URL("https://hostname/index.html"); 
} catch (MalformedURLException e) {
} 

Зауважте, що я взагалі не рекомендую варіант №2 . Вимкнення менеджера довіри перемагає деякі частини SSL і робить вас вразливим до людини в атаках посередині. Віддайте перевагу варіанту №1, а ще краще, щоб сервер використовував "справжній" сертифікат, підписаний добре відомим CA.


7
Не лише напад MIM. Це робить вас вразливим до підключення до неправильного сайту. Це абсолютно небезпечно. Дивіться RFC 2246. Я проти публікувати цей TrustManager постійно. Це навіть не правильна wrt власна специфікація.
Маркіз Лорн

10
@EJP Я дійсно не рекомендую другий варіант (я оновив свою відповідь, щоб було зрозуміло). Однак, якщо не розміщувати повідомлення, це нічого не вирішить (це є загальнодоступною інформацією) і не заслуговує на те, щоб ІМХО заслуговував скорочення.
Паскаль Тивеннт

135
@EJP Це, навчаючи людей, ви їх виховуєте, а не приховуючи речі. Тож зберігання речей у таємниці чи незрозумілості зовсім не є рішенням. Цей код є загальнодоступним, API Java є загальнодоступним, краще говорити про нього, ніж ігнорувати його. Але я можу жити з вами, не погоджуючись.
Паскаль Тивеннт

10
Інший варіант - той, про який ви не згадуєте, - це виправити сертифікат сервера або виправити його самостійно, або зателефонувавши до відповідної служби підтримки. Одиночні сертифікати хоста - це дуже дешево; гальмування навколо самопідписаних речей - нерозумно копіє фунта (тобто для тих, хто не знайомий з цією англійською ідіомою, абсолютно дурний набір пріоритетів, який коштує багато, щоб заощадити майже нічого).
стипендіати доналу

2
@Rich, пізніше 6 років, але ви також можете захопити серверну версію у firefox, натиснувши піктограму блокування -> додаткова інформація -> Переглянути сертифікат -> вкладка Деталі -> Експорт ... Це добре приховано.
Сіддхартха

9

Я вирішив цю проблему до постачальника сертифікатів, який не є частиною стандартних хостів JVM за замовчуванням JDK 8u74. Постачальником є www.identrust.com , але це був не той домен, до якого я намагався підключитися. Цей домен отримав сертифікат від цього провайдера. Див. Чи охоплює довіреність до кореневого кореня за списком за замовчуванням у JDK / JRE? - зачитайте пару записів. Також дивіться, які браузери та операційні системи підтримують Let's Encrypt .

Отже, для підключення до цікавого домену, який мав сертифікат, виданий від identrust.comмене, я зробив наступні кроки. В основному, мені довелося отримати DST Root CA X3сертифікат identrust.com ( ), якому слід довіряти JVM. Мені вдалося це зробити за допомогою Apache HttpComponents 4.5 так:

1: Отримайте сертифікат від indettrust в Інструкції з завантаження ланцюжка сертифікатів . Клацніть посилання DST Root CA X3 .

2: Збережіть рядок у файл під назвою "DST Root CA X3.pem". Обов’язково додайте рядки "----- BEGIN CERTIFICATE -----" та "----- END CERTIFICATE -----" у файл на початку та в кінці.

3: Створіть файл сховища Java, cacerts.jks, виконавши таку команду:

keytool -import -v -trustcacerts -alias IdenTrust -keypass yourpassword -file dst_root_ca_x3.pem -keystore cacerts.jks -storepass yourpassword

4: Скопіюйте отриманий сховище cacerts.jks в каталог ресурсів програми java / (maven).

5: Використовуйте наступний код, щоб завантажити цей файл і приєднати його до Apache 4.5 HttpClient. Це вирішить проблему для всіх доменів, які мають сертифікати, видані від indetrust.comutil oracle, включає сертифікат у сховище ключів JRE за замовчуванням.

SSLContext sslcontext = SSLContexts.custom()
        .loadTrustMaterial(new File(CalRestClient.class.getResource("/cacerts.jks").getFile()), "yourpasword".toCharArray(),
                new TrustSelfSignedStrategy())
        .build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
        sslcontext,
        new String[] { "TLSv1" },
        null,
        SSLConnectionSocketFactory.getDefaultHostnameVerifier());
CloseableHttpClient httpclient = HttpClients.custom()
        .setSSLSocketFactory(sslsf)
        .build();

Коли проект буде побудовано, тоді cacerts.jks буде скопійовано у класний шлях та завантажено звідти. На даний момент я не перевіряв інші сайти ssl, але якщо вищезгаданий код "ланцюжок" у цьому сертифікаті, вони також будуть працювати, але знову ж таки, я не знаю.

Довідка: Спеціальний контекст SSL та як я можу прийняти самопідписаний сертифікат з Java HttpsURLConnection?


Питання - про самопідписаний сертифікат. Ця відповідь не є.
Маркіз Лорн

4
Прочитайте питання (і відповідь) трохи ближче.
К.Ніколас

9

Apache HttpClient 4.5 підтримує прийняття самопідписаних сертифікатів:

SSLContext sslContext = SSLContexts.custom()
    .loadTrustMaterial(new TrustSelfSignedStrategy())
    .build();
SSLConnectionSocketFactory socketFactory =
    new SSLConnectionSocketFactory(sslContext);
Registry<ConnectionSocketFactory> reg =
    RegistryBuilder.<ConnectionSocketFactory>create()
    .register("https", socketFactory)
    .build();
HttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(reg);        
CloseableHttpClient httpClient = HttpClients.custom()
    .setConnectionManager(cm)
    .build();
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse sslResponse = httpClient.execute(httpGet);

Це будує завод SSL розеток, який буде використовувати TrustSelfSignedStrategy , реєструє його у користувальницькому менеджері з'єднань, а потім робить HTTP GET за допомогою цього менеджера з'єднань.

Я погоджуюся з тими, хто скандує "не робити цього на виробництві", проте є випадки використання для прийняття самопідписаних сертифікатів поза виробництвом; ми використовуємо їх в автоматизованих тестах інтеграції, щоб ми використовували SSL (як і у виробництві), навіть коли не працюємо на виробничому обладнання.


6

Замість того, щоб встановлювати заводські розетки за замовчуванням (що IMO - це погано) - це буде впливати тільки на поточне з'єднання, а не на всі SSL-з'єднання, які ви намагаєтесь відкрити:

URLConnection connection = url.openConnection();
    // JMD - this is a better way to do it that doesn't override the default SSL factory.
    if (connection instanceof HttpsURLConnection)
    {
        HttpsURLConnection conHttps = (HttpsURLConnection) connection;
        // Set up a Trust all manager
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager()
        {

            public java.security.cert.X509Certificate[] getAcceptedIssuers()
            {
                return null;
            }

            public void checkClientTrusted(
                java.security.cert.X509Certificate[] certs, String authType)
            {
            }

            public void checkServerTrusted(
                java.security.cert.X509Certificate[] certs, String authType)
            {
            }
        } };

        // Get a new SSL context
        SSLContext sc = SSLContext.getInstance("TLSv1.2");
        sc.init(null, trustAllCerts, new java.security.SecureRandom());
        // Set our connection to use this SSL context, with the "Trust all" manager in place.
        conHttps.setSSLSocketFactory(sc.getSocketFactory());
        // Also force it to trust all hosts
        HostnameVerifier allHostsValid = new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };
        // and set the hostname verifier.
        conHttps.setHostnameVerifier(allHostsValid);
    }
InputStream stream = connection.getInputStream();

2
Ви checkServerTrustedне реалізуєте необхідну логіку, щоб фактично довірити сертифікат та забезпечити відхилення недовірених сертифікатів. Це може спричинити хороше читання: Найнебезпечніший код у світі: перевірка SSL-сертифікатів у програмному забезпеченні, що не переглядає браузер .
jww

1
Ваш getAcceptedIssuers()метод не відповідає специфікації, і це "рішення" залишається докорінно небезпечним.
Маркіз Лорн

4

Існує краща альтернатива довірі до всіх сертифікатів: Створіть, TrustStoreщо спеціально довіряє даному сертифікату, і використовуйте це для створення, SSLContextз якого отримати SSLSocketFactoryнабір на HttpsURLConnection. Ось повний код:

File crtFile = new File("server.crt");
Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream(crtFile));

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("server", certificate);

TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);

HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
connection.setSSLSocketFactory(sslContext.getSocketFactory());

Ви також можете завантажити KeyStore безпосередньо з файлу або отримати сертифікат X.509 з будь-якого надійного джерела.

Зауважте, що з цим кодом сертифікати в cacertsне використовуватимуться. Це конкретно HttpsURLConnectionбуде довіряти лише цьому конкретному сертифікату.


1

Довіряйте всім SSL-сертифікатам: - Ви можете обійти SSL, якщо хочете протестувати на тестовому сервері. Але не використовуйте цей код для виробництва.

public static class NukeSSLCerts {
protected static final String TAG = "NukeSSLCerts";

public static void nuke() {
    try {
        TrustManager[] trustAllCerts = new TrustManager[] { 
            new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    X509Certificate[] myTrustedAnchors = new X509Certificate[0];  
                    return myTrustedAnchors;
                }

                @Override
                public void checkClientTrusted(X509Certificate[] certs, String authType) {}

                @Override
                public void checkServerTrusted(X509Certificate[] certs, String authType) {}
            }
        };

        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String arg0, SSLSession arg1) {
                return true;
            }
        });
    } catch (Exception e) { 
    }
}

}

Будь ласка, зателефонуйте на цю функцію у функцію onCreate () у розділі Діяльність або у вашому додатку.

NukeSSLCerts.nuke();

Це можна використовувати для Volley в Android.


Не зовсім впевнений у тому, чому ви проголосували, якщо код не працює. Можливо, це припущення, що Android якимось чином пов'язаний, коли в оригінальному запитанні Android не було тегів.
Ло-Тан

1

Прийнята відповідь чудова, але я хотів би додати щось до цього, оскільки я використовував IntelliJ на Mac і не зміг змусити його працювати за допомогою JAVA_HOME змінної шляху.

Виявляється, Java Home відрізнялася під час запуску програми від IntelliJ.

Щоб точно зрозуміти, де він знаходиться, ви можете просто зробити System.getProperty("java.home")так, звідки читаються довірені сертифікати.


0

Якщо "вони" використовують самопідписаний сертифікат, вони повинні вжити заходів, необхідних для використання їх сервера. Зокрема це означає надання надійного вам сертифіката в автономному режимі. Тож змусьте їх зробити це. Потім ви імпортуєте це у свій довірений магазин, використовуючи інструмент керування ключами, як описано у Довідковому посібнику JSSE. Навіть не думайте про небезпечний TrustManager, розміщений тут.

EDIT На користь сімнадцяти (!) Низовин та численних коментаторів нижче, які явно не читали, що я написав тут, це не єремія проти самопідписаних сертифікатів. Немає нічого поганого в самопідписаних сертифікатах, якщо їх правильно виконати. Але правильний спосіб їх реалізації полягає в безпечній доставці сертифіката через офлайн-процес, а не через несанкціонований канал, який вони використовуватимуть для аутентифікації. Звичайно, це очевидно? Це, безумовно, очевидно для кожної організації, що знає безпеку, в якій я коли-небудь працював, від банків з тисячами відділень до моїх власних компаній. «Рішення» на базі коду на базі коду довіряти всімСертифікати, включаючи власноруч підписані сертифікати, підписані абсолютно будь-яким чи будь-яким арбітражним органом, який зареєструвався як КА, ipso facto не є захищеним. Це просто гра в безпеку. Це безглуздо. Ви спілкуєтесь із приватною розмовою, захищеною від несанкціонованої роботи, стійкою до відповідей та ін'єкцій із… кимось. Будь-хто. Чоловік посередині. Імперсонатор. Будь-хто. Ви також можете просто використовувати непростий текст.


2
Тільки тому, що деякі сервери вирішили використовувати https, це не означає, що людина з клієнтом надає лайно про безпеку в своїх цілях.
Гас

3
Я здивований, що ця відповідь настільки знизила відповідь. Я хотів би трохи більше зрозуміти, чому. Схоже, що EJP пропонує, що ви не повинні робити варіант 2, оскільки це дозволяє мати великий пропуск у безпеці. Може хтось пояснить, чому (окрім налаштувань перед розгортанням), чому ця відповідь низької якості?
cr1pto

2
Ну, я гадаю, все це. Що означає "саме вони вживати заходів, необхідних для того, щоб зробити їх сервер зручним для використання" ? Що повинен зробити оператор сервера, щоб зробити його корисним? Які кроки ви маєте на увазі? Чи можете ви надати їх список? Але відступивши на 1000 футів, як це взагалі стосується проблеми, яку поставила ОП? Він хоче знати, як змусити клієнта прийняти самопідписаний сертифікат на Java. Це проста справа надання довіри до коду, оскільки ОП вважає сертифікат прийнятним.
jww

2
@EJP - Виправте мене, якщо я помиляюся, але прийняття самопідписаного сертифіката - це рішення політики клієнта. Це не має нічого спільного з операціями на стороні сервера. Клієнт повинен прийняти рішення. Паритети повинні спільно працювати над вирішенням ключової проблеми розподілу, але це не має нічого спільного з тим, щоб зробити сервер придатним для використання.
jww

3
@EJP - я точно не сказав: "довіряй усім сертифікатам" . Можливо, мені щось не вистачає (або ти занадто багато читаєш у речах) ... ОП має сертифікат і прийнятний для нього. Він хоче знати, як йому довіряти. Я, мабуть, розщеплює волоски, але церт не потрібно доставляти офлайн. Потрібно використовувати позавірну перевірку, але це не те саме, що "має бути доставлено клієнту в автономному режимі" . Він може бути навіть магазином, що виробляє сервер і клієнт, тому він має необхідні апріорні знання.
jww

0

Це не є вирішенням всієї проблеми, але Oracle має хорошу детальну документацію щодо використання цього ключового інструменту. Це пояснює, як це зробити

  1. використовувати keytool.
  2. генерувати certs / самопідписані certs за допомогою keytool.
  3. імпортувати створені серти для клієнтів Java.

https://docs.oracle.com/cd/E54932_01/doc.705/e54936/cssg_create_ssl_cert.htm#CSVSG178


0

Замість використання keytool, як пропонує головний коментар, на RHEL ви можете використовувати update-ca-trust, починаючи з новіших версій RHEL 6. Вам потрібно мати cert у форматі pem. Тоді

trust anchor <cert.pem>

Відредагуйте /etc/pki/ca-trust/source/cert.p11-kit та змініть "категорію сертифікатів: інший запис" на "категорію сертифікатів: повноваження". (Або використовуйте sed для цього в сценарії.) Потім зробіть

update-ca-trust

Пара застережень:

  • Я не міг знайти "довіру" на моєму сервері RHEL 6, і ви не запропонували його встановити. Я в кінцевому підсумку використовував його на сервері RHEL 7 і копіював файл .p11-kit.
  • Щоб ця робота була для вас, можливо, вам доведеться це зробити update-ca-trust enable. Це замінить / etc / pki / java / cacerts символічним посиланням, що вказує на / etc / pki / ca-trust / extracted / java / cacerts. (Тож ви, можливо, захочете створити резервну копію першої.)
  • Якщо ваш клієнт java використовує касети, які зберігаються в іншому місці, вам потрібно вручну замінити його на симпосилання на / etc / pki / ca-trust / extracted / java / cacerts або замінити його на цей файл.

0

У мене виникло питання про те, що я передавав URL-адресу в бібліотеку, яка дзвонила, url.openConnection();я адаптував відповідь jon-Daniel,

public class TrustHostUrlStreamHandler extends URLStreamHandler {

    private static final Logger LOG = LoggerFactory.getLogger(TrustHostUrlStreamHandler.class);

    @Override
    protected URLConnection openConnection(final URL url) throws IOException {

        final URLConnection urlConnection = new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile()).openConnection();

        // adapated from
        // /programming/2893819/accept-servers-self-signed-ssl-certificate-in-java-client
        if (urlConnection instanceof HttpsURLConnection) {
            final HttpsURLConnection conHttps = (HttpsURLConnection) urlConnection;

            try {
                // Set up a Trust all manager
                final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {

                    @Override
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }

                    @Override
                    public void checkClientTrusted(final java.security.cert.X509Certificate[] certs, final String authType) {
                    }

                    @Override
                    public void checkServerTrusted(final java.security.cert.X509Certificate[] certs, final String authType) {
                    }
                } };

                // Get a new SSL context
                final SSLContext sc = SSLContext.getInstance("TLSv1.2");
                sc.init(null, trustAllCerts, new java.security.SecureRandom());
                // Set our connection to use this SSL context, with the "Trust all" manager in place.
                conHttps.setSSLSocketFactory(sc.getSocketFactory());
                // Also force it to trust all hosts
                final HostnameVerifier allHostsValid = new HostnameVerifier() {
                    @Override
                    public boolean verify(final String hostname, final SSLSession session) {
                        return true;
                    }
                };

                // and set the hostname verifier.
                conHttps.setHostnameVerifier(allHostsValid);

            } catch (final NoSuchAlgorithmException e) {
                LOG.warn("Failed to override URLConnection.", e);
            } catch (final KeyManagementException e) {
                LOG.warn("Failed to override URLConnection.", e);
            }

        } else {
            LOG.warn("Failed to override URLConnection. Incorrect type: {}", urlConnection.getClass().getName());
        }

        return urlConnection;
    }

}

За допомогою цього класу можна створити нову URL-адресу за допомогою:

trustedUrl = new URL(new URL(originalUrl), "", new TrustHostUrlStreamHandler());
trustedUrl.openConnection();

Це має перевагу в тому, що він локалізований і не замінює типовий URL.openConnection.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.