Documentation
¶
Overview ¶
Package webauthn asks Windows to authenticate somebody, in pure Go with CGO_ENABLED=0.
It calls the Win32 WebAuthn API in webauthn.dll — the same one browsers use. Windows finds the authenticator, shows its own dialog, collects a PIN or a fingerprint, and hands back a signed assertion.
Why not CTAP, as on Linux and macOS ¶
Because Windows will not let a normal program talk to a security key at all. Since Windows 10 version 1903, opening a FIDO HID device requires elevation: Microsoft closed that door and pointed everyone at this API. A CTAP transport here would work only for administrators, which is not a program anybody should ship. (The one project that does it anyway installs an elevated Windows service and relays CTAPHID over a named pipe — the size of that workaround is the measure of the wall.)
So this package is shaped differently from its siblings, and deliberately. github.com/go-authn/fido is not underneath it: the protocol is Windows's business here, and what comes back is an assertion, not a CTAP reply.
What was proved, and what only somebody could tell you ¶
An assertion says a credential answered and, through its flags, whether the authenticator verified who was holding it. It does NOT say what convinced it. Windows Hello accepts a face, a fingerprint, or a **PIN** — and a PIN is something known, not something anyone is. A caller that read "user verified" as "biometrics" would be reporting the wrong kind of proof.
Two things narrow it, and this package offers both:
- Request.Attachment constrains what may answer at all. CrossPlatform asks for a security key — something carried. Platform asks for the one built into this machine.
- Assertion.Transport reports what actually answered, when Windows says. Asking is a request; this is an observation, and they can differ.
Index ¶
Constants ¶
const COSEAlgorithmES256 int32 = -7
COSEAlgorithmES256 is ECDSA over P-256 with SHA-256: the one algorithm every authenticator supports, and the default here.
const MaxUserIDLength = 64
MaxUserIDLength is WEBAUTHN_MAX_USER_ID_LENGTH. A longer handle is refused by the specification, not merely by Windows.
Variables ¶
var ( // ErrUnsupported is returned off Windows, and on a Windows too old to have // the API -- it arrived in version 1903. ErrUnsupported = errors.New("webauthn: the Windows WebAuthn API is not available here; " + "this package targets 64-bit Windows (amd64, arm64), as go-mswin/win32 does") // ErrCancelled is returned when the person dismissed the dialog, or the // context ended and the operation was cancelled. ErrCancelled = errors.New("webauthn: the operation was cancelled") )
Functions ¶
Types ¶
type Assertion ¶
type Assertion struct {
// AuthenticatorData is the signed statement: relying-party hash, flags,
// signature counter, and any extension output.
AuthenticatorData []byte
// Signature covers AuthenticatorData followed by the SHA-256 of
// ClientDataJSON.
Signature []byte
// ClientDataJSON is what was hashed into the signature. It is returned
// because a verifier needs the exact bytes, not a reconstruction: rebuild
// it and one different space makes every signature look forged.
ClientDataJSON []byte
// CredentialID says which credential answered, which matters when the
// request allowed several.
CredentialID []byte
// UserID is the user handle, present for a discoverable credential.
UserID []byte
// Transport is what actually answered, or [TransportUnknown] when this
// Windows did not say.
Transport Transport
}
Assertion is what Windows returns.
func Assert ¶
Assert is unavailable off Windows.
It reports ErrUnsupported rather than a failure: a Mac has not refused anybody, it has no Windows WebAuthn API. The macOS and Linux siblings are go-macos/fido and go-gnulinux/fido, over github.com/go-authn/fido.
type Attachment ¶
type Attachment uint32
Attachment says which authenticators may answer.
const ( // AnyAttachment lets Windows offer whatever it has. The answer does not // say which kind was used unless [Assertion.Transport] does. AnyAttachment Attachment = 0 // Platform is the authenticator built into this machine: Windows Hello. Platform Attachment = 1 // CrossPlatform is something carried and plugged in: a security key. CrossPlatform Attachment = 2 )
The attachments, numbered as webauthn.h numbers them.
func (Attachment) String ¶
func (a Attachment) String() string
type AttestationPreference ¶ added in v0.2.0
type AttestationPreference uint32
AttestationPreference says how much the authenticator should say about itself when a credential is made.
const ( AttestationAny AttestationPreference = 0 AttestationNone AttestationPreference = 1 AttestationIndirect AttestationPreference = 2 AttestationDirect AttestationPreference = 3 )
The preferences, numbered as webauthn.h numbers them.
type Credential ¶
type Credential struct {
// ID is what a registration returned.
ID []byte
}
Credential names one registered credential.
type Error ¶
type Error struct {
// Op is the call that failed.
Op string
// HResult is the code Windows returned.
HResult uint32
// Name is what WebAuthNGetErrorName calls it, when it could be asked.
Name string
}
Error is a failure reported by Windows, carrying the name Windows gives it.
func (*Error) Cancelled ¶
Cancelled reports whether Windows says the person dismissed the dialog.
It is not a failure of authentication: nobody was refused, somebody changed their mind. A caller that reported it as a refusal would be accusing them.
func (*Error) Unavailable ¶
Unavailable reports whether there was nothing here to ask.
NTE_DEVICE_NOT_FOUND and NTE_NOT_FOUND mean no authenticator could serve the request -- no key plugged in, or none holding the credential that was asked for. NTE_NOT_SUPPORTED means this Windows cannot do what was asked. None of those is somebody failing to authenticate, and a policy counts them separately.
type RegisterRequest ¶ added in v0.2.0
type RegisterRequest struct {
// RPID is the relying party the credential will belong to, and RPName is
// what Windows shows a person. Both are required.
RPID, RPName string
// Origin is what goes in the client data. For a program that is not a web
// page, use "https://" + RPID.
Origin string
// User is who the credential belongs to. ID is required.
User User
// Challenge is the bytes to be signed over. Fresh, always.
Challenge []byte
// Algorithms are the COSE algorithm identifiers to offer, most preferred
// first. Empty offers ES256 alone, which every authenticator supports.
Algorithms []int32
// Exclude lists credentials that must NOT answer, so an authenticator
// already registered here says so instead of making a second one.
Exclude []Credential
// Attachment constrains what may answer.
Attachment Attachment
// Discoverable asks for a credential the authenticator can find without
// being told its id. Read [Registration.Discoverable] to learn whether it
// did: a key with no room left says no and makes an ordinary one.
Discoverable bool
// Verification says whether the authenticator must establish who is
// holding it.
Verification UserVerification
// Attestation says how much the authenticator should reveal about itself.
// The default, [AttestationAny], lets Windows decide.
Attestation AttestationPreference
// TimeoutMilliseconds is guidance to Windows, which may override it.
TimeoutMilliseconds uint32
// Window is the HWND the dialog belongs to. Zero borrows the foreground.
Window uintptr
}
RegisterRequest makes a credential.
type Registration ¶ added in v0.2.0
type Registration struct {
// CredentialID is what a later [Request.Allow] names.
CredentialID []byte
// AuthenticatorData carries the flags, the sign counter, and the new
// credential's public key.
AuthenticatorData []byte
// AttestationObject is the CBOR object a relying party verifies, when it
// verifies attestation at all.
AttestationObject []byte
// Format is the attestation statement format, e.g. "packed" or "none".
Format string
// ClientDataJSON is what was hashed into the signature.
ClientDataJSON []byte
// Transport is what actually answered, or [TransportUnknown] when this
// Windows did not say.
Transport Transport
// Discoverable reports whether a discoverable credential was actually
// made, and Known says whether Windows reported it at all.
//
// ⛔ [Registration.Discoverable] is an OBSERVATION. Asking for one through
// [RegisterRequest.Discoverable] is a request, and an authenticator may
// decline it while still making a perfectly good credential -- which then
// cannot be used without naming its id, and a caller that assumed
// otherwise has locked somebody out of an account they can no longer
// select.
Discoverable, DiscoverableKnown bool
}
Registration is what Windows returns when a credential is made.
func Register ¶ added in v0.2.0
func Register(context.Context, RegisterRequest) (*Registration, error)
Register is unavailable off Windows.
type Request ¶
type Request struct {
// RPID is the relying party: the domain the credential belongs to.
RPID string
// Origin is what goes in the client data, and Windows checks that it
// matches RPID. For a program that is not a web page, use
// "https://" + RPID.
Origin string
// Challenge is the bytes to be signed over. It must be fresh: a repeated
// challenge makes a recorded assertion replayable for ever.
Challenge []byte
// Allow lists the credentials that may answer. Empty asks the
// authenticator for a discoverable one, which it has only if a
// registration asked for that.
Allow []Credential
// Attachment constrains what may answer. See the package documentation for
// why this is how a caller keeps a claim about KINDS honest.
Attachment Attachment
// Verification says whether the authenticator must establish who is
// holding it, rather than only that somebody is.
Verification UserVerification
// TimeoutMilliseconds is guidance to Windows, which may override it. Zero
// leaves the choice to Windows.
TimeoutMilliseconds uint32
// Window is the HWND the Windows dialog belongs to. Zero borrows whatever
// is in the foreground, which is what a console program has to do.
//
// A program that HAS a window should pass its own. Borrowing means the
// modal dialog parents to somebody else's window: it can end up behind
// theirs, or move when they move. go-mswin/win32 makes windows; this field
// is a uintptr rather than a win32.HWND because Request is compiled on
// every platform and that type is not.
Window uintptr
}
Request is one authentication.
type Transport ¶
type Transport uint32
Transport is how an authenticator was reached. Windows reports it in the assertion, from version 4 of that structure onwards.
const ( TransportUnknown Transport = 0 TransportUSB Transport = 0x01 TransportNFC Transport = 0x02 TransportBLE Transport = 0x04 TransportTest Transport = 0x08 TransportInternal Transport = 0x10 TransportHybrid Transport = 0x20 TransportSmartCard Transport = 0x40 )
The transports, as the WEBAUTHN_CTAP_TRANSPORT_* bits.
func (Transport) Carried ¶
Carried reports whether the authenticator was a separate object rather than part of this machine.
It is the honest half of the question a policy wants to ask. "internal" is this computer; usb, nfc, ble, smart-card and hybrid are all something brought to it. An UNREPORTED transport answers neither way, and this says so through ok rather than guessing -- older Windows fills in no transport at all, and treating silence as "not carried" would quietly downgrade every security key on those machines.
type User ¶ added in v0.2.0
type User struct {
// ID is the user handle. It is returned in a later assertion, so it should
// identify the account WITHOUT being the account name -- a random 32 bytes
// stored beside the account, not an email address, which is a WebAuthn
// privacy rule rather than a preference of this package.
ID []byte
// Name is the account name, e.g. "jane@example.test".
Name string
// DisplayName is what a person is called, e.g. "Jane Smith".
DisplayName string
}
User is who the credential belongs to.
None of this is a secret and none of it is checked by anybody: Windows shows Name and DisplayName in its own dialog so a person can tell one credential from another, and hands ID back to a relying party later.
type UserVerification ¶
type UserVerification uint32
UserVerification says how hard Windows should insist on knowing who is there.
const ( VerificationAny UserVerification = 0 VerificationRequired UserVerification = 1 VerificationPreferred UserVerification = 2 VerificationDiscouraged UserVerification = 3 )
The requirements, numbered as webauthn.h numbers them.
func (UserVerification) String ¶
func (v UserVerification) String() string