Question

Un module J'ajoute à notre grande application Java doit entretenir avec le site Web sécurisé SSL d'une autre société. Le problème est que le site utilise un certificat auto-signé. J'ai une copie du certificat pour vérifier que je ne suis pas rencontrer un homme-in-the-middle, et je dois intégrer ce certificat dans notre code de telle sorte que la connexion au serveur sera couronnée de succès.

Voici le code de base:

void sendRequest(String dataPacket) {
  String urlStr = "https://host.example.com/";
  URL url = new URL(urlStr);
  HttpURLConnection conn = (HttpURLConnection)url.openConnection();
  conn.setMethod("POST");
  conn.setRequestProperty("Content-Length", data.length());
  conn.setDoOutput(true);
  OutputStreamWriter o = new OutputStreamWriter(conn.getOutputStream());
  o.write(data);
  o.flush();
}

Sans aucune manipulation supplémentaire en place pour le certificat auto-signé, ce meurt à conn.getOutputStream () à l'exception suivante:

Exception in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
....
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
....
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

Idéalement, mon code doit enseigner Java pour accepter un certificat auto-signé, pour cette première place dans l'application, et nulle part ailleurs.

Je sais que je peux importer le certificat dans le magasin d'autorité de certification de JRE, et qui permettra à Java de l'accepter. Ce n'est pas une approche que je veux prendre si je peux aider; il semble très invasive à faire sur toutes les machines de nos clients pour un module, ils ne peuvent pas utiliser; il aurait une incidence sur toutes les autres applications Java utilisant le même JRE, et je n'aime pas que même si les chances de toute autre application Java accédant à jamais ce site sont nuls. Il est pas non plus une opération triviale. Sous UNIX-je obtenir des droits d'accès pour modifier le JRE de cette façon

Je l'ai aussi vu que je peux créer une instance TrustManager qui fait des vérifications sur mesure. On dirait que je pourrais même être en mesure de créer un TrustManager que les délégués à la vraie TrustManager dans tous les cas, sauf celui-ci certificat. Mais il semble que ce TrustManager est installé dans le monde, et je suppose que affecterait toutes les autres connexions de notre application, et cela ne me sent pas tout à fait droit, non plus.

Quel est le préféré, standard, ou la meilleure façon de mettre en place une application Java d'accepter un certificat auto-signé? Puis-je accomplir tous les objectifs que j'ai à l'esprit ci-dessus, ou je vais devoir faire des compromis? Y at-il une option impliquant des fichiers et des répertoires et paramètres de configuration, et peu ou pas de code?

Était-ce utile?

La solution

Créer une usine de vous SSLSocket, et le mettre sur la connexion avant HttpsURLConnection.

...
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslFactory);
conn.setMethod("POST");
...

Vous voulez créer un et le garder SSLSocketFactory autour. Voici un croquis de la façon de l'initialiser:

/* Load the keyStore that includes self-signed cert as a "trusted" entry. */
KeyStore keyStore = ... 
TrustManagerFactory tmf = 
  TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, tmf.getTrustManagers(), null);
sslFactory = ctx.getSocketFactory();

Si vous avez besoin d'aide pour créer la clé de stockage, s'il vous plaît commentaire.


Voici un exemple de chargement de la clé de stockage:

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(trustStore, trustStorePassword);
trustStore.close();

Pour créer la clé de stockage avec un certificat de format PEM, vous pouvez écrire votre propre code à l'aide CertificateFactory, ou tout simplement l'importer avec de la keytool JDK (keytool pas pour le travail une « entrée de clé », mais il est très bien pour une « entrée de confiance »).

keytool -import -file selfsigned.pem -alias server -keystore server.jks

Autres conseils

J'ai lu beaucoup d'endroits en ligne pour résoudre cette chose. Voici le code que j'ai écrit pour le faire fonctionner:

                ByteArrayInputStream derInputStream = new ByteArrayInputStream(app.certificateString.getBytes());
                CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
                X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(derInputStream);
                String alias = "alias";//cert.getSubjectX500Principal().getName();

                KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
                trustStore.load(null);
                trustStore.setCertificateEntry(alias, cert);
                KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
                kmf.init(trustStore, null);
                KeyManager[] keyManagers = kmf.getKeyManagers();

                TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
                tmf.init(trustStore);
                TrustManager[] trustManagers = tmf.getTrustManagers();

                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(keyManagers, trustManagers, null);
                URL url = new URL(someURL);
                conn = (HttpsURLConnection) url.openConnection();
                conn.setSSLSocketFactory(sslContext.getSocketFactory());

app.certificateString est une chaîne qui contient le certificat, par exemple:

            static public String certificateString=
            "-----BEGIN CERTIFICATE-----\n" +
            "MIIGQTCCBSmgAwIBAgIHBcg1dAivUzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE" +
            "BhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBE" +
            ... a bunch of characters...
            "5126sfeEJMRV4Fl2E5W1gDHoOd6V==\n" +
            "-----END CERTIFICATE-----";

Je l'ai testé que vous pouvez mettre tous les caractères dans la chaîne de certificat, si elle est auto-signé, aussi longtemps que vous gardez au-dessus de la structure exacte. J'ai obtenu la chaîne de certificat avec la ligne de commande Terminal de mon ordinateur portable.

Si vous créez un n'est pas une SSLSocketFactory option il suffit d'importer la clé dans la machine virtuelle Java

  1. Récupérer la clé publique: $openssl s_client -connect dev-server:443, puis créer un fichier dev-server.pem qui ressemble à

    -----BEGIN CERTIFICATE----- 
    lklkkkllklklklklllkllklkl
    lklkkkllklklklklllkllklkl
    lklkkkllklk....
    -----END CERTIFICATE-----
    
  2. Importer la clé: #keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem. Mot de passe: changeit

  3. Redémarrer JVM

Source: Comment résoudre javax.net.ssl. SSLHandshakeException

Nous copions truststore JRE et joignons nos certificats personnalisés à ce truststore, puis dire l'application à utiliser truststore personnalisé avec une propriété du système. De cette façon, nous quittons le JRE par défaut truststore seul.

L'inconvénient est que lorsque vous mettez à jour le JRE vous ne recevez pas automatiquement son nouveau truststore fusionné avec votre commande un.

Vous pourriez peut-être gérer ce scénario en ayant une routine d'installation ou de démarrage qui vérifie le truststore / jdk et vérifie un décalage ou met à jour automatiquement le truststore. Je ne sais pas ce qui se passe si vous mettez à jour le truststore alors que l'application est en cours d'exécution.

Cette solution est 100% élégant ou à toute épreuve, mais il est simple, fonctionne, et ne nécessite pas de code.

J'ai dû faire quelque chose comme ça lors de l'utilisation commons-httpclient pour accéder à un serveur https interne avec un certificat auto-signé. Oui, notre solution était de créer un TrustManager personnalisé tout simplement passé (journalisation un message de débogage).

Cela revient à avoir notre propre SSLSocketFactory qui crée des sockets SSL de notre SSLContext locale, qui est mis en place pour avoir seulement notre TrustManager locale associée. Vous n'avez pas besoin d'aller près d'un keystore / certstore du tout.

C'est donc dans notre LocalSSLSocketFactory:

static {
    try {
        SSL_CONTEXT = SSLContext.getInstance("SSL");
        SSL_CONTEXT.init(null, new TrustManager[] { new LocalSSLTrustManager() }, null);
    } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("Unable to initialise SSL context", e);
    } catch (KeyManagementException e) {
        throw new RuntimeException("Unable to initialise SSL context", e);
    }
}

public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
    LOG.trace("createSocket(host => {}, port => {})", new Object[] { host, new Integer(port) });

    return SSL_CONTEXT.getSocketFactory().createSocket(host, port);
}

Avec d'autres méthodes de mise en œuvre SecureProtocolSocketFactory. LocalSSLTrustManager est la mise en œuvre du gestionnaire de confiance factice mentionné ci-dessus.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top