The phrase is multi-factor, not multi-check. A passphrase and a
recovery code are both things you know: whoever learned one has usually learned the
other from the same place, and requiring both buys far less than the count suggests. So the
classification is the point — Policy{Count: 2, DistinctKinds: true} is what
two-factor is normally taken to mean, and two passphrases never satisfy it.
And a factor answers a different question from a directory.
Is this person here, now? is not who is this person, and what would prove them?
A site's people are already written down somewhere — a database, an LDAP server, a file — and
what proves each of them is not one thing: NTLMv2 needs the password or its
MD4(UTF16LE(password)), so an LDAP bind cannot authenticate an SMB
session, and neither can a bcrypt. directory
is one model over the three sources that carries what proves somebody rather than a password
field, and authnd serves it back out over
LDAP, because almost everything speaks LDAP.
Nothing here is platform-specific. A Transport moves
64-byte reports to and from an authenticator, and where those come from — IOKit on macOS,
hidraw on Linux, WebHID in a browser — is somebody else's problem. The macOS one is
go-macos/fido: four calls to IOKit, no framework,
no driver, no cgo. A FIDO key opens with no entitlement and no user consent, which is what makes
the whole path reachable from Go at all.
The reference was read first, and it earned its keep. In pure Go,
client-side, there was nothing to reuse: the reference of the field is Yubico's
libfido2, written in C, whose one serious Go
binding goes through cgo. So libfido2 and the CTAP specification are read here as
documentation. That reading caught two faults a key would never have shown, because a
short ping comes back the same either way: CTAPHID_KEEPALIVE is not an answer, and
the message limit is the framing's rather than the length field's. CBOR, by contrast, does
have a Go reference — fxamacker/cbor —
so it is used rather than rewritten.
Repositories
fido
libprotocol
v0.5.0 · github.com/go-authn/fido
The FIDO client-to-authenticator protocol over any transport: CTAPHID framing, the handshake, GetInfo, MakeCredential, GetAssertion, ClientPIN, and the authenticator data parsed down to a *ecdsa.PublicKey.
mfa
libpolicy
v0.2.0 · github.com/go-authn/mfa
How many factors, of which kinds, and what to say when some refuse. A factor that could not be asked has not failed; a factor not reached is not reported as failed; and there is no default policy.
v0.1.0 · github.com/go-authn/keyfactor
The piece between the two: a security key as an mfa.Factor, over any transport. It lived in a platform package until a second copy was about to be written that differed in one line.
totp
libfactor
v0.1.0 · github.com/go-authn/totp
Time-based one-time passwords (RFC 6238), and the factor a policy asks. A code is valid for a whole step, so the RFC requires the verifier to refuse a second use of one – and every step up to the last accepted, not merely the same one.
v0.4.0 · github.com/go-authn/directory
Who somebody is and what proves them, over a database (sqldir), an LDAP server (ldapdir) or a file (hcldir). What proves somebody is not one thing: an LDAP bind cannot authenticate an SMB session, and neither can a bcrypt.
oidc
libidentity
v0.1.0 · github.com/go-authn/oidc
An OpenID Connect token verified into an identity: discovery, JWKS, and the refusals that matter. For the things that speak a token instead of a directory.
v0.2.0 · github.com/go-authn/authnd
An LDAP server for people who are somewhere else – a SQL database, a configuration file, another directory – because almost everything speaks LDAP. The second factor goes on the end of the password, which is the only field a simple bind has.
krb5
libprotocol
v0.1.0 · github.com/go-authn/krb5
The acceptor half of Kerberos, and only that half: an AP-REQ verified against a keytab and answered with the AP-REP gokrb5 refuses to build – without which a GSS-API client asking for mutual authentication completes no context at all. No KDC, no tickets issued. sec=krb5 and sec=krb5i, not sec=krb5p.
Every factor answers one question — is this person here, now?
— and the policy counts the answers by KIND, because two of the same thing is one factor.
A directory answers the other one, and carries what proves each person rather than a
password field. Pure Go with CGO_ENABLED=0, gated in CI on Linux, macOS and
Windows. The standard is 100% statement coverage: fido, mfa and
keyfactor are gated on it and hold it, and the newer repos carry a floor CI will
not let them fall below, with the shortfall printed on every run. The protocol is proven
against a real security key: registered, asserted, and the signature verified with the public
key the key itself handed out — which is what proves the parsing rather than asserting
it, since a COSE key could not verify anything if its extent were wrong by a byte. The LDAP
side is proven the same way, by something that is not this code:
OpenLDAP's own ldapsearch reads the server, and is refused with
Invalid credentials (49) for a wrong password and for the empty one. BSD-3-Clause
throughout. Consumers: go-macos/fido for the
transport, and go-fileshare/fileshare,
which describes its people with the same users block.