2012-01-20 12 views
6

Pracuję nad projektem, do którego chcę dodać protokół SSL, dlatego utworzyłem prostą implementację testu klient/serwer, aby sprawdzić, czy zadziałało i otrzymałem wyjątek NoSuchAlgorithmException. Poniżej zamieszczona jest mój kod serwera, który rzuca wyjątek:Wyrzucanie kodu SSL Java NoSuchAlgorithException

import java.io.*; 
import java.net.*; 
import java.security.KeyManagementException; 
import java.security.KeyStore; 
import java.security.KeyStoreException; 
import java.security.NoSuchAlgorithmException; 
import java.security.SecureRandom; 
import java.security.UnrecoverableKeyException; 
import java.security.cert.CertificateException; 

import javax.net.ssl.*; 

public class SslServer 
{ 
    private static final int PORT = 5555; 

    public static void main(String[] args) 
    { 
     SecureRandom sr = new SecureRandom(); 
     sr.nextInt(); 

     try { 
      //client.public is the keystore file that holds the client's public key (created with keytool) 
      KeyStore clientKeyStore = KeyStore.getInstance("JKS"); 
      clientKeyStore.load(new FileInputStream("client.public"), "clientpublicpw".toCharArray()); 

      //server.private is the key pair for the server (created with keytool) 
      KeyStore serverKeyStore = KeyStore.getInstance("JKS"); 
      clientKeyStore.load(new FileInputStream("server.private"), "serverprivatepw".toCharArray()); 

      TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); 
      tmf.init(clientKeyStore); 

      //This next line is where the exception occurs 
      KeyManagerFactory kmf = KeyManagerFactory.getInstance("TLS"); 
      kmf.init(serverKeyStore, "serverprivatepw".toCharArray()); 

      SSLContext sslContext = SSLContext.getInstance("TLS"); 
      sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), sr); 

      SSLServerSocketFactory sf = sslContext.getServerSocketFactory(); 
      SSLServerSocket ss = (SSLServerSocket)sf.createServerSocket(SslServer.PORT); 
      ss.setNeedClientAuth(true); 

      BufferedReader in = new BufferedReader(new InputStreamReader(ss.accept().getInputStream())); 

      String line = null; 
      while((line = in.readLine()) != null) 
      { 
       System.out.println(line); 
      } 
      in.close(); 
      ss.close(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } catch (NoSuchAlgorithmException e) { 
      e.printStackTrace(); 
     } catch (CertificateException e) { 
      e.printStackTrace(); 
     } catch (KeyStoreException e) { 
      e.printStackTrace(); 
     } catch (UnrecoverableKeyException e) { 
      e.printStackTrace(); 
     } catch (KeyManagementException e) { 
      e.printStackTrace(); 
     } 
    } 

} 

StackTrace pojawia się:

java.security.NoSuchAlgorithmException: TLS KeyManagerFactory not available 
    at sun.security.jca.GetInstance.getInstance(Unknown Source) 
    at javax.net.ssl.KeyManagerFactory.getInstance(Unknown Source) 
    at SslServer.main(SslServer.java:32) 

Próbowałem zastępując „TLS” z „SSL” i nadal mam ten sam wyjątek. To nie miało dla mnie sensu. W jaki sposób protokół TLS i SSL mogą nie być obsługiwane? To jest mój pierwszy raz, kiedy próbuję implementować SSL i wydaje się, że trudno jest znaleźć dobre zasoby na ten temat za pomocą przykładów kodu, które są dobrze wyjaśnione. Czy ktoś może mi powiedzieć, dlaczego otrzymuję ten wyjątek lub wskazuje coś nie tak z moim kodem?

Odpowiedz

15

Istnieje szereg problemów:

  • Nazywa TLS (Transport Layer Security), nie TSL (dla SSLContext).
  • Sugeruję użyciu domyślnego tutaj: TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) (Domyślna będzie PKIX na Oracle JRE`)
  • (EDIT:) Domyślna KeyManagerFactory jest SunX509 (TLS nie istnieje tutaj). Ponownie użyj getDefaultAlgorithm().
  • Po przeczytaniu należy zamknąć swoje FileInputStream.
  • Nie jest jasne, dlaczego w tym samym miejscu znajduje się magazyn kluczy klienta i serwera. Powinny to być dwa programy: jeden dla klienta i serwera (i setNeedClientAuth(true) jest przydatny tylko po stronie serwera). Byłoby wyraźniej nazywać to czymś innym niż "sklep klienta", jeśli jest to faktycznie twój plik kluczy. (Dodatkowo, ponieważ wydaje się, że uczysz się, jak to zrobić, sugerowałbym najpierw próbę bez uwierzytelniania certyfikatu klienta, w takim przypadku serwer nie będzie potrzebował magazynu zaufanych certyfikatów: użyj null jako drugiego parametru od SSLContext.init(...) do użyj wartości domyślnej.)
  • NIE DAJ pliku kluczy serwera do klienta. Wyeksportuj jego certyfikat tylko do nowego magazynu kluczy, który będzie używany jako magazyn zaufanych certyfikatów. Każda jednostka (klient i serwer) powinna zachować prywatne klucze prywatne.
  • To nie jest tak ważny klucz publiczny (tylko) zdalnej strony, którą chcesz mieć w swoim sklepie zaufania: będzie to jej certyfikat . Upewnij się, że nie tylko zaimportowałeś swój klucz publiczny, ale cały certyfikat.
  • Aby doprecyzować, zachowaj odpowiednie rozszerzenia plików: użyj pliku .jks dla pliku kluczy , co pozwoli Ci zaoszczędzić bóle głowy później.
  • Możesz użyć null dla SecureRandom w SSLContext.init(...): użyje to wartości domyślnej zgodnie z dostawcą zabezpieczeń.

Coś jak to powinno działać lepiej:

KeyStore trustStore = KeyStore.getInstance("JKS"); 
InputStream tsis = new FileInputStream("trustedcerts.jks"); 
trustStore.load(tsis, "clientpublicpw".toCharArray()); 
tsis.close(); 

KeyStore serverKeyStore = KeyStore.getInstance("JKS"); 
InputStream ksis = new FileInputStream("server.jks"); 
clientKeyStore.load(ksis.close(), "serverprivatepw".toCharArray()); 
ksis.close(); 

TrustManagerFactory tmf = 
    TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 
tmf.init(trustStore); 

KeyManagerFactory kmf = 
    KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 
kmf.init(serverKeyStore, "serverprivatepw".toCharArray()); 

SSLContext sslContext = SSLContext.getInstance("TLS"); 
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); 

SSLServerSocketFactory sf = sslContext.getServerSocketFactory(); 
SSLServerSocket ss = (SSLServerSocket)sf.createServerSocket(SslServer.PORT); 
ss.setNeedClientAuth(true); 
+0

k Edytowałem mój wpis, aby dodać tę zmianę. Nadal mam ten sam wyjątek, nawet jeśli używam "TLS" – MattL922

+0

Jeśli naprawdę martwisz się hasłami, powinieneś użyć 'char []' bezpośrednio lub jako hasło zwrotne i nigdy nie zakodowuj swojego hasła w prawdziwa aplikacja (patrz http://stackoverflow.com/a/8889285/372643). – Bruno

+0

Na marginesie czekanie, aż 'readLine' zwróci' null', nie jest dobrym sposobem, aby wiedzieć, kiedy przerwać czytanie (z dowolnego rodzaju gniazda), zobacz dyskusję tutaj: http://stackoverflow.com/a/6425509/372643 – Bruno

1

Prawidłowa nazwa SSLContext jest "TLS".Lista standardowych nazw algorytmów znajduje się pod następującym numerem: here.

+0

To był błąd i został naprawiony. Nadal mam wyjątek z "TLS" – MattL922

+0

Zobacz edycję Bruno do jego odpowiedzi dotyczącej KeyManagerFactory, który byłby twoim innym problemem. –

2

Zobacz przykłady dla przykładów i nazwy obsługiwanego algorytmu. Wygląda na to, że "SunX509" i "NewSunX509" są algorytmami obsługiwanymi przez KeyManagerFactory. Protokół nazywa się TLS, a nie TSL.

+0

Ponownie, był to błąd i został naprawiony. Nadal mam wyjątek z "TLS" – MattL922

+0

Czy przeczytałeś moją odpowiedź? * Wygląda na to, że "SunX509" i "NewSunX509" są algorytmami obsługiwanymi przez KeyManagerFactory *. –

+0

+ 1'ed (tak jak zrobiłem literówkę w mojej początkowej odpowiedzi 'X509' ->' SunX509'). @ MattL922: 'getDefaultAlgorithm()' jest często najbezpieczniejszym zakładem. – Bruno