Alerte de prise de contact SSL: erreur de nom non reconnue depuis la mise à niveau vers Java 1.7.0
Je suis passé de Java 1.6 à Java 1.7 aujourd'hui. Depuis lors, une erreur se produit lorsque j'essaie d'établir une connexion à mon serveur Web via SSL:
javax.net.ssl.SSLProtocolException: handshake alert: unrecognized_name
at sun.security.ssl.ClientHandshaker.handshakeAlert(ClientHandshaker.java:1288)
at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:1904)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1027)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1262)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1289)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1273)
at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:523)
at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1296)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254)
at java.net.URL.openStream(URL.java:1035)
Voici le code:
SAXBuilder builder = new SAXBuilder();
Document document = null;
try {
url = new URL(https://some url);
document = (Document) builder.build(url.openStream());
} catch (NoSuchAlgorithmException ex) {
Logger.getLogger(DownloadLoadiciousComputer.class.getName()).log(Level.SEVERE, null, ex);
}
C'est seulement un projet de test c'est pourquoi j'autorise et utilise des certificats non approuvés avec le code:
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) {
}
}
};
try {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e) {
Logger.getLogger(DownloadManager.class.getName()).log(Level.SEVERE, null, e);
}
J'ai essayé avec succès de me connecter à https://google.com . où est ma faute?
Merci.
16 answers
Java 7 a introduit le support SNI qui est activé par défaut. J'ai découvert que certains serveurs mal configurés envoient un avertissement "Nom non reconnu" dans la prise de contact SSL qui est ignoré par la plupart des clients... sauf pour Java. Comme @Bob Kerns l'a mentionné, les ingénieurs Oracle refusent de "corriger" ce bogue/fonctionnalité.
Comme solution de contournement, ils suggèrent de définir la propriété jsse.enableSNIExtension
. Pour permettre à vos programmes de fonctionner sans re-compiler, exécutez votre application comme:
java -Djsse.enableSNIExtension=false yourClass
La propriété peut être également défini dans le code Java, mais il doit être défini avant toute action SSL. Une fois la bibliothèque SSL chargée, vous pouvez modifier la propriété, mais n'aura aucun effet sur le statut SNI. Pour désactiver SNI lors de l'exécution (avec les limitations susmentionnées), utilisez:
System.setProperty("jsse.enableSNIExtension", "false");
L'inconvénient de la définition de cet indicateur est que SNI est désactivé partout dans l'application. Afin d'utiliser SNI et de toujours prendre en charge les serveurs mal configurés:
- Créer un
SSLSocket
, avec le nom d'hôte que vous souhaitez vous connecter. Nommons cecisslsock
. - Essayez d'exécuter
sslsock.startHandshake()
. Cela bloquera jusqu'à ce que ce soit fait ou lèvera une exception en cas d'erreur. Chaque fois qu'une erreur s'est produite dansstartHandshake()
, obtenez le message d'exception. S'il est égal àhandshake alert: unrecognized_name
, vous avez trouvé un serveur mal configuré. - Lorsque vous avez reçu l'avertissement
unrecognized_name
(fatal en Java), réessayez d'ouvrir unSSLSocket
, mais cette fois sans nom d'hôte. Cela désactive efficacement SNI (après tout, l'extension SNI concerne ajouter un nom d'hôte au message ClientHello).
Pour le proxy SSL Webscarab, ce commit implémente la configuration de secours.
J'ai eu ce que je crois que le même problème est. J'ai trouvé que j'avais besoin d'ajuster la configuration Apache pour inclure un nom de serveur ou ServerAlias pour l'hôte.
Ce code a échoué:
public class a {
public static void main(String [] a) throws Exception {
java.net.URLConnection c = new java.net.URL("https://mydomain.com/").openConnection();
c.setDoOutput(true);
c.getOutputStream();
}
}
Et ce code a fonctionné:
public class a {
public static void main(String [] a) throws Exception {
java.net.URLConnection c = new java.net.URL("https://google.com/").openConnection();
c.setDoOutput(true);
c.getOutputStream();
}
}
Wireshark a révélé que pendant le TSL / SSL Bonjour l'avertissement Alerte (Niveau: Avertissement, Description: Nom non reconnu), Serveur Bonjour A été envoyé à partir du serveur vers le client. Ce n'était qu'un avertissement, cependant, Java 7.1 a ensuite répondu immédiatement avec un "Fatal, Description: Unexpected Message", que je suppose signifie que les bibliothèques SSL Java n'aiment pas voir l'avertissement de nom non reconnu.
Du Wiki sur Transport Layer Security (TLS):
112 Avertissement de nom non reconnu TLS uniquement; l'indicateur de nom de serveur du client spécifiait un nom d'hôte non pris en charge par le serveur
Cela m'a amené à regarder mes fichiers de configuration Apache et j'ai constaté que si j'ajoutais un nom de serveur ou ServerAlias pour le nom envoyé du côté client / java, il a fonctionné correctement sans erreur.
<VirtualHost mydomain.com:443>
ServerName mydomain.com
ServerAlias www.mydomain.com
Vous pouvez désactiver l'envoi d'enregistrements SNI avec la propriété système jsse.enableSNIExtension=false.
Si vous pouvez changer le code, il est utile d'utiliser SSLCocketFactory#createSocket()
(sans paramètre host ou avec un socket connecté). Dans ce cas, il n'enverra pas d'indication server_name.
Nous avons également rencontré cette erreur sur une nouvelle version du serveur Apache.
Le correctif dans notre cas était de définir un ServerAlias
dans le httpd.conf
qui correspondait au nom d'hôte auquel Java essayait de se connecter. Notre ServerName
a été défini sur le nom d'hôte interne. Notre certificat SSL utilisait le nom d'hôte externe, mais cela n'était pas suffisant pour éviter l'avertissement.
Pour aider au débogage, vous pouvez utiliser cette commande ssl:
openssl s_client -servername <hostname> -connect <hostname>:443 -state
S'il y a un problème avec ce nom d'hôte, il affichera ceci message près du haut de la sortie:
SSL3 alert read: warning:unrecognized name
Je dois également noter que nous n'avons pas obtenu cette erreur lors de l'utilisation de cette commande pour nous connecter au nom d'hôte interne, même si elle ne correspondait pas au certificat SSL.
Au lieu de compter sur le mécanisme d'hôte virtuel par défaut dans apache, vous pouvez définir un dernier catchall virtualhost qui utilise un nom de serveur arbitraire et un caractère générique ServerAlias, par exemple
ServerName catchall.mydomain.com
ServerAlias *.mydomain.com
De cette façon, vous pouvez utiliser SNI et apache ne renverra pas l'avertissement SSL.
Bien sûr, cela ne fonctionne que si vous pouvez décrire tous vos domaines facilement en utilisant une syntaxe générique.
Cela devrait être utile. Pour réessayer une erreur SNI dans Apache HttpClient 4.4-la façon la plus simple que nous ayons trouvée (voir HTTPCLIENT-1522):
public class SniHttpClientConnectionOperator extends DefaultHttpClientConnectionOperator {
public SniHttpClientConnectionOperator(Lookup<ConnectionSocketFactory> socketFactoryRegistry) {
super(socketFactoryRegistry, null, null);
}
@Override
public void connect(
final ManagedHttpClientConnection conn,
final HttpHost host,
final InetSocketAddress localAddress,
final int connectTimeout,
final SocketConfig socketConfig,
final HttpContext context) throws IOException {
try {
super.connect(conn, host, localAddress, connectTimeout, socketConfig, context);
} catch (SSLProtocolException e) {
Boolean enableSniValue = (Boolean) context.getAttribute(SniSSLSocketFactory.ENABLE_SNI);
boolean enableSni = enableSniValue == null || enableSniValue;
if (enableSni && e.getMessage() != null && e.getMessage().equals("handshake alert: unrecognized_name")) {
TimesLoggers.httpworker.warn("Server received saw wrong SNI host, retrying without SNI");
context.setAttribute(SniSSLSocketFactory.ENABLE_SNI, false);
super.connect(conn, host, localAddress, connectTimeout, socketConfig, context);
} else {
throw e;
}
}
}
}
Et
public class SniSSLSocketFactory extends SSLConnectionSocketFactory {
public static final String ENABLE_SNI = "__enable_sni__";
/*
* Implement any constructor you need for your particular application -
* SSLConnectionSocketFactory has many variants
*/
public SniSSLSocketFactory(final SSLContext sslContext, final HostnameVerifier verifier) {
super(sslContext, verifier);
}
@Override
public Socket createLayeredSocket(
final Socket socket,
final String target,
final int port,
final HttpContext context) throws IOException {
Boolean enableSniValue = (Boolean) context.getAttribute(ENABLE_SNI);
boolean enableSni = enableSniValue == null || enableSniValue;
return super.createLayeredSocket(socket, enableSni ? target : "", port, context);
}
}
Et
cm = new PoolingHttpClientConnectionManager(new SniHttpClientConnectionOperator(socketFactoryRegistry), null, -1, TimeUnit.MILLISECONDS);
Utilisation:
- Système.setProperty ("jsse.enableSNIExtension", "false");
- Redémarrez votre serveur Tomcat (important)
Vous ne pouvez pas fournir de propriétés système au jarsigner.outil exe, malheureusement.
J'ai soumis un défaut 7177232, référencement du défaut de @ eckes7127374 et expliquer pourquoi il a été fermé par erreur.
Mon défaut concerne spécifiquement l'impact sur l'outil jarsigner, mais cela les conduira peut-être à rouvrir l'autre défaut et à résoudre correctement le problème.
MISE À JOUR: En fait, il s'avère que vous POUVEZ fournir des propriétés système au Outil Jarsigner, ce n'est tout simplement pas dans le message d'aide. Utiliser jarsigner -J-Djsse.enableSNIExtension=false
Rencontré ce problème avec spring boot et jvm 1.7 et 1.8. Sur AWS, nous n'avions pas la possibilité de modifier ServerName et ServerAlias pour qu'ils correspondent (ils sont différents), nous avons donc fait ce qui suit:
Dans construire.gradle {[3] } nous avons ajouté ce qui suit:
System.setProperty("jsse.enableSNIExtension", "false")
bootRun.systemProperties = System.properties
Cela nous a permis de contourner le problème avec le "Nom non reconnu".
J'ai rencontré le même problème et il s'est avéré que le dns inverse n'était pas configuré correctement, il pointait vers un mauvais nom d'hôte pour l'IP. Après avoir corrigé le dns inverse et redémarré httpd, l'avertissement a disparu. (si je ne corrige pas le dns inverse, l'ajout de ServerName a également fait l'affaire pour moi)
Mon VirtualHost
's ServerName
a été commentée par défaut. Cela a fonctionné après uncommenting.
Si vous construisez un client avec Resttemplate, vous ne pouvez définir le point de terminaison que comme ceci: https://IP/path_to_service et définissez la requestFactory.
Avec cette solution, vous n'avez pas besoin de REDÉMARRER votre TOMCAT ou Apache:
public static HttpComponentsClientHttpRequestFactory requestFactory(CloseableHttpClient httpClient) {
TrustStrategy acceptingTrustStrategy = new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
};
SSLContext sslContext = null;
try {
sslContext = org.apache.http.ssl.SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy)
.build();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
final SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext,hostnameVerifier);
final Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", new PlainConnectionSocketFactory())
.register("https", csf)
.build();
final PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
cm.setMaxTotal(100);
httpClient = HttpClients.custom()
.setSSLSocketFactory(csf)
.setConnectionManager(cm)
.build();
HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
return requestFactory;
}
J'ai également rencontré ce problème lors de la mise à niveau de Java 1.6_29 vers 1.7.
De manière alarmante, mon client a découvert un paramètre dans le panneau de configuration Java qui résout ce problème.
Dans l'onglet Avancé, vous pouvez cocher "Utiliser le format ClientHello compatible SSL 2.0".
Cela semble résoudre le problème.
Nous utilisons des applets Java dans un navigateur Internet Explorer.
J'espère que cela aide.
J'ai eu le même problème avec un serveur Linux Ubuntu exécutant subversion lors de l'accès via Eclipse.
Il a montré que le problème était lié à un avertissement lors du (re)démarrage d'Apache:
[Mon Jun 30 22:27:10 2014] [warn] NameVirtualHost *:80 has no VirtualHosts
... waiting [Mon Jun 30 22:27:11 2014] [warn] NameVirtualHost *:80 has no VirtualHosts
Cela est dû à une nouvelle entrée dans ports.conf
, où une autre directive NameVirtualHost
a été entrée parallèlement à la directive dans sites-enabled/000-default
.
Après avoir supprimé la directive dans ports.conf
, le problème avait disparu (après le redémarrage d'Apache, naturellement)
Juste pour ajouter une solution ici. Cela pourrait aider pour les utilisateurs de LAMP
Options +FollowSymLinks -SymLinksIfOwnerMatch
La ligne mentionnée ci-dessus dans la configuration de l'hôte virtuel était le coupable.
Configuration de l'hôte virtuel en cas d'erreur
<VirtualHost *:80>
DocumentRoot /var/www/html/load/web
ServerName dev.load.com
<Directory "/var/www/html/load/web">
Options +FollowSymLinks -SymLinksIfOwnerMatch
AllowOverride All
Require all granted
Order Allow,Deny
Allow from All
</Directory>
RewriteEngine on
RewriteCond %{SERVER_PORT} !^443$
RewriteRule ^/(.*) https://%{HTTP_HOST}/$1 [NC,R=301,L]
</VirtualHost>
Configuration de travail
<VirtualHost *:80>
DocumentRoot /var/www/html/load/web
ServerName dev.load.com
<Directory "/var/www/html/load/web">
AllowOverride All
Options All
Order Allow,Deny
Allow from All
</Directory>
# To allow authorization header
RewriteEngine On
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]
# RewriteCond %{SERVER_PORT} !^443$
# RewriteRule ^/(.*) https://%{HTTP_HOST}/$1 [NC,R=301,L]
</VirtualHost>
Il existe un moyen plus simple où vous pouvez simplement utiliser votre propre HostnameVerifier pour faire implicitement confiance à certaines connexions. Le problème vient avec Java 1.7 où des extensions SNI ont été ajoutées et votre erreur est due à une mauvaise configuration du serveur.
Vous pouvez utiliser "-Djsse.enableSNIExtension=false " pour désactiver SNI sur toute la JVM ou lire mon blog où j'explique comment implémenter un vérificateur personnalisé au-dessus d'une connexion URL.