web-dev-qa-db-fra.com

Comment réparer l'erreur "Java.security.cert.CertificateException: No subject alternative names present"?

J'ai un client de service Web Java, qui consomme un service Web via HTTPS.

import javax.xml.ws.Service;

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

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

Lorsque je me connecte à l'URL du service (https://AAA.BBB.CCC.DDD:9443/ISomeService), j'obtiens l'exception Java.security.cert.CertificateException: No subject alternative names present.

Pour résoudre ce problème, j'ai d'abord exécuté openssl s_client -showcerts -connect AAA.BBB.CCC.DDD:9443 > certs.txt et le contenu suivant a été inséré dans le fichier 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)
---

Autant que je sache, maintenant je dois

  1. extraire la partie de certs.txt entre -----BEGIN CERTIFICATE----- et -----END CERTIFICATE-----,
  2. modifiez-le pour que le nom du certificat soit égal à AAA.BBB.CCC.DDD et
  3. puis importez le résultat en utilisant keytool -importcert -file fileWithModifiedCertificate (où fileWithModifiedCertificate est le résultat des opérations 1 et 2).

Est-ce correct?

Si oui, comment puis-je faire en sorte que le certificat de l'étape 1 fonctionne avec une adresse IP (AAA.BBB.CCC.DDD)?

Update 1 (23.10.2013 15:37 MSK): En réponse à une question similaire , j'ai lu le texte suivant:

Si vous ne maîtrisez pas ce serveur, utilisez son nom d'hôte (à condition que Il existe au moins un CN correspondant à ce nom d'hôte dans le certificat Existant).

Que signifie exactement "utiliser"?

77
DP_

J'ai résolu le problème en désactivant les contrôles HTTPS en utilisant l'approche présentée ici :

J'ai mis le code suivant dans la classe 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();
    }
}

Puisque j'utilise le https://AAA.BBB.CCC.DDD:9443/ISomeService uniquement à des fins de test, c'est une bonne solution.

126
DP_

J'ai le même problème et résolu avec ce code… .. J'ai mis ce code avant le premier appel à mes services 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");
    }
});

C'est simple et fonctionne bien.

Ici est la source originale.

26
juanmhidalgo

La vérification de l'identité du certificat est effectuée par rapport aux demandes du client.

Lorsque votre client utilise https://xxx.xxx.xxx.xxx/something (où xxx.xxx.xxx.xxx est une adresse IP), l'identité du certificat est vérifiée par rapport à cette adresse IP (en théorie, uniquement à l'aide d'une extension IP SAN).

Si votre certificat ne possède pas de réseau IP IP, mais DNS SAN (ou si aucun réseau SAN DNS, un nom commun dans le nom distinctif du sujet), vous pouvez le faire en demandant à votre client d'utiliser une URL avec ce nom d'hôte pour lequel le certificat serait valide, s’il existe plusieurs valeurs possibles). Par exemple, si votre certificat porte un nom pour www.example.com, utilisez https://www.example.com/something.

Bien sûr, vous aurez besoin de ce nom d'hôte pour le résoudre en cette adresse IP.

De plus, s'il existe des SAN DNS, le nom de domaine dans le nom distinctif du sujet sera ignoré. Par conséquent, utilisez un nom qui correspond à l'un des SAN DNS dans ce cas.

19
Bruno

Pour importer le certificat:

  1. Extrayez le certificat du serveur, par exemple openssl s_client -showcerts -connect AAA.BBB.CCC.DDD:9443 > certs.txt Ceci extraira les certs au format PEM.
  2. Convertissez le certificat au format DER car c’est ce qu’attend Keytool, par exemple. openssl x509 -in certs.txt -out certs.der -outform DER
  3. Maintenant, vous voulez importer ce certificat dans le fichier 'cacert' par défaut du système. Localisez le fichier 'cacerts' par défaut du système pour votre installation Java. Jetez un oeil à Comment obtenir l'emplacement de cacerts de l'installation Java par défaut?
  4. Importez les certificats dans ce fichier cacerts: Sudo keytool -importcert -file certs.der -keystore <path-to-cacerts> Le mot de passe par défaut de cacerts est 'changeit'.

Si le certificat est émis pour un nom de domaine complet et que vous essayez de vous connecter par adresse IP dans votre code Java, cela devrait probablement être corrigé dans votre code plutôt que de jouer avec le certificat. Changez votre code pour vous connecter par FQDN. Si le nom de domaine complet ne peut pas être résolu sur votre ordinateur dev, ajoutez-le simplement à votre fichier hosts ou configurez votre ordinateur avec un serveur DNS capable de résoudre ce nom de domaine complet.

10
AndreyT

C'est une vieille question, mais j'avais le même problème lorsque je passais de JDK 1.8.0_144 à jdk 1.8.0_191

Nous avons trouvé un indice dans le changelog:

Changelog

nous avons ajouté la propriété système supplémentaire suivante, ce qui a permis dans notre cas de résoudre ce problème:

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

Vous souhaiterez peut-être ne pas désactiver toutes les vérifications SSL. Vous pouvez donc désactiver la vérification du nom d'hôte via cette méthode, qui est un peu moins effrayante que l'alternative:

HttpsURLConnection.setDefaultHostnameVerifier(
    SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

[MODIFIER]

Comme indiqué par conapart3, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER est maintenant obsolète. Il pourrait donc être supprimé dans une version ultérieure. Vous pourriez donc être obligé de lancer le vôtre à l'avenir, bien que je dirais toujours que je m'éloignerais de toute solution où la vérification est désactivée .

3
lifesoordinary

mon problème avec l'obtention de cette erreur a été résolu en utilisant l'URL complète "qatest.ourCompany.com/webService" au lieu de "qatest/webService". La raison en était que notre certificat de sécurité avait un caractère générique, à savoir "* .ourCompany.com". Une fois que j'ai mis l'adresse complète, l'exception est partie. J'espère que cela t'aides.

3
Shahriar

J'ai corrigé ce problème correctement en ajoutant les noms d'objet alt dans le certificat plutôt que d'apporter des modifications au code ou de désactiver SSL, contrairement à ce que proposent d'autres réponses. Si vous voyez clairement que l'exception indique que "les noms alt Suject sont manquants", il convient donc de les ajouter. 

Veuillez regarder ce lien pour comprendre pas à pas .

L'erreur ci-dessus signifie que votre fichier JKS ne contient pas le domaine requis sur lequel vous essayez d'accéder à l'application. Vous devrez utiliser Open SSL et l'outil de clé pour ajouter plusieurs domaines.

  1. Copiez le fichier openssl.cnf dans un répertoire en cours
  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. Exportez le fichier de clé publique (.pem) au format PKS12. Cela vous demandera un mot de passe

    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. Créer un fichier .JKS à partir d'un fichier PEM auto-signé

    keytool -importkeystore -destkeystore keystore.jks -deststoretype PKCS12 -srcstoretype PKCS12 -srckeystore keystore.p12
    
  7. Générer un certificat à partir du fichier de clés ou du fichier JKS ci-dessus

    keytool -export -keystore keystore.jks -alias myalias -file selfsigned.crt
    
  8. Comme le certificat ci-dessus est auto-signé et n'est pas validé par l'autorité de certification, il doit être ajouté dans le magasin de données de confiance (le fichier Cacerts situé sous l'emplacement pour MAC, sous Windows, recherchez l'emplacement d'installation de votre JDK.)

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

Réponse originale affichée sur ce lien ici

1
Abhishek Galoda

Je suis arrivé à cette question après si obtenu ce même message d'erreur. Cependant, dans mon cas, nous avions deux URL avec différents sous-domaines ( http://example1.xxx.com/someservice et http://example2.yyy.com/someservice ) qui étaient dirigés vers le même serveur. Ce serveur ne possédait qu'un seul certificat générique pour le domaine * .xxx.com. Lors de l'utilisation du service via le second domaine, le certificat trouvé (* .xxx.com) ne correspond pas au domaine demandé (* .yyy.com) et l'erreur se produit.

Dans ce cas, nous ne devrions pas essayer de résoudre un tel message d'erreur en abaissant la sécurité SSL, mais devrions vérifier le serveur et les certificats qui s'y trouvent.

0
Gert

J'y ai déjà répondu dans https://stackoverflow.com/a/53491151/1909708 .

Cela échoue car ni le nom commun du certificat (CN dans la certification Subject) ni aucun des autres noms (Subject Alternative Name dans le certificat) ne correspondent au nom d'hôte ou à l'adresse IP cible. 

Par exemple, depuis une machine virtuelle Java, lorsque vous essayez de vous connecter à une adresse IP (WW.XX.YY.ZZ) et non au nom DNS ( https://stackoverflow.com ), la connexion HTTPS échouera car le certificat stocké dans le magasin de clés de confiance Java cacerts s'attend à ce que le nom commun (ou le nom alternatif du certificat, tel que stackexchange.com ou * .stackoverflow.com, etc.) corresponde à l'adresse cible. 

Veuillez vérifier: 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();

Ci-dessus, passé un objet HostnameVerifier implémenté qui renvoie toujours true:

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

Je passais par SSL à 2 voies dans springboot. J'ai fait tout le service de configuration correct serveur Tomcat et appelant de service RestTemplate. mais je recevais une erreur en tant que "Java.security.cert.CertificateException: aucun autre nom de sujet n'est présent"

Après avoir examiné des solutions, j’ai constaté que JVM avait besoin de ce certificat, sinon il donnerait une erreur de prise de contact.

Maintenant, comment ajouter ceci à la JVM.

accédez au fichier jre/lib/security/cacerts. nous devons ajouter notre fichier de certificat de serveur à ce fichier cacerts de jvm.

Commande pour ajouter un certificat de serveur au fichier cacerts via la ligne de commande dans 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

Vérifiez que le cert du serveur est installé ou non:

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

vous pouvez voir la liste des certificats installés:

pour plus de détails: 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

ajouter l'entrée Host avec l'adresse IP correspondant au CN dans le certificat

CN = someSubdomain.someorganisation.com

maintenant, mettez à jour l'adresse IP avec le nom CN où vous essayez d'accéder à l'URL.

Cela a fonctionné pour moi.

0
manoj