SSLハンドシェイクアラート:Java 1.7.0へのアップグレード以降のunrecognized_nameエラー


225

今日、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への接続に成功しました。私のせいはどこですか

ありがとう。

回答:


304

Java 7では、デフォルトで有効になっているSNIサポートが導入されました。特定の誤って設定されたサーバーが、SSLハンドシェイクで「Unrecognized Name」警告を送信することがわかりました。これは、Javaを除くほとんどのクライアントで無視されます。以下のよう@Bobカーンズが言及した、オラクルのエンジニアは、このバグ/機能「修正」することを拒否します。

回避策として、jsse.enableSNIExtensionプロパティを設定することをお勧めします。プログラムを再コンパイルせずに機能させるには、次のようにアプリを実行します。

java -Djsse.enableSNIExtension=false yourClass

このプロパティはJavaコードでも設定できますが、SSLアクションの前に設定する必要があります。SSLライブラリが読み込まれたら、プロパティを変更できますが、SNIステータスには影響ません。ランタイムでSNIを無効にするには(前述の制限付き)、以下を使用します。

System.setProperty("jsse.enableSNIExtension", "false");

このフラグを設定するデメリットは、SNIがアプリケーションのすべての場所で無効になることです。SNIを利用し、誤って構成されたサーバーを引き続きサポートするには:

  1. SSLSocket接続するホスト名でを作成します。これに名前を付けましょうsslsock
  2. 実行してみてくださいsslsock.startHandshake()。これは、完了するまでブロックするか、エラー時に例外をスローします。でエラーが発生するたびstartHandshake()に、例外メッセージを取得します。に等しい場合はhandshake alert: unrecognized_name、サーバーの設定が間違っていることがわかります。
  3. unrecognized_name警告(Javaでは致命的)を受け取ったら、を開くことを再試行してSSLSocketください。これにより、実質的にSNIが無効になります(結局のところ、SNI拡張は、ClientHelloメッセージにホスト名を追加することです)。

Webscarab SSLプロキシの場合、このコミットはフォールバック設定実装します。


5
うまくいきました!IntelliJ IDEA subversionクライアントは、HTTPS経由で接続するときに同じエラーが発生しました。idea.exe.vmoptionsファイルを次の行で更新するだけです:-Djsse.enableSNIExtension = false
Dima

2
java webstartの場合、これを使用します:javaws -J-Djsse.enableSNIExtension = false yourClass
Torsten

httpsレストサーバーを使用するJerseyクライアントでもまったく同じ問題が発生しました。私はあなたのトリックを使用し、それがうまくいったことがわかりました!ありがとう!
shahshi15 14年

4
Java 8について不思議に思う人のために、Oracleはまだ動作を変更しておらず、プログラマーにSNIを(適切に)サポートしていないサーバーをキャッチするように要求しています:docs.oracle.com/javase/8/docs/technotes/guides/security/jsse /…
Lekensteyn 14

2
@Blauhirnウェブホストのサポートに連絡してください。設定を修正する必要があります。Apacheを使用している場合は、必要な変更を探します。
Lekensteyn 2016

89

同じ問題だと私は思いました。ホストの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 Hello中に警告アラート(レベル:警告、説明:認識されない名前)、サーバーHelloがサーバーからクライアントに送信されていることを明らかにしました。これは単なる警告でしたが、Java 7.1はすぐに「致命的な説明:予期しないメッセージ」で応答しました。これは、Java SSLライブラリが認識されない名前の警告を表示したくないことを意味していると思います。

トランスポート層セキュリティ(TLS)に関するWikiから:

112認識されない名前の警告TLSのみ。クライアントのサーバー名インジケーターがサーバーでサポートされていないホスト名を指定しました

これにより、Apacheの構成ファイルを確認しましたが、クライアント/ Java側から送信された名前にServerNameまたはServerAliasを追加すると、エラーなしで正常に動作することがわかりました。

<VirtualHost mydomain.com:443>
  ServerName mydomain.com
  ServerAlias www.mydomain.com

3
これは、JavaのTLS実装のバグのようです。
パウロEbermann

1
私は実際にバグをオープンエンドにしています。この番号を割り当てました(まだ表示されていません): bugs.sun.com/bugdatabase/view_bug.do
bug_id=

1
おかげで、ServerAlias私のために働いて追加しました。Wiresharkで同じTLSアラートを確認しました。
Jared Beck

2
これも私にとってはうまくいきました。(もし私がそれを信じて別の道
エンドユーザー

10
@JosephShraibman、オラクルの従業員がバカだと言うのは不適切です。2つServerNameのを使用すると、Apacheはunrecognized_name 警告アラート(致命的なアラートになることもあります)で応答します。RFC 6066は、このトピックについてこれを正確に述べています。この従業員が犯した唯一の間違いは、これが致命的な警告であったと想定することです。これはApacheのものと同じくらいJREのバグです。
ブルーノ

36

システムプロパティjsse.enableSNIExtension = falseを使用して、SNIレコードの送信を無効にできます。

コードを変更できる場合は、それを使用すると役立ちますSSLCocketFactory#createSocket()(ホストパラメータなし、または接続されたソケットを使用)。この場合、server_name通知は送信されません。


11
:それはこのように行われているSystem.setProperty ("jsse.enableSNIExtension", "false");
jn1kk

私はそれが常にうまくいくとは限らないことを発見しました。なぜそれがいくつかのケースで機能し、他のケースでは機能しないのか分かりません。
ジョセフシュライブマン、2012年

2
で私のために働く-Djsse.enableSNIExtension=false。しかし、それは他に何をしますか?それは残りのJavaのセキュリティにとって有害で​​すか?
2013

2
いいえ、それは、共有IPの背後にある複数のホスト名を使用する一部のサイト(特にWebサーバー)が送信する証明書を知らないことを意味します。しかし、Javaアプリが何百万ものWebサイトに接続する必要がない限り、その機能は必要ありません。このようなサーバーに遭遇した場合、証明書の検証が失敗し、接続が中止される可能性があります。したがって、予想されるセキュリティ上の問題はありません。
eckes 2013

17

また、新しいApacheサーバービルドでこのエラーに遭遇しました。

私たちの場合の修正はServerAliashttpd.conf Javaがに接続しようとしていたことをホスト名に対応しています。私たちServerNameは内部ホスト名に設定されました。SSL証明書は外部ホスト名を使用していましたが、警告を回避するには不十分でした。

デバッグを支援するために、次のsslコマンドを使用できます。

openssl s_client -servername <hostname> -connect <hostname>:443 -state

そのホスト名に問題がある場合は、出力の上部近くに次のメッセージが出力されます。

SSL3 alert read: warning:unrecognized name

また、SSL証明書と一致していなくても、そのコマンドを使用して内部ホスト名に接続すると、エラーが発生しなかったことにも注意してください。


6
を使用して問題を再現する方法の例を含めていただきありがとうございopensslます。とても助かります。
sideshowbarker

2
opensslクライアントがメッセージを発信する名前の違いを確認する方法はありますSSL3 alert read: warning:unrecognized nameか?印刷されたアラートが表示されますが、出力では実際にこの名前の違いを確認できません
mox601

これは私にはうまくいきません。でテストされOpenSSL 1.0.1e-fips 11 Feb 2013OpenSSL 1.0.2k-fips 26 Jan 2017そしてLibreSSL 2.6.5
Greg Dubicki、

15

Apacheのデフォルトの仮想ホストメカニズムに依存する代わりに、任意のServerNameとワイルドカードServerAliasを使用する最後の1つのキャッチオール仮想ホストを定義できます。

ServerName catchall.mydomain.com
ServerAlias *.mydomain.com

この方法では、SNIを使用でき、ApacheはSSL警告を送り返しません。

もちろん、これは、ワイルドカード構文を使用してすべてのドメインを簡単に記述できる場合にのみ機能します。


私が理解している限り、Apacheは、クライアントがServerNameまたはServerAliasとして構成されていない名前を使用している場合にのみ、この警告を送信します。したがって、ワイルドカード証明書がある場合は、使用されているすべてのサブドメインを指定するか*.mydomain.com、ServerAlias の構文を使用します。たとえばを使用して複数のエイリアスを追加できることに注意してください。ServerAliasServerAlias *.mydomain.com mydomain.com *.myotherdomain.com
Michael Paesold

13

役に立つはずです。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);

このようにすると、どのように対処するかverifyHostname(sslsock, target)SSLConnectionSocketFactory.createLayeredSockettargetは空の文字列に変更されるため、verifyHostname成功しません。ここに明らかなものがないのですか?
Haozhun 2017

2
これはまさに、私が探していたもので、本当にありがとうございました!この「unrecognized_name」警告で同時に応答するSNIとサーバーをサポートする他の方法は見つかりませんでした。
コープ2017

このソリューションは、プロキシサーバーを使用していない限り機能します。
カエル

verifyHostname()から始めて、Apacheクライアントコードを介してクイックデバッグを実行したところ、DefaultHostnameVerifierを使用してこれがどのように機能するかわかりません。このソリューションでは、ホスト名の検証を無効にする必要がある(たとえば、NoopH​​ostnameVerifierを使用する)と思われます。これは、中間者攻撃を許可するため、良い考えではありません。
FlyingSheep


6

春のブーツでこの問題に遭遇しましたSpringとJVM 1.7および1.8で遭遇しました。AWSでは、ServerNameとServerAliasを一致するように変更する(それらは異なる)ため、次のようにしました。

build.gradle我々は、次を追加しました:

System.setProperty("jsse.enableSNIExtension", "false")
bootRun.systemProperties = System.properties

これにより、「認識できない名前」の問題を回避できました。


5

残念ながら、jarsigner.exeツールにシステムプロパティを指定することはできません。

私は欠陥提出した7177232を @eckes'欠陥参照し、7127374を、それは誤りで閉鎖された理由を説明します。

私の欠点は具体的にはjarsignerツールへの影響に関するものですが、おそらく他の欠点を再度開いて問題に適切に対処することになります。

更新:実際には、Jarsignerツールにシステムプロパティを提供できることがわかりました。これはヘルプメッセージには含まれていません。使用するjarsigner -J-Djsse.enableSNIExtension=false


1
フォローアップしてくれたボブに感謝します。悲しいことにOracleはまだ全体を理解していません。SNIアラートは致命的ではありません。致命的な場合があります。しかし、ほとんどの一般的なSSLクライアント(つまり、ブラウザー)は、それが実際のセキュリティ問題ではないため、無視することを選択しました(サーバー証明書を確認する必要があり、誤ったエンドポイントを検出するため)。もちろん、CAが設定できないのは悲しいことです。適切に構成されたhttpsサーバーをセットアップします。
eckes 2012

2
実際、システムプロパティをJarsignerツールに提供できることがわかりました。それはヘルプメッセージには含まれていません。それはドキュメントでカバーされています。オンラインテキストを信じて愚かな私。もちろん、彼らはそれを修正しない言い訳としてこれを使いました。
Bob Kerns、2012年

ところで、私は現在この問題についてsecurity-dev @ openjdkで話し合っており、評価しているところ、シマンテックがGEOTrustタイムスタンプサーバーを修正したようですが、警告なしでURLを正しく受け入れます。しかし、それはまだ修正されるべきだと私は思います。見たい場合、テストクライアントプロジェクトとディスカッション
eckes

3

同じ問題が発生し、リバースDNSが正しく設定されていないことがわかりました。IPのホスト名が間違っていることを示していました。リバースDNSを修正してhttpdを再起動すると、警告が表示されなくなります。(リバースDNSを修正しない場合は、ServerNameを追加することでうまくいきました)



2

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;
}

1

Java 1.6_29から1.7にアップグレードしているときにもこの問題に遭遇しました。

驚いたことに、私の顧客はこれを解決するJavaコントロールパネルの設定を発見しました。

[詳細設定]タブで、[SSL 2.0互換のClientHello形式を使用する]をオンにします。

これで問題は解決したようです。

Internet ExplorerブラウザーでJavaアプレットを使用しています。

お役に立てれば。


0

Eclipse経由でアクセスしたときにSubversionを実行しているUbuntu Linuxサーバーで同じ問題が発生しました。

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

これは、の新しいエントリによるものでports.conf、別のNameVirtualHostディレクティブがのディレクティブと一緒に入力されましたsites-enabled/000-default

でディレクティブを削除した後ports.conf、問題は消えました(Apacheを再起動した後、当然です)


0

ここにソリューションを追加するだけです。これは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>

0

これは、Appache httpclient 4.5.11のソリューションです。ワイルドカードの件名を持つ証明書に問題がありました*.hostname.com。同じ例外が返さSystem.setProperty("jsse.enableSNIExtension", "false");れましたが、Googleロケーションクライアントでエラーが発生したため、プロパティによる無効化は使用しません。

私は簡単な解決策を見つけました(ソケットを変更するだけ):

import io.micronaut.context.annotation.Bean;
import io.micronaut.context.annotation.Factory;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;

import javax.inject.Named;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.util.List;

@Factory
public class BeanFactory {

    @Bean
    @Named("without_verify")
    public HttpClient provideHttpClient() {
        SSLConnectionSocketFactory connectionSocketFactory = new SSLConnectionSocketFactory(SSLContexts.createDefault(), NoopHostnameVerifier.INSTANCE) {
            @Override
            protected void prepareSocket(SSLSocket socket) throws IOException {
                SSLParameters parameters = socket.getSSLParameters();
                parameters.setServerNames(List.of());
                socket.setSSLParameters(parameters);
                super.prepareSocket(socket);
            }
        };

        return HttpClients.custom()
                .setSSLSocketFactory(connectionSocketFactory)
                .build();
    }


}

-1

独自のHostnameVerifierを使用して特定の接続を暗黙的に信頼する簡単な方法があります。この問題は、SNI拡張が追加されたJava 1.7で発生し、エラーはサーバーの設定ミスが原因です。

"-Djsse.enableSNIExtension = false"を使用して、JVM全体でSNIを無効にするか、URL接続の上にカスタム検証を実装する方法を説明するブログを読むことができます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.