今日はJava 1.6からJava 1.7にアップグレードしました。それ以来、SSLを介してWebサーバーへの接続を確立しようとするとエラーが発生します。
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)
これがコードです:
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);
}
その唯一のテストプロジェクトは、私が信頼できない証明書をコードで許可して使用する理由です。
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);
}
私は https://google.com に接続しようとしました。私のせいですか?
ありがとう。
Java 7では、デフォルトで有効になっているSNIサポートが導入されました。私は、特定の設定が誤っているサーバーがSSLハンドシェイクで "Unrecognized Name"という警告を送信することを発見しました。これはほとんどのクライアントで無視されます... Javaを除いて。 @ Bob Kerns が述べたように、Oracleのエンジニアはこのバグ/機能の「修正」を拒否しています。
回避策として、彼らはjsse.enableSNIExtension
プロパティを設定することを提案します。プログラムを再コンパイルしなくても機能するようにするには、次のようにアプリを実行します。
Java -Djsse.enableSNIExtension=false yourClass
このプロパティはJavaコードでも設定できますが、SSLアクションの前にを設定する必要があります。 SSLライブラリがロードされたら、プロパティを変更できますが、 はSNIステータスに影響しません 。実行時にSNIを無効にするには(前述の制限付きで)、次のようにします。
System.setProperty("jsse.enableSNIExtension", "false");
このフラグを設定することの欠点は、SNIがアプリケーションの至る所で無効になっていることです。 SNIを利用し、まだ設定ミスのサーバをサポートするためには:
SSLSocket
を作成します。これをsslsock
と名付けましょう。sslsock.startHandshake()
を実行してみてください。これは完了するまでブロックするか、エラー時に例外を投げます。 startHandshake()
でエラーが発生したときはいつでも、例外メッセージを受け取ります。それがhandshake alert: unrecognized_name
と等しい場合、あなたは間違って設定されたサーバを見つけました。unrecognized_name
警告(Javaでは致命的)を受け取った場合は、SSLSocket
のオープンを再試行してください。ただし、今回はホスト名は指定しません。これは実質的にSNIを無効にします(結局のところ、SNI拡張子はClientHelloメッセージにホスト名を追加することです)。Webscarab SSLプロキシの場合、 このコミット はフォールバック設定を実装します。
私は私が同じ問題があると信じるものを持っていました。ホストにServerNameまたはServerAliasを含めるようにApache設定を調整する必要があることがわかりました。
このコードは失敗しました:
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();
}
}
そしてこのコードはうまくいきました:
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は、TSL/SSLハロー中に警告アラート(レベル:警告、説明:不明な名前)、サーバーハローがサーバーからクライアントに送信されていることを明らかにしました。ただの警告ですが、Java 7.1はすぐに「致命的な説明:予期しないメッセージ」で応答しました。これは、Java SSLライブラリが認識できない名前の警告を表示したくないことを意味します。
トランスポート層セキュリティ(TLS)に関するWikiから:
112認識されない名前警告TLSのみ。クライアントのサーバー名インジケーターが、サーバーでサポートされていないホスト名を指定しました
これで私は私のApache設定ファイルを見るようになりました、そして私がクライアント/ Java側から送られた名前のためにServerNameかServerAliasを追加するならば、それはエラーなしで正しく働いたことがわかりました。
<VirtualHost mydomain.com:443>
ServerName mydomain.com
ServerAlias www.mydomain.com
システムプロパティjsse.enableSNIExtension = falseでSNIレコードの送信を無効にすることができます。
コードを変更できるのであれば、SSLCocketFactory#createSocket()
を使用すると便利です(Hostパラメータなし、または接続ソケットあり)。この場合、server_nameの指示は送信されません。
私達はまた新しいApacheサーバー構築でこのエラーに遭遇しました。
今回の修正は、Javaが接続しようとしているホスト名に対応するhttpd.conf
にServerAlias
を定義することでした。私たちのServerName
は内部のホスト名に設定されました。当社のSSL証明書は外部ホスト名を使用していましたが、それは警告を回避するのに十分ではありませんでした。
デバッグを容易にするために、このsslコマンドを使用できます。
openssl s_client -servername <hostname> -connect <hostname>:443 -state
そのホスト名に問題があるなら、それは出力の一番上の近くにこのメッセージをプリントするでしょう:
SSL3 alert read: warning:unrecognized name
また、そのコマンドを使用して内部ホスト名に接続しても、そのエラーが発生しなかったことにも注意してください。たとえそれがSSL証明書と一致しなくてもです。
Apacheのデフォルトの仮想ホストメカニズムに頼る代わりに、任意のServerNameとワイルドカードServerAliasを使用する最後の包括仮想ホストを定義することができます。
ServerName catchall.mydomain.com
ServerAlias *.mydomain.com
そのようにしてSNIを使うことができ、ApacheはSSL警告を送り返しません。
もちろん、これはワイルドカード構文を使用してすべてのドメインを簡単に記述できる場合にのみ機能します。
それは役に立つはずです。 Apache HttpClient 4.4でSNIエラーを再試行する - 最も簡単な方法( 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;
}
}
}
}
そして
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);
}
}
そして
cm = new PoolingHttpClientConnectionManager(new SniHttpClientConnectionOperator(socketFactoryRegistry), null, -1, TimeUnit.MILLISECONDS);
つかいます:
spring bootとjvm 1.7および1.8でこの問題に遭遇しました。 AWSでは、ServerNameとServerAliasを一致させるように変更することはできませんでした(それらは異なります)。そのため、次のようにしました。
build.gradleに以下を追加しました。
System.setProperty("jsse.enableSNIExtension", "false")
bootRun.systemProperties = System.properties
それにより、「認識されない名前」の問題を回避することができました。
私は同じ問題にぶつかりました、そしてそれは逆DNSが正しくセットアップされていないことが判明しました、それはIPの間違ったホスト名を指していました。リバースDNSを修正してhttpdを再起動すると、警告は消えます。 (逆DNSを修正しない場合は、ServerNameを追加してもうまくいきます)
私のVirtualHost
のServerName
はデフォルトでコメントアウトされています。コメント解除してからうまくいきました。
Resttemplateを使用してクライアントを構築している場合は、エンドポイントを以下のようにしか設定できません。 https:// IP/path_to_service そしてrequestFactoryを設定します。
この方法では、Tomcatや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;
}
私はまた、Java 1.6_29から1.7へのアップグレード中にこの問題に遭遇しました。
驚いたことに、私の顧客はこれを解決するJavaコントロールパネルの設定を発見しました。
[詳細設定]タブで、[SSL 2.0互換のClientHello形式を使用する]を選択できます。
これで問題は解決したようです。
Internet ExplorerブラウザでJavaアプレットを使用しています。
お役に立てれば。
ここに解決策を追加するだけです。これはLAMPユーザーにとって役に立つかもしれません
Options +FollowSymLinks -SymLinksIfOwnerMatch
仮想ホスト構成の上記の行が原因でした。
エラー時の仮想ホスト設定
<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>
動作設定
<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>
Subversionを実行しているUbuntu LinuxサーバーでEclipse経由でアクセスしたときにも同じ問題がありました。
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
これは、別のNameVirtualHost
ディレクティブがports.conf
内のディレクティブと一緒に入力された、sites-enabled/000-default
内の新規エントリーによるものです。
ports.conf
のディレクティブを削除した後、問題は消えました(当然のことながらApacheを再起動した後に)