it-roy-ru.com

Как исправить ошибку "Java.security.cert.CertificateException: альтернативные имена субъектов отсутствуют"?

У меня есть клиент веб-службы Java, который использует веб-службу через HTTPS.

import javax.xml.ws.Service;

@WebServiceClient(name = "ISomeService", targetNamespace = "http://tempuri.org/", wsdlLocation = "...")
public class ISomeService
    extends Service
{

    public ISomeService() {
        super(__getWsdlLocation(), ISOMESERVICE_QNAME);
    }

Когда я подключаюсь к сервису URL (https://AAA.BBB.CCC.DDD:9443/ISomeService), я получаю исключение Java.security.cert.CertificateException: No subject alternative names present.

Чтобы это исправить, я сначала запустил openssl s_client -showcerts -connect AAA.BBB.CCC.DDD:9443 > certs.txt и получил следующее содержимое в файле certs.txt:

CONNECTED(00000003)
---
Certificate chain
 0 s:/CN=someSubdomain.someorganisation.com
   i:/CN=someSubdomain.someorganisation.com
-----BEGIN CERTIFICATE-----
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-----END CERTIFICATE-----
---
Server certificate
subject=/CN=someSubdomain.someorganisation.com
issuer=/CN=someSubdomain.someorganisation.com
---
No client certificate CA names sent
---
SSL handshake has read 489 bytes and written 236 bytes
---
New, TLSv1/SSLv3, Cipher is RC4-MD5
Server public key is 512 bit
Compression: NONE
Expansion: NONE
SSL-Session:
    Protocol  : TLSv1
    Cipher    : RC4-MD5            
    Session-ID: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    Session-ID-ctx:                 
    Master-Key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    Key-Arg   : None
    Start Time: 1382521838
    Timeout   : 300 (sec)
    Verify return code: 21 (unable to verify the first certificate)
---

AFAIK, теперь мне нужно

  1. извлечь часть certs.txt между -----BEGIN CERTIFICATE----- и -----END CERTIFICATE-----,
  2. измените его так, чтобы имя сертификата было равно AAA.BBB.CCC.DDD и
  3. затем импортируйте результат, используя keytool -importcert -file fileWithModifiedCertificate (где fileWithModifiedCertificate - результат операций 1 и 2).

Это правильно?

Если да, то как именно можно заставить сертификат из шага 1 работать с IP-адресом (AAA.BBB.CCC.DDD)?

Обновление 1 (23.10.2013 15:37 мск): В ответе на похожий вопрос я прочитал следующее:

Если вы не контролируете этот сервер, используйте его имя хоста (при условии Что по крайней мере CN соответствует этому имени хоста в существующем Cert).

Что именно означает «использовать»?

77
DP_

Я исправил проблему, отключив проверки HTTPS, используя представленный подход здесь :

Я поместил следующий код в класс ISomeService:

static {
    disableSslVerification();
}

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

        // Install the all-trusting trust manager
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new Java.security.SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

        // Create all-trusting Host name verifier
        HostnameVerifier allHostsValid = new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };

        // Install the all-trusting Host verifier
        HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (KeyManagementException e) {
        e.printStackTrace();
    }
}

Поскольку я использую https://AAA.BBB.CCC.DDD:9443/ISomeService только для целей тестирования, это достаточно хорошее решение.

126
DP_

У меня та же проблема, и я решил этот код .. Я поместил этот код перед первым вызовом своих веб-сервисов.

javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
new javax.net.ssl.HostnameVerifier(){

    public boolean verify(String hostname,
            javax.net.ssl.SSLSession sslSession) {
        return hostname.equals("localhost");
    }
});

Все просто и отлично работает.

Здесь является оригинальным источником.

26
juanmhidalgo

Проверка подлинности сертификата выполняется в соответствии с тем, что запрашивает клиент.

Когда ваш клиент использует https://xxx.xxx.xxx.xxx/something (где xxx.xxx.xxx.xxx - это IP-адрес), идентичность сертификата проверяется по этому IP-адресу (теоретически, только с использованием расширения IP SAN).

Если у вашего сертификата нет IP SAN, но есть DNS SAN (или если нет DNS SAN, общее имя в DN субъекта), вы можете заставить это работать, заставив вашего клиента использовать URL с этим именем хоста (или именем хоста) для которого сертификат будет действительным, если есть несколько возможных значений). Например, если у вас есть сертификат для имени www.example.com, используйте https://www.example.com/something.

Конечно, вам потребуется это имя хоста для разрешения этого IP-адреса.

Кроме того, если есть какие-либо SAN DNS, CN в DN субъекта будет игнорироваться, поэтому в этом случае используйте имя, соответствующее одной из SAN DNS.

19
Bruno

Чтобы импортировать сертификат:

  1. Извлеките сертификат из сервера, например, openssl s_client -showcerts -connect AAA.BBB.CCC.DDD:9443 > certs.txt Это позволит извлечь сертификаты в формате PEM.
  2. Преобразуйте сертификат в формат DER, как это и ожидалось, например, в keytool. openssl x509 -in certs.txt -out certs.der -outform DER
  3. Теперь вы хотите импортировать этот сертификат в системный файл 'cacert' по умолчанию. Найдите системный файл по умолчанию 'cacerts' для вашей установки Java. Посмотрите на Как получить расположение каскадов установки Java по умолчанию?
  4. Импортируйте сертификаты в этот файл cacerts: Sudo keytool -importcert -file certs.der -keystore <path-to-cacerts> Пароль cacerts по умолчанию - «changeit».

Если сертификат выдан для полного доменного имени, и вы пытаетесь подключиться по IP-адресу в своем коде Java, то это, вероятно, следует исправить в коде, а не связываться с самим сертификатом. Измените свой код для подключения по FQDN. Если полное доменное имя не может быть разрешено на вашем компьютере разработчика, просто добавьте его в файл hosts или настройте на своем компьютере DNS-сервер, который может разрешить это полное доменное имя.

10
AndreyT

Это старый вопрос, но у меня была такая же проблема при переходе с JDK 1.8.0_144 на jdk 1.8.0_191

Мы нашли подсказку в журнале изменений:

Журнал изменений

мы добавили следующее дополнительное системное свойство, которое помогло в нашем случае решить эту проблему:

-Dcom.Sun.jndi.ldap.object.disableEndpointIdentification=true
9
Markus

Возможно, вы не захотите отключать все проверки SSL, поэтому вы можете просто отключить проверку hostName через это, что немного менее страшно, чем альтернатива:

HttpsURLConnection.setDefaultHostnameVerifier(
    SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

[Правка]

Как упомянуто conapart3, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER устарел, поэтому он может быть удален в более поздней версии, так что в будущем вы можете быть вынуждены откатить свои собственные, хотя я все же сказал бы, что буду избегать любых решений, где вся проверка отключена ,.

3
lifesoordinary

моя проблема с получением этой ошибки была решена с использованием полного URL "qatest.ourCompany.com/webService" вместо просто "qatest/webService". Причина в том, что наш сертификат безопасности имел подстановочный знак, т.е. "* .ourCompany.com". Как только я указал полный адрес, исключение исчезло. Надеюсь это поможет.

3
Shahriar

Я исправил эту проблему правильным образом, добавив альтернативные имена субъекта в сертификат, вместо того, чтобы вносить какие-либо изменения в код или отключать SSL в отличие от других ответов, предложенных здесь. Если вы ясно видите, что исключение говорит: «Suject alt имена отсутствуют», значит, нужно добавить их правильно. 

Пожалуйста, посмотрите на эту ссылку чтобы понять шаг за шагом .

Вышеуказанная ошибка означает, что в вашем файле JKS отсутствует требуемый домен, на котором вы пытаетесь получить доступ к приложению. Вам потребуется использовать Open SSL и инструмент ключа для добавления нескольких доменов.

  1. Скопируйте openssl.cnf в текущий каталог
  2. echo '[ subject_alt_name ]' >> openssl.cnf
  3. echo 'subjectAltName = DNS:example.mydomain1.com, DNS:example.mydomain2.com, DNS:example.mydomain3.com, DNS: localhost'>> openssl.cnf
  4. openssl req -x509 -nodes -newkey rsa:2048 -config openssl.cnf -extensions subject_alt_name -keyout private.key -out self-signed.pem -subj '/C=gb/ST=edinburgh/L=edinburgh/O=mygroup/OU=servicing/CN=www.example.com/[email protected]' -days 365
  5. Экспортируйте файл с открытым ключом (.pem) в формат PKS12. Это предложит вам пароль

    openssl pkcs12 -export -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -export -in
    self-signed.pem -inkey private.key -name myalias -out keystore.p12
    
  6. Создать .JKS из самоподписанного PEM (Keystore)

    keytool -importkeystore -destkeystore keystore.jks -deststoretype PKCS12 -srcstoretype PKCS12 -srckeystore keystore.p12
    
  7. Сгенерируйте сертификат из Keystore или файла JKS

    keytool -export -keystore keystore.jks -alias myalias -file selfsigned.crt
    
  8. Поскольку вышеуказанный сертификат является самоподписанным и не проверен CA, его необходимо добавить в Truststore (файл Cacerts в расположении ниже для MAC, для Windows, узнайте, где установлен ваш JDK).

    Sudo keytool -importcert -file selfsigned.crt -alias myalias -keystore /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/security/cacerts
    

Оригинальный ответ размещен по этой ссылке здесь

1
Abhishek Galoda

Я получил этот вопрос после того, как получил это же сообщение об ошибке. Однако в моем случае у нас было два URL с разными поддоменами ( http://example1.xxx.com/someservice и http://example2.yyy.com/someservice ), которые были направлены на один и тот же сервер. На этом сервере был только один подстановочный сертификат для домена * .xxx.com. При использовании сервиса через второй домен найденный сертификат (* .xxx.com) не совпадает с запрашиваемым доменом (* .yyy.com), и возникает ошибка.

В этом случае мы не должны пытаться исправить такое сообщение об ошибке путем снижения безопасности SSL, но должны проверить сервер и сертификаты на нем.

0
Gert

Ответили на это уже в https://stackoverflow.com/a/53491151/1909708 .

Это не удается, поскольку ни общее имя сертификата (CN в сертификате Subject), ни какое-либо из альтернативных имен (Subject Alternative Name в сертификате) не совпадают с целевым именем хоста или IP-адресом. 

Например, из JVM при попытке подключиться к IP-адресу (WW.XX.YY.ZZ), а не к DNS-имени ( https://stackoverflow.com ), соединение HTTPS не будет установлено, так как сертификат хранится в доверенном хранилище Java cacerts ожидает, что общее имя (или альтернативное имя сертификата, например stackexchange.com или * .stackoverflow.com и т. д.) будет соответствовать целевому адресу. 

Пожалуйста, проверьте: https://docs.Oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#HostnameVerifier

    HttpsURLConnection urlConnection = (HttpsURLConnection) new URL("https://WW.XX.YY.ZZ/api/verify").openConnection();
    urlConnection.setSSLSocketFactory(socketFactory());
    urlConnection.setDoOutput(true);
    urlConnection.setRequestMethod("GET");
    urlConnection.setUseCaches(false);
    urlConnection.setHostnameVerifier(new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession sslSession) {
            return true;
        }
    });
    urlConnection.getOutputStream();

Выше передан реализованный объект HostnameVerifier, который всегда возвращает true:

new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession sslSession) {
            return true;
        }
    }
0
Himadri Pant