Java, bazy danych i SSL 1. MySQL: własności połączenia, transmisja z wykorzystaniem SSL. 2. Własne programy wykorzystujące SSL. 3. RMI i SSL - implementacja własnych wersji klas ServerSocket i Socket. - wykorzystanie własnych gniazd w ramach RMI - implementacja własnej klasy RMIClientSocketFactory. - implementacja własnej klasy RMIServerSocketFactory. - szyfrowanie SSL w RMI 1
MySQL i JDBC Driver JDBC do bazy MySQL: http://www.mysql.com/products/connector/j/. Dokumentacja: http://dev.mysql.com/doc/refman/5.0/en/java-connector.html. Nawiązanie połączenia: Connection con = DriverManager.getConnection( "jdbc:mysql://localhost/test?user=monty&password="); Ogólnie "connectstring" ma postać: jdbc:mysql://[host][,failoverhost...][:port]/[database] [?propertyname1][=propertyvalue1][&propertyname2][=propertyvalue2]... Domyślny adres host'a to '127.0.0.1'. Domyślny port to '3306'. jdbc:mysql://[host:port],[host:port].../[database] [?propertyname1][=propertyvalue1][&propertyname2][=propertyvalue2]... Jeśli nie zostanie podana nazwa bazy danych, w przyszłości należy ją ustawić metodą setcatalog() na rzecz obiektu Connection lub w zapytaniach przesyłać pełne nazwy tabel: (np. SELECT dbname.tablename.colname FROM dbname.tablename). 2
Wybrane własności połączenia user nazwa użytkownika (domyślnie ''), password - hasło (''), usecompression czy używać kompresji podczas komunikacji z serwerem (false), autoreconnect czy odtwarzać nieaktywne połączenia (false), usessl czy używać SSL'a podczas komunikacji (false), requiressl wymagać SSL'a gdy usessl=true? (false), logger nazwa klasy logującej implementującej com.mysql.jdbc.log.log, używanej do logowania zdarzeń (com.mysql.jdbc.log.standardlogger), zerodatetimebehavior jak obsługiwać daty wypełnione zerami, opcje: exception, round oraz converttonull (exception). 3
4 Kodowanie znaków Wszystkie teksty wysyłane przez sterownik do bazy danych są automatycznie konwertowane z Unikodu (natywne kodowanie w Javie) do kodowania używanego przez komputer klienta. Kodowanie znaków między klientem i serwerem jest wykrywane automatycznie podczas nawiązania połączenia. Kodowanie używane przez sterownik jest ustawiane po stronie serwera poprzez character_set (przed 4.1.0) lub character_set_server (od 4.1.0). Aby zmienić automatyczne kodowanie należy ustawić własność characterencoding w connectstring'u. Najczęstsze kodowania nazwa MySQL (nazwa Java): usa7 (US-ASCII), latin1 (ISO8859_1), latin2 (ISO8859_2), win1250ch (Cp1250), utf8 (UTF-8), ucs2 (UnicodeBig).
5 Używanie SSL'a SSL szyfruje wszystkie przesyłane dane. Wydajność komunikacji spada o 35-50%. Konfiguracja serwera: http://dev.mysql.com/doc/refman/5.0/en/secure-connections.html. W skrócie: konfiguracja kompilacji: SHOW VARIABLES LIKE 'have_openssl'; wygenerowanie certyfikatów SSL: http://dev.mysql.com/doc/refman/5.0/en/secure-create-certs.html?ff=nopfpls zmiany w pliku my.cnf, uruchomienie serwera.
Używanie SSL'a 1. Import certyfikatu serwera MySQL: >cp /etc/mysql/openssl/cacert.pem cacert.pem >keytool -import -alias mysql -file cacert.pem -keystore mysql.store Enter keystore password: mysqljava Owner: CN=Michal Ciesla, O=Internet Widgits Pty Ltd, ST=Some-State, C=pl Issuer: CN=Michal Ciesla, O=Internet Widgits Pty Ltd, ST=Some-State, C=pl Serial number: c55bf7ad0557670b Valid from: Sun Mar 19 20:54:21 CET 2006 until: Tue Apr 18 21:54:21 CEST 2006 Certificate fingerprints: MD5: FA:3C:B9:34:9E:11:FB:0E:D9:1C:C9:40:A5:3E:CB:E8 SHA1: 4F:9F:A8:C9:B1:3B:8F:CE:0F:7D:B0:CC:C6:E6:5A:53:EA:4B:B7:FC Trust this certificate? [no]: yes Certificate was added to keystore > 6
7 Używanie SSL'a 2. Ewentualne wygenerowanie certyfikatu klienta: >keytool -genkey -keyalg rsa -alias client -keystore client.store 3. Ustawienie właściwości JVM: -Djavax.net.ssl.keyStore=/sciezka/do/mysql.store -Djavax.net.ssl.keyStorePassword=********* -Djavax.net.ssl.trustStore=/sciezka/do/client.store -Djavax.net.ssl.trustStorePassword=********* 4. Dodanie do przy połączeniu opcji: usessl=true 5. Sprawdzenie uruchomienie JVM z opcją -Djavax.net.debug=all
Używanie SSL'a import java.sql.*; public class MySQLDb { public static void main(string[] args){ String sstring = "jdbc:mysql://localhost/test?" + "user=root&password=haslo&usessl=true"; System.setProperty("javax.net.ssl.trustStore", "/path/to/mysql.store"); System.setProperty("javax.net.debug","all"); try { Class.forName("com.mysql.jdbc.Driver").newInstance(); catch (Exception ex) { ex.printstacktrace(); return; try { Connection con = DriverManager.getConnection(sString); Statement stmt =con.createstatement(); ResultSet rs = stmt.executequery("select NOW()"); rs.next(); System.out.println(rs.getString(1)); con.close(); catch (SQLException ex) { ex.printstacktrace(); 8
9 SSL i Java Protokół SSL umożliwia bezpieczną (szyfrowaną) transmisję danych poprzez niezabezpieczoną sieć. Dodatkowo SSL umożliwia autoryzację stron komunikacji. W tym celu wykorzystywany jest mechanizm certyfikatów. Za transmisję z użyciem protokołu SSL odpowiedzialne są klasy zgrupowane w pakiecie javax.net.ssl. Implementacja SSH jest dostępna poprzez zewnętrzne biblioteki. Jedną z nich jest jsch (http://www.jcraft.com/jsch/).
import javax.net.ssl.*; import java.io.*; Przykład: serwer echo public class EchoServer { public static void main(string[] args) { try { SSLServerSocketFactory factory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); SSLServerSocket ss = (SSLServerSocket) factory.createserversocket(9999); SSLSocket s = (SSLSocket) ss.accept(); InputStreamReader isr = new InputStreamReader(s.getInputStream()); BufferedReader br = new BufferedReader(isr); String stmp = null; while ((stmp = br.readline())!= null) { System.out.println(sTmp); System.out.flush(); catch (Exception ex) { ex.printstacktrace(); 10
import javax.net.ssl.*; import java.io.*; Przykład: klient echo public class EchoClient { public static void main(string[] args) { try { SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault(); SSLSocket s = (SSLSocket) factory.createsocket("localhost", 9999); InputStreamReader isr = new InputStreamReader(System.in); BufferedReader br = new BufferedReader(isr); OutputStreamWriter osw = new OutputStreamWriter(s.getOutputStream()); BufferedWriter bw = new BufferedWriter(osw); String stmp = null; while ((stmp = br.readline())!= null) { bw.write(stmp + '\n'); bw.flush(); catch (Exception ex) { ex.printstacktrace(); 11
Przykład: uruchomienie programów Pierwsza czynność to wygenerowanie klucza: keytool -genkey -keystore mysrvkeystore -keyalg RSA Uruchomienie serwera: java -Djavax.net.ssl.keyStore=mySrvKeystore -Djavax.net.ssl.keyStorePassword=123456 EchoServer Uruchomienie klienta: java -Djavax.net.ssl.trustStore=mySrvKeystore -Djavax.net.ssl.trustStorePassword=123456 EchoClient Dodatkowe parametry wywołania pozwolą zobaczyć informacje związane z połączeniem SSL: -Djava.protocol.handler.pkgs= com.sun.net.ssl.internal.www.protocol -Djavax.net.debug=ssl Przykład ze strony: http://tvilda.stilius.net/java/java_ssl.php 12
13 SSL i autoryzacja Domyślnie tylko jedna strona komunikacji (serwer) musi potwierdzać swoją tożsamość. Jeśli konieczne jest potwierdzenie tożsamości klienta należy użyć metod: setneedclientauth(true) lub setwantclientauth(true) wywołanych na rzecz obiektu SSLServerSocket. Jeśli chcemy aby żadna ze stron nie musiała potwierdzać swojej tożsamości musimy zmienić domyślne algorytmy kodowania. Najłatwiej zrobić to tworząc własne rozszerzenie klasy SSLSocketFactory. Listę obsługiwanych i domyślnych algorytmów uzyskamy za pomocą metod: getsuppotredciphersuites() oraz getdefaultciphersuites().
SSL i autoryzacja listy algorytmów Domyślne: SSL_RSA_WITH_RC4_128_MD5 SSL_RSA_WITH_RC4_128_SHA TLS_RSA_WITH_AES_128_CBC_SHA TLS_DHE_RSA_WITH_AES_128_CBC_SHA TLS_DHE_DSS_WITH_AES_128_CBC_SHA SSL_RSA_WITH_3DES_EDE_CBC_SHA SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA SSL_RSA_WITH_DES_CBC_SHA SSL_DHE_RSA_WITH_DES_CBC_SHA Wspierane: wszystkie domyślne oraz: SSL_DHE_RSA_WITH_DES_CBC_SHA SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA SSL_RSA_WITH_NULL_MD5 SSL_RSA_WITH_NULL_SHA SSL_DH_anon_WITH_RC4_128_MD5 TLS_DH_anon_WITH_AES_128_CBC_SHA SSL_DH_anon_WITH_3DES_EDE_CBC_SHA SSL_DH_anon_WITH_DES_CBC_SHA SSL_DH_anon_EXPORT_WITH_RC4_40_MD5 SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA SSL_DHE_DSS_WITH_DES_CBC_SHA SSL_RSA_EXPORT_WITH_RC4_40_MD5 SSL_RSA_EXPORT_WITH_DES40_CBC_SHA SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA 14
15 Szyfrowanie w RMI Technologia RMI została zaprojektowana tak, aby można było jej używać z dowolnymi mechanizmami zapewniającymi transport danych przez sieć, działającymi ponad protokołem TCP. W praktyce odbywa się to poprzez implementację własnego obiektu (tzw. socket factory) dostarczającego gniazda wykorzystywane do komunikacji RMI. Implementacja własnego socket factory składa się z trzech kroków: 1. Implementacja własnych wersji klas ServerSocket i Socket. 2. Implementacja własnej klasy ClientSocketFactory. 3. Implementacja własnej klasy ServerSocketFactory.
16 1. Implementacja własnych gniazd Przygotujemy gniazda, które będą umożliwiały przesyłanie przez siec danych kodowanych za pomocą operacji XOR i ustalonego, ośmiobitowego wzorca. XorSocket Socket XorServerSocket ServerSocket
17 Implementacja XorSocket import java.io.*; import java.net.*; class XorSocket extends Socket { private final byte pattern; // wzorzec kodowania private InputStream in = null; private OutputStream out = null; public XorSocket(byte pattern) throws IOException { super(); this.pattern = pattern; public XorSocket(String host, int port, byte pattern) throws IOException { super(host, port); this.pattern = pattern;
18 Implementacja XorSocket public synchronized InputStream getinputstream() throws IOException { if (in == null) { in = new XorInputStream(super.getInputStream(), pattern); return in; public synchronized OutputStream getoutputstream() throws IOException { if (out == null) { out = new XorOutputStream(super.getOutputStream(), pattern); return out;
19 Implementacja XorServerSocket import java.io.*; import java.net.*; class XorServerSocket extends ServerSocket { private final byte pattern; public XorServerSocket(int port, byte pattern) throws IOException { super(port); this.pattern = pattern; public Socket accept() throws IOException { Socket s = new XorSocket(pattern); this.implaccept(s); return s;
20 Implementacja strumieni import java.io.*; class XorOutputStream extends FilterOutputStream { private final byte pattern; public XorOutputStream(OutputStream out, byte pattern) { super(out); this.pattern = pattern; public void write(int b) throws IOException { out.write((b ^ pattern) & 0xFF);
Implementacja strumieni import java.io.*; class XorInputStream extends FilterInputStream { private final byte pattern; public XorInputStream(InputStream in, byte pattern) { super(in); this.pattern = pattern; public int read() throws IOException { int b = in.read(); if (b!= -1) b = (b ^ pattern) & 0xFF; return b; public int read(byte b[], int off, int len) throws IOException { int n = in.read(b, off, len); if (n <= 0) return n; for(int i = 0; i < n; i++) b[off + i] = (byte)((b[off + i] ^ pattern) & 0xFF); return n; 21
22 Implementacja socket factories W technologi RMI serwer (zdalny obiekt) decyduje o rodzaju transmisji. Dzięki temu kod programu klienckiego jest niezależny od zmian (np. szyfrowanie) w protokole komunikacji. Z drugiej strony wszelkie dane potrzebne do zainicjowania połączenia są do klienta przesyłane przez sieć czyli muszą być serializowalne. Aby utworzyć gniazdo służące do komunikacji RMI korzysta z interfejsów SocketFactory oraz ServerSocketFactory, które udostępniają odpowiednio metody createsocket(string host, int port) i createserversocket(int port).
Implementacja RMIClientSocketFactory import java.io.*; import java.net.*; import java.rmi.server.*; public class XorClientSocketFactory implements RMIClientSocketFactory, Serializable { private byte pattern; public XorClientSocketFactory(byte pattern) { this.pattern = pattern; public Socket createsocket(string host, int port) throws IOException { return new XorSocket(host, port, pattern); public int hashcode() { return (int) pattern; public boolean equals(object obj) { return (getclass() == obj.getclass() && pattern == ((XorClientSocketFactory) obj).pattern); 23
Implementacja RMIServerSocketFactory import java.io.*; import java.net.*; import java.rmi.server.*; public class XorServerSocketFactory implements RMIServerSocketFactory { private byte pattern; public XorServerSocketFactory(byte pattern) { this.pattern = pattern; public ServerSocket createserversocket(int port) throws IOException { return new XorServerSocket(port, pattern); public int hashcode() { return (int) pattern; public boolean equals(object obj) { return (getclass() == obj.getclass() && pattern == ((XorServerSocketFactory) obj).pattern); 24
Program serwera import java.io.*; import java.rmi.*; import java.rmi.server.*; import java.rmi.registry.*; public class HelloImpl implements Hello { public HelloImpl() { public String gethello() { return "Hello World!"; public static void main(string args[]) { byte pattern = (byte) 0xC5; // 10100101 try { HelloImpl obj = new HelloImpl(); RMIClientSocketFactory csf = new XorClientSocketFactory(pattern); RMIServerSocketFactory ssf = new XorServerSocketFactory(pattern); 25
26 Program serwera Hello stub = (Hello) UnicastRemoteObject.exportObject(obj, 0, csf, ssf); try { reg = LocateRegistry.getRegistry(); catch (RemoteException ex1) { try { reg = LocateRegistry.createRegistry(1099); catch (RemoteException ex2) { return; reg.rebind("helloservice", stub); catch (Exception e) { e.printstacktrace();
27 Program klienta import java.rmi.naming; import java.rmi.registry.locateregistry; import java.rmi.registry.registry; public class HelloClient { public static void main(string args[]) { try { Hello obj = (Hello) Naming.lookup("rmi://localhost/HelloService"); System.out.println(obj.getHello()); catch (Exception e) { e.printstacktrace();
28 Kompilacja i uruchomienie 1. Kompilacja: javac *.java 2. Wygenerowanie klas łącznikowych: rmic HelloImpl 3. Uruchomienie programu serwera: java HelloImpl 4. Uruchomienie programu klienta: java HelloClient
29 RMI i SSL import java.io.ioexception; import java.io.serializable; import java.net.socket; import java.rmi.server.rmiclientsocketfactory; import javax.net.ssl.sslsocketfactory; public class RMISSLClientSocketFactory implements RMIClientSocketFactory, Serializable { public Socket createsocket(string arg0, int arg1) throws IOException { SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault(); return factory.createsocket(arg0, arg1);
30 RMI i SSL import java.io.ioexception; import java.io.serializable; import java.net.serversocket; import java.rmi.server.rmiserversocketfactory; import javax.net.ssl.sslserversocketfactory; public class RMISSLServerSocketFactory implements RMIServerSocketFactory, Serializable{ public ServerSocket createserversocket(int arg0) throws IOException { SSLServerSocketFactory factory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); return factory.createserversocket(arg0);
31 RMI i SSL W programie serwera (HelloImpl.java) należy zmienić linie odpowiadające za tworzenie obiektów SocketFactory na: RMIClientSocketFactory csf = new RMISSLClientSocketFactory(); RMIServerSocketFactory ssf = new RMISSLServerSocketFactory(); UWAGA: przy uruchomieniu programu korzystającego z SSL należy uwzględnić magazyn kluczy używanych do autoryzacji i szyfrowania.
32 Podsumowanie Bezpieczeństwo jest się jednym z priorytetów przy tworzeniu oprogramowania. Częściowo może być ono zapewnione poprzez szyfrowanie przesyłanych informacji. Java standardowo wspiera protokół TLS-SSL jak również jest przygotowana do łatwej implementacji nowych rozwiązań w tej dziedzinie.