2011-07-19 26 views
23

Łączę klienta SSL z moim serwerem SSL. Gdy klient nie może zweryfikować certyfikatu z powodu katalogu głównego nieistniejącego w magazynie kluczy klienta, potrzebuję opcji, aby dodać ten certyfikat do lokalnego magazynu kluczy w kodzie i kontynuować. Istnieją przykłady, które zawsze akceptują wszystkie certyfikaty, ale chcę, aby użytkownik zweryfikował certyfikat i dodał go do lokalnego magazynu kluczy bez opuszczania aplikacji.Połączenie Java SSL, programowe dodawanie certyfikatu serwera do pliku kluczy

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); 
SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket("localhost", 23467); 
try{ 
    sslsocket.startHandshake(); 
} catch (IOException e) { 
    //here I want to get the peer's certificate, conditionally add to local key store, then reauthenticate successfully 
} 

Jest dużo rzeczy o niestandardowych SocketFactory, TrustManager, SSLContext, etc, a ja naprawdę nie rozumiem jak oni wszyscy pasują do siebie lub która byłaby najkrótsza droga do mojego celu.

+0

To może być trochę za późno, ale http://jcalcote.wordpress.com/2010/06/22/managing-a-dynamic-java-trust-store/ ten link pomaga. – goh

Odpowiedz

19

Można to zaimplementować za pomocą X509TrustManager.

Uzyskaj SSLContext z

SSLContext ctx = SSLContext.getInstance("TLS"); 

Następnie zainicjować ją ze swoim zwyczajem X509TrustManager za pomocą SSLContext#init. SecureRandom i KeyManager [] mogą mieć wartość zerową. Ten ostatni jest przydatny tylko w przypadku uwierzytelniania klienta, jeśli w scenariuszu tylko serwer musi uwierzytelnić, nie musisz go ustawiać.

Z tego SSLContekstu, uzyskaj certyfikat SSLSocketFactory za pomocą SSLContext#getSocketFactory i postępuj zgodnie z planem.

Jako dotyczy implementacji X509TrustManager, może to wyglądać tak:

TrustManager tm = new X509TrustManager() { 
    public void checkClientTrusted(X509Certificate[] chain, 
        String authType) 
        throws CertificateException { 
     //do nothing, you're the client 
    } 

    public X509Certificate[] getAcceptedIssuers() { 
     //also only relevant for servers 
    } 

    public void checkServerTrusted(X509Certificate[] chain, 
        String authType) 
        throws CertificateException { 
     /* chain[chain.length -1] is the candidate for the 
     * root certificate. 
     * Look it up to see whether it's in your list. 
     * If not, ask the user for permission to add it. 
     * If not granted, reject. 
     * Validate the chain using CertPathValidator and 
     * your list of trusted roots. 
     */ 
    } 
}; 

Edit:

Ryan miał rację, zapomniałem wyjaśnić, jak dodać nowy korzeń do już istniejących. Załóżmy, że Twój aktualny magazyn kluczy zaufanych katalogów został wyprowadzony z cacerts ("domyślny magazyn zaufania Java" dostarczany z pakietem JDK, znajdującym się w katalogu jre/lib/security). Zakładam, że załadowałeś ten magazyn kluczy (w formacie JKS) z ładunkiem KeyStore # (InputStream, char []).

KeyStore ks = KeyStore.getInstance("JKS"); 
FileInputStream in = new FileInputStream("<path to cacerts""); 
ks.load(in, "changeit".toCharArray); 

Domyślne hasło cacerts jest „changeit” jeśli nie, cóż, zmienił go.

Następnie możesz dodać dodatkowe zaufane korzenie przy użyciu KeyStore#setEntry. Można pominąć parametr ProtectionParameter (np. Null), KeyStore.Entry będzie to TrustedCertificateEntry, który pobiera nowy katalog główny jako parametr do swojego konstruktora.

KeyStore.Entry newEntry = new KeyStore.TrustedCertificateEntry(newRoot); 
ks.setEntry("someAlias", newEntry, null); 

Jeśli chcesz utrzymywać zmienioną sklep zaufania w pewnym momencie, można to osiągnąć z KeyStore#store(OutputStream, char[].

+2

Należy pamiętać, że jest to w 100% niepewne. Równie dobrze możesz nie używać SSL. Jest to podatne na ataki man-in-the-middle. – EJP

+1

@EJP: Proszę wyjaśnić. MITM z SSL jest normalnie możliwe tylko w przypadku zaniedbania weryfikacji nazwy hosta. To jest nadal wykonywane wewnętrznie przez DefaultHostnameVerifier. – emboss

+0

Bez względu na poprawność zabezpieczenia, nie odpowiada na pytanie. Pytanie brzmi, jak programowo dodać certyfikat do magazynu kluczy. –

5

W interfejsie API JSSE (część zajmująca się protokołem SSL/TLS) sprawdzenie, czy certyfikat jest zaufany, nie musi obejmować wartości KeyStore. Dotyczy to w zdecydowanej większości przypadków, ale zakładając, że zawsze będzie jedna, jest niepoprawna. Odbywa się to poprzez TrustManager. Ten TrustManager prawdopodobnie użyje domyślnego magazynu zaufanych certyfikatów KeyStore lub określonego przez właściwość systemową javax.net.ssl.trustStore, ale niekoniecznie tak jest, a ten plik niekoniecznie jest $JAVA_HOME/jre/lib/security/cacerts (wszystko to zależy od ustawień zabezpieczeń JRE).

W ogólnym przypadku nie można uzyskać wartości KeyStore używanej przez menedżera zaufania używanego przez aplikację, a tym bardziej, jeśli domyślny magazyn zaufanych certyfikatów jest używany bez żadnych ustawień właściwości systemu. Nawet jeśli były w stanie dowiedzieć się, co to KeyStore jest taka, że ​​nadal będzie obliczu dwóch możliwych problemów (co najmniej):

  • może nie być w stanie pisać do tego pliku (który niekoniecznie jest problemem jeśli nie zapisujesz zmian na stałe).
  • Ten KeyStore może nawet nie być oparty na plikach (np. Może to być Keychain na OSX) i może nie mieć dostępu do zapisu również.

Co proponuję to napisać otoki wokół domyślnie TrustManager (dokładniej: X509TrustManager), który przeprowadza kontrole przed domyślnego menedżera zaufania, a jeśli ten początkowy test się nie powiedzie, wykonuje wywołania zwrotnego dla użytkownika interfejs aby sprawdzić, czy dodać go do "lokalnego" magazynu zaufanych certyfikatów.

Można to zrobić, jak pokazano w this example (z brief unit test), jeśli chcesz użyć czegoś takiego jak jSSLutils.

Przygotowanie SSLContext byłoby zrobić tak:

KeyStore keyStore = // ... Create and/or load a keystore from a file 
        // if you want it to persist, null otherwise. 

// In ServerCallbackWrappingTrustManager.CheckServerTrustedCallback 
CheckServerTrustedCallback callback = new CheckServerTrustedCallback { 
    public boolean checkServerTrusted(X509Certificate[] chain, 
      String authType) { 
     return true; // only if the user wants to accept it. 
    } 
} 

// Without arguments, uses the default key managers and trust managers. 
PKIXSSLContextFactory sslContextFactory = new PKIXSSLContextFactory(); 
sslContextFactory 
    .setTrustManagerWrapper(new ServerCallbackWrappingTrustManager.Wrapper(
        callback, keyStore)); 
SSLContext sslContext = sslContextFactory.buildSSLContext(); 
SSLSocketFactory sslSocketFactory = sslContext.getSslSocketFactory(); 
// ... 

// Use keyStore.store(...) if you want to save the resulting keystore for later use. 

(Oczywiście, nie trzeba korzystać z tej biblioteki i jej SSLContextFactory, ale zaimplementować własną X509TrustManager, „opakowanie”, czy nie domyślny jeden, jak wolisz.)

Innym czynnikiem, który musisz wziąć pod uwagę, jest interakcja użytkownika z tym oddzwanianiem. Do czasu, gdy użytkownik podjął decyzję o kliknięciu przycisku akceptuj lub odrzuć (na przykład), może upłynąć limit czasu uzgadniania, więc może być konieczna ponowna próba połączenia, gdy użytkownik zaakceptuje certyfikat.

Inną kwestią, którą należy wziąć pod uwagę przy projektowaniu wywołania zwrotnego, jest to, że menedżer zaufania nie wie, które gniazdo jest używane (w przeciwieństwie do jego odpowiednika X509KeyManager), więc nie powinno być tak mało dwuznaczności co do działania użytkownika to wyskakujące okienko (lub jakkolwiek chcesz zaimplementować wywołanie zwrotne). Jeśli wykonano wiele połączeń, nie chciałbyś potwierdzić niewłaściwego. Wydaje się, że możliwe jest rozwiązanie tego problemu za pomocą odrębnego wywołania zwrotnego, SSLContext i SSLSocketFactory na SSLSocket, który powinien utworzyć nowe połączenie, pewien sposób związania SSLSocket i wywołania zwrotnego z działaniem podjętym przez użytkownika w celu wywołania tej próby połączenia w pierwsze miejsce.

+0

Wszystkie interesujące punkty! Zadziwiające jest to, jak różne języki wdrażają takie rzeczy i jak wiele sposobów podejścia do tego samego problemu. W moim przypadku używam pojedynczego gniazda na klienta i niestandardowego magazynu kluczy określonego przez właściwość systemu. Odpowiedź sugerowana przez emboss wydaje się po prostu spełniać moje wymagania dotyczące tego zadania, ale cieszę się, że znam szerszy obraz i jak może wpłynąć na te i przyszłe projekty. – iratepr

Powiązane problemy