developer tip

"java.security.cert.CertificateException : 제목 대체 이름 없음"오류를 수정하는 방법은 무엇입니까?

copycodes 2020. 8. 26. 08:01
반응형

"java.security.cert.CertificateException : 제목 대체 이름 없음"오류를 수정하는 방법은 무엇입니까?


HTTPS를 통해 웹 서비스를 사용하는 Java 웹 서비스 클라이언트가 있습니다.

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)
---

AFAIK, 이제

  1. certs.txt사이 의 일부를 추출 -----BEGIN CERTIFICATE-----하고 -----END CERTIFICATE-----,
  2. 인증서 이름이 같도록 수정 AAA.BBB.CCC.DDD하고
  3. 그런 다음 keytool -importcert -file fileWithModifiedCertificate(여기서는 fileWithModifiedCertificate작업 1과 2 의 결과)를 사용하여 결과를 가져옵니다 .

이 올바른지?

그렇다면 IP 기반 주소 ( AAA.BBB.CCC.DDD) 에서 1 단계의 인증서를 정확히 어떻게 작동시킬 수 있습니까?

업데이트 1 (2013년 10월 23일 15시 37분 MSK는) : A와 답변에 비슷한 질문 , 나는 다음을 읽어

해당 서버를 제어하지 않는 경우 해당 호스트 이름을 사용합니다 (기존 인증서에 해당 호스트 이름과 일치하는 CN이 적어도있는 경우).

"사용"이란 정확히 무엇을 의미합니까?


여기에 제시된 접근 방식을 사용하여 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테스트 목적으로 만 사용하고 있기 때문에 충분한 솔루션입니다.


나는 같은 문제를 가지고 있으며이 코드로 해결되었습니다. 웹 서비스를 처음 호출하기 전에이 코드를 넣었습니다.

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

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

간단하고 잘 작동합니다.

다음 은 원본입니다.


클라이언트가 요청하는 내용에 대해 인증서 ID 확인이 수행됩니다.

클라이언트가 https://xxx.xxx.xxx.xxx/something(여기서는 xxx.xxx.xxx.xxxIP 주소)를 사용할 때이 IP 주소에 대해 인증서 ID를 확인합니다 (이론상 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 중 하나와 일치하는 이름을 사용하십시오.


이것은 오래된 질문이지만 JDK 1.8.0_144에서 jdk 1.8.0_191로 이동할 때 동일한 문제가 발생했습니다.

변경 로그에서 힌트를 찾았습니다.

변경 로그

다음과 같은 추가 시스템 속성을 추가하여이 문제를 해결하는 데 도움이되었습니다.

-Dcom.sun.jndi.ldap.object.disableEndpointIdentification=true

인증서를 가져 오려면 :

  1. 서버에서 인증서를 추출합니다. 예를 들어 openssl s_client -showcerts -connect AAA.BBB.CCC.DDD:9443 > certs.txtPEM 형식으로 인증서를 추출합니다.
  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을 확인할 수없는 경우 호스트 파일에 추가하거나이 FQDN을 확인할 수있는 DNS 서버로 컴퓨터를 구성합니다.


다른 답변이 여기에서 제안하는 것과 달리 코드를 변경하거나 SSL을 비활성화하는 대신 인증서에 제목 대체 이름을 추가하여이 문제를 올바른 방법으로 해결했습니다. 명확하게 표시되는 경우 예외에 "Suject alt names are missing"이라는 메시지가 표시되므로 올바른 방법으로 추가해야합니다.

단계별로 이해하려면링크를 참조하십시오 .

위의 오류는 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/emailAddress=postmaster@example.com' -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 (Keystore)에서 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 (MAC의 경우 아래 위치에있는 Cacerts 파일, Windows의 경우 JDK가 설치된 위치를 확인하십시오.)에 추가해야합니다.

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

여기에이 링크에 게시 원래 답변 .


이 오류가 발생하는 문제는 "qatest / webService"대신 전체 URL "qatest.ourCompany.com/webService"를 사용하여 해결되었습니다. 그 이유는 보안 인증서에 "* .ourCompany.com"과 같은 와일드 카드가 있기 때문입니다. 전체 주소를 입력하면 예외가 사라졌습니다. 도움이 되었기를 바랍니다.


모든 SSL 검증을 비활성화하고 싶지 않을 수 있으므로 대안보다 약간 덜 무서운 이것을 통해 hostName 검증을 비활성화 할 수 있습니다.

HttpsURLConnection.setDefaultHostnameVerifier(
    SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

[편집하다]

conapart3에서 언급했듯이 SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER이제는 더 이상 사용되지 않으므로 이후 버전에서 제거 될 수 있으므로 모든 확인이 꺼져있는 모든 솔루션에서 벗어나겠다고 말할 수 있지만 나중에 직접 롤링해야 할 수도 있습니다.


https://stackoverflow.com/a/53491151/1909708 에서 이미 답변했습니다 .

인증서 일반 이름 ( CN인증서 Subject)이나 대체 이름 ( Subject Alternative Name인증서)이 대상 호스트 이름 또는 IP 주소와 일치 하지 않기 때문에 실패합니다 .

예를 들어, JVM WW.XX.YY.ZZ에서 DNS 이름 ( https://stackoverflow.com )이 아닌 IP 주소 ( ) 에 연결하려고 하면 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;
        }
    }

이 같은 오류 메시지가 나타나면이 질문에 답했습니다. 그러나 제 경우 에는 동일한 서버로 향하는 서로 다른 하위 도메인 ( http://example1.xxx.com/someservicehttp://example2.yyy.com/someservice )을 가진 두 개의 URL 이있었습니다. 이 서버에는 * .xxx.com 도메인에 대해 하나의 와일드 카드 인증서 만 있습니다. 두 번째 도메인을 통해 서비스를 사용할 때 발견 된 인증서 (* .xxx.com)가 요청 된 도메인 (* .yyy.com)과 일치하지 않고 오류가 발생합니다.

이 경우 SSL 보안을 낮춰 이러한 오류 메시지를 수정하지 말고 서버와 인증서를 확인해야합니다.


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

I was going through 2 way SSL in springboot. I have made all correct configuration service tomcat server and service caller RestTemplate. but I was getting error as "java.security.cert.CertificateException: No subject alternative names present"

After going through solutions, I found, JVM needs this certificate otherwise it gives handshaking error.

Now, how to add this to JVM.

go to jre/lib/security/cacerts file. we need to add our server certificate file to this cacerts file of jvm.

Command to add server cert to cacerts file via command line in windows.

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

Check server cert is installed or not:

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

you can see list of certificates installed:

for more details: https://sachin4java.blogspot.com/2019/08/javasecuritycertcertificateexception-no.html


add the host entry with the ip corresponding to the CN in the certificate

CN=someSubdomain.someorganisation.com

now update the ip with the CN name where you are trying to access the url.

It worked for me.


Add your IP address in the hosts file.which is in the folder of C:\Windows\System32\drivers\etc. Also add IP and Domain Name of the IP address. example: aaa.bbb.ccc.ddd abc@def.com


I have solved the issue by the following way.

1. Creating a class . The class has some empty implementations

class MyTrustManager implements X509TrustManager {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
    return null;
}

public void checkClientTrusted(X509Certificate[] certs, String authType) {
}

public void checkServerTrusted(X509Certificate[] certs, String authType) {
}

@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate, String paramString)
        throws CertificateException {
    // TODO Auto-generated method stub

}

@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate, String paramString)
        throws CertificateException {
    // TODO Auto-generated method stub

}

2. Creating a method

private static void disableSSL() {
    try {
        TrustManager[] trustAllCerts = new TrustManager[] { new MyTrustManager() };

        // Install the all-trusting trust manager
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new java.security.SecureRandom());
        HostnameVerifier allHostsValid = new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };
        HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

  1. Call the disableSSL() method where the exception is thrown. It worked fine.

참고URL : https://stackoverflow.com/questions/19540289/how-to-fix-the-java-security-cert-certificateexception-no-subject-alternative

반응형