2005.01_Pyro i OpenSSL–bezpieczny komunikator internetowy_[Programowanie].pdf

(863 KB) Pobierz
439110635 UNPDF
dla programistów
Pyro i OpenSSL
– bezpieczny
komunikator internetowy
Marek Sawerwain
tne informacje poprzez Inter-
net, to wprowadzenie szy-
frowania danych w naszym
aplikacjach rozproszonych jest bardzo
ważnym zagadnieniem. Istnieje wiele
bibliotek wspomagających nas w tym
temacie. Jedną z najbardziej znanych jest
OpenSSL . Jest ona dostępna we wszyst-
kich większych dystrybucjach Linuk-
sa. Odpowiada za realizację bezpiecz-
nych kanałów komunikacyjnych w opar-
ciu o protokół SSL i jest wykorzystywana
przez wiele programów, w tym przeglą-
darki WWW.
Zastosowanie biblioteki OpenSSL naj-
lepiej pokazać na przykładzie. W nume-
rze Linux+ 10/2003 prezentowałem prosty
komunikator na wzór bardzo popularne-
go Gadu-Gadu. Jedną z niedoskonało-
ści tamtego programu była komunikacja
bez jakiegokolwiek szyfrowania danych.
W tym artykule postaram się pokazać,
jak małym kosztem zmienić ten stan
rzeczy.
opisu interfejsu pochodzących z progra-
mu Glade.
Do poprawnego działania będzie
wymagał, tak jak poprzednio, dwóch uru-
chomionych usług Pyro: serwera nazw
(usługę uruchamiamy poprzez wydanie
polecenia ns ) oraz serwera zdarzeń (usługę
należy uruchomić wydając polecenie es ).
Warto przedstawić sposób działa-
nia naszej aplikacji w postaci schema-
tu, który znajduje się na Rysunku 1. Jak
widać, mamy trzy specjalne serwery. Ser-
wery zdarzeń i nazw należą do systemu
Pyro, ale główny serwer, nazwany ser-
werem rozmów, musimy napisać samo-
dzielnie, a raczej poprawić kod, doda-
jąc obsługę SSL, bo przecież serwer już
wcześniej napisaliśmy.
Typowy serwer Pyro
Program komunikatora to wbrew pozo-
rom niewielka aplikacja. Łączny kod źró-
dłowy serwera i klienta to tylko 7 kB
tekstu źródłowego w Pythonie. Zanim
wprowadzimy obsługę SSL do komunika-
tora, chciałbym przedstawić typową apli-
kację Pyro.
Listing 1 zawiera kod klasy o nazwie
test_obj . Poprzez Pyro chcemy udostęp-
nić tę klasę w sieci tak, aby inny użyt-
kownicy Pythona mogli ją wykorzy-
stać we własnych programach. Pierwszą
czynnością jest definicja klasy (Listing 1),
którą zapisujemy w oddzielnym pliku
o nazwie test_obj.py .
Istnieje kilka sposobów na napisanie
serwera. Poniżej zaprezentuję sposób, który
został użyty w komunikatorze. Pierwszym
elementem w implementacji serwera jest
dołączenie odpowiednich pakietów:
Na płycie CD/DVD
Na płycie CD/DVD znajdują się
wykorzystywane biblioteki, kod
źródłowy programu oraz wszyst-
kie listingi z artykułu.
Kilka postanowień
na początek
Najważniejszym zadaniem będzie doda-
nie do komunikatora obsługi szyfrowa-
nego kanału. Uprzedzając dalszą część
artykułu, już teraz mogę zdradzić, iż jest
to zadanie łatwe do wykonania. Głównie
dlatego, iż Pyro znakomicie współpracu-
je z biblioteką OpenSSL.
Aby w naszym komunikatorze znala-
zła się obsługa SSL, wymagana jest jesz-
cze instalacja jednego pakietu o nazwie
M2Crypt . Kilka uwag na ten temat zawiera
ramka Kompilacja i instalacja pakietów .
Pozostałe założenia naszego progra-
mu pozostają niezmienione. Nadal jest
to program napisany w Pythonie. Będzie
korzystał z biblioteki GTK+ oraz z plików
O autorze
Autor zajmuje się tworzeniem
oprogramowania dla WIN32
i Linuksa. Zainteresowania:
teoria języków programowania
oraz dobra literatura.
Kontakt z autorem:
autorzy@lpmagazine.org
import Pyro.core
import Pyro.naming
import test_obj
70 styczeń 2005
G dy planujemy przesyłać isto-
439110635.023.png 439110635.024.png 439110635.025.png 439110635.026.png 439110635.001.png
python/pyro/ssl
dla programistów
Kompilacja oraz
instalacja pakietów
Nasza aplikacja to dość mały program.
Wielkość jest wynikiem zastosowania
kilku bibliotek, które pozwalają zaim-
plementować całkiem zgrabną apli-
kację minimalną ilością kodu. Z tego
powodu, aby nasz komunikator zadzia-
łał, potrzebny jest język programowa-
nia Python oraz pakiet OpenSSL. Sam
język, jak również bibliotekę, znajdzie-
my bez problemów w każdej dystrybucji
Linuksa. Autorzy biblioteki Pyro zale-
cają najnowszą wersję Pythona, naj-
lepiej 2.3.3, oraz wersję OpenSSL nie
starszą niż 0.9.7 (jeśli w dystrybucji jest
starsza wersja, to koniecznie trzeba
dokonać aktualizacji tego pakietu).
Biblioteki Pyro raczej nie znajdzie-
my, ale zanim przystąpimy do jej insta-
lacji, należy zainstalować inną biblio-
tekę o nazwie M2Crypt . Będzie ona
obsługiwać połączenia szyfrowane
SSL. Biblioteka M2Crypto ma również
swoje wymagania i do poprawnej kom-
pilacji wymaga pakietu SWIG w wersji
przynajmniej 1.3.21. Pakiet SWIG jest
oferowany przez niektóre dystrybucje,
ale gdy pojawi się konieczność jego
kompilacji, to dzięki skryptowi coni-
gure sprawdza się ona do trzech pole-
ceń:
Rysunek 1. Schemat działania komunikatora
Dwa pierwsze dotyczą Pyro, ale ostatni
pakiet to nasza klasa, którą mamy zamiar
udostępnić. W tym celu w serwerze
definiujemy specjalną klasę pośrednią
w następujący sposób:
ma zainstalowanej sieci i jest dostępny tylko
adres lokalny 127.0.0.1 .
Następnym krokiem w implementa-
cji serwera jest uzyskanie obiektu ser-
wera nazw:
class test_obj_class S
(Pyro.core.ObjBase, test_obj.test_obj):
def __init__(self):
Pyro.core.ObjBase.__init__(self)
ns=Pyro.naming.NameServerLocator().getNS()
Ostatnim obiektem do utworzenia jest
daemon :
# ./configure –prefix=/katalog
# make
# make install
Następnie uruchomiamy serwer metodą
initServer :
daemon=Pyro.core.Daemon()
Konieczną operacją jest podanie mu
obiektu serwera nazw i to wykonujemy
w następujący sposób:
Pyro.core.initServer()
Proces kompilacji oraz instalacji pakie-
tu M2Crypto (jak również Pyro) sprowa-
dza się do dwóch poleceń:
W przeciwieństwie do wcześniejszego
kodu serwera komunikatora, określa-
my jeszcze kilka dodatkowych informa-
cji. Na początku określamy ilość infor-
macji, które będą zapisywane do pliku
zdefiniowanego za pomocą następnej
linii:
daemon.useNameServer(ns)
# python setup.py build
# python setup.py install
Ostatnie czynności w serwerze polegają
na podłączeniu naszego obiektu. W pierw-
szym parametrze wykorzystujemy klasę
pomocniczą test_obj_class , a w drugim
ciąg znaków reprezentujący nasz obiekt,
czyli test_obj . Na koniec możemy wywo-
łać metodę requestLoop , co spowoduje
uruchomienie serwera. Kod tych dwóch
czynności przedstawia się następująco:
Pamiętajmy, aby wcześniej zainsta-
lować wszystkie pakiety bezpośred-
nio związane z interpreterem Pytho-
na oraz pakiety związane z biblioteką
OpenSSL.
Nasz komunikator wymaga jeszcze
jednej biblioteki do poprawnego działa-
nia, a mianowicie PyGTK+ , gdyż wyko-
rzystujemy GTK+ oraz pakiet libgla-
de . Wiele dystrybucji, np. Aurox , Man-
drakelinux czy Fedora Core , zawiera-
ją odpowiednie pakiety – wystarczy je
tylko doinstalować.
Pyro.config.PYRO_TRACELEVEL=3
Pyro.config.PYRO_LOGFILE='server_log'
Później określamy komputer, na którym
został uruchamiany serwer nazw:
Pyro.config.PYRO_NS_HOSTNAME='localhost'
uri=daemon.connect(test_obj_class(), "test_obj")
daemon.requestLoop()
Ustalenie adresu serwera nazw oraz serwe-
ra zdarzeń ( Pyro.config.PYRO_ES_HOSTNAME )
jest szczególnie istotne, gdy będziemy testo-
wać program na komputerze, na którym nie
Klient Pyro
W programie klienta, podobnie jak w ser-
werze, na początek dołączamy potrzeb-
ne pakiety:
www.lpmagazine.org
71
439110635.002.png 439110635.003.png 439110635.004.png 439110635.005.png 439110635.006.png 439110635.007.png 439110635.008.png
 
dla programistów
Listing 1. Postać klasy test_obj
import Pyro.util
import Pyro.core
class test_obj :
Następnie inicjujemy klienta:
def m1 ( s , string ):
print "Metoda m1 parametr:" , string
return "Długość ciągu znaków: " + str ( len ( string ))
def m2 ( s , number ):
print "Metoda m2 parametr:" , number
return "Kwadrat liczby:" +str( number * number )
Pyro.core.initClient()
Później ustalamy parametry pomocnicze,
takie jak adres komputera, na którym
znajduje się serwer nazw.
Gdy to zrobimy, możemy uzyskać
referencję do zdalnego obiektu. Wyko-
rzystujemy metodę getProxyForURI :
Listing 2. Pełny kod serwera oparty o protokół SSL
#! /usr/bin/env python
test = Pyro.core.getProxyForURI S
("PYRONAME://test_obj")
import sys
import Pyro . core
import Pyro . naming
import Pyro . util
import Pyro . protocol
Po tych czynnościach metody ze zdalne-
go obiektu wywołujemy tak samo, jak
przy lokalnych obiektach:
print test.m1("Abcdef")
print test.m2(5)
from Pyro . errors import PyroError , NamingError
from Pyro . protocol import getHostname
Dodajemy obsługę SSL
do serwera
Jak widać, napisanie nieskompliko-
wanego serwera i klienta przy zasto-
sowaniu Pyro to dość łatwe zada-
nie. Kod źródłowy biblioteki zawie-
ra wiele przykładów, które polecam
przejrzeć czytelnikom, którzy w tym
momencie zainteresowali się tą biblio-
teką. Naszym głównym zadaniem jest
jednak komunikacja za pomocą proto-
kołu SSL.
Listing 2 zawiera pełny kod serwera
udostępniającego obiekt test_obj przy
zastosowaniu protokołu SSL. Postać ser-
wera jest bardzo podobna do poprzed-
niego przykładu, w którym komunika-
cja jest jawna.
Pierwszą różnicą są pakiety, które
dołączamy. Istotny jest Pyro.protocol ,
używany w klasie printCertValidator .
Kolejna różnica kryje się w tworzeniu
obiektu daemon . W argumencie konstruk-
tora podajemy wartość PYROSSL :
import test_obj
class test_obj_class ( Pyro . core . ObjBase , test_obj . test_obj ):
def __init__ ( self ):
Pyro . core . ObjBase . __init__ ( self )
class printCertValidator ( Pyro . protocol . BasicSSLValidator ):
def checkCertificate ( self , cert ):
if cert is None :
return ( 0 , 3 )
print "Cert Subject: %s" % cert . get_subject ()
return ( 1 , 0 )
##### main program #####
Pyro . core . initServer ()
Pyro . config . PYRO_TRACELEVEL = 3
Pyro . config . PYRO_NS_HOSTNAME = 'localhost'
Pyro . config . PYRO_LOGFILE = 'server_log'
ns = Pyro . naming . NameServerLocator () . getNS ()
daemon = Pyro . core . Daemon ( prtcol = 'PYROSSL' )
daemon . setNewConnectionValidator ( printCertValidator ())
daemon . useNameServer ( ns )
daemon=Pyro.core.Daemon(prtcol='PYROSSL')
W ten sposób nakazujemy stosowanie
protokołu SSL.
Następnie podłączamy klasę print-
CertValidator , której zadaniem jest
sprawdzenie poprawności certyfikatu:
uri = daemon . connect ( test_obj_class () , "test_obj" )
print "Server is ready. Let's go!!!"
daemon . requestLoop ()
daemon.setNewConnectionValidator S
(printCertValidator())
72
styczeń 2005
439110635.009.png 439110635.010.png
 
python/pyro/ssl
dla programistów
Kolejne czynności pozostają niezmie-
nione w porównaniu do zwykłego ser-
wera.
Zajmijmy się teraz klasą printCert-
Validator . Zawiera ona jedną metodę
checkCertificate . Musi ona zwrócić
pewne określone wartości. Jeśli chcemy
zaakceptować certyfikat, to jako war-
tość powrotną zwracamy parę (1,0) .
W przypadku odrzucenia certyfikatu,
za pomocą słowa return musimy
przekazać parę (0, kod_błędu) . Przez
kod_błędu rozumiemy jedną z poniż-
szych wartości:
Listing 3. Postać plików z certyikatami dla protokołu SSL
Plik z głównym certyikatem:
-----BEGIN CERTIFICATE-----
MIIDkjCCAvugAwIBAgIBA i tak dalej ....
-----END CERTIFICATE-----
Plik client.pem :
-----BEGIN CERTIFICATE-----
MIIDvzCCAyigAwIBAgIBATANBgkqh i tak dalej ....
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQC7ixGOs2Sq i tak dalej ...
-----END RSA PRIVATE KEY-----
Plik server.pem :
-----BEGIN CERTIFICATE-----
MIIDvzCCAyigAwIBAgIBATANBgkqh i tak dalej ....
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQC7ixGOs2Sq i tak dalej ...
-----END RSA PRIVATE KEY-----
Pyro.constants.DENIED_UNSPECIFIED
– dowolny powód błędu;
Pyro.constants.DENIED_SERVERTOOBUSY
– serwer jest zbyt zajęty;
Pyro.constants.DENIED_HOSTBLOCKED
– blokada komputera;
Pyro.constants.DENIED_SECURITY
– błąd związany z bezpieczeństwem.
Program klienta
W programie serwera najważniej-
szą zmianą było dodanie w kon-
struktorze obiektu daemon informacji
o tym, że będziemy korzystać z pro-
tokołu SSL. W kliencie nie musimy
wykonywać żadnych zmian! Bibliote-
ka Pyro samodzielnie wykryje fakt, że
serwer korzysta z protokołu SSL i klient
samoczynnie przełączy się na komu-
nikację poprzez SSL. Z tego powodu
uzyskanie referencji do obiektu nadal
możemy wykonywać w ten sposób:
test1 = Pyro.core.getProxyForURI S
("PYRONAME://test_obj")
Na Listingu 2, gdy nie ma certyfikatu,
zwracamy parę (0,3) , co oznacza błąd
związany z bezpieczeństwem.
Jak widzimy, dodanie obsługi SSL
wymaga dołączenia jednej klasy oraz
kosmetycznych zmian w kodzie. Oprócz
serwera, musimy wygenerować certyfi-
katy dla serwera i klienta. Informacje o
tym, jak tego dokonać, podam w dal-
szej części artykułu. Teraz zajmiemy się
klientem.
Jeśli jednak chcemy jawnie określić
komputer oraz fakt użycia przez nas pro-
tokołu SSL, to możemy w wywołaniu
metody getProxyForURI podać pełne
dane w następujący sposób:
test2 = Pyro.core.getProxyForURI
("PYROLOCSSL://localhost/test_obj")
Ostatni składnik w adresie to nazwa obie-
ktu, którą podaliśmy w drugim argu-
mencie metody connect podczas rejestro-
wania naszej klasy w serwerze.
Jak widać, siłą Pyro jest automaty-
zacja wielu operacji. Istotne zmiany,
które należy wprowadzić, aby zwięk-
szyć bezpieczeństwo aplikacji Pyro,
wykonujemy tylko po stronie serwe-
ra. Uzbrojeni w tę wiedzę w bardzo
łatwy sposób możemy dokonać zmian
w naszym komunikatorze.
Rysunek 2. Nasza aplikacja podczas rozmowy
Generowanie certyikatów
Bezpieczeństwo komunikacji ściśle zależy
od utworzonych certyfikatów. Z tego
powodu, zanim zaczniemy modyfiko-
wać nasz program, wygenerujemy sto-
sowne certyfikaty. Nie jest to trudne,
ale musimy znać obsługę programu
openssl . Możemy ułatwić sobie zada-
nie, jeśli będziemy korzystać ze skryp-
tu pomocniczego, dostępnego w kodzie
źródłowym pakietu OpenSSL. Potrzeb-
ny skrypt można odnaleźć wtedy
www.lpmagazine.org
73
439110635.011.png 439110635.012.png 439110635.013.png 439110635.014.png 439110635.015.png 439110635.016.png
dla programistów
Rysunek 3. Strona domowa PYRO
z pliku newcert.pem do pliku
o nazwie client.pem , a następnie dołą-
czamy do niego opis klucza prywatnego
z pliku newkey.pem .
Z certyfikatem przeznaczonym dla
serwera postępujemy w podobny
sposób, ale nie generujemy już cer-
tyfikatu autoryzacji (jest wspólny dla
klienta i serwera), lecz następny cer-
tyfikat żądania. Podczas wypełnia-
nia pól informacyjnych warto zwrócić
uwagę na to, aby pole Common Name
posiadało inną wartość dla klienta
i dla serwera.
Po zakończeniu operacji tworze-
nia plików client.pem oraz server.pem
brakuje nam jeszcze jednego pliku,
a mianowicie głównego certyfikatu. Znaj-
duje się on w katalogu demoCA pod nazwą
cacert. Wystarczy ten plik przekopiować do
katalogu, w którym znajdują się pozostałe
pliki naszej aplikacji. Zmieniamy nazwę pliku
z certyfikatem na ca.pem oraz kasujemy
początkowe informacje o certyfikacie aż do
linii rozpoczynającej certyfikat:
w katalogu Apps . Pojawia się on w dwóch
wersjach: dla języka Perl, jako plik
o nazwie CA.pl , oraz jako zwykły skrypt
BASH-a – CA.sh . My będziemy korzystać
z tego drugiego (jego kopia znajduje się
również na płycie CD/DVD).
Pierwszym krokiem jest utworzenie
certyfikatu autoryzacji, czyli pliku CA.
Gdy korzystamy z pomocy skryptu CA.sh ,
wystarczy wydać krótkie polecenie:
Otrzymaliśmy wszystkie niezbędne
dane, aby utworzyć certyfikat dla
klienta. Niestety, musimy samodziel-
nie przenieść pewne dane z plików
wygenerowanych przez skrypt CA.sh
i program openssl . Kopiujemy cześć
opisującą certyfikat (Listing 3 przed-
stawia ogólną postać tych plików)
-----BEGIN CERTIFICATE-----
W ten sposób dysponujemy trzema pli-
kami, które są niezbędne do bezpiecz-
nej komunikacji pomiędzy serwerem
a klientem.
Listing 4. Najważniejsze fragmenty programu serwera
# ./CA.sh -newca
#! /usr/bin/python
import sys
import Pyro . core
import Pyro . naming
from Pyro . EventService . Clients import Publisher
Skrypt utworzy kilka katalogów oraz plik
z certyfikatem (znajdzie się on w katalo-
gu demoCA ). Zostaniemy również popro-
szeni o wypełnienie kilku pól.
Następnie generujemy certyfikat żąda-
# identyczna postać jak na Listingu 2
class printCertValidator ( Pyro . protocol . BasicSSLValidator ):
class TalkSrv ( Pyro.core.ObjBase, Publisher ):
nia:
# ./CA.sh -newreq
Pyro . core . initServer ()
Pyro . config . PYRO_TRACELEVEL = 3
Pyro . config . PYRO_NS_HOSTNAME = 'localhost'
P yro.config.PYRO_LOGFILE = 'server_log'
Tutaj ponownie zostaniemy poproszeni
o podanie kilku informacji. Po zakoń-
czeniu procesu powstanie plik o nazwie
newreq.pem . Następnym krokiem jest
podpis certyfikatu żądania:
ns = Pyro . naming . NameServerLocator () . getNS ()
daemon = Pyro . core . Daemon ( prtcol = 'PYROSSL' )
daemon . setNewConnectionValidator ( printCertValidator ())
daemon . useNameServer ( ns )
# ./CA.sh -sign
Powstanie plik o nazwie newcert.pem .
Teraz możemy odczytać frazę klucza
i w tym celu wydajemy polecenie:
uri = daemon . connect ( TalkSrv () , "TalkSrv" )
print "TalkServer is ready."
daemon . requestLoop ()
# openssl rsa < newreq.pem > newkey.pem
74
styczeń 2005
439110635.017.png 439110635.018.png 439110635.019.png 439110635.020.png 439110635.021.png 439110635.022.png
 
Zgłoś jeśli naruszono regulamin