Łukasz Przywarty 171018 Wrocław, 17.01.2013 r. Grupa: WT/N 11:15-14:00 Sprawozdanie z zajęć laboratoryjnych: OpenSSL - API Prowadzący: mgr inż. Mariusz Słabicki 1 / 5
1. Treść zadania laboratoryjnego W ramach zajęć laboratoryjnych należało napisać aplikację klient-serwer. Komunikacja między serwerem i klientem miała być bezpieczna. W tym celu trzeba było wykorzystać certyfikaty serwera oraz klienta wygenerowane podczas poprzednich zajęć. Aplikacje powinny nawiązywać między sobą bezpieczne połączenie SSL i wzajemnie weryfikować swoje certyfikaty. Należało również określić rodzaj aplikacji, którą się implementuje, na przykład: serwer chat pozwalający na połączenie się kilku klientów i przesyłanie komunikatów, tablica ogłoszeń klient łączy się z serwerem i odczytuje komunikaty pozostawione dla niego lub publiczne, sam może kasować swoje ogłoszenia i wysyłać nowe. Zadanie kładzie nacisk na weryfikację tożsamości klienta za pomocą certyfikatu oraz samo ustanowienie szyfrowanej komunikacji za pomocą SSL. W miarę możliwości należało zapewnić wybór parametrów połączenia np. konkretnej metody szyfrowania. 2. Realizacja zadań laboratoryjnych Wykonywania zadania laboratoryjnego rozpoczęto od określenia rodzaju aplikacji, która będzie implementowana. Wybór padł na aplikację chat. Program został napisany w języku JAVA. Na samym początku połączono certyfikaty serwera i klienta do pojedynczych plików PEM (listing 1). Listing 1: Łączenie podpisanych certyfikatów i kluczy cat CA/private/server.key CA/certs/server.crt > CA/certs/server.pem cat CA/private/user1.key CA/certs/user1.crt > CA/certs/user1.pem Certyfikaty wyeksportowano dodatkowo do plików PKCS12 (tylko takie pliki obsługują biblioteki z pakietu java.security) listing 2. Listing 2: Eksport certyfikatów do plików PKCS12 openssl pkcs12 -export -out CA/certs/server.p12 -in CA/certs/server.pem -name "Server" openssl pkcs12 -export -out CA/certs/user1.p12 -in CA/certs/user1.pem -name "Lukasz Przywarty" Aby aplikacja poprawnie akceptowała certyfikaty podpisane przez własne CA, należy dodać CA do 2 / 5
zbioru cacerts, który jest przechowywany w jednym z folderów utworzonych przez JDK podczas instalacji prezentuje to listing 3. Listing 3: Dodawanie CA do zbioru zaufanych wystawców sudo keytool -import -alias myca -file CA/certs/ca.crt -keystore /etc/ssl/certs/java/cacerts Po wykonaniu kroków przygotowawczych przystąpiono do realizacji właściwego zadania. Korzystano przy tym z klas zgromadzonych w pakietach: java.net.ssl klasy odpowiedzialne za transmisję przy wykorzystaniu SSL, java.security a dokładnie klasa KeyStore reprezentująca zbiór kluczy i certyfikatów zapisanych w pamięci. Klasę serwera prezentuje listing 4. Listing 4: Klasa server 1. public class server { 2. public static void main(string[] args) { 3. String ServerCert = "server.p12"; 4. String CertPassword = "bus12"; 5. 6. try { 7. KeyStore ks = KeyStore.getInstance("PKCS12"); 8. ks.load(new FileInputStream(ServerCert), CertPassword.toCharArray()); 9. KeyManagerFactory kmf=keymanagerfactory.getinstance("sunx509"); 10. kmf.init(ks, CertPassword.toCharArray()); 11. SSLContext sc = SSLContext.getInstance("SSL"); 12. sc.init(kmf.getkeymanagers(), null, null); 13. System.out.print("Server started \n"); 14. SSLServerSocketFactory ssf = sc.getserversocketfactory(); 15. SSLServerSocket ServerSecureSocket = (SSLServerSocket) ssf.createserversocket(8000); 16. ServerSecureSocket.setNeedClientAuth(true); 17. SSLSocket SecureSocket = (SSLSocket)ServerSecureSocket.accept(); 18. 19. BufferedWriter SecureOutput = new BufferedWriter(new OutputStreamWriter(SecureSocket.getOutputStream())); 20. BufferedReader SecureInput = new BufferedReader(new InputStreamReader(SecureSocket.getInputStream())); 21. 22. String temp = "Welcome to the Chat Jungle!"; 23. SecureOutput.write(temp,0,temp.length()); 24. SecureOutput.newLine(); 25. SecureOutput.flush(); 26. while ((temp=secureinput.readline())!= null) { 3 / 5
27. System.out.println(temp); 28. SecureOutput.write(temp,0,temp.length()); 29. SecureOutput.newLine(); 30. SecureOutput.flush(); 31. } 32. SecureOutput.close(); 33. SecureInput.close(); 34. SecureSocket.close(); 35. ServerSecureSocket.close(); 36. } catch (Exception e) { 37. System.err.println(e.toString()); 38. } 39. } 40.} Objaśnienia dotyczące komunikacji SSL: linia 3 deklaracja nazwy certyfikatu serwera (w formacie PKCS12), linia 4 deklaracja hasła do certyfikatu serwera, linie 7 do 12 utworzenie kontekstu SSL (określenie typu certyfikatu oraz klucza, określenie rodzaju szyfrowania, inicjalizacja), linie 14 do 17 stworzenie bezpiecznego gniazdka do nawiązywania połączeń SSL (na porcie numer 8000), linie 19 do 20 deklaracja szyfrowanych strumieni do wymiany danych, linie 26 do 30 pętla odpowiedzialna za przekazywanie komunikatów, linie 32 do 35 zamykanie szyfrowanych strumieni i gniazdek. Jeśli chodzi o klasę klienta, wygląda ona bardzo podobnie do klasy serwera listing 5. Listing 5: Klasa klienta 1. public class client { 2. public static void main(string[] args) { 3. String ClientCert = "user1.p12"; 4. String CertPassword = "bus12"; 5. 6. try { 7. KeyStore ks = KeyStore.getInstance("PKCS12"); 8. ks.load(new FileInputStream(ClientCert), CertPassword.toCharArray()); 10. KeyManagerFactory kmf=keymanagerfactory.getinstance("sunx509"); 11. kmf.init(ks, CertPassword.toCharArray()); 12. SSLContext sc = SSLContext.getInstance("SSL"); 13. sc.init(kmf.getkeymanagers(), null, null); 14. SSLSocketFactory sf = sc.getsocketfactory(); 15. SSLSocket SecureSocket = (SSLSocket) 4 / 5
sf.createsocket("localhost", 8000); 16. SecureSocket.startHandshake(); 17. 18. BufferedWriter SecureOutput = new BufferedWriter(new OutputStreamWriter(SecureSocket.getOutputStream())); 19. BufferedReader SecureInput = new BufferedReader(new InputStreamReader(SecureSocket.getInputStream())); 20. 21. String temp = null; 22. while ((temp=secureinput.readline())!= null) { 23. System.out.println(temp); 24. temp = new BufferedReader(new InputStreamReader(System.in)).readLine(); 25. temp = SecureSocket.getLocalPort()+": "+temp; 26. SecureOutput.write(temp,0,temp.length()); 27. SecureOutput.newLine(); 28. SecureOutput.flush(); 29. } 30. SecureOutput.close(); 31. SecureInput.close(); 32. SecureSocket.close(); 33. } catch (Exception e) { 34. System.err.println(e.toString()); 35. } 36. } 37. } W tym przypadku również określono lokalizację certyfikatu (tym razem klienta) oraz hasło do certyfikatu. Następnie utworzono kontekst SSL oraz bezpieczne gniazdko na porcie 8000. Kolejne linie kodu deklarują szyfrowane strumienie. W tym momencie zawarto również pętlę odpowiedzialną za wyświetlanie komunikatów. Całość wieńczy fragment zamykający bezpieczne gniazdko i strumienie. 3. Wyniki Program działa poprawnie. Zarówno serwer jak i klient musi potwierdzić swoją tożsamość aby połączenie zostało poprawnie nawiązane. Rysunek 1: Okno klienta 5 / 5