web-dev-qa-db-ja.com

「Java.security.cert.CertificateException:サブジェクト代替名は存在しません」エラーを修正する方法

HTTPSを介してWebサービスを利用するJava Webサービスクライアントがあります。

import javax.xml.ws.Service;

@WebServiceClient(name = "ISomeService", targetNamespace = "http://tempuri.org/", wsdlLocation = "...")
public class ISomeService
    extends Service
{

    public ISomeService() {
        super(__getWsdlLocation(), ISOMESERVICE_QNAME);
    }

サービスURL(https://AAA.BBB.CCC.DDD:9443/ISomeService)に接続すると、例外Java.security.cert.CertificateException: No subject alternative names presentが発生します。

これを修正するために、私は最初にopenssl s_client -showcerts -connect AAA.BBB.CCC.DDD:9443 > certs.txtを実行し、ファイルcerts.txtに次の内容を入れました:

CONNECTED(00000003)
---
Certificate chain
 0 s:/CN=someSubdomain.someorganisation.com
   i:/CN=someSubdomain.someorganisation.com
-----BEGIN CERTIFICATE-----
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-----END CERTIFICATE-----
---
Server certificate
subject=/CN=someSubdomain.someorganisation.com
issuer=/CN=someSubdomain.someorganisation.com
---
No client certificate CA names sent
---
SSL handshake has read 489 bytes and written 236 bytes
---
New, TLSv1/SSLv3, Cipher is RC4-MD5
Server public key is 512 bit
Compression: NONE
Expansion: NONE
SSL-Session:
    Protocol  : TLSv1
    Cipher    : RC4-MD5            
    Session-ID: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    Session-ID-ctx:                 
    Master-Key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    Key-Arg   : None
    Start Time: 1382521838
    Timeout   : 300 (sec)
    Verify return code: 21 (unable to verify the first certificate)
---

私の知る限りでは、

  1. certs.txt-----BEGIN CERTIFICATE-----の間の-----END CERTIFICATE-----の部分を抽出します。
  2. 証明書名がAAA.BBB.CCC.DDDと等しくなるように変更して
  3. 次にkeytool -importcert -file fileWithModifiedCertificateを使用して結果をインポートします(fileWithModifiedCertificateは操作1と2の結果です)。

これは正しいです?

もしそうなら、どのように私はステップ1からの証明書をIPベースのアドレス(AAA.BBB.CCC.DDD)で動作させることができますか?

Update 1(23.10.2013 15:37 MSK):類似の質問 への回答では、次のように読みました。

そのサーバーを管理していない場合は、そのホスト名を使用してください(既存の証明書にそのホスト名と一致するCNが少なくともある場合)。

「使用する」とは正確にはどういう意味ですか?

86
DP_

提示されたアプローチを使用してHTTPSチェックを無効にすることで問題を解決しました ここ

以下のコードをISomeServiceクラスに入れました。

static {
    disableSslVerification();
}

private static void disableSslVerification() {
    try
    {
        // Create a trust manager that does not validate certificate chains
        TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() {
            public Java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return null;
            }
            public void checkClientTrusted(X509Certificate[] certs, String authType) {
            }
            public void checkServerTrusted(X509Certificate[] certs, String authType) {
            }
        }
        };

        // Install the all-trusting trust manager
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new Java.security.SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

        // Create all-trusting Host name verifier
        HostnameVerifier allHostsValid = new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };

        // Install the all-trusting Host verifier
        HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (KeyManagementException e) {
        e.printStackTrace();
    }
}

私はhttps://AAA.BBB.CCC.DDD:9443/ISomeServiceをテスト目的でのみ使っているので、それは十分に良い解決策です。

136
DP_

私は同じ問題を抱えており、このコードで解決しました。私は私のWebサービスへの最初の呼び出しの前にこのコードを入れました。

javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
new javax.net.ssl.HostnameVerifier(){

    public boolean verify(String hostname,
            javax.net.ssl.SSLSession sslSession) {
        return hostname.equals("localhost");
    }
});

それは簡単で、うまくいきます。

ここ が元のソースです。

29
juanmhidalgo

証明書IDの検証は、クライアントが要求した内容に対して実行されます。

クライアントがhttps://xxx.xxx.xxx.xxx/something(ここでxxx.xxx.xxx.xxxはIPアドレス)を使用する場合、証明書の識別情報はこのIPアドレスに対してチェックされます(理論上は、IP SAN拡張子のみを使用)。

証明書にIP SANがなく、DNS SANがある場合(またはDNS SANがない場合は、サブジェクトDNに共通名)、クライアントに代わりにそのホスト名(またはホスト名)のURLを使用させることでこれを機能させることができます。可能な値が複数ある場合は、証明書が有効になります。たとえば、certにwww.example.comの名前がある場合は、https://www.example.com/somethingを使用します。

もちろん、そのIPアドレスに解決するにはそのホスト名が必要です。

さらに、DNS SANがある場合は、サブジェクトDNのCNは無視されるため、この場合はいずれかのDNS SANと一致する名前を使用してください。

21
Bruno

これは古い質問ですが、JDK 1.8.0_144からjdk 1.8.0_191に移行したときにも同じ問題がありました。

変更履歴にヒントが見つかりました。

変更履歴

次のシステムプロパティを追加しました。これは、この問題を解決するのに役立ちました。

-Dcom.Sun.jndi.ldap.object.disableEndpointIdentification=true
15
Markus

証明書をインポートするには

  1. サーバーから証明書を抽出します。 openssl s_client -showcerts -connect AAA.BBB.CCC.DDD:9443 > certs.txtこれは証明書をPEMフォーマットで抽出します。
  2. Keytoolが期待するものであるため、証明書をDER形式に変換します。 openssl x509 -in certs.txt -out certs.der -outform DER
  3. この証明書をシステムのデフォルトの「cacert」ファイルにインポートします。ご使用のJavaインストール用のシステムデフォルトの 'cacerts'ファイルを見つけます。ご覧ください デフォルトのJavaインストールのcacertsの場所を取得する方法?
  4. そのcacertsファイルに証明書をインポートします。Sudo keytool -importcert -file certs.der -keystore <path-to-cacerts>デフォルトのcacertsパスワードは 'changeit'です。

証明書がFQDN用に発行されていて、Javaコード内のIPアドレスで接続しようとしている場合は、証明書自体を台無しにするのではなく、おそらくコード内で修正する必要があります。 FQDNで接続するようにコードを変更してください。 FQDNがあなたの開発マシンで解決できない場合は、単にそれをあなたのhostsファイルに追加するか、あるいはこのFQDNを解決できるDNSサーバであなたのマシンを設定してください。

12
AndreyT

あなたはすべてのSSL検証を無効にしたくないかもしれませんので、あなたは単にこれを介してhostName検証を無効にすることができます。

HttpsURLConnection.setDefaultHostnameVerifier(
    SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

[編集]

Conapart3で述べられているように、SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIERは現在は非推奨であり、将来のバージョンで削除される可能性がありますので、将来的には自分でロールバックすることを余儀なくされる可能性があります。 。

3
lifesoordinary

私はこの問題を正しい方法で修正しました。他の答えが示すように、コードを変更したりSSLを無効にしたりするのではなく、証明書にサブジェクト代替名を追加します。明らかに例外が「Suject alt名がありません」と表示されている場合、正しい方法はそれらを追加することです。

こちらをご覧ください 段階的に理解するためのリンク

上記のエラーは、あなたがJKSファイルにアプリケーションにアクセスしようとしている必要なドメインがないことを意味します。複数のドメインを追加するにはOpen SSLと主要なツールを使用する必要があります。

  1. Openssl.cnfを現在のディレクトリにコピーします。
  2. echo '[ subject_alt_name ]' >> openssl.cnf
  3. echo 'subjectAltName = DNS:example.mydomain1.com, DNS:example.mydomain2.com, DNS:example.mydomain3.com, DNS: localhost'>> openssl.cnf
  4. openssl req -x509 -nodes -newkey rsa:2048 -config openssl.cnf -extensions subject_alt_name -keyout private.key -out self-signed.pem -subj '/C=gb/ST=edinburgh/L=edinburgh/O=mygroup/OU=servicing/CN=www.example.com/[email protected]' -days 365
  5. 公開鍵(.pem)ファイルをPKS12形式にエクスポートします。これでパスワードの入力を求められます

    openssl pkcs12 -export -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -export -in
    self-signed.pem -inkey private.key -name myalias -out keystore.p12
    
  6. 自己署名PEM(キーストア)からa.JKSを作成します。

    keytool -importkeystore -destkeystore keystore.jks -deststoretype PKCS12 -srcstoretype PKCS12 -srckeystore keystore.p12
    
  7. 上記のキーストアまたはJKSファイルから証明書を生成します。

    keytool -export -keystore keystore.jks -alias myalias -file selfsigned.crt
    
  8. 上記の証明書は自己署名されており、CAによって検証されていないため、Truststoreに追加する必要があります(Windows用のMACの場合はCacertsファイル、JDKがインストールされている場所を確認します)。

    Sudo keytool -importcert -file selfsigned.crt -alias myalias -keystore /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/security/cacerts
    

原文の回答が投稿されました このリンク上に

3
Abhishek Galoda

このエラーを取得することに関する私の問題は、単に "qatest/webService"の代わりに完全なURL "qatest.ourCompany.com/webService"を使用することによって解決されました。これは、セキュリティ証明書にワイルドカード、つまり「* .ourCompany.com」が含まれていたためです。フルアドレスを入力すると、例外は解消されました。お役に立てれば。

3
Shahriar

すでに回答しています https://stackoverflow.com/a/53491151/1909708

証明書の共通名(証明書CN内のSubject)も、代替名(証明書内のSubject Alternative Name)もターゲットのホスト名またはIPアドレスと一致しないため、これは失敗します。

たとえば、JVMから、DNS名ではなくIPアドレス(WW.XX.YY.ZZ)に接続しようとすると( https://stackoverflow.com )、証明書が次の場所に保存されているためHTTPS接続は失敗します。 Javaトラストストアcacertsは、ターゲットアドレスと一致する共通名(またはstackexchange.comや* .stackoverflow.comなどの証明書代替名)を必要とします。

確認してください。 https://docs.Oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#HostnameVerifier

    HttpsURLConnection urlConnection = (HttpsURLConnection) new URL("https://WW.XX.YY.ZZ/api/verify").openConnection();
    urlConnection.setSSLSocketFactory(socketFactory());
    urlConnection.setDoOutput(true);
    urlConnection.setRequestMethod("GET");
    urlConnection.setUseCaches(false);
    urlConnection.setHostnameVerifier(new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession sslSession) {
            return true;
        }
    });
    urlConnection.getOutputStream();

上記では、常にHostnameVerifierを返す実装済みのtrueオブジェクトを渡しました。

new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession sslSession) {
            return true;
        }
    }
1
Himadri Pant

この同じエラーメッセージが表示されたら、私は後にこの質問にたどり着きました。ただし、私の場合は、サブドメインが異なる2つのURLがありました( http://example1.xxx.com/someservicehttp://example2.yyy.com/someservice )。これは同じサーバーに送信されました。このサーバーは、*。xxxx.comドメインに対してワイルドカード証明書を1つだけ持っていました。 2番目のドメイン経由でサービスを使用しているときに、見つかった証明書(* .xxx.com)が要求されたドメイン(* .yyy.com)と一致せず、エラーが発生します。

この場合、SSLセキュリティを低下させることでこのようなエラーメッセージを修正するのではなく、サーバーとその証明書を確認する必要があります。

0
Gert

私は、スプリングブートで2方向SSLを実行していました。すべての正しい構成サービスTomcatサーバーとサービス呼び出し元RestTemplateを作成しました。しかし、「Java.security.cert.CertificateException:サブジェクトの別名がありません」としてエラーが発生していました

ソリューションを通過した後、JVMはこの証明書を必要とします。そうしないと、ハンドシェイクエラーが発生します。

次に、これをJVMに追加する方法。

jre/lib/security/cacertsファイルに移動します。 jvmのこのcacertsファイルにサーバー証明書ファイルを追加する必要があります。

Windowsのコマンドラインを使用してcacertsファイルにサーバー証明書を追加するコマンド。

C:\ Program Files\Java\jdk1.8.0_191\jre\lib\security>keytool -import -noprompt -trustcacerts -alias sslserver -file E:\ spring_cloud_sachin\ssl_keys\sslserver.cer -keystore cacerts -storepass changeit

サーバー証明書がインストールされているかどうかを確認します。

C:\ Program Files\Java\jdk1.8.0_191\jre\lib\security>keytool -list -keystore cacerts

インストールされている証明書のリストを見ることができます:

詳細: https://sachin4Java.blogspot.com/2019/08/javasecuritycertcertificateexception-no.html

0
Sachin Rane
public class RESTfulClientSSL {

    static TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            // TODO Auto-generated method stub
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            // TODO Auto-generated method stub
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            // TODO Auto-generated method stub
            return null;
        }
    }};

    public class NullHostNameVerifier implements HostnameVerifier {
        /*
         * (non-Javadoc)
         *
         * @see javax.net.ssl.HostnameVerifier#verify(Java.lang.String,
         * javax.net.ssl.SSLSession)
         */
        @Override
        public boolean verify(String arg0, SSLSession arg1) {
            // TODO Auto-generated method stub
            return true;
        }
    }

    public static void main(String[] args) {

        HttpURLConnection connection = null;
        try {

            HttpsURLConnection.setDefaultHostnameVerifier(new RESTfulwalkthroughCer().new NullHostNameVerifier());
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());


            String uriString = "https://172.20.20.12:9443/rest/hr/exposed/service";
            URL url = new URL(uriString);
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            //connection.setRequestMethod("POST");

            BASE64Encoder encoder = new BASE64Encoder();
            String username = "admin";
            String password = "admin";
            String encodedCredential = encoder.encode((username + ":" + password).getBytes());
            connection.setRequestProperty("Authorization", "Basic " + encodedCredential);

            connection.connect();
            BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            int responseCode = connection.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                StringBuffer stringBuffer = new StringBuffer();
                String line = "";
                while ((line = reader.readLine()) != null) {
                    stringBuffer.append(line);
                }
                String content = stringBuffer.toString();
                System.out.println(content);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }
}
0
user11888077

証明書のCNに対応するIPを持つホストエントリを追加します

CN = someSubdomain.someorganisation.com

uRLにアクセスしようとしているCN名でIPを更新します。

それは私のために働いた。

0
manoj