english

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 w menu z lewej strony.


rys. 1 moja elektroniczna Enigma

let's go - dodajmy MFA do OpenVPN!

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ę :

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.

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). Jeśli nie, to zapraszam do mojego opisu w menu po lewej stronie. Starałem się opisać tam 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.


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
15III II V06 16 02
F  P  B
UKW-BGT YC EJ UA RX PN IS WB MH ZVY Z Kaenig4 -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

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.


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


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
16I II III07 11 15
G  K  O
UKW-BWZ AB MO TF RX SG QU VI YN ELW Y Taenig4 -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.).

07/2024 (c) MS  
ms<małpa> helion kropka pl