SSLHandshakeException: Otrzymany alert krytyczny: handshake_failure podczas ustawiania szyfrów na serwerze tomcat 7
Mam serwer WWW Tomcat7, który próbowałem skonfigurować, aby akceptować bezpieczne połączenia, dodając to złącze doserver.xml
plik:
<Connector SSLEnabled="true"
acceptCount="100"
connectionTimeout="20000"
executor="tomcatThreadPool"
keyAlias="server"
keystoreFile="c:\opt\engine\conf\tc.keystore"
keystorePass="o39UI12z"
maxKeepAliveRequests="15"
port="8443"
protocol="HTTP/1.1"
redirectPort="8443"
scheme="https"
secure="true"
sslProtocol="TLS"/>
Używam certyfikatu samopodpisanego wygenerowanego za pomocą tego polecenia:
%JAVA_HOME%/bin/keytool -genkeypair -keystore c:\opt\engine\conf\tc.keystore -storepass o39UI12z-keypass o39UI12z-dname "cn=Company, ou=Company, o=Com, c=US" -alias server -validity 36500
Po stronie klienta mam wiosenną aplikację, która łączy się z serwerem za pomocąRestTemplate
. Podczas uruchamiania kontekstu aplikacji inicjujęrestTemplate
przykład w ten sposób:
final ClientHttpRequestFactory clientHttpRequestFactory =
new MyCustomClientHttpRequestFactory(new NullHostNameVerifier(), serverInfo);
restTemplate.setRequestFactory(clientHttpRequestFactory);
Klasa MyCustomClientHttpRequestFactory wygląda tak:
public class MyCustomClientHttpRequestFactory extends SimpleClientHttpRequestFactory {
private static final Logger LOGGER = LoggerFactory
.getLogger(MyCustomClientHttpRequestFactory.class);
private final HostnameVerifier hostNameVerifier;
private final ServerInfo serverInfo;
public MyCustomClientHttpRequestFactory (final HostnameVerifier hostNameVerifier,
final ServerInfo serverInfo) {
this.hostNameVerifier = hostNameVerifier;
this.serverInfo = serverInfo;
}
@Override
protected void prepareConnection(final HttpURLConnection connection, final String httpMethod)
throws IOException {
if (connection instanceof HttpsURLConnection) {
((HttpsURLConnection) connection).setHostnameVerifier(hostNameVerifier);
((HttpsURLConnection) connection).setSSLSocketFactory(initSSLContext()
.getSocketFactory());
}
super.prepareConnection(connection, httpMethod);
}
private SSLContext initSSLContext() {
try {
System.setProperty("https.protocols", "TLSv1");
// Set ssl trust manager. Verify against our server thumbprint
final SSLContext ctx = SSLContext.getInstance("TLSv1");
final SslThumbprintVerifier verifier = new SslThumbprintVerifier(serverInfo);
final ThumbprintTrustManager thumbPrintTrustManager =
new ThumbprintTrustManager(null, verifier);
ctx.init(null, new TrustManager[] { thumbPrintTrustManager }, null);
return ctx;
} catch (final Exception ex) {
LOGGER.error(
"An exception was thrown while trying to initialize HTTP security manager.", ex);
return null;
}
}
Do tej pory wszystko działało dobrze. Kiedy umieściłem punkt przerwania w klasie SslThumbprintVerifier, kod dotarł do tego punktu, a także do klasy NullHostNameVerifier. To zostało przetestowane w produkcji i działało świetnie.
Teraz chciałem rozszerzyć zabezpieczenia, ograniczając zestawy szyfrów i dodałem tę właściwość do przedstawionego na początku Connectora:
ciphers="TLS_KRB5_WITH_RC4_128_SHA,SSL_RSA_WITH_RC4_128_SHA,SSL_DHE_RSA_WITH_AES_256_CBC_SHA,SSL_RSA_WITH_AES_256_CBC_SHA,SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA,SSL_RSA_WITH_3DES_EDE_CBC_SHA,SSL_DHE_RSA_WITH_AES_128_CBC_SHA,SSL_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_RC4_128_SHA,TLS_RSA_WITH_3DES_EDE_CBC_SHA,TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_KRB5_WITH_3DES_EDE_CBC_SHA"
Teraz, gdy używam kodu klienta, otrzymuję ten wyjątek:
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
Dzieje się tak wrestTemplate.doExecute()
metoda. Kod nie dociera do klasy weryfikatora odcisku palca ani do klasy weryfikatora nazw hostów, tak jak przed dodaniem szyfrów.
W debugowaniu sprawdziłemctx.getSocketFactory().getSupportedCipherSuites()
który pokazał:
[SSL_RSA_WITH_RC4_128_MD5, SSL_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_DES_CBC_SHA, SSL_DHE_RSA_WITH_DES_CBC_SHA, SSL_DHE_DSS_WITH_DES_CBC_SHA, SSL_RSA_EXPORT_WITH_RC4_40_MD5, SSL_RSA_EXPORT_WITH_DES40_CBC_SHA, SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV, SSL_RSA_WITH_NULL_MD5, SSL_RSA_WITH_NULL_SHA, SSL_DH_anon_WITH_RC4_128_MD5, TLS_DH_anon_WITH_AES_128_CBC_SHA, TLS_DH_anon_WITH_AES_256_CBC_SHA, SSL_DH_anon_WITH_3DES_EDE_CBC_SHA, SSL_DH_anon_WITH_DES_CBC_SHA, SSL_DH_anon_EXPORT_WITH_RC4_40_MD5, SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA, TLS_KRB5_WITH_RC4_128_SHA, TLS_KRB5_WITH_RC4_128_MD5, TLS_KRB5_WITH_3DES_EDE_CBC_SHA, TLS_KRB5_WITH_3DES_EDE_CBC_MD5, TLS_KRB5_WITH_DES_CBC_SHA, TLS_KRB5_WITH_DES_CBC_MD5, TLS_KRB5_EXPORT_WITH_RC4_40_SHA, TLS_KRB5_EXPORT_WITH_RC4_40_MD5, TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA, TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5]
a także sprawdzonectx.getSocketFactory().getDefaultCipherSuites()
:
[SSL_RSA_WITH_RC4_128_MD5, SSL_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_DES_CBC_SHA, SSL_DHE_RSA_WITH_DES_CBC_SHA, SSL_DHE_DSS_WITH_DES_CBC_SHA, SSL_RSA_EXPORT_WITH_RC4_40_MD5, SSL_RSA_EXPORT_WITH_DES40_CBC_SHA, SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
i wreszciectx.getSupportedSSLParameters().getCipherSuites()
:
[SSL_RSA_WITH_RC4_128_MD5, SSL_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_DES_CBC_SHA, SSL_DHE_RSA_WITH_DES_CBC_SHA, SSL_DHE_DSS_WITH_DES_CBC_SHA, SSL_RSA_EXPORT_WITH_RC4_40_MD5, SSL_RSA_EXPORT_WITH_DES40_CBC_SHA, SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV, SSL_RSA_WITH_NULL_MD5, SSL_RSA_WITH_NULL_SHA, SSL_DH_anon_WITH_RC4_128_MD5, TLS_DH_anon_WITH_AES_128_CBC_SHA, TLS_DH_anon_WITH_AES_256_CBC_SHA, SSL_DH_anon_WITH_3DES_EDE_CBC_SHA, SSL_DH_anon_WITH_DES_CBC_SHA, SSL_DH_anon_EXPORT_WITH_RC4_40_MD5, SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA, TLS_KRB5_WITH_RC4_128_SHA, TLS_KRB5_WITH_RC4_128_MD5, TLS_KRB5_WITH_3DES_EDE_CBC_SHA, TLS_KRB5_WITH_3DES_EDE_CBC_MD5, TLS_KRB5_WITH_DES_CBC_SHA, TLS_KRB5_WITH_DES_CBC_MD5, TLS_KRB5_EXPORT_WITH_RC4_40_SHA, TLS_KRB5_EXPORT_WITH_RC4_40_MD5, TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA, TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5]
O ile rozumiem, jeśli istnieje skrzyżowanie między obsługiwanymi / włączonymi zestawami szyfrów klienta i zestawami szyfrów obsługiwanymi przez serwer, powinien on działać (mamy takie skrzyżowanie zSSL_RSA_WITH_RC4_128_SHA
na przykład). A jednak otrzymuję ten błąd.
W następnym kroku dodałem ten parametr java:
-Djavax.net.debug=ssl,handshake,failure
Dziennik pokazywał tylko klienta-hello, bez odpowiedzi serwera-hello:
[2013-03-20 15:29:51.315] [INFO ] data-service-pool-37 System.out trigger seeding of SecureRandom
[2013-03-20 15:29:51.315] [INFO ] data-service-pool-37 System.out done seeding SecureRandom
[2013-03-20 15:30:38.894] [INFO ] data-service-pool-37 System.out Allow unsafe renegotiation: false
[2013-03-20 15:30:38.894] [INFO ] data-service-pool-37 System.out Allow legacy hello messages: true
[2013-03-20 15:30:38.894] [INFO ] data-service-pool-37 System.out Is initial handshake: true
[2013-03-20 15:30:38.894] [INFO ] data-service-pool-37 System.out Is secure renegotiation: false
[2013-03-20 15:30:38.894] [INFO ] data-service-pool-37 System.out %% No cached client session
[2013-03-20 15:30:38.894] [INFO ] data-service-pool-37 System.out *** ClientHello, TLSv1
[2013-03-20 15:30:38.941] [INFO ] data-service-pool-37 System.out RandomCookie: GMT: 1363720446 bytes = { 99, 249, 173, 214, 110, 82, 58, 52, 189, 92, 74, 169, 133, 128, 250, 109, 160, 64, 112, 253, 50, 160, 255, 196, 85, 93, 33, 172 }
[2013-03-20 15:30:38.941] [INFO ] data-service-pool-37 System.out Session ID: {}
[2013-03-20 15:30:38.941] [INFO ] data-service-pool-37 System.out Cipher Suites: [SSL_RSA_WITH_RC4_128_MD5, SSL_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_DES_CBC_SHA, SSL_DHE_RSA_WITH_DES_CBC_SHA, SSL_DHE_DSS_WITH_DES_CBC_SHA, SSL_RSA_EXPORT_WITH_RC4_40_MD5, SSL_RSA_EXPORT_WITH_DES40_CBC_SHA, SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
[2013-03-20 15:30:38.956] [INFO ] data-service-pool-37 System.out Compression Methods: { 0 }
[2013-03-20 15:30:38.956] [INFO ] data-service-pool-37 System.out ***
[2013-03-20 15:30:38.956] [INFO ] data-service-pool-37 System.out data-service-pool-37, WRITE: TLSv1 Handshake, length = 81
[2013-03-20 15:30:38.956] [INFO ] data-service-pool-37 System.out data-service-pool-37, READ: TLSv1 Alert, length = 2
[2013-03-20 15:30:38.956] [INFO ] data-service-pool-37 System.out data-service-pool-37, RECV TLSv1 ALERT: fatal, handshake_failure
[2013-03-20 15:30:38.972] [INFO ] data-service-pool-37 System.out data-service-pool-37, called closeSocket()
[2013-03-20 15:30:38.972] [INFO ] data-service-pool-37 System.out data-service-pool-37, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
Po usunięciu szyfrów z pliku server.xml działa ponownie.Wspomnę również, że testuję zarówno serwer, jak i klienta na tym samym komputerze. Próbowałem ustawić adres IP serwera w żądaniu klienta na „localhost” i na rzeczywistą maszynę ip i nie działała w obu przypadkach. Ponadto miałem ten serwer działający na innym komputerze z linuxem (magazyn kluczy został wygenerowany na maszynie linuxowej oczywiście ze ścieżką linux) i nadal - działa bez szyfrów i przestaje działać z szyframi.