SSH-sleutels instellen op macOS met Touch ID
Macs beschikken over een ingebouwde zogenaamde Secure Enclave, waarin we "identiteiten" kunnen creëren die simpelweg niet gestolen kunnen worden (tenzij je wellicht vijanden hebt gemaakt van een natiestaat). Deze identiteiten worden beschermd met Touch ID en kunnen worden gebruikt voor SSH-authenticatie. Het gebruik hiervan biedt aanzienlijke voordelen ten opzichte van traditionele SSH-sleutelbestanden:
- De private sleutelmateriaal kan niet worden geëxtraheerd, wordt nooit getoond en kan niet worden gestolen.
- Je krijgt een melding telkens wanneer de identiteit wordt gebruikt.
- Geen sleutelbestanden (zoals
~/.ssh/id_ed25519.puben aanverwanten) die het bestandssysteem vervuilen; er is bovendien geen risico dat ze per ongeluk worden verwijderd.
Deze extra beveiliging is actief telkens wanneer je SSH gebruikt, inclusief git-over-ssh zoals bij GitHub. Het is geweldig dat geavanceerde beveiligingstechnologie standaard beschikbaar is op enkele van de meest verspreide computers ter wereld. Een paar jaar geleden was dit soort zaken alleen te vinden bij CERN en/of in romans van Dan Brown. Laten we kijken hoe dit kan worden ingesteld op je consumenten-Mac.
"Het is essentieel dat de menselijke interface is ontworpen voor gebruiksgemak, zodat gebruikers de beschermingsmechanismen routinematig en automatisch correct toepassen."
— Saltzer en Schroeder, “The Protection of Information in Computer Systems”
Snelstart
We maken een veilige identiteit aan in de Secure Enclave, vertellen SSH vervolgens hoe deze voor authenticatie moet worden gebruikt, en voegen tot slot de sleutel toe aan GitHub.
Stap 1: Maak de identiteit aan die je op deze Mac wilt gebruiken:
sc_auth create-ctk-identity -l ssh-key -k p-256-ne -t bio # één keer uitvoeren om identiteit te maken
Je krijgt de vraag om Touch ID te gebruiken. Je identiteit is nu aangemaakt en veilig opgeslagen (de parameters worden uitgelegd in het diepere technische deel hieronder).
Stap 2: Vertel SSH dat de identiteit moet worden gebruikt (druk bij een vraag om een PIN simpelweg op enter):
ssh-add -K -S /usr/lib/ssh-keychain.dylib # uitvoeren na een herstart
Vanaf dit moment weet SSH dat de identiteit uit de Secure Enclave moet worden gebruikt bij een SSH-verbinding. Omdat SSH geen goed geheugen heeft, moet je dit na elke herstart opnieuw doen.
Stap 3: Telkens wanneer je je sleutel moet registreren bij een nieuwe server (zoals GitHub), voer je dit commando uit:
ssh-add -L # uitvoeren wanneer je de publieke sleutel nodig hebt
Als er meerdere identiteiten worden vermeld, gebruik dan de regel die begint met sk-ecdsa-sha2-nistp256..... Dit is een deelbaar kenmerk (handle) voor de identiteit, en dit is de SSH-sleutel waar GitHub om zal vragen.
De output van ssh-add -L kan worden toegevoegd als een GitHub SSH-sleutel.
Dat is alles wat nodig is voor een superveilige configuratie! De nieuwe identiteit kan nu worden gebruikt met SSH, en je krijgt een Touch ID-prompt tijdens de authenticatie:
git push # triggert de Touch ID-prompt
Niemand kan je identiteit stelen (je kunt hem zelfs niet vrijwillig weggeven, aangezien hij niet uit de Secure Enclave kan worden geëxtraheerd), maar met grote macht komt grote verantwoordelijkheid: als je Mac kapot gaat, gaat de identiteit ook verloren. Je zult dan een nieuwe moeten aanmaken (en deze opnieuw moeten registreren waar dat nodig is, zoals bij GitHub).
Achter de schermen
We gaan nu proberen te begrijpen wat er onder de motorkap gebeurt. We beginnen met het daadwerkelijk aanmaken van de identiteit in de Secure Enclave, kijken daarna hoe SSH via ssh-keychain.dylib mag communiceren met de Secure Enclave, en analyseren vervolgens wat er gebeurt in een volledige SSH-inlogflow.
Deze informatie is het resultaat van het lezen van berichten (zoals het onderzoek van arianvp), handleidingen en experimenten. Veel hiervan is ongedocumenteerd; dit is de minimale informatie die ik nodig had om een mentaal model te vormen van wat er gebeurt en om mezelf ervan te overtuigen dat het inderdaad werkt.
Let op: Sommige van de onderstaande command-outputs zijn ingekort voor de leesbaarheid.
Een identiteit aanmaken op de Secure Enclave
Identiteiten worden beheerd met het scauth commando (waarbij sc staat voor Smart Card). In Apple-termen zijn dit “CryptoTokenKit” identiteiten, of kortweg CTK-identiteiten. Waarom niet Smart Card Identities? Wat is een Smart Card? Geen idee. Er is veel vreemde Apple-terminologie, maar in de praktijk zijn het gewoon private sleutels die zijn opgeslagen in de Secure Enclave:
$ sc_auth list-ctk-identities
Key Type Public Key Hash Prot Label
p-256-ne 458F712F281B53C683F23FC02E69D44DAFDAA3DE bio ssh-key
Als je de Snelstart hebt gevolgd, zou je hier precies één identiteit moeten zien. Laten we het create-ctk-identity commando in detail bekijken:
sc_auth create-ctk-identity -l <label> -k <key type> -t <protection>
Bij gebruik van het sleuteltype p-256-ne maakt dit commando de identiteit aan in de Secure Enclave. Waarschijnlijk gebeurt dit door eerst entropie te verzamelen, vervolgens private sleutelmateriaal te genereren en alles te alloceren wat nodig is voor de administratie, waarbij wordt gewaarborgd dat er alleen via je vingerafdruk bij kan worden gegrepen.
- De label (
-l) is vrij vormgegeven en is slechts een notitie bij de sleutel. - Het sleuteltype (
-k) is een cryptografische instelling (de gebruikte elliptische curve). Op het moment van schrijven worden de volgende waarden geaccepteerd:p-256,p-384,p-521,p-256-ne,p-384-ne. Het suffix-nestaat voor “Non-Extractable”, wat betekent dat de sleutel door de enclave zelf wordt gegenereerd en niet uit de Secure Enclave kan worden gehaald. Voor SSH hebben we alleen ondertekening (signing) nodig, wat door (en in) de Secure Enclave zelf wordt gedaan; op geen enkel moment hoeven of willen we de private sleutel exporteren. Dep-384curve wordt niet ondersteund door (Open)SSH, waardoor de keuze overblijft opp-256-ne. - Bescherming (
-t) kanbioofnonezijn. Bijbiois het gebruik van de sleutel afhankelijk van biometrie (zoals Touch ID). Bijnonekan de sleutel nog steeds niet worden geëxtraheerd (gestolen), maar kunnen programma's SSH stilletjes gebruiken. Ik raadbioaan, maar dit is een persoonlijke keuze; als je geirriteerd raakt door de Touch ID-prompts bij elkegit pull, kun je hier voornonekiezen.
Vertalen tussen SSH en de Secure Enclave
Er is sprake van een impedance mismatch, en SSH weet standaard niet hoe het de identiteit moet gebruiken:
$ ssh -T git@github.com
git@github.com: Permission denied (publickey).
De SSH-agent kan de identiteiten van de Secure Enclave niet direct gebruiken, maar Apple heeft ons voorzien van een kleine vertaallaag in de vorm van ssh-keychain.dylib, een dlopen-able bibliotheek die standaard beschikbaar is op alle recente macOS-versies:
$ ssh-add -K -S /usr/lib/ssh-keychain.dylib
...
Resident identity added: ECDSA-SK SHA256:QZ+Cx6/AkS9sek9INtsXt6t7wuHOtA555CLJu8a8V7w
Hier maken we gebruik van de ingebouwde ondersteuning van ssh-add voor FIDO-stijl authenticators via de -K parameter. Dit laat de SSH-agent weten dat hij geen standaard private sleutel (zoals ~/.ssh/id_ed25519) moet lezen, maar dat de sleutels "extern" zijn opgeslagen. Met alleen -K zou ssh-add zoeken naar publieke sleutels op USB-apparaten (denk aan een YubiKey); we kunnen dit gedrag echter aanpassen door een “security key provider” te registreren met -S <provider>.
Deze provider moet een stateless dynamisch laadbare bibliotheek zijn, wat precies is wat /usr/lib/ssh-keychain.dylib is. De interface is niet gedocumenteerd, maar we kunnen kijken naar wat ssh-keychain.dylib exposeert:
$ dyld_info -exports /usr/lib/ssh-keychain.dylib
/usr/lib/ssh-keychain.dylib:
-exports:
offset symbol
0x00001A2C _C_GetFunctionList
0x00000F30 _sk_api_version
0x00000F38 _sk_enroll
0x0000136C _sk_load_resident_keys
0x00000F40 _sk_sign
Waarschijnlijk is skloadresidentkeys bedoeld om een lijst met (publieke sleutels van) identiteiten terug te geven, en zal sksign een payload ondertekenen met een specifieke identiteit (hier betekent resident dat de sleutel zich bijvoorbeeld in de Secure Enclave bevindt).
SSH-inlogflow met de ssh-keychain provider
Laten we kijken naar wat er nodig is voor authenticatie bij een remote SSH-server. De SSH-agent heeft op elk moment twee dingen nodig: (1) een lijst met (publieke) sleutels die kunnen worden gebruikt voor authenticatie, en (2) een manier om aan een server te bewijzen dat hij over de bijbehorende private sleutel beschikt.
Wanneer ssh-add voor het eerst wordt aangeroepen met -S <provider>, wordt de routine skloadresidentkeys aangeroepen. Deze geeft de publieke sleutels terug van alle resident-sleutels op de FIDO-stijl authenticator. Deze worden opgeslagen in de SSH-agent (samen met hun bijbehorende provider, namelijk ssh-keychain.dylib) en worden later aangeboden als kandidaten aan elke remote SSH-server waarmee we verbinding proberen te maken. (Nu de SSH-agent op de hoogte is van de Secure Enclave-identiteit, kunnen we de publieke sleutel in SSH-stijl bekijken met ssh-add -L).
Stel dat er een SSH-verbinding wordt geïnitieerd: de remote server accepteert een van de door de agent aangeboden publieke sleutels en daagt de client uit om zijn identiteit te bewijzen. De agent roept sksign aan om een handtekening te maken met de private sleutel (die matcht met de geaccepteerde publieke sleutel) en de challenge. Dit wordt doorgestuurd naar de Secure Enclave, die Touch ID activeert. Bij goedkeuring wordt een handtekening voor de challenge geretourneerd. De handtekening wordt teruggestuurd naar de server, die kan bevestigen dat wij — de client — over de bijbehorende private sleutel beschikken.
In de praktijk ziet dit er als volgt uit:
$ ssh -T git@github.com
git@github.com: Permission denied (publickey).
$ ssh-add -K -S /usr/lib/ssh-keychain.dylib
Enter PIN for authenticator:
Resident identity added: ...
$ ssh -vvv -T git@github.com
debug1: get_agent_identities: agent returned 1 keys
debug1: Offering public key: ssh: ECDSA-SK SHA256:QZ+Cx6/AkS9sek9INtsXt6t7wuHOtA555CLJu8a8V7w authenticator agent
debug1: Server accepts key: ssh: ECDSA-SK SHA256:QZ+Cx6/AkS9sek9INtsXt6t7wuHOtA555CLJu8a8V7w authenticator agent
debug3: sign_and_send_pubkey: signing using git@github.com SHA256:QZ+Cx6/AkS9sek9INtsXt6t7wuHOtA555CLJu8a8V7w
< op dit punt verschijnt de Touch ID-prompt >
Authenticated to github.com ([20.250.119.67]:22) using "publickey".
Hi nmattia! You've successfully authenticated, but GitHub does not provide shell access.
De SSH-debuglogs laten zien dat er één sleutel door de agent wordt aangeboden, geaccepteerd en vervolgens wordt gebruikt om een handtekening te maken. Nadat de server de handtekening heeft geverifieerd, wordt de authenticatie verleend.
Een kanttekening: telkens wanneer we de identiteit registreren bij de SSH-agent, vraagt ssh-add om een PIN. Waarom is dat, en waarom werkt het invoeren van een lege PIN? Is dat niet onveilig?
Nee, dat is niet onveilig. Wanneer we ssh-add vertellen een FIDO-stijl authenticator te gebruiken, vermoed ik dat het standaardgedrag is om naar een PIN te vragen, omdat het ervan uitgaat dat we een USB-authenticator (zoals een YubiKey) gebruiken. We omzeilen dit echter met ssh-keychain.dylib; de beveiliging komt hier niet van een PIN, maar van de huid onder je vingertoppen. Hoewel dit niet ideaal is qua gebruiksvriendelijkheid, is het vanuit technisch oogpunt logisch.
Conclusie
Om op te sommen: we kunnen een enigszins cryptisch en slecht gedocumenteerd “Smart Card”-hulpmiddel gebruiken om een “CryptoTokenKit”-identiteit aan te maken in de “Secure Enclave”. Vervolgens kunnen we, via de eveneens slecht gedocumenteerde ssh-keychain.dylib, deze identiteit gebruiken om handtekeningen te genereren die de SSH-client van Apple kan gebruiken.
Er is geen garantie dat in alle ontbrekende documentatie niet staat: “Gebruik dit niet; in de praktijk is dit vreselijk onveilig”. Maar goed, met alle praatjes over beveiliging van Apple zouden ze ons toch niet voorliegen, right?
Alle grapjes opzij: het is erg cool om sci-fi-achtige beveiligingsmodules ingebouwd te hebben in de meeste moderne apparaten. Hoe meer mensen deze gaan gebruiken en aandringen op openheid, hoe beter de ondersteuning van fabrikanten zal worden. We kunnen mooie dingen hebben!
Groetjes,