fido

package module
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 3, 2026 License: BSD-3-Clause Imports: 20 Imported by: 0

README

fido

Go Reference License CI

Speaks the FIDO client-to-authenticator protocol to a security key, in pure Go with CGO_ENABLED=0, on any operating system.

k, err := fido.Open(ctx, transport)   // handshake, channel, capabilities
defer k.Close()
fmt.Println(k)                        // YubiKey FIDO (CTAPHID v2, firmware 5.7.4, wink, ctap2, ctap1)
err = k.Wink(ctx)                     // the key blinks: which one is this?

Nothing here is platform-specific. A Transport moves 64-byte reports to and from one 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.

The second factor

An operating system already offers the first: Touch ID, a watch, a passcode. Those answer is the person at this machine the one who unlocked it? A security key answers a different question: is the thing they carry present, right now, and did a human touch it? Multi-factor means asking both and getting two independent answers.

Reading the reference first

In pure Go, client-side, there was nothing to reuse. The reference of the field is Yubico's libfido2, written in C; its one serious Go binding wraps it through cgo and has not moved in ten months, and the pure-Go candidates are small and young. So libfido2 and the CTAP specification are read here as documentation, and the code is owned.

Reading them first caught two faults that testing against a key would not have, because a short ping comes back the same either way:

  • CTAPHID_KEEPALIVE is not an answer. A key waiting for a finger sends one about every hundred milliseconds for as long as the person takes. A reader that returns the first complete message hands back a status byte instead of the reply — and does it early, so it reads as the key talking nonsense rather than as a missing feature.
  • The message limit is the framing's, not the length field's. Two length bytes would allow 65535; the framing reaches 7609, because past that the sequence numbers run into the high bit and a key reads them as the start of a new message. An earlier draft used 65535 and carried a comment asserting the framing reached it. The arithmetic was wrong.

What is here

The CTAPHID framing, the handshake, channel negotiation, capabilities, ping and wink — covered to 100%, with a fake transport that reassembles what it is sent, so a key that refuses, a key that stalls, a key that keeps saying it is busy and another program talking on the same key are all ordinary tests.

GetInfo asks a CTAP2 authenticator to describe itself -- versions, extensions, AAGUID, options -- and is decoded against a reply CAPTURED FROM A REAL KEY rather than one written by hand from the specification. A fixture written from the spec only proves the code agrees with whoever wrote the fixture.

CBOR comes from fxamacker/cbor, which is what go-webauthn/webauthn depends on and which ships CTAP2EncOptions for exactly this. Unlike the CTAP client space, CBOR in Go has a reference, so this uses it.

MakeCredential registers a credential and GetAssertion asks the authenticator to sign, with the parameters numbered as the specification numbers them and the authenticator data parsed -- flags, sign count, credential id, COSE public key, extensions.

The authenticator-data parser is checked against a sample from go-webauthn/webauthn's own tests: an EXTERNAL witness. Every other fixture here came from hardware or from this code, so it is the only one that can catch those two agreeing with each other and both being wrong. It caught something immediately -- the prose describing that sample says its flags are 0x45, and the bytes say 0x41. The bytes win.

AuthData.PublicKey turns the credential's COSE key into a *ecdsa.PublicKey, so a caller can check the signature with the standard library and nothing else. That is a CONVERSION, not a verification: what counts as valid -- which algorithms, what to do about a sign counter that went backwards -- belongs to whoever is protecting something.

All of it has been run against a real YubiKey FIDO 5.7.4: registered, asserted, and the signature verified against the public key the key itself handed out.

ClientPIN is here: both PIN/UV auth protocols, PINRetries, KeyAgreement and PINToken, and a token wires into MakeCredential and GetAssertion to turn "somebody touched the key" into "somebody who knows its PIN touched it".

The two protocols are not a version number. Protocol one hashes the ECDH output once and uses the same 32 bytes as both AES and HMAC key, with a zero initialisation vector and a 16-byte truncated HMAC; protocol two derives two separate keys through HKDF, prepends a fresh IV, and truncates nothing. A package that treated them as interchangeable would pass every round-trip test and be wrong on the wire, so a test plays the AUTHENTICATOR's side and checks that both sides derive the same secret.

The status-code table comes from libfido2's err.h, and it had to: an earlier version was written from memory and five of its entries were wrong. A real key caught it by answering 0x35 -- no PIN set -- which the table did not name while claiming 0x2F meant that.

Documentation

Overview

Package fido speaks the FIDO client-to-authenticator protocol to a security key, in pure Go with CGO_ENABLED=0 and on any operating system.

Nothing here is platform-specific. A Transport carries 64-byte reports to and from one authenticator, and where those reports come from -- IOKit on macOS, hidraw on Linux, WebHID in a browser -- is somebody else's problem. See go-macos/fido for the macOS one.

The second factor

An operating system already offers the first factor: Touch ID, a watch, a passcode. Those answer one question -- is the person at this machine the one who unlocked it? A security key answers a different one: is the thing they carry present, right now, and did a human touch it? Multi-factor means asking both, and getting two independent answers.

CTAPHID

A message is cut into 64-byte reports: one INITIALISATION packet carrying the command and the total length, then CONTINUATION packets numbered from zero. A channel id is negotiated first, with CmdInit, and every later message carries it.

Yubico's libfido2 and the CTAP specification were read as documentation for this, and the reading caught two faults that testing against a key would not have: see CmdKeepalive and MaxMessage.

Index

Constants

View Source
const (
	CmdMakeCredential byte = 0x01
	CmdGetAssertion   byte = 0x02
)

The CTAP2 commands that make and use credentials.

View Source
const (
	// CmdPing echoes its payload back. It is the honest way to check that a
	// channel works, because a key that answers a ping with the same bytes has
	// received, framed and returned them.
	CmdPing byte = 0x01
	// CmdInit negotiates a channel. Sent on [BroadcastChannel] with an
	// eight-byte nonce, it comes back with that nonce, a fresh channel id, and
	// what the key can do.
	CmdInit byte = 0x06
	// CmdWink makes the key blink or flash. It changes nothing and stores
	// nothing, and it is the cheapest way to ask a person WHICH of the keys in
	// front of them is this one.
	CmdWink byte = 0x08
	// CmdCBOR carries a CTAP2 message. Not used yet; named so the capability
	// below is not a number without a meaning.
	CmdCBOR byte = 0x10
	// CmdMsg carries an older CTAP1/U2F message.
	CmdMsg byte = 0x03
	// CmdLock reserves the key for one channel. Optional, and not used here.
	CmdLock byte = 0x04
	// CmdCancel abandons a request the key is still working on -- which for
	// anything needing a touch means the person never touched it.
	CmdCancel byte = 0x11
	// CmdKeepalive is what a key sends WHILE it works, and it is not an answer.
	// A key waiting for a finger sends one about every hundred milliseconds for
	// as long as the person takes, and a reader that returns the first complete
	// message it sees returns THAT instead of the reply. libfido2 skips them in
	// its receive loop and keeps waiting; so does this.
	CmdKeepalive byte = 0x3B
	// CmdError is what a key answers with when it will not do something.
	CmdError byte = 0x3F
)

The CTAPHID commands used here. The command byte travels with its high bit set, which is what distinguishes an initialisation packet from a continuation one -- a continuation packet carries a sequence number in the same position, and sequence numbers never reach 0x80.

View Source
const AlgES256 int64 = -7

AlgES256 is the COSE identifier for ECDSA over P-256 with SHA-256. It is the one algorithm every FIDO authenticator supports, which is why it is the default here: asking for something else and being refused is a worse first experience than asking for the thing that always works.

View Source
const BroadcastChannel uint32 = 0xFFFFFFFF

BroadcastChannel is the channel a key is addressed on before it has given one out. Only CmdInit may be sent there.

View Source
const CmdClientPIN byte = 0x06

CmdClientPIN is authenticatorClientPIN.

View Source
const (
	// CmdGetInfo asks the authenticator to describe itself. It takes no
	// parameters at all -- the message is the command byte and nothing else --
	// which is why it is the first CTAP2 command worth having: it needs a CBOR
	// DECODER and no encoder.
	CmdGetInfo byte = 0x04
)

The CTAP2 commands. Only the ones this package sends are named; a constant for something unsent would be a claim about behaviour nothing here has.

View Source
const MaxContinuations = 128

MaxContinuations is how many continuation packets one message may use. The sequence number shares its byte with the command, and a command is marked by its high bit, so a sequence number may never reach 0x80. libfido2 checks the same thing when it sends.

View Source
const MaxMessage = initPayload + MaxContinuations*contPayload

MaxMessage is the largest message CTAPHID can actually carry.

The two length bytes would allow 65535, and an earlier version of this file used that -- with a comment claiming the framing reached it. It does not: 57 bytes in the initialisation packet plus 128 continuations of 59 is 7609, and a longer message would need sequence numbers past 0x7F, which the key would read as initialisation packets. The arithmetic was wrong and the comment asserted it anyway.

View Source
const ReportSize = 64

ReportSize is the size of every CTAPHID report, in bytes. The specification fixes it at 64 and every authenticator observed publishes exactly that in both directions.

Variables

View Source
var (
	// ErrNoKey means no FIDO authenticator is attached.
	ErrNoKey = errors.New("fido: no security key is attached")
	// ErrTooLong means the message will not fit in a CTAPHID transfer.
	ErrTooLong = errors.New("fido: the message is longer than CTAPHID can carry")
	// ErrShortPacket means a report arrived that is not a whole CTAPHID packet.
	ErrShortPacket = errors.New("fido: a report was shorter than a CTAPHID packet")
	// ErrWrongChannel means a report arrived for a different channel, which is
	// what happens when two programs talk to one key at once.
	ErrWrongChannel = errors.New("fido: the report belongs to another channel")
	// ErrOutOfOrder means continuation packets did not arrive in sequence.
	ErrOutOfOrder = errors.New("fido: a continuation packet arrived out of order")
	// ErrTruncated means the key stopped sending before the length it promised.
	ErrTruncated = errors.New("fido: the key sent less than it said it would")
)

Errors this package returns for what a caller can act on.

Functions

func Split

func Split(channel uint32, cmd byte, data []byte) ([][]byte, error)

Split cuts a message into the reports that carry it: one initialisation packet then as many continuation packets as are needed, each exactly ReportSize bytes and zero-padded.

Every packet is padded to the full report size deliberately. A short output report is legal HID and some keys accept it, but the specification says the transfer is report-sized and a key that reads past the bytes given would read whatever the previous transfer left there.

Types

type Assertion added in v0.3.0

type Assertion struct {
	// Credential says which credential answered. An authenticator may leave it
	// out when the request named exactly one.
	Credential Credential
	AuthData   []byte
	Parsed     AuthData
	// Signature covers the authenticator data followed by the client data
	// hash, in that order. Verifying it is the caller's, with the public key
	// from the registration.
	Signature []byte
	// UserID is the user handle, present for a discoverable credential.
	UserID []byte
	// Available is how many credentials could have answered, when the
	// authenticator said. More than one means the caller may ask for the next.
	Available uint
}

Assertion is what an authenticator answers with.

type Attestation added in v0.3.0

type Attestation struct {
	// Format names the attestation statement's shape: "packed", "none", and
	// others.
	Format string
	// AuthData is the raw authenticator data, kept because a signature is over
	// these bytes and re-encoding [Parsed] would not reproduce them.
	AuthData []byte
	Parsed   AuthData
	// Statement is the attestation statement, left as CBOR: verifying it means
	// choosing a certificate library, and that choice is not this package's.
	Statement cbor.RawMessage
}

Attestation is what a registration returns.

type AuthData added in v0.3.0

type AuthData struct {
	// RPIDHash is SHA-256 of the relying party id. It is a hash, not a name:
	// the authenticator never learns the name.
	RPIDHash [32]byte
	Flags    Flags
	// SignCount rises with use on authenticators that keep one. A count that
	// goes BACKWARDS is the classic sign of a cloned credential -- and a count
	// that stays at zero means this authenticator does not keep one, which is
	// allowed and is not evidence of anything.
	SignCount uint32

	// The rest is present only when [FlagAT] is set, which is a registration.
	AAGUID       [16]byte
	CredentialID []byte
	// COSEKey is the credential's public key, as COSE_Key CBOR. It is kept
	// encoded because that is what arrived; [AuthData.PublicKey] turns it into
	// an ECDSA key when it is one.
	COSEKey cbor.RawMessage

	// Extensions is present only when [FlagED] is set.
	Extensions cbor.RawMessage
}

AuthData is the authenticator data an authenticator signs over.

It is the part of a FIDO answer that says what happened: which relying party it was for, whether a person was present and whether they were verified. The signature covers it, so reading it is how a caller learns anything at all beyond "the key answered".

func ParseAuthData added in v0.3.0

func ParseAuthData(b []byte) (AuthData, error)

ParseAuthData reads authenticator data.

The layout is fixed-width up to the flag byte and then conditional on it, which is where the mistakes live: a parser that assumes attested data is present reads a credential id out of an assertion's extensions, and one that assumes it is absent silently ignores a registration. Both flags are obeyed here, and a length that does not add up is an error rather than a slice.

func (AuthData) PublicKey added in v0.3.0

func (a AuthData) PublicKey() (*ecdsa.PublicKey, error)

PublicKey turns the credential's COSE key into an ECDSA public key.

Only P-256 with ES256 is decoded, which is not a limitation in practice: it is the one algorithm every FIDO authenticator supports and the one this package asks for by default. Anything else is refused by name rather than half-decoded, because a key of the wrong curve that verified nothing would be worse than no key at all.

This is a CONVERSION and not a verification. Checking a signature needs a decision about what counts as valid -- which algorithms, what to do with a sign counter that went backwards -- and that decision belongs to whoever is protecting something. crypto/ecdsa is the standard library, so returning one of its keys imposes no choice on anybody.

The signature an assertion carries is over the authenticator data followed by the client data hash, in that order, hashed with SHA-256:

signed := append(append([]byte{}, a.AuthData...), clientDataHash...)
digest := sha256.Sum256(signed)
ok := ecdsa.VerifyASN1(pub, digest[:], a.Signature)

func (AuthData) String added in v0.3.0

func (a AuthData) String() string

String renders the authenticator data the way a log line reads.

type Capabilities

type Capabilities byte

Capabilities is what a key said it can do, in the CTAPHID_INIT reply.

const (
	// CapWink means [CmdWink] does something visible.
	CapWink Capabilities = 0x01
	// CapCBOR means the key speaks CTAP2.
	CapCBOR Capabilities = 0x04
	// CapNoMsg means the key does NOT speak the older CTAP1/U2F messages. It is
	// spelled as an absence in the specification, and kept that way here rather
	// than inverted, so a reader comparing this with the specification does not
	// have to hold two conventions at once.
	CapNoMsg Capabilities = 0x08
)

The capability bits.

func (Capabilities) Has

func (c Capabilities) Has(want Capabilities) bool

Has reports whether every bit in c is set.

func (Capabilities) String

func (c Capabilities) String() string

String lists what the key can do, in the order the bits are defined.

type Credential added in v0.3.0

type Credential struct {
	// Type is "public-key" for everything current.
	Type string
	// ID is the credential id the authenticator gave out.
	ID []byte
	// Transports, when known, say how the credential can be reached.
	Transports []string
}

Credential names one credential an authenticator holds.

type Flags added in v0.3.0

type Flags byte

Flags are the authenticator data's flag byte: what the authenticator did before it signed.

const (
	// FlagUP means a person was PRESENT: something touched the key. It says
	// nothing about who.
	FlagUP Flags = 1 << 0
	// FlagUV means the person was VERIFIED -- a PIN, a fingerprint on the key
	// itself. This is the bit that makes an assertion worth more than
	// possession.
	FlagUV Flags = 1 << 2
	// FlagBE means the credential may be backed up, and FlagBS that it
	// currently is. A backed-up credential is not confined to this one device,
	// which matters to anyone counting it as "something you have".
	FlagBE Flags = 1 << 3
	FlagBS Flags = 1 << 4
	// FlagAT means attested credential data follows, which a registration has
	// and an assertion does not.
	FlagAT Flags = 1 << 6
	// FlagED means extension data follows.
	FlagED Flags = 1 << 7
)

The flag bits, as the specification numbers them.

func (Flags) Has added in v0.3.0

func (f Flags) Has(want Flags) bool

Has reports whether every bit in want is set.

func (Flags) String added in v0.3.0

func (f Flags) String() string

String lists the flags that are set, in bit order.

type GetAssertionRequest added in v0.3.0

type GetAssertionRequest struct {
	RPID           string
	ClientDataHash []byte
	// Allow narrows the request to particular credentials. Leaving it empty
	// asks the authenticator to use a DISCOVERABLE credential, which it only
	// has if one was registered with the "rk" option.
	Allow   []Credential
	Options map[string]bool
	// Token, when valid, authorises the request. See
	// [MakeCredentialRequest.Token].
	Token Token
}

GetAssertionRequest asks an authenticator to prove it holds a credential.

type Info added in v0.2.0

type Info struct {
	// Versions are the protocols it speaks, such as "U2F_V2", "FIDO_2_0" and
	// "FIDO_2_1".
	Versions []string
	// Extensions it supports, such as "hmac-secret".
	Extensions []string
	// AAGUID identifies the MODEL of authenticator, not the individual one.
	AAGUID [16]byte
	// Options are the capabilities it declares, such as "rk" (it can store
	// credentials), "up" (it can test for a person's presence), "uv" (it can
	// verify who they are) and "clientPin".
	Options map[string]bool
	// MaxMsgSize is the largest message it will accept, or zero when it did
	// not say.
	MaxMsgSize uint
	// PINProtocols are the PIN protocol versions it supports.
	PINProtocols []uint
	// Transports it can be reached over, such as "usb" or "nfc".
	Transports []string
}

Info is what an authenticator says about itself, from authenticatorGetInfo.

Only the fields with a caller are decoded. The rest of the reply is left alone rather than half-read: a struct field that is always the zero value because nothing fills it is worse than no field, since it reads as "this authenticator does not have one".

func (Info) Has added in v0.2.0

func (i Info) Has(option string) bool

Has reports whether the authenticator declares an option AND says it is true. An option a key does not mention is not the same as one it sets to false -- the first means "not applicable", the second "supported but off" -- and both answer no here, which is what a caller deciding whether to try something needs.

func (Info) String added in v0.2.0

func (i Info) String() string

String renders the authenticator the way a listing reads.

type InitReply

type InitReply struct {
	// Nonce is the nonce sent, echoed back. A reply whose nonce does not match
	// belongs to somebody else's INIT, which happens when two programs
	// initialise one key at the same time.
	Nonce [8]byte
	// Channel is the channel id to use from now on.
	Channel uint32
	Version Version
	Caps    Capabilities
}

InitReply is what CTAPHID_INIT answers.

func ParseInit

func ParseInit(data []byte) (InitReply, error)

ParseInit reads the reply to CmdInit.

It does NOT check the nonce: whether a mismatched nonce is somebody else's reply to ignore or a fault to report is the caller's decision, and this returns what arrived.

type Key

type Key struct {
	// contains filtered or unexported fields
}

Key is an authenticator with a negotiated channel.

func Open

func Open(ctx context.Context, t Transport) (*Key, error)

Open negotiates a channel on t and returns the key behind it.

The handshake is not optional and is done here rather than left to the caller: every later message carries the channel id, so a Key without one could not be used for anything, and returning one would be handing back a half-built object to be misused.

The Key takes ownership of t: Key.Close closes it, and so does a failed Open, so a caller never has to unwind a half-open device.

func (*Key) CBOR added in v0.2.0

func (k *Key) CBOR(ctx context.Context, cmd byte, params []byte) ([]byte, error)

CBOR sends one CTAP2 command and returns the reply's body, with the status byte already checked.

params is the command's CBOR-encoded parameters, or nil for a command that takes none. The command byte is prepended here, because the CTAPHID payload of a CBOR message is the command followed by the parameters and getting that join wrong is the sort of thing each caller should not repeat.

func (*Key) Capabilities

func (k *Key) Capabilities() Capabilities

Capabilities is what the key said it can do.

func (*Key) Channel

func (k *Key) Channel() uint32

Channel is the negotiated channel id.

func (*Key) Close

func (k *Key) Close() error

Close releases the transport.

func (*Key) GetAssertion added in v0.3.0

func (k *Key) GetAssertion(ctx context.Context, r GetAssertionRequest) (*Assertion, error)

GetAssertion asks the authenticator to sign, proving it holds the credential.

func (*Key) GetInfo added in v0.2.0

func (k *Key) GetInfo(ctx context.Context) (Info, error)

GetInfo asks the authenticator to describe itself.

A key that does not declare CapCBOR is not asked: it would answer CTAPHID_ERROR, and the caller would have to tell that refusal from a real fault.

func (*Key) KeyAgreement added in v0.5.0

func (k *Key) KeyAgreement(ctx context.Context, proto PINProtocol) (*ecdh.PublicKey, error)

KeyAgreement asks the authenticator for a public key to agree with.

It is exported because it is the one half of the PIN dance that needs no PIN: a key with none set still answers, so this is how a caller checks that the whole chain -- CBOR out, COSE in, ECDH -- works before anybody is asked for a secret.

func (*Key) MakeCredential added in v0.3.0

func (k *Key) MakeCredential(ctx context.Context, r MakeCredentialRequest) (*Attestation, error)

MakeCredential registers a new credential.

The authenticator will ask for a person: a touch, and a PIN or a fingerprint when the request or the authenticator demands verification. It sends CTAPHID_KEEPALIVE the whole time it waits, which the transport skips, so the only bound on how long this takes is the context.

func (*Key) Name

func (k *Key) Name() string

Name is what the key calls itself.

func (*Key) PINRetries added in v0.5.0

func (k *Key) PINRetries(ctx context.Context) (Retries, error)

PINRetries asks how many attempts are left.

It needs no PIN and no key agreement, which makes it the one clientPIN subcommand that can be asked of a key nobody has configured -- and the right thing to ask BEFORE prompting somebody, so a prompt is never the last one they get.

func (*Key) PINToken added in v0.5.0

func (k *Key) PINToken(ctx context.Context, pin string, proto PINProtocol, perms Permissions, rpID string) (Token, error)

PINToken exchanges a PIN for a token.

permissions is what the token will be allowed to authorise, and rpID narrows it further. Both are CTAP 2.1; an authenticator that does not list "pinUvAuthToken" in its options is asked the CTAP 2.0 way instead, where a token authorises everything and neither argument is sent.

The PIN never leaves in the clear: what is sent is the first sixteen bytes of its SHA-256, encrypted under a secret agreed for this exchange alone.

A wrong PIN COSTS a retry, and an authenticator that runs out locks until it is unplugged, then permanently. Key.PINRetries before prompting is not politeness.

func (*Key) Ping

func (k *Key) Ping(ctx context.Context, data []byte) ([]byte, error)

Ping sends data and returns what came back, which a working channel returns unchanged. It is how to check a key is still there without asking it to do anything.

func (*Key) String

func (k *Key) String() string

String renders the key the way a log reads.

func (*Key) Version

func (k *Key) Version() Version

Version is the key's firmware and transport version.

func (*Key) Wink

func (k *Key) Wink(ctx context.Context) error

Wink makes the key blink, when it said it can.

It is the one thing here a PERSON can see, which is what makes it useful for more than diagnostics: asked to prove which key is which, or that the key is the one on the desk rather than one left in a hub across the room, a blink answers.

A key without CapWink is not asked, because a key that does not wink answers CTAPHID_ERROR and the caller would have to tell that refusal from a real fault.

type MakeCredentialRequest added in v0.3.0

type MakeCredentialRequest struct {
	// ClientDataHash is SHA-256 of the client data the caller built. It is
	// hashed here by nobody: what goes into the client data is the caller's
	// business, and the authenticator signs the hash it is given.
	ClientDataHash []byte
	RP             RelyingParty
	User           User
	// Algorithms are COSE identifiers, most preferred first. Empty asks for
	// [AlgES256].
	Algorithms []int64
	// Exclude lists credentials that must NOT be registered again, which is how
	// a relying party stops one key holding two credentials for one account.
	Exclude []Credential
	// Options are the authenticator options for this request, such as "rk" for
	// a discoverable credential and "uv" to demand verification rather than
	// mere presence.
	Options map[string]bool
	// Token, when valid, authorises the request: it is what turns "somebody
	// touched the key" into "somebody who knows its PIN touched the key", and
	// it is the only way to get the verified bit set on an authenticator whose
	// verification IS a PIN.
	Token Token
}

MakeCredentialRequest is a registration.

type Message

type Message struct {
	Channel uint32
	Cmd     byte
	Data    []byte
}

Message is a CTAPHID message reassembled from its reports.

func (Message) IsError

func (m Message) IsError() bool

IsError reports whether the key refused, rather than answered.

func (Message) String

func (m Message) String() string

String renders the message the way a probe log reads.

type PINProtocol added in v0.5.0

type PINProtocol int

PINProtocol is a PIN/UV auth protocol version.

Two exist and they differ in more than a number: protocol one hashes the ECDH output once and truncates its HMAC to sixteen bytes, protocol two derives two separate keys through HKDF and truncates nothing. An authenticator says which it supports in Info.PINProtocols.

const (
	PINProtocolOne PINProtocol = 1
	PINProtocolTwo PINProtocol = 2
)

The protocols. Higher is better where an authenticator offers both: protocol two separates the encryption key from the authentication key and uses a fresh initialisation vector per message.

func (PINProtocol) String added in v0.5.0

func (p PINProtocol) String() string

String names the protocol.

type Permissions added in v0.5.0

type Permissions uint

Permissions are what a token is allowed to authorise. An authenticator that speaks CTAP 2.1 wants them; one that only speaks 2.0 has no notion of them and is asked the older way.

const (
	PermMakeCredential Permissions = 0x01
	PermGetAssertion   Permissions = 0x02
)

The permissions this package can ask for.

type Reassembler

type Reassembler struct {
	// contains filtered or unexported fields
}

Reassembler puts a message back together from the reports a key sends.

It is a type rather than a function because the reports arrive one at a time, from a callback, and the caller needs to know after each one whether the message is complete.

func NewReassembler

func NewReassembler(channel uint32) *Reassembler

NewReassembler collects reports for one channel, refusing any other.

func (*Reassembler) Feed

func (r *Reassembler) Feed(report []byte) (msg Message, done bool, err error)

Feed takes one report. done is true when the message is whole, and the message is then returned and the reassembler is ready for the next one.

A report for another channel is an error rather than something to ignore: two programs talking to one key is a real situation, and silently dropping the other one's traffic would leave a caller waiting for a reply that was already discarded.

type RelyingParty added in v0.3.0

type RelyingParty struct {
	// ID is the domain the credential is scoped to. An authenticator hashes it
	// and never learns it.
	ID string
	// Name is for a person to read on the authenticator's own screen, when it
	// has one.
	Name string
}

RelyingParty is the site or program a credential belongs to.

type Retries added in v0.5.0

type Retries struct {
	// PIN is how many PIN attempts remain before the authenticator locks. It
	// falls on a wrong PIN and is restored by a correct one.
	PIN int
	// PowerCycle is true when the authenticator will accept no more PINs until
	// it is unplugged and plugged back in -- which is not the same as being
	// locked for good, and telling a person the difference matters.
	PowerCycle bool
}

Retries is what an authenticator says about how many attempts are left.

type Status added in v0.2.0

type Status byte

Status is the byte a CTAP2 reply begins with. Zero is success.

const StatusOK Status = 0x00

StatusOK means the authenticator did what was asked.

func (Status) Error added in v0.2.0

func (s Status) Error() bool

Error reports whether the status is a refusal.

func (Status) String added in v0.2.0

func (s Status) String() string

String names the status, or gives its number when it has no name here.

type Token added in v0.5.0

type Token struct {
	// contains filtered or unexported fields
}

Token is a pinUvAuthToken: permission, for a while, to do the things it was asked for.

It is deliberately not a plain []byte. A token authenticates every later request, so it must be paired with the protocol it was obtained under -- authenticating with the wrong one produces sixteen bytes where thirty-two are expected, which an authenticator reports as a bad PIN rather than as a mismatched protocol.

func (Token) Protocol added in v0.5.0

func (t Token) Protocol() PINProtocol

Protocol is the protocol this token belongs to.

func (Token) Valid added in v0.5.0

func (t Token) Valid() bool

Valid reports whether the token holds anything.

type Transport

type Transport interface {
	// Send writes one report of exactly [ReportSize] bytes.
	Send(report []byte) error
	// Receive returns the next report.
	Receive(ctx context.Context) ([]byte, error)
	// Name is what the device calls itself, for error messages a person reads.
	Name() string
	// Close releases the device.
	Close() error
}

Transport carries CTAPHID reports to and from ONE authenticator.

It is deliberately this small. Everything above it -- framing, the handshake, keepalives, the commands -- is the same on every operating system, so an implementation has only to move 64 bytes at a time and say what the device is called.

Receive must return the NEXT report, blocking until one arrives, the context ends, or the device goes away. It must not drop reports between calls: a key answers a ping in under a millisecond, and an implementation that only listens while asked will miss it.

type User added in v0.3.0

type User struct {
	// ID is an opaque handle chosen by the relying party. It comes BACK in an
	// assertion, so it is how a caller knows who signed in -- and for that
	// reason it must not be an email address or anything else that identifies
	// a person to whoever holds the key.
	ID []byte
	// Name and DisplayName are shown to a person choosing between credentials.
	Name        string
	DisplayName string
}

User is who the credential is for, as the authenticator will remember them.

type Version

type Version struct {
	// CTAPHID is the transport protocol version, 2 on everything current.
	CTAPHID byte
	// Major, Minor and Build are the key's own firmware version.
	Major, Minor, Build byte
}

Version is what a key answered about itself.

func (Version) String

func (v Version) String() string

String renders the version the way the manufacturer writes it.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL