keyfactor – der registrierte Schlüssel, berührt
Hat der Schlüssel geantwortet, der registriert wurde, und hat eine Person ihn berührt?
keyfactor ist das eine Stück zwischen fido,
das CTAP spricht und keine Richtlinie kennt, und
mfa, das entscheidet, wie viel Nachweis
genug ist, und kein Gerät kennt. Keines importiert das andere; dieses importiert beide.
go get github.com/go-authn/keyfactor@v0.4.2In v0.4.2 verlangt sein go.mod mindestens go-authn/fido v0.6.2 und
go-authn/mfa v0.5.0, die Versionen, die diese Seiten beschreiben.
Die Plattform liefert einen Opener – wie ein Schlüssel auf diesem System gefunden wird – und
behält nur diesen. Alles andere ist überall gleich, wo es CTAP gibt: die Assertion,
die Prüfungen, die Weigerung, den letzten PIN-Versuch einer Person zu verbrauchen.
Beispiel
package main
import (
"context"
"crypto/ecdsa"
"errors"
"fmt"
"github.com/go-authn/fido"
"github.com/go-authn/keyfactor"
"github.com/go-authn/mfa"
)
func main() {
// The platform supplies the Opener: how a key is found on this system.
// This one finds nothing, and says so the way a policy understands.
open := func(context.Context) (fido.Transport, error) {
return nil, keyfactor.Unavailable(errors.New("no security key is plugged in"))
}
// From the registration: the credential ID and its public key, which
// fido.AuthData.PublicKey returns from the attestation.
var pub *ecdsa.PublicKey // nil here, to show the refusal
f := keyfactor.New(open, keyfactor.Options{
RPID: "example.test",
CredentialID: []byte{0x01, 0x02},
PublicKey: pub,
})
fmt.Println(f.Name(), f.Kind())
_, err := mfa.Verify(context.Background(), mfa.Policy{Count: 1}, f)
fmt.Println(err)
}your security key possession
mfa: 1 factor(s) needed, 0 answered: your security key (possession): keyfactor: no public key for the credential; without one any device that answers would pass, so pass the one from the registrationDie Ablehnung kommt, bevor der Opener aufgerufen wird. Mit dem öffentlichen Schlüssel
aus der Registrierung öffnet derselbe Faktor den Schlüssel, fordert von ihm eine Assertion über eine
frische zufällige Challenge an und prüft die Antwort.
Was es ablehnt und warum
Dass ein Gerät antwortet, heißt nicht, dass der Schlüssel antwortet. Eine programmierbare USB-Platine spricht CTAP ebenso gut wie ein Sicherheitsschlüssel, daher wird die Antwort gegen die Registrierung geprüft:
| abgelehnt | Meldung |
|---|---|
bevor irgendetwas geöffnet wird: kein Opener, keine RPID, kein PublicKey oder ein Schlüssel nicht auf P-256 | keyfactor: no opener; …, … needs a relying party id …, … no public key for the credential; …, … is not on P-256 … |
ein Relying-Party-Hash, der nicht SHA-256 von RPID ist | … answered for another relying party |
ein anderes Credential als CredentialID, wenn das Gerät eines nennt | … answered with a credential other than the one asked for |
eine Signatur über die Authenticator-Daten und die Challenge, die nicht von PublicKey stammt | … answered with a signature the credential's key did not make |
kein UP-Flag | … answered without anyone touching it |
kein UV-Flag, wenn eine PIN angegeben wurde | … was asked to establish who holds it and did not |
Der letzte PIN-Versuch wird nicht auf einen Rateversuch verschwendet. Zuerst wird PINRetries abgefragt;
ein Schlüssel, der bis zum Abziehen keine PINs mehr annimmt, wird abgelehnt, ebenso
einer, dem ein Versuch oder weniger bleibt. Nach CTAP sperren drei falsche PINs hintereinander
die PIN eines Schlüssels, bis er abgezogen wird, und ein Versuchszähler, der null erreicht, sperrt
sie endgültig: Nur ein Reset hebt das auf und löscht dabei jedes Credential auf ihm.
Lehnt der Schlüssel selbst eine PIN ab, ist der Fehler der von fido, und ab
fido v0.6.2 nennt er den richtigen der beiden Fälle:
too many wrong PINs in a row; unplug the key and plug it in again (0x34),
wenn das Abziehen die Sperre aufhebt, und
the PIN is blocked: no tries are left, and only a reset, which erases the credentials, unblocks it (0x32),
wenn nur ein Reset sie aufhebt. Vor fido v0.6.2 waren die beiden Sätze vertauscht.
Eine PIN ist kein zweiter Faktor. Die Art bleibt mfa.Possession: Eine in den Schlüssel
eingegebene PIN schützt den Schlüssel und identifiziert gegenüber dieser Maschine niemanden.
Ein leerer Port ist keine Ablehnung. Ein Opener, der nichts findet, umhüllt seinen
Fehler mit keyfactor.Unavailable, was ihn als mfa.ErrUnavailable kennzeichnet, und eine
Richtlinie zählt konnte nicht gefragt werden getrennt von hat Nein gesagt.
Was es nicht schützen kann: Der Hash der PIN erreicht jedes Gerät, das antwortet
CTAP lässt die Plattform dem Schlüssel LEFT(SHA-256(PIN), 16) übergeben, verschlüsselt mit einem
mit dem Gerät selbst vereinbarten Geheimnis, bevor irgendetwas signiert wird. Ein gefälschtes
Gerät wird als Faktor abgelehnt, hat diesen Hash aber erhalten, und eine kurze PIN
fällt einer Offline-Suche zum Opfer. So ist das Protokoll. Verwenden Sie eine lange PIN, und geben Sie
keine in einen Schlüssel ein, dessen Herkunft unbekannt ist.
Wogegen es geprüft wird
Die Tests fälschen den Authenticator, nicht den Transport, gebaut aus
dem eigenen exportierten Framing von go-authn/fido: ein CTAPHID-Handshake, eine CTAP2-Assertion
und ein ClientPIN-Austausch. Die PIN-Hälfte spielt die Seite des Authenticators von
Protokoll zwei unabhängig – mit eigenem ECDH, HKDF und AES-CBC, nach der
Spezifikation geschrieben (fakepin_test.go) –, sodass eine Ableitung, die um ein Byte abweicht,
den Austausch scheitern ließe. forged_test.go ist das Gerät, das kein
Credential hält. Die Coverage wird bei 100 % gehalten, und die Tests laufen auf acht
Architekturen.
Referenz
Erstellt am von tannevaled
Geändert am von tannevaled