EDYCJA: Przepraszam, jeśli mój pierwotny wpis był źle sformułowany. Doprowadziło to do pewnego zamieszania, reprezentowanego przez komentarze do oryginalnego postu. Pozwól mi spróbować ponownie:Protokoły i zestawy szyfrów SSL/TLS z AndroidHttpClient
Zacząłem od pytania. Chciałem rozwiązać problem na Androida, ale nie wiedziałem jak. Spędziłem dużo czasu na szukaniu rozwiązań, ale nie znalazłem ani jednej dyskusji na ten temat. Niemniej jednak wiele dyskusji, w tym wątki StackOverflow, doprowadziło mnie do techniki, która wyglądała obiecująco. Rozwiązałem problem. Ale rozwiązanie było trochę zaangażowane. Postanowiłem więc zadać to pytanie, myśląc: a) musi istnieć lepsze rozwiązanie, i mam nadzieję, że ktoś będzie znał i zamieszczał odpowiedź tutaj; lub b) może to dobre rozwiązanie, a ponieważ nie znalazłem nigdzie indziej dyskusji w tej sprawie, być może moje rozwiązanie tego problemu przydałoby się innym próbującym zrobić to samo. Tak czy inaczej, wynik byłby nowym wkładem do StackOverflow: pytanie, na które nie ma odpowiedzi w innym miejscu, z, ewentualnie, poprawną odpowiedzią w ten czy w inny sposób. W istocie StackOverflow zaprosił mnie nawet do odpowiedzi na moje własne pytanie, dzieląc się moją wiedzą, kiedy ją pierwotnie opublikowałem. To w rzeczywistości było częścią mojej motywacji. Nawet fakty w tej sprawie nie są zbierane nigdzie, co znalazłem.
Więc:
Q. Przy użyciu AndroidHttpClient do żądań REST przez HTTPS, w jaki sposób mogę określić, które protokoły SSL i szyfrów w użyciu?
To jest ważne. Chodzi o to, że na serwerze można wiele zrobić, ale są granice. Ten sam serwer musi obsługiwać przeglądarki, w tym stare, a także innych klientów. Oznacza to, że serwer musi obsługiwać szeroki zakres protokołów i szyfrów. Nawet w systemie Android, jeśli musisz obsługiwać wiele różnych wersji, będziesz musiał obsługiwać wiele różnych protokołów i szyfrów.
Co ważniejsze, domyślnie OpenSSL honoruje preferencje szyfru klienta, , a nie na serwerze podczas uzgadniania SSL. Zobacz na przykład: post, która mówi, że można zastąpić to zachowanie w kliencie, ustawiając SSL_OP_CIPHER_SERVER_PREFERENCE. Nie jest całkowicie jasne, czy ta opcja może być ustawiona na SSLSocket w Javie. Nawet jeśli tak, możesz ustawić listę szyfrów samodzielnie lub powiedzieć klientowi, aby uszanował listę serwerów. W przeciwnym razie uzyskujesz wartość domyślną Androida, niezależnie od wersji, z której korzystasz (a nie wersji, z którą łączysz się).
Jeśli wybierzesz ustawienia domyślne, lista preferencji wysłana przez klienta do serwera przez klienta Jellybean 4.2+ będzie widoczna jako here, począwszy od linii 504. Domyślna lista protokołów zaczyna się wokół linii 620. Chociaż Jellybean 4.2+ obejmuje obsługę OpenSSL 1.0.1, w szczególności TLSv1.1 i TLSv1.2, protokoły te nie są domyślnie włączone. Jeśli nie zrobisz czegoś podobnego, co zrobiłem, nie możesz skorzystać z obsługi TLSv1.2, mimo że obsługa TLSv1.2 jest reklamowana w najnowszych wersjach Androida. Szczegóły różnią się nieco od poprzednich wersji Androida. Przynajmniej możesz przyjrzeć się wartościom domyślnym we wszystkich obsługiwanych wersjach i zobaczyć, co faktycznie robi twój klient. Możesz być zaskoczony.
Istnieje wiele innych informacji na temat obsługi różnych protokołów i szyfrów. Chodzi o to, że czasami może być konieczna zmiana tych ustawień w kliencie.
A. użyć niestandardowego SSLSocketFactory
Ten pracował dobrze dla mnie, a na koniec, to nie było bardzo dużo kodu.Ale było trochę cierniste z kilku powodów:
- Istnieją dwie różne klasy SSLSocketFactory. Klient potrzebuje org.apache.http.conn.ssl.SSLSocketFactory, ale OpenSSL zwraca javax.net.ssl.SSLSocketFactory. To jest zdecydowanie mylące. Użyłem delegacji , aby jedno połączenie było drugie, bez większego problemu.
- Uważaj na różnicę między OpenSSLContextImpl a SSLContextImpl. Jeden tylko owija drugi, ale nie są wymienne. Kiedy użyłem metody SSLContextImpl.engineGetSocketFactory - zapomniałem, co dokładnie stało się , ale coś po cichu zawiodło. Należy użyć OpenSSLContextImpl, aby uzyskać fabrykę gniazda, a nie SSLContextImpl. Możesz również móc użyć javax.net.ssl.SSLSocketFactory.getDefault(), ale nie jestem pewien.
- Nie można łatwo podklasować AndroidHttpClient, ponieważ jego konstruktorem jest prywatny. To niefortunne, ponieważ zapewnia inne fajne gadżety, takie jak upewnienie się, że zamknąłeś je prawidłowo, zamiast przeciekać zasoby . DefaultHttpClient działa dobrze. Pożyczyłem metodę newInstance z AndroidHttpClient (około linii 105).
Kluczowe punkty:
public class SecureSocketFactory extends SSLSocketFactory {
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
// order should not matter here; the server should select the highest
// one from this list that it supports
s.setEnabledProtocols(new String[] { "TLSv1.2", "TLSv1" });
// order matters here; specify in preference order
s.setEnabledCipherSuites(new String[] { "ECDHE-RSA-RC4-SHA", "RC4-SHA" });
wówczas:
// when creating client
HttpParams params;
SchemeRegistry schemeRegistry = new SchemeRegistry();
// use custom socket factory for https
SSLSocketFactory sf = new SecureSocketFactory();
schemeRegistry.register(new Scheme("https", sf, 443));
// use the default for http
schemeRegistry.register(new Scheme("http",
PlainSocketFactory.getSocketFactory(), 80));
ClientConnectionManager manager =
new ThreadSafeClientConnManager(params, schemeRegistry);
HttpClient client = new DefaultHttpClient(manager, params);
Poniżej Android 3.0 (Honeycomb/SDK 11), wspierane wybory szyfrów stają się bardziej ograniczone, a tam mniej motywacji do ręcznego wartości domyślne. Na FROYO/SDK 8, mój SecureSocketFactory z jakiegoś powodu wysadza się w powietrze, a jury wychodzi na 10. Ale wygląda na to, że działa od 11 wzwyż.
Pełne rozwiązanie znajduje się w publicznym githubie repo.
Innym rozwiązaniem może być użycie HttpsUrlConnection, co ułatwia korzystanie z niestandardowej fabryki gniazd, ale wyobrażam sobie, że prawdopodobnie stracisz jeszcze więcej wygody klienta HTTP wysokiego poziomu. Nie mam żadnego doświadczenia z HttpsUrlConnection.
Stackoverflow nie jest przeznaczony do dyskusji. Odwołaj się do cytatu z [strona centrum pomocy] (http://stackoverflow.com/help): "Jeśli twoja motywacja do zadawania pytania brzmi:" Chciałbym wziąć udział w dyskusji o ______ ", wtedy nie powinieneś pytać tutaj." W sekcji "Jakich pytań powinienem unikać?" sekcja – FoamyGuy
Motywacją do postawienia pytania jest poznanie właściwej odpowiedzi. Przepraszam, jeśli źle to zrozumiałeś. –
Z pewnością nie mogę twierdzić, że wiem, jaka była Twoja motywacja. ale to "" ale myślę, że StackOverflow jest lepszym miejscem do tej dyskusji. "" Oczywiście sprawia, że wydaje się, jakbyś po dyskusji.Przez definicje wymienione na [stronie pomocy] (http://stackoverflow.com/help/dont-ask) twój post jest nie na temat dla SO. Mogę docenić, że jest to dyskusja warta posiadania, ale jak to teraz jest, to naprawdę nie jest odpowiednie miejsce, aby to się stało. – FoamyGuy