152 votes

Comment corriger l'erreur "java.security.cert.CertificateException: aucun nom alternatif de sujet présent"?

Je dispose d'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), je reçois l'exception java.security.cert.CertificateException: Aucun nom alternatif de sujet présent.

Pour corriger cela, j'ai d'abord exécuté openssl s_client -showcerts -connect AAA.BBB.CCC.DDD:9443 > certs.txt et obtenu le contenu suivant 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)
---

À ma connaissance, je dois maintenant

  1. extraire la partie de certs.txt entre -----BEGIN CERTIFICATE----- et -----END CERTIFICATE-----,
  2. le modifier afin que le nom du certificat soit égal à AAA.BBB.CCC.DDD et
  3. puis importer 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 fonctionner le certificat de l'étape 1 avec une adresse IP (AAA.BBB.CCC.DDD) ?

Mise à jour 1 (23.10.2013 15:37 MSK) : Dans une réponse à une question similaire, j'ai lu ce qui suit :

Si vous n'avez pas le contrôle de ce serveur, utilisez son nom d'hôte (à condition qu'il y ait au moins un CN correspondant à ce nom d'hôte dans le certificat existant).

Que signifie exactement "utiliser" ?

189voto

Dmitri Pisarenko Points 1293

J'ai résolu le problème en désactivant les vérifications 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
    {
        // Créer un gestionnaire de confiance qui ne valide pas les chaînes de certificats
        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) {
            }
        }
        };

        // Installer le gestionnaire de confiance qui accepte tout
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new java.security.SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

        // Créer un vérificateur de noms d'hôte qui accepte tout
        HostnameVerifier allHostsValid = new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };

        // Installer le vérificateur de noms d'hôte qui accepte tout
        HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (KeyManagementException e) {
        e.printStackTrace();
    }
}

Étant donné que j'utilise le https://AAA.BBB.CCC.DDD:9443/ISomeService uniquement à des fins de test, c'est une solution suffisante, mais ne le faites pas en production.

Notez que vous pouvez également désactiver SSL pour "une connexion à la fois" ex:

 // ne pas appeler disableSslVerification mais utiliser son code interne:
 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 if (conn instanceof HttpsURLConnection) {
    HttpsURLConnection httpsConn = (HttpsURLConnection) conn;
    httpsConn.setHostnameVerifier(allHostsValid);
    httpsConn.setSSLSocketFactory(sc.getSocketFactory());
 }

49voto

diswutz Points 521

C'est une vieille question, pourtant j'ai eu le même problème en passant de JDK 1.8.0_144 à jdk 1.8.0_191

Nous avons trouvé un indice dans le journal des modifications :

Journal des modifications

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

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

42voto

juanmhidalgo Points 895

J'ai le même problème et je l'ai 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"); // or return true
      }
  });

C'est simple et ça fonctionne bien.

Ici se trouve la source originale.

32voto

Bruno Points 47560

La vérification de l'identité du certificat est effectuée en fonction de ce que demande le 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, en utilisant uniquement une extension IP SAN).

Si votre certificat n'a pas d'extension IP SAN, mais des extensions DNS SAN (ou s'il n'y a pas d'extension DNS SAN, un nom commun dans le DN du sujet), vous pouvez le faire fonctionner en demandant à votre client d'utiliser une URL avec ce nom d'hôte à la place (ou un nom d'hôte pour lequel le certificat serait valide, s'il y a plusieurs valeurs possibles). Par exemple, si votre certificat a un nom pour www.example.com, utilisez https://www.example.com/something.

Évidemment, vous aurez besoin que ce nom d'hôte corresponde à cette adresse IP.

De plus, s'il y a des extensions DNS SAN, le CN dans le DN du sujet sera ignoré, utilisez donc un nom qui correspond à l'un des DNS SAN dans ce cas.

25voto

andreycpp Points 839

Pour importer le certificat :

  1. Extraire le certificat du serveur, par exemple openssl s_client -showcerts -connect AAA.BBB.CCC.DDD:9443 > certs.txt Cela extraira les certificats au format PEM.
  2. Convertir le certificat au format DER car c'est ce que keytool attend, 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 œil à 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 Le mot de passe par défaut de cacerts est 'changeit'.

Si le certificat est délivré pour un FQDN et que vous essayez de vous connecter avec une adresse IP dans votre code Java, cela devrait probablement être corrigé dans votre code plutôt que de manipuler le certificat lui-même. Modifiez votre code pour vous connecter par FQDN. Si le FQDN n'est pas résolvable sur votre machine de développement, ajoutez-le simplement à votre fichier hosts, ou configurez votre machine avec un serveur DNS capable de résoudre ce FQDN.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X