Passkey op Android: de kleine details die tot grote problemen leiden
Inleiding
Passkey is een technologie die is gecreëerd en wordt onderhouden door de FIDO alliance, opgericht door tal van bekende bedrijven zoals Google, Microsoft en Apple. Het doel van deze technologie, die een implementatie is van de FIDO2-standaarden, is om wachtwoorden te vervangen en te onderdrukken.
De technologie is, op het moment van schrijven, voortdurend in ontwikkeling, wat het moeilijker maakt om te ontwikkelen en te onderhouden.
Een paar weken geleden besloot ik een Android-applicatie te ontwikkelen die deze technologie als authenticatiemechanisme zou gebruiken om te begrijpen hoe het werkt. Dit was niet zo eenvoudig als ik dacht, omdat veel vereisten voor de Android-implementatie niet op één plaats zijn gegroepeerd en verspreid over het web te vinden zijn.
Het doel van dit bericht is om dit probleem op te lossen en enkele hoogtepunten te geven van de veelvoorkomende valkuilen waarin je kunt vallen bij het ontwikkelen van een Android-applicatie die passkeys gebruikt.
De meeste vereisten moeten server-side worden voldaan, en hebben niets te maken met de code van je applicatie zelf. We beginnen met deze vereisten zodat je kunt beginnen met het testen van je passkeys-communicatie onder goede omstandigheden.
Een bekende bestand
Voor de implementatie van passkeys op Android om te communiceren met je server, moet Google, dat de leiding heeft over Android, in staat zijn om het bestand “/.well-known/assetlinks.json” op je server te controleren. Dit bestand moet informatie bevatten over de Android-applicaties die toestemming hebben om met de server te communiceren. Google heeft een tool beschikbaar gesteld om ontwikkelaars te helpen hun bestand te testen.
Alle vereiste informatie en beperkingen zijn hier te vinden. Uit deze documentatie kunnen we leren dat de volgende beperkingen voor het bestand gelden (direct geciteerd):
- Het moe openbaar beschikbaar zijn en niet achter een VPN.
- Het moet openbaar beschikbaar zijn en niet achter een VPN.
- Het moet worden geserveerd met Content-type: application/json.
- Het moet toegankelijk zijn via HTTPS
- Het moet direct worden geserveerd met een HTTP 200-reactie (geen HTTP 300-omleiding).
- Zorg ervoor dat geen robots TXT het verhindert:
- User-agent: *
- Allow: /.well-known/
- User-agent: *
Een geregistreerd domein
Heb je gemerkt dat ik zei dat “Google moet controleren” en niet “Android moet controleren”? Dat komt omdat Android een Google-server zal vragen of je server “legitiem” is en dit niet zelf zal controleren. Dus, als je server niet toegankelijk is vanaf het internet, zal de passkey API van Android weigeren te communiceren en je een cryptische foutmelding geven:
Bovendien, voor zover ik weet, zal het simpelweg gebruiken van een IP-adres niet werken; je server moet achter een domeinnaam staan om door de Google-server te worden gecontroleerd.
Een oorsprong die geen oorsprong is
Tijdens het authenticatieproces bevat het verzoek met de reactie op de uitdaging, een willekeurige string die is ondertekend met de privésleutel van de geselecteerde passkey-invoer, ook een “Origin”-veld in de clientData-parameter. Tot nu toe is er geen probleem… totdat je de inhoud van de Origin leest, wat een handtekening is van het certificaat dat is gebruikt om de APK te ondertekenen, niet een Origin in de traditionele HTTP-betekenis. Bijvoorbeeld, als we de clientDataJson van de credential-manager nemen, kunnen we dit observeren:
Figure 1. Decoderen van de clientDataJSON met CyberChef
Dit kan slecht nieuws zijn als je, net als ik, een bibliotheek zoals passport-fido2-webauthn hebt gebruikt om je te helpen passkeys aan de serverzijde te beheren. Dit is een probleem omdat de bibliotheek deze Origins, die zijn gegenereerd door de passkey API van Android, niet ondersteunt in de clientdata van de verzoeken. Dit betekent dat je server alle authenticatieverzoeken van je Android-applicatie zal weigeren.
Om dit probleem te omzeilen, moet je een manier vinden om de certificaathandtekening in je configuraties te registreren zodat de server deze accepteert. Dit kan betekenen dat je de bibliotheek zelf moet aanpassen als deze de Origins van Android niet ondersteunt. Voor degenen onder jullie die dezelfde bibliotheek hebben gebruikt, heb ik een PR ingediend om het probleem op te lossen.
Het laatste deel van de puzzel
Dit is het enige deel van deze post dat over de code van de Android-applicatie zelf gaat. Om passkey te gebruiken met de Android API, moeten twee hoofdfuncties worden gebruikt met specifieke argumenten:
- createCredentialAsync():Maak een passkey-sleutelpaar en onderteken een uitdaging
- getCredentialAsync(): Gebruik een al bestaand passkey-sleutelpaar om een uitdaging te ondertekenen
Om deze te gebruiken, is een CredentialManager-object vereist, dat kan worden verkregen door er een te maken met behulp van de applicatiecontext: CredentialManager.create(getApplicationContext())
Dit is vrij eenvoudig als we een van hun parameters negeren: het verzoek.
Voor het maken van een nieuw passkey-sleutelpaar moet een JSON worden opgebouwd die aan een specifiek formaat voldoet. De vereiste velden en het formaat van de JSON zijn hier te vinden.
Ter referentie, hier is een voorbeeld van code die ik een paar dagen geleden heb gemaakt:
Figure 2. Genereren van een Passkey-sleutelpaar
Figure 3. De function die wordt gebruikt om de opties te bouwen
Conclusie
Passkey is een geweldige technologie die snel evolueert. Dit komt met een prijs: niet alle vereisten om passkey goed te laten werken in een Android-applicatie zijn goed gedocumenteerd. Deze post was bedoeld om die punten te benadrukken, zodat je je passkey-reis op een rustigere manier kunt beginnen.