OpenVPN z wykorzystaniem szyfrowania... Enigmy!?
Spis treści
Kolega poprosił mnie abym napisał coś krótkiego związanego z bezpieczeństwem IT. Szczerze mówiąc nie miałem ani weny ani pomysłu. Odmówiłem wykręcając się brakiem czasu, zwłaszcza że miałem nową zabawkę - elektroniczną Enigmę do zlutowania. Jednakże zgłębiając tematykę i historię łamania szyfrogramów Enigmy pomyślałem, że może dałoby się te dwa tematy jakoś luźno połączyć. W ten sposób pół żartem, pół serio napisałem instrukcję implementacji mechanizmu dwu-etapowego uwierzytelniania (MFA) w OpenVPN-ie z wykorzystaniem szyfrowania Enigmy i prawdziwej niemieckiej książki kodów. Więcej na temat samej Enigmy (tej prawdziwej jak i elektronicznej), zasady działania, ciekawostek - znajdziesz tu: https://enigma.3d.pl/opis.
Let's go - dodajmy MFA do OpenVPN!
rys. 1 moja elektroniczna Enigma
Jakiś czas temu wdrażałem nową bramkę OpenVPN i pamiętam, że nie było wtedy gotowego mechanizmu MFA dostępnego dla OpenVPN. Aby dodać dwu-etapowe uwierzytelnianie napisałem swój skrypt uwierzytelniający i podpiąłem go po stronie serwera (auth-user-pass-verify).
Jeśli w configu OpenVPN, po stronie klienta, dopiszesz dyrektywę: auth-user-pass spowoduje to, że OpenVPN jeszcze przed połączeniem wyświetli monit o podanie użytkownika i hasła - patrz rys.2. Hasło to (oraz login) możemy wykorzystać właśnie w celu przeprowadzenia dodatkowego uwierzytelnienia (MFA).
rys.2 OpenVPN - okienko dialogowe z możliwością wprowadzenia użytkownika i hasła
OpenVPN w pierwszej kolejności sprawdzi podstawowe metody zabezpieczeń - tj. zgodność klucza ta.key, datę ważności i prawidłowość certyfikatu użytkownika (czy został podpisany przez nasze zaufane CA, czy nie został odwołany i umieszczony na liście CRL itd.). Dopiero gdy wszystkie zabezpieczenia w warstwie TLS powiodą się, wówczas klient prześle do serwera wprowadzone przez użytkownika parametry. Aby serwer mógł zweryfikować wprowadzone dane (tj. użytkownika i hasło), należy w jego pliku konfiguracyjnym dodać linię :
auth-user-pass-verify /etc/openvpn/server/mfa_helper.bash via-file
gdzie /etc/openvpn/server/mfa_helper.bash to skrypt weryfikujący wprowadzone dane. Serwer OpenVPN wywoła wówczas wskazany skrypt i przekaże do niego ścieżkę tymczasowego pliku, w którym zapisane będą wprowadzone przez użytkownika dane (credentiale).
/etc/openvpn/server/mfa_helper.bash /sciezka/do/tymczasowego/pliku/z/credentialami
Skrypt należy napisać samemu, gdyż twórcy OpenVPN-a pozostawiają nam tutaj zupełną dowolność co do metod weryfikacji wprowadzonych danych jak i użytych do tego narzędzi. Skrypt możesz napisać w bashu, perlu, pythonie - czymkolwiek. Nazwa użytkownika oraz hasło zapisane są w pliku tymczasowym odpowiednio w pierwszej i drugiej linii. Przykładowy - najprostszy skrypt napisany w bashu pokazałem na listingu 1.
#!/bin/bash
if [ "$#" -ne 1 ] ; then
echo "bad syntax"
exit 1
fi
# odczytujemy uzytkownika i hasło z przekazanego jako parametr pliku tymaczasowego
readarray -t lines < $1
username=${lines[0]}
password=${lines[1]}
if [[ "$password" == "mySeCrEtPaSs" ]]; then
#echo "ok"
exit 0
fi
#echo "bad password"
exit 1
## eof ##########################################################
listing 1. Najprostszy skrypt weryfikujący wprowadzone dane.
Dla OpenVPN-a ważny jest tak naprawdę kod z jakim skrypt zakończył działanie. Jeśli skrypt zakończy działanie z kodem 0 oznacza to, że uwierzytelnienie powiodło się i OpenVPN umożliwi zestawienie tunelu. Jeśli zakończymy skrypt kodem 1 (błąd) wówczas połączenie zostanie przerwane!. Nie musisz martwić się o uprawnienia jak i kasowanie pliku tymczasowego - OpenVPN sam o to dba.
Tyle teorii odnośnie sposobu w jaki OpenVPN umożliwia dodatkową weryfikację wprowadzonych przez użytownika danych. Tak naprawdę w tym momencie mógłbym zakończyć ten tutorial. Wybór dalszej implementacji zależy bowiem od środowiska w firmie, inwencji admina czy przyjętej polityki. Możesz tutaj podpiąć skrypt odpytujący serwer radiusa. Możesz pokusić się o uwierzytelnienie przez Active Directory. Możesz także dodać dość popularny mechanizm TOTP (Time-based one-time password). Świetnie nada się w tym celu biblioteka do Pythona pyotp. Być może zbiorę się w sobie niebawem i opiszę dokładnie mechanizm TOTP jako MFA do OpenVPN-a. Tymczasem miało być o Enigmie!
MFA z wykorzystaniem szyfrów Enigmy
W tej części zakładam, że czytelnik zna podstawy działania maszyny Enigma i wie czym są pojęcia Grundstellung (ustawienie wirników), Ringstellung (ustawienia pierścieni) czy też Steckerbrett verbindungen (połączenia na krosownicy kablowej). Starałem się opisać wszystkie te najważniejsze terminy prosto i konkretnie.
Po stronie serwera będziemy potrzebowali programu aenig4. Jest to konsolowy emulator Enigmy M4. Po rozpakowaniu ZIP-a program należy skompilować (jest też gotowa binarka pod Windows). Upewnij się, że w swojej dystrybucji linuksa masz zainstalowany kompilator gcc, program make itd. W przypadku Debiana i jego pochodnych mam tutaj na myśli pakiet build-essential. Upewnij się też czy masz zainstalowany pakiet help2man i w razie potrzeby doinstaluj (sudo apt-get install help2man). Na listingu 2 przedstawiłem proces kompilacji i instalacji.
root@msdev:/src/aenig4-master# ./bootstrap
autoreconf: export WARNINGS=
autoreconf: Entering directory '.'
autoreconf: configure.ac: not using Gettext
autoreconf: running: aclocal
autoreconf: configure.ac: tracing
autoreconf: configure.ac: not using Libtool
autoreconf: configure.ac: not using Intltool
autoreconf: configure.ac: not using Gtkdoc
autoreconf: running: /usr/bin/autoconf
autoreconf: running: /usr/bin/autoheader
autoreconf: running: automake --add-missing --copy --no-force
autoreconf: Leaving directory '.'
root@msdev:/src/aenig4-master# ./configure --prefix=/usr/local/aenig4 && make && make install
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a race-free mkdir -p... /usr/bin/mkdir -p
checking for gawk... no
checking for mawk... mawk
checking whether make sets $(MAKE)... yes
checking whether make supports nested variables... yes
checking for gcc... gcc
[ ... ]
[ ... ]
/usr/bin/install -c -m 644 AUTHORS README NEWS COPYING '/usr/local/aenig4/share/doc/aenig4'
make[2]: Opuszczenie katalogu '/src/aenig4-master'
Listing 2. kompilacja programu aenig4
Po stronie klienta (użytkownika) posłużymy się emulatorem Enigmy pod Androida. Teraz musisz zastanowić się, czy ustawienia parametrów Enigmy (wirniki, pierścienie) będą generowane na podstawie jakichś zmiennych środowiskowych (data, czas, adres IP etc.) czy też na podstawie wcześniej wygenerowanej listy (odpowiednik niemieckiej książki kodów). Na potrzeby niniejszego artykułu przyjąłem, że ustawienia wirtualnej Enigmy będą brane z wcześniej wygenerowanej listy kodów - dokładnie jak miało to miejsce podczas II Wojny. Przygotowałem w tym celu specjalny generator ustawień Enigmy. Jednakże aby uczynić nasz przykład jeszcze bardziej realistyczny wykorzystamy tutaj istniejący niemiecki dokument - patrz rys.3.
rys. 3 - miesięczna lista kodów obowiązująca w październiku 1944 (CSV)
Zauważ, że powżysza lista przygotowana jest dla Enigmy M3. Na serwerze mamy jednak emulator Enigmy M4 (program aenig4). Będziemy musieli to uwzględnić, mianowicie w skrypcie na serwerze będziemy musieli ustawić czwarty wirnik w pozycji "A". Więcej na temat kompatybilności Enigmy M3 z M4 oraz innych ciekawostek znajdziesz w osbnych wpisach w menu z lewej strony ekranu. Jak widać na rysunku 3 nie mamy tutaj początkowych ustawień wirników (Grundstellung). Na potrzeby tego artykułu przyjąłem, że ustawienia wirników będą przedostatnią grupą kolumny Kenngruppen. Lista kodów z rys. 3 nie zawiera także informacji, którego reflektora (Umkehrwalze) należy użyć. W takim przypadku używano reflektora B i my także tak uczynimy. Zatem, dla 15-tego dnia ustawienia wyglądają następująco:
dzień | ustawienia wirników (Walzenlage) |
ustawienia pierścieni (Ringstellung) |
reflektor (UKW) |
ustawienia łącznicy (stecker verb.) |
ustawienia początkowe (Grundstellung) |
parametry wywołania aenig4 |
---|---|---|---|---|---|---|
15 | III II V | 06 16 02 F P B |
UKW-B | GT YC EJ UA RX PN IS WB MH ZV | Y Z K | aenig4 -k "b beta III II V 01 06 16 02 AYZK GT YC EJ UA RX PN IS WB MH ZV" |
rys. 4 - konfigurowanie ustawień
rys. 5 - ustawienia początkowe
rys. 6 - zakodowana testowa wiadomość
Ustaw aplikacje w telefonie zgodnie z parametrami z rys. 4. Następnie wpisz na wirtualnej klawiatuże TESTOWAXWIADOMOSC. Teraz na serwerze VPN wywołaj polecenie
echo TESTOWAXWIADOMOSC | aenig4 -k "B beta III II V 01 06 16 02 AYZK GT YC EJ UA RX PN IS WB MH ZV" --filter
marek@MS:~$ echo TESTOWAXWIADOMOSC | aenig4 -k "B beta III II V 01 06 16 02 AYZK GT YC EJ UA RX PN IS WB MH ZV" --filter
UFJSCLSJUKWXGIPKK
Porównaj teraz wynik polecenia z tekstem jaki pojawił się w aplikacji na telefonie. Dla powyższych ustawień zakodowany tekst to: UFJSCLSJUKWXGIPKK. Jeśli ciągi znaków nie zgadzają się, musisz dokładnie przeanalizować każdy z parametrów. W szczególności łatwo o pomyłkę w łącznicy. Zauważ, że w przypadku aplikacji na telefon ustawienia pierścieni podajemy literami, a w linuksie cyframi. Tutaj także łatwo o pomyłkę.
Parser pliku CSV - generator ustawien Enigmy
Pozostało nam napisanie parsera książki kodów dla danego miesiąca. Zakładam tutaj, że książka kodów będzie dostępna w postaci pliku CSV na serwerze. OpenVPN podczas łączenia klienta musi przeanalizować (przeparsować) ten plik aby odczytać parametry Enigmy dla danego dnia. Następnie skrypt musi wywołać emulator Enigmy aby uzyskać zakodowaną wartość i porównać ją z hasłem przekazanym przez użytkownika. Poniżej przedstawiam wycinek pliku CSV dla listy kodów z rysunku 3.
17;IV I II;12 08 21;ME HX BF WY ZD TR FJ AG IL KQ;tak pjs kdh jvh
16;I II III;07 11 15;WZ AB MO TF RX SG QU VI YN EL;pzg evw wyt iye
15;III II V;06 16 02;GT YC EJ UA RX PN IS WB MH ZV;bhe xzm yzk evp
Listing 3. wycinek pliku schluesselTafel.csv
Na listingu 4. przedstawiłem przykładowy helper do OpenVPN-a. Skrypt napisany w bashu, parsuje plik CSV i porównuje zaszyfrowane hasło z ciągiem wprowadzonym przez użytkownika w kliencie OpenVPN.
#!/bin/bash
if [ "$#" -ne 1 ] ; then
echo "bad syntax"
exit 1
fi
# odczytujemy uzytkownika i hasło z przekazanego jako parametr pliku tymaczasowego
readarray -t lines < $1
providedUsername=${lines[0]}
providedPassword=${lines[1]}
LOGFILE='/etc/openvpn/server/logs/mfa.log'
#zmienna pomocnicza
ERROR=0
egrep -q "^`date +%d`;" schluesselTafel.csv || exit 1
DAY=`date +%d`
Walzen=`egrep "^$DAY;" /etc/openvpn/server/schluesselTafel.csv |awk -F';' '{print $2}'`
Ringst=`egrep "^$DAY;" /etc/openvpn/server/schluesselTafel.csv |awk -F';' '{print "01 "$3}'`
Grundst=`egrep "^$DAY;" /etc/openvpn/server/schluesselTafel.csv |awk -F';' '{print $5}' |awk '{print "A" $3}' | tr [:lower:] [:upper:]`
Stecker=` egrep "^$DAY;" /etc/openvpn/server/schluesselTafel.csv |awk -F';' '{print $4}'`
ClearTextPass=`egrep "^$DAY;" /etc/openvpn/server/schluesselTafel.csv |awk -F';' '{print $5}' |awk -F' ' '{print $1$2}'| tr [:lower:] [:upper:] | tr -d ' '`
## sprawdzamy czy aenig4 nie zglosi bledu. Jesli tak, ustawiamy zmienna ERROR na wartosc 1
echo "$ClearTextPass" | /usr/local/aenig4/bin/aenig4 -k "B beta $Walzen $Ringst $Grundst $Stecker" --filter > /tmp/enigma_output.txt || ERROR=1
if [ $ERROR -eq 1 ] ; then
echo "500 `date +%d-%m-%Y__%H:%M` => $providedUsername MFA - blad ustawien Enigmy: `cat /tmp/enigma_output.txt`" >> $LOGFILE
exit 1
fi
Encrypted=`echo "$ClearTextPass" | /usr/local/aenig4/bin/aenig4 -k "B beta $Walzen $Ringst $Grundst $Stecker" --filter`
###echo "ClearTextPass: $ClearTextPass => $Encrypted" >> $LOGFILE
if [ $providedPassword == $Encrypted ] ; then
echo "200 `date +%d-%m-%Y__%H:%M` => $providedUsername MFA authorization OK!" >> $LOGFILE
exit 0
fi
## skoro stringi sie nie zgadzaja wychodzimy z kodem 1
echo "403 `date +%d-%m-%Y__%H:%M` => $providedUsername MFA authorization FAILED" >> $LOGFILE
exit 1
#### EOF ######################
Listing 4. Przykładowy helper dla OpenVPN-a napisany w bashu.
Pozostaje jeszcze kwestia hasła jakie podlegać będzie szyfrowaniu (co użytkownik ma wprowadzić jako tekst do zakodowania). Na potrzeby artykułu przyjąłem, że hasło będą stanowić dwa pierwsze bloki ostatniej kolumny (Kenngruppen) - pisane bez spacji i z wielkiej litery. W omawiannym przypadku będzie to BHEXZM. Możesz wymyśleć cokolwiek innego, ważne aby helper po stronie serwera "znał" to hasło, by móc je zaszyfrować i potem porównać z wprowadzonym przez użytkownika hasłem.
Dalszą część artykułu pisałem następnego dnia, tj. 16/07. Na rysunkach 7-10 przedstawiono zrzuty z aplikacji Androidowej dla tego dnia. Zakodowane hasło należy podać w kliencie OpenVPN.
rys. 7-10 ustawienia emulatora Enigmy
dzień | ustawienia wirników (Walzenlage) |
ustawienia pierścieni (Ringstellung) |
reflektor (UKW) |
ustawienia łącznicy (stecker verb.) |
ustawienia początkowe (Grundstellung) |
parametry wywołania aenig4 |
---|---|---|---|---|---|---|
16 | I II III | 07 11 15 G K O |
UKW-B | WZ AB MO TF RX SG QU VI YN EL | W Y T | aenig4 -k "B beta I II III 01 07 11 15 AWYT WZ AB MO TF RX SG QU VI YN EL" |
Jeśli wprowadzone hasło będzie zgodne z ciągiem wygenerowanym na serwerze, wówczas skrypt na serwerze zakończy działanie z kodem 0 i połączenie VPN zestawi się. Gdyby się tak nie stało należałoby sprawdzić jakie hasło wygenerował serwer. W tym celu najprościej będzie zapisać je do pliku loga wraz z pozostałymi parametrami wirtualnej Enigmy. Być może w samym pliku CSV jest dzieś błąd i emulator Enigmy zgłasza błąd.
Podobny mechanizm weryfikacji dwu-etapowej możesz zastosować dla innych usług - np. serwera SSH. Wówczas możnaby wykorzystać któryś z istniejących modułów / rozszerzeń dla PAM (pam_script.so, pam_python.so etc.).
Zobacz nasze propozycje
-
- Druk
- PDF + ePub + Mobi
(44,85 zł najniższa cena z 30 dni)
41.40 zł
69.00 zł (-40%) -
- Druk
- PDF + ePub + Mobi
Niedostępna
-
- Druk
- PDF + ePub + Mobi
Czasowo niedostępna
-
- Druk
- PDF + ePub + Mobi
Niedostępna