Java, bazy danych i SSL
1
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
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
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).
4
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.
5
Używanie SSL'a
6
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
>
Używanie SSL'a
7
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
8
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();
}
}
SSL i Java
9
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
Przykład: serwer echo
10
import
javax.net.ssl.*;
import
java.io.*;
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();
}
}
}
Przykład: klient echo
11
import
javax.net.ssl.*;
import
java.io.*;
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();
}
}
}
Przykład: uruchomienie programów
12
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
SSL i autoryzacja
13
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
14
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
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
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
Szyfrowanie w RMI
15
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
.
1. Implementacja własnych gniazd
16
Przygotujemy gniazda, które będą umożliwiały przesyłanie przez siec danych
kodowanych za pomocą operacji
XOR
i ustalonego, ośmiobitowego wzorca.
XorServerSocket
Socket
XorSocket
ServerSocket
Implementacja XorSocket
17
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;
}
Implementacja XorSocket
18
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
;
}
}
Implementacja XorServerSocket
19
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;
}
}
Implementacja strumieni
20
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
21
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;
}
}
Implementacja socket factories
22
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
23
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
);
}
}
Implementacja
RMIServerSocketFactory
24
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
);
}
}
Program serwera
25
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);
Program serwera
26
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();
}
}
}
Program klienta
27
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();
}
}
}
Kompilacja i uruchomienie
28
1. Kompilacja:
javac *.java
2. Wygenerowanie klas łącznikowych:
rmic HelloImpl
3. Uruchomienie programu serwera:
java HelloImpl
4. Uruchomienie programu klienta:
java HelloClient
RMI i SSL
29
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);
}
}
RMI i SSL
30
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);
}
}
RMI i SSL
31
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.
Podsumowanie
32
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.