Klucze SSH mogą być wykorzystane jako sposób identyfikacji danej osoby przy logowaniu się do zdalnego serwera SSH. Te klucze zawsze występują w parach -- jeden prywatny, drugi publiczny. Pierwszy z nich jest znany tylko nam i powinien być trzymany w sekrecie i pilnie strzeżony. Klucz publiczny z kolei zaś jest przesyłany na każdy serwer SSH, z którym chcemy się połączyć. Gdy serwer jest w posiadaniu naszego klucza publicznego i widzi przy tym, że próbujemy nawiązać połączenie, używa on tego klucza by wysłać do nas zapytanie (challange) -- jest ono zakodowane i musi na nie zostać udzielona odpowiednia odpowiedź, a tej może dokonać ktoś, kto jest w posiadaniu klucza prywatnego. Nie ma innej opcji by rozkodować wiadomość, dlatego też nikt inny nie może udzielić na nią prawidłowej odpowiedzi. To rozwiązanie eliminuje wrażliwość na różne formy podsłuchu -- ten kto nasłuchuje nie będzie w stanie przechwycić pakietów zawierających hasło, bo ono nie jest nigdy transmitowane prze sieć. No i oczywiście jeśli chodzi o samo hasło -- odpadają nam ataki bruteforce pod kątem jego złamania.

Jako, że klucze SSH zawierają wrażliwe informacje, można je zaszyfrować, tak by każdorazowe ich wykorzystanie wymagało podania hasła ale wtedy przy logowaniu się do serwera trzeba podać hasło do klucza zamiast do konta, co niekoniecznie może być wygodniejsze w przypadku ciągłego wywoływania ssh lub scp . Nic też nie zyskujemy przy posiadaniu wielu różnych kluczy -- każdy inny dla każdego kolejnego serwera SSH. Niemniej jednak, mamy do dyspozycji narzędzia, np. gpg-agent, które to pozwalają na dodanie i przechowywanie szeregu kluczy i udzielaniu do nich dostępu przy wykorzystaniu tylko jednego hasła -- mechanizm znany z choćby gnome-keyring. W taki sposób możemy zdefiniować wiele kluczy, przechowywać je w bezpieczny sposób i mieć tylko jedno hasło by nimi wszystkimi zarządzać. W przypadku gpg-agent , możemy także ustawić czas dostępu do klucza. Po jego upłynięciu, trzeba będzie ponownie wprowadzić hasło by wydobyć klucz od agenta.

Konfiguracja gpg-agenta

Zwykle w środowiskach graficznych, gpg-agent startuje po rozpoczęciu sesji graficznej. Ja używam samotnego openboxa i muszę zatroszczyć się sam o skonfigurowanie wszystkich komponentów.

Potrzebna nam będzie linijka startująca gpg-agenta oraz także trzeba wyeksportować pewne zmienne. Dodajemy poniższy blok do autostartu openboxa -- plik /home/morfik/.config/openbox/autostart :

### Start the GnuPG agent and enable OpenSSH agent emulation
gnupginf="${HOME}/.gpg-agent-info"

if pgrep -x -u "${USER}" gpg-agent >/dev/null 2>&1; then
    eval `cat $gnupginf`
    eval `cut -d= -f1 $gnupginf | xargs echo export`
else
    eval `gpg-agent -s --enable-ssh-support --daemon --write-env-file "$gnupginf"`
fi

if [ -f "${HOME}/.gpg-agent-info" ]; then
    eval $(cat ${HOME}/.gpg-agent-info)
    eval $(cut -d= -f 1 < ${HOME}/.gpg-agent-info | xargs echo export)
fi

Dodatkowo musimy umieścić poniższe linijki w pliku ~/.bashrc :

if [ -f "${HOME}/.gpg-agent-info" ]; then
    eval $(cat ${HOME}/.gpg-agent-info)
    eval $(cut -d= -f 1 < ${HOME}/.gpg-agent-info | xargs echo export)
fi

GPG_TTY=$(tty)
export GPG_TTY

Po zresetowaniu openboxa, powinniśmy mieć w systemie poniższy proces:

$ ps -eo user,group,pid,args | grep -i gpg
morfik   morfik    38002 gpg-agent -s --enable-ssh-support --daemon --write-env-file /home/morfik/.gpg-agent-info

Dodatkowo powinniśmy porównać zmienne zawarte w pliku .gpg-agent-info z tymi widocznymi w środowisku terminala:

$ cat /home/morfik/.gpg-agent-info 
GPG_AGENT_INFO=/tmp/gpg-ttSm27/S.gpg-agent:38002:1
SSH_AUTH_SOCK=/tmp/gpg-cxS7ZS/S.gpg-agent.ssh
SSH_AGENT_PID=38002

$ env | grep -i gpg_agent
GPG_AGENT_INFO=/tmp/gpg-ttSm27/S.gpg-agent:38002:1

$ env | grep -i ssh_auth
SSH_AUTH_SOCK=/tmp/gpg-cxS7ZS/S.gpg-agent.ssh

$ env | grep -i ssh_agent
SSH_AGENT_PID=38002

Jeśli wszystko się zgadza, oznacza to, że konfiguracja daemona przebiegła pomyślnie. Dostosujmy jeszcze trochę samą konfigurację agenta -- interesujący nas plik znajdziemy w /home/morfik/.gnupg/gpg-agent.conf . Domyślnie jest w nim sprecyzowanych kilka opcji. My w nim ustawimy dodatkowo te poniższe:

default-cache-ttl 1800
default-cache-ttl-ssh 1800

write-env-file /home/morfik/.gpg-agent-info

pinentry-program /usr/bin/pinentry-gtk-2

Opcje default-cache-ttl oraz default-cache-ttl-ssh ustalają czas (w sekundach) ważności kluczy.

Wybór i generowanie klucza

Są różne typy kluczy SSH -- zwykle wykorzystuje się klucze RSA 1024/2048/4096 bitowe. Innym typem klucza jest ECDSA , który to opiera się o krzywe eliptyczne zapewniając ten sam poziom bezpieczeństwa co klucze RSA, z tym, że sam klucz w przypadku ECDSA jest o wiele krótszy. Istnieje jeszcze inny typ klucza -- ED25519, który to jest zmodyfikowaną wersją ECDSA i powstał w odpowiedzi na podejrzenia pod kątem NSA, czy prawdziwe, tego nie wiem. Na necie natomiast można się natknąć na informację, że te krzywe wykorzystywane przy ECDSA nie są chyba aż tak idealnie krzywe [1] czy coś i mogą stanowić zagrożenie bezpieczeństwa. W każdym razie, poniżej jest tabelka porównująca długość poszczególnych kluczy:

Symmetric  |  ECC2N  |  ECP  |  DH/DSA/RSA
       80  |   163   |  192  |     1024
      128  |   283   |  256  |     3072
      192  |   409   |  384  |     7680
      256  |   571   |  521  |    15360

Jak widzimy, zaledwie 521 bitów wystarczy by zapewnić poziom bezpieczeństwa, który można uzyskać stosując klucze RSA 15360 bitów.

Część oprogramowania może być różnie skonfigurowana i nie wszystkie typy kluczy będą działać prawidłowo. Ja, póki co, zaobserwowałem problemy przy kluczach RSA dłuższych niż 4096 bitów, oraz są także problemy z obsługą kluczy typu ED25519 przez gpg-agenta -- same klucze działają bez problemu.

By wygenerować klucze, wpisujemy poniższe linijki:

$ ssh-keygen -t ecdsa -b 521 -C "$(whoami)@$(hostname)-$(date -I)"
$ ssh-keygen -t ed25519 -C "$(whoami)@$(hostname)-$(date -I)"
$ ssh-keygen -t rsa -b 4096 -C "$(whoami)@$(hostname)-$(date -I)"

Wszystkie utworzone klucze są przechowywane w katalogu ~/.ssh . Każdy też może je podejrzeć, dlatego warto też zadbać o odpowiednie prawa dostępu do plików ale trzeba przy tym pamiętać o jednej istotniej rzeczy -- użytkownik root może i tak obejść te ograniczenia dostępu i jeśli nie ufamy adminowi, lepiej nie trzymać w jego systemie kluczy prywatnych. W każdym razie, jeśli to my jesteśmy administratorem systemu i przy tym mamy w nim wielu użytkowników, możemy zabezpieczyć swoje klucze przed nieuprawnionym dostępem:

$ chmod 700 .ssh/
$ chmod 600 ~/.ssh/*
$ ls -al ~/.ssh | grep -i id
-rw-------  1 morfik morfik  444 Oct  1 22:19 id_ecdsa
-rw-------  1 morfik morfik  283 Oct  1 22:19 id_ecdsa.pub
-rw-------  1 morfik morfik  464 Oct  1 22:21 id_ed25519
-rw-------  1 morfik morfik  111 Oct  1 22:21 id_ed25519.pub
-rw-------  1 morfik morfik 3.3K Sep 30 18:57 id_rsa
-rw-------  1 morfik morfik  744 Sep 30 18:57 id_rsa.pub

Wykorzystanie gpg-agenta

Klucze przy dodawaniu do agenta są odzierane z ewentualnie założonego wcześniej szyfru -- nie potrzebny on już, bo od tego momentu wszystkie klucze będą zaszyfrowane przez agenta. Dodajmy zatem nasze klucze:

$ ssh-add /home/morfik/.ssh/id_ecdsa
Enter passphrase for /home/morfik/.ssh/id_ecdsa:
Identity added: /home/morfik/.ssh/id_ecdsa (/home/morfik/.ssh/id_ecdsa)

$ ssh-add /home/morfik/.ssh/id_rsa
Enter passphrase for /home/morfik/.ssh/id_rsa:
Identity added: /home/morfik/.ssh/id_rsa (/home/morfik/.ssh/id_rsa)

$ ssh-add /home/morfik/.ssh/id_ed25519
Enter passphrase for /home/morfik/.ssh/id_ed25519:
SSH_AGENT_FAILURE
Could not add identity: /home/morfik/.ssh/id_ed25519

Jak widzimy wyżej, klucz id_ed25519 zwraca błąd. W takim przypadku by korzystać z tego klucza, trzeba będzie wpisywać hasło do niego, no chyba, że zrezygnujemy z hasła zupełnie, co nie jest zalecane. Ja generalnie bym wolał już używać kluczy RSA.

Od chwili dodania kluczy do agenta, te są przechowywane w katalogu /home/morfik/.gnupg/private-keys-v1.d/ pod nazwą 40 znakowego ciągu hexalnego + końcówka .key . Ten ciąg to tzw. keygrip, czyli hash wszystkich publicznych parametrów klucza. Jest on również wpisany do pliku /home/morfik/.gnupg/sshcontrol wraz z dodatkowymi informacjami:

# Key added on: 2014-10-01 22:32:10
# Fingerprint:  b0:b2:c9:08:6d:8c:41:33:15:d4:41:d9:93:d6:b8:77
576C2BF129D68EF23D4729F53711D44FEFB49855 0
# Key added on: 2014-10-01 22:32:29
# Fingerprint:  cb:a2:a5:70:01:12:fb:04:99:3d:be:bd:76:9a:29:ef
1B42644E0A0EEB24C8F0152458E7EA2EC87AF108 0

Jeśli mamy zaimportowane klucze w agencie, klucze prywatne w katalogu /home/morfik/.ssh/ nie są nam już do niczego potrzebne.

Przesyłanie klucza publicznego

Musimy teraz skopiować nasz klucz publiczny na serwer, do którego chcemy się podłączyć. Możemy to zrobić na kilka sposobów. Pierwszym z nich jest posłużenie się skryptem ssh-copy-id -- tworzy on listę z odciskami palca kluczy i próbuje ją wykorzystać do zalogowania się do systemu by sprawdzić czy jakiś klucz jest już zainstalowany na zdalnej maszynie. Jeśli mamy kilka kluczy i każdy przy tym ma inne hasło oraz nie korzystamy z gpg-agenta, zostaniemy poproszeni o podanie hasła do każdego z kluczy. Po tej operacji zostanie zebrana lista kluczy, przy pomocy których nie udało się zalogować do systemu, po czym zostaną one dodane do pliku ~/.ssh/authorized_keys na zdalnym systemie, na koncie użytkownika, na które skrypt zdołał się zalogować. Poniżej jest przedstawiony proces dodawania kluczy z wykorzystaniem w/w skryptu:

$ ssh-copy-id 11.22.55.66
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 2 key(s) remain to be installed -- if you are prompted now it is to install the new keys
morfik@11.22.55.66's password:

Number of key(s) added: 2

Now try logging into the machine, with:   "ssh '11.22.55.66'"
and check to make sure that only the key(s) you wanted were added.

Jeśli mamy wiele kluczy i chcemy posłużyć się tylko jakimś określonym z nich, trzeba skorzystać z parametru -i i podać ścieżkę do pliku, przykładowo:

$ ssh-copy-id -i ~/.ssh/id_ecdsa.pub 11.22.55.66

Powyższa metoda nie zawsze jednak jest skuteczna. Jeśli nie działa ona w naszym przypadku, możemy skorzystać z opcji manualnego przeprowadzenia opisanych wyżej operacji:

$ scp ~/.ssh/id_ecdsa.pub morfik@11.22.55.66:
$ ssh morfik@11.22.55.66
$ mkdir ~/.ssh
$ cat ~/id_ecdsa.pub >> ~/.ssh/authorized_keys
$ rm ~/id_ecdsa.pub

W pierwszej linijce, po adresie hosta jest dodany : -- określa on katalog domowy, nie trzeba podawać ścieżki typu /home/morfik/ zamiast niego.

Jeśli mamy zamiar łączyć się do wielu serwerów, dobrze jest upchnąć ich konfigurację w pliku ~/.ssh/config:

Host 22.11.55.66
    User morfik
    IdentitiesOnly yes
    IdentityFile ~/.ssh/id_ecdsa
    CheckHostIP yes
    Port 53214

W powyższym pliku można zdefiniować wiele takich bloków, a wszystkie możliwe opcje są opisane w manie [2].

Dodatkowe zabezpieczenia

Na maszynie zdalnej, po tym jak już przeprowadzimy wszystkie czynności związane z instalacją klucza publicznego, dobrze jest zabezpieczyć plik authorized_keys przed zmianami, tak by żaden nowy klucz nie mógł być dodany do tego pliku -- w przeciwnym wypadku, umożliwiłoby to zalogowanie się komuś trzeciemu na nasze konto. Jeśli nie posiadamy dostępu do konta admina na zdalnym serwerze, zawsze można poprosić administratora systemu by zabezpieczył nam ten plik:

$ chmod 400 ~/.ssh/authorized_keys
# chattr +i ~/.ssh/authorized_keys

By dodatkowo zwiększyć bezpieczeństwo serwera, przydałoby się wyłączyć możliwość logowania przy pomocy hasła -- w przypadku gdy ktoś trzeci będzie się chciał podłączyć do serwera i nie będzie przy tym posiadał klucza prywatnego, będzie on mógł próbować odgadnąć hasło do naszego konta. By zrezygnować z uwierzytelniania opartego o login i hasło, w pliku /etc/ssh/sshd_config zmieniamy poniższe linijki:

PasswordAuthentication no
ChallengeResponseAuthentication no

Po wyłączeniu logowania z użyciem haseł, jeśli stracimy klucz prywatny, nie dostaniemy się wtedy do serwera. Możemy zostawić opcję logowania via hasło, z tym, że samo hasło powinno zawierać dłuższy losowy ciąg znaków. Taki hash można sobie zaszyfrować lokalnie, np. przy pomocy kluczy gpg (lub zwyczajnie schować) i trzymać na wypadek zgubienia klucza prywatnego. Wszelkie próby złamania hasła w takim przypadku są pozbawione sensu, także mamy pewność, że nikt się nie dostanie do systemu przez ewentualne ataki bruteforce na hasło.

Makulatura

Pisane w oparciu o:

https://www.gnupg.org/documentation/manuals/gnupg/ [3]
https://wiki.archlinux.org/index.php/SSH_keys [4]
http://security.stackexchange.com/questions/5096/rsa-vs-dsa-for-ssh-authentication-keys [5]
https://www.debian.org/devel/passwordlessssh [6]


Przypisy:

  1. http://www.hyperelliptic.org/tanja/vortraege/20130531.pdf
  2. http://manpages.ubuntu.com/manpages/trusty/man5/ssh_config.5.html
  3. https://www.gnupg.org/documentation/manuals/gnupg/
  4. https://wiki.archlinux.org/index.php/SSH_keys
  5. http://security.stackexchange.com/questions/5096/rsa-vs-dsa-for-ssh-authentication-keys
  6. https://www.debian.org/devel/passwordlessssh