Java 11 не принимает самозаверяющий сертификат

#java #ssl #tomcat

#java #ssl #кот

Вопрос:

Я столкнулся со странной проблемой с Tomcat 9 и OpenJDK 11.0.2 на сервере Linux. Tomcat настроен только как HTTPS, нет HTTP-порта (есть HTTP-порт, но он перенаправляет на HTTPS). Tomcat содержит множество веб-приложений, и только HTTPS был одним из требований безопасности.

Одно из этих веб-приложений фактически должно подключаться к тому же Tomcat (к приложению manager) по протоколу HTTPS для автоматического развертывания других веб-приложений (простое копирование в папку webapps невозможно, поскольку автоматическое развертывание WARs отключено).). Приложение выдавало ошибки SSL, потому что сертификат HTTPS для Tomcat является самоподписанным. Никакие реальные пользователи не используют эти приложения, к ним подключаются другие серверы, так что самоподписывание здесь «нормально», и мы могли бы заставить другие серверы принимать самоподписанные сертификаты.

Однако, хоть убейте, я не могу настроить JVM так, чтобы он принимал самозаверяющий сертификат, на использование которого настроен Tomcat. Я попробовал два разных подхода.

Подход 1:

  1. Создал самозаверяющий сертификат с помощью OpenSSL (чтобы в нем были поля SAN)
  2. Преобразован в PKCS12 и создал хранилище ключей JKS для Tomcat
  3. Импортировал самозаверяющий сертификат в файл Java cacerts как trustedCertEntry

Подход 2:

  1. Создал ключ CA и сертификат с помощью OpenSSL
  2. Импортировал сертификат CA в файл Java cacerts как trustedCertEntry
  3. Создан новый ключ
  4. Создал CSR с ключом
  5. Подписал CSR с помощью ключа и сертификата центра сертификации и создал новый сертификат
  6. Преобразован в PKCS12 и создал хранилище ключей JKS для Tomcat

В обоих случаях удаленный сервер принимает сертификат, но веб-приложение на локальном сервере Tomcat продолжает выдавать одно и то же исключение:

 PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
 

Я уже делал подобные конфигурации раньше, и это всегда срабатывало. Может быть, я упускаю здесь что-то маленькое, но сейчас я не уверен.

Технические детали для подхода 1:

Вот самозаверяющий сертификат в файле cacerts:

 keytool -list -v -cacerts -alias tomcat
Enter keystore password:
Alias name: tomcat
Creation date: Feb 18, 2021
Entry type: trustedCertEntry

Owner: CN=xxx, OU=xxx, O=xxx, L=xxx, ST=xxx, C=xxx
Issuer: CN=xxx, OU=xxx, O=xxx, L=xxx, ST=xxx, C=xxx
Serial number: e582a6448ae33c84
Valid from: Tue Feb 16 17:23:34 CET 2021 until: Fri Feb 14 17:23:34 CET 2031
Certificate fingerprints:
         SHA1: 2B:B2:3D:4C:42:12:49:EF:66:C1:15:66:A1:58:39:12:2D:A0:A6:0D
         SHA256: B6:BA:23:93:C8:AA:C0:F6:3F:3F:FB:B6:8E:0F:CE:A9:0D:55:F9:BB:8D:14:82:C4:39:12:D8:67:99:B9:FB:63
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 3

Extensions:

#1: ObjectId: 2.5.29.37 Criticality=false
ExtendedKeyUsages [
  serverAuth
]

#2: ObjectId: 2.5.29.15 Criticality=false
KeyUsage [
  DigitalSignature
  Non_repudiation
  Key_Encipherment
]

#3: ObjectId: 2.5.29.17 Criticality=false
SubjectAlternativeName [
  DNSName: xxx
  DNSName: xxx
  IPAddress: x.x.x.x
]
 

Same in the JKS keystore for Tomcat:

 keytool -list -v -keystore tomcat-keystore.jks
Enter keystore password:
Keystore type: JKS
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: tomcat
Creation date: Feb 16, 2021
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=xxx, OU=xxx, O=xxx, L=xxx, ST=xxx, C=xxx
Issuer: CN=xxx, OU=xxx, O=xxx, L=xxx, ST=xxx, C=xxx
Serial number: e582a6448ae33c84
Valid from: Tue Feb 16 17:23:34 CET 2021 until: Fri Feb 14 17:23:34 CET 2031
Certificate fingerprints:
         SHA1: 2B:B2:3D:4C:42:12:49:EF:66:C1:15:66:A1:58:39:12:2D:A0:A6:0D
         SHA256: B6:BA:23:93:C8:AA:C0:F6:3F:3F:FB:B6:8E:0F:CE:A9:0D:55:F9:BB:8D:14:82:C4:39:12:D8:67:99:B9:FB:63
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 3

Extensions:

#1: ObjectId: 2.5.29.37 Criticality=false
ExtendedKeyUsages [
  serverAuth
]

#2: ObjectId: 2.5.29.15 Criticality=false
KeyUsage [
  DigitalSignature
  Non_repudiation
  Key_Encipherment
]

#3: ObjectId: 2.5.29.17 Criticality=false
SubjectAlternativeName [
  DNSName: xxx
  DNSName: xxx
  IPAddress: x.x.x.x
]
 

Technical details for approach 2:

Here is my CA in cacerts:

 keytool -list -v -cacerts -alias MY_CA
Enter keystore password:
Alias name: MY_CA
Creation date: Feb 18, 2021
Entry type: trustedCertEntry

Owner: EMAILADDRESS=yyy, CN=yyy, OU=yyy, O=yyy, L=yyy, ST=yyy, C=yyy
Issuer: EMAILADDRESS=yyy, CN=yyy, OU=yyy, O=yyy, L=yyy, ST=yyy, C=yyy
Serial number: a9dc4764a1b6c856
Valid from: Thu Feb 18 21:19:59 CET 2021 until: Wed Feb 13 21:19:59 CET 2041
Certificate fingerprints:
         SHA1: 16:FE:9A:BC:9F:05:9F:03:31:61:B2:29:CF:AD:E4:C4:44:6A:6A:92
         SHA256: 5B:BB:CA:A2:96:D5:C5:21:48:03:E9:73:20:13:92:91:E6:3D:32:2F:E4:19:83:1D:49:BF:03:23:48:D7:61:B5
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 3

Extensions:

#1: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
KeyIdentifier [
0000: 25 08 24 A4 78 B1 C7 3B   BD D1 BA E2 96 D1 94 4C  %.$.x..;.......L
0010: 6A A3 7A 64                                        j.zd
]
]

#2: ObjectId: 2.5.29.19 Criticality=false
BasicConstraints:[
  CA:true
  PathLen:2147483647
]

#3: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 25 08 24 A4 78 B1 C7 3B   BD D1 BA E2 96 D1 94 4C  %.$.x..;.......L
0010: 6A A3 7A 64                                        j.zd
]
]
 

Here is the Tomcat JKS:

 [tsmadmin@cs13bapp02 security]$ /opt/Avaya/javas/jdk-11.0.2/bin/keytool -list -v -keystore tomcat2.jks
Enter keystore password:
Keystore type: PKCS12
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: tomcat
Creation date: Feb 18, 2021
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=xxx, OU=xxx, O=xxx, L=xxx, ST=xxx, C=xxx
Issuer: EMAILADDRESS=yyy, CN=yyy, OU=yyy, O=yyy, L=yyy, ST=yyy, C=yyy
Serial number: badb5c4856595561
Valid from: Thu Feb 18 21:38:30 CET 2021 until: Sun Feb 16 21:38:30 CET 2031
Certificate fingerprints:
         SHA1: 8F:CD:8B:6F:4D:00:D8:8D:2F:E6:25:4A:97:69:28:A0:CF:2D:5C:EA
         SHA256: A7:9D:36:6C:E2:4C:2C:3D:C0:E9:FB:4B:19:9F:15:B5:59:CA:23:27:64:1A:78:51:02:4C:DF:3E:A1:08:43:7C
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 3

Extensions:

#1: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
KeyIdentifier [
0000: 25 08 24 A4 78 B1 C7 3B   BD D1 BA E2 96 D1 94 4C  %.$.x..;.......L
0010: 6A A3 7A 64                                        j.zd
]
]

#2: ObjectId: 2.5.29.19 Criticality=false
BasicConstraints:[
  CA:false
  PathLen: undefined
]

#3: ObjectId: 2.5.29.37 Criticality=false
ExtendedKeyUsages [
  serverAuth
]

#4: ObjectId: 2.5.29.15 Criticality=false
KeyUsage [
  DigitalSignature
  Non_repudiation
  Key_Encipherment
  Data_Encipherment
]

#5: ObjectId: 2.5.29.17 Criticality=false
SubjectAlternativeName [
  DNSName: xxx
  IPAddress: x.x.x.x
]
 

В обоих случаях я включил отладочный SSL-журнал в Java (-Djavax.net.debug=ssl:рукопожатие), но я не могу разобраться ни с головой, ни с хвостом. Я могу только разобрать эту ошибку:

 javax.net.ssl|DEBUG|30|https-jsse-nio-9443-exec-9|2021-02-18 17:54:25.184 CET|Alert.java:232|Received alert message (
"Alert": {
  "level"      : "fatal",
  "description": "certificate_unknown"
}
)
javax.net.ssl|ERROR|30|https-jsse-nio-9443-exec-9|2021-02-18 17:54:25.184 CET|TransportContext.java:313|Fatal (CERTIFICATE_UNKNOWN): Received fatal alert: certificate_unknown (
"throwable" : {
  javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown
        at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:128)
        at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
        at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:308)
        at java.base/sun.security.ssl.Alert$AlertConsumer.consume(Alert.java:279)
        at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:181)
        at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:164)
        at java.base/sun.security.ssl.SSLEngineImpl.decode(SSLEngineImpl.java:672)
        at java.base/sun.security.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:627)
        at java.base/sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:443)
        at java.base/sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:422)
        at java.base/javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:634)
        at org.apache.tomcat.util.net.SecureNioChannel.handshakeUnwrap(SecureNioChannel.java:499)
        at org.apache.tomcat.util.net.SecureNioChannel.handshake(SecureNioChannel.java:238)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1567)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.base/java.lang.Thread.run(Thread.java:834)}

)
 

Тот же Tomcat, являющийся одновременно клиентом и сервером, все регистрируется дважды для TLS, что затрудняет его чтение.

Есть какой-нибудь совет? Даже что-то вроде того, на что стоит обратить внимание. Я могу прикрепить больше журналов, я просто не знаю, что я ищу в журнале отладки SSL.

Ответ №1:

Дружественный, быстрый обзор…

SSL — это доверие.

Доверенное хранилище — это то, кому вы доверяете.

Хранилище ключей / сертификат — это то, кем вы себя идентифицируете.

Таким образом, ваш экземпляр tomcat должен будет доверять самому себе. Это означает, что ему потребуется иметь свой самозаверяющий сертификат в своем файле truststore.

Шаги

Примечание: Эти шаги предполагают, что вы не выполняете взаимную аутентификацию.

Есть несколько способов сделать это, но способ, которым я это делаю, заключается в использовании Java-приложения под названием InstallCert.

Если ваш самозаверяющий сертификат запущен на вашем экземпляре tomcat, запустите InstallCert, и в каталоге, в котором вы запустили программу InstallCert, будет создан новый файл jssecacerts1.

Затем вам нужно будет запустить экземпляр tomcat, используя этот новый файл jssecacerts1, добавив следующие параметры в сценарий запуска tomcat:

 -Djavax.net.ssl.trustStore="<directories>/jssecacerts1" -Djavax.net.ssl.trustStorePassword="changeit"
 

Если все идет правильно, когда ваше приложение отправляет запросы к самому себе по https://localhost все должно работать правильно.

Комментарии:

1. Ну, я попробовал. При запуске InstallCert появляется сообщение «Загрузка хранилища ключей cacerts… Ошибок нет, сертификат уже является доверенным «. Тем не менее, я создал файл jssecacerts и использовал его в качестве хранилища доверия, как вы упомянули. Я также попытался использовать исходный файл cacerts в качестве хранилища доверия. Тем не менее, я продолжаю получать ту же ошибку. Похоже, что Tomcat не работает с JVM, с которой, как я полагаю, он работает, но вывод процесса Linux говорит, что это так.

2. Можете ли вы подтвердить, что вы запросили сертификат локального хоста, введя localhost<:port > ?

3. Да, я также пробовал использовать localhost, IP и полное доменное имя, соединение работало для всех. Сам сертификат содержал ПОЛНОЕ доменное имя и IP-адрес в полях SAN.

Ответ №2:

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

Комментарии:

1. Это неверный ответ на вопрос… Да, вы также можете отключить SSL, и это было бы гораздо более чистым решением.