passkey

package module
v1.1.3 Latest Latest
Warning

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

Go to latest
Published: Jun 28, 2025 License: MIT Imports: 17 Imported by: 1

README ΒΆ

passkey-go

Go Reference Go Report Card CI codecov

passkey-go is a Go library for handling server-side WebAuthn / passkey verification.

It provides low-level parsing and high-level assertion verification compatible with browser APIs like navigator.credentials.

Installation

go get -u github.com/aethiopicuschan/passkey-go

Usage

πŸ“Œ Challenge Generation

Create a base64url-encoded challenge:

challenge, err := passkey.GenerateChallenge()

You must persist this challenge per user during registration or login.

🏁 Registration Flow
1. Parse attestation object from client
att, err := passkey.ParseAttestationObject(attestationBase64)
2. Parse authenticator data
authData, err := passkey.ParseAuthData(att.AuthData)
3. Convert COSE key to ECDSA
pubKey, err := passkey.ConvertCOSEKeyToECDSA(authData.PublicKey)

You can now persist:

  • authData.CredID (base64url-encoded)
  • pubKey (as *ecdsa.PublicKey)
  • authData.SignCount (initial counter)
βœ… Authentication Flow (High-level)

Prefer the high-level API:

newCount, err := passkey.VerifyAssertion(
    rawJSONRequest,     // []byte from the client
    expectedOrigin,     // e.g., "http://localhost:8080"
    expectedRPID,       // e.g., "localhost"
    expectedChallenge,  // base64url-encoded challenge issued to this user
    storedSignCount,    // last known signCount
    pubKey,             // *ecdsa.PublicKey for this credential
)

If err == nil, then:

  • The signature is valid
  • clientData.origin, challenge, and rpID match expectations
  • signCount is newer than stored

Update your stored signCount to newCount.

πŸ› οΈ Advanced: Manual Parsing (optional)

You can also verify assertions step-by-step:

parsed, _ := passkey.ParseAssertion(rawBody)
clientData, _ := passkey.ParseClientDataJSON(parsed.ClientData)
_ = passkey.VerifyAssertionSignature(parsed.AuthData, parsed.ClientData, parsed.Signature, pubKey)
authParsed, _ := passkey.ParseAuthData(parsed.AuthData)
_ = passkey.CheckSignCount(stored, authParsed.SignCount)
⚠️ Error Handling

Use structured PasskeyError types to map errors to HTTP responses:

if err := someFn(); err != nil {
	var perr *passkey.PasskeyError
	if errors.As(err, &perr) {
		http.Error(w, perr.Message, perr.HTTPStatus)
		return
	}
	http.Error(w, "internal server error", 500)
}
πŸ“Ž Notes
  • This library does not persist credentials or challenges. You must manage:
    • Challenge issuance and storage
    • User lookup by credential ID
    • RP ID and origin enforcement
  • Only ES256 (ECDSA w/ SHA-256) is supported (per WebAuthn recommendations).
  • Challenge and credential IDs are expected to be base64url-encoded.
  • Client requests should follow WebAuthn spec (e.g., from navigator.credentials.get())
  • For complete usage examples, please refer to the example directory.

Documentation ΒΆ

Index ΒΆ

Constants ΒΆ

This section is empty.

Variables ΒΆ

View Source
var (
	ErrInvalidAttestationFormat = &PasskeyError{"E2001", "invalid attestation format", 400, "attestation"}
	ErrUnsupportedKeyType       = &PasskeyError{"E2002", "unsupported COSE key type", 400, "coseKey"}
	ErrInvalidCOSEKey           = &PasskeyError{"E2003", "invalid COSE key format", 400, "coseKey"}
	ErrPublicKeyParseFailed     = &PasskeyError{"E2004", "failed to parse public key", 400, "publicKey"}
)

--- Registration Errors ---

View Source
var (
	ErrInvalidClientData = &PasskeyError{"E1001", "invalid clientDataJSON", 400, "clientDataJSON"}
	ErrChallengeMismatch = &PasskeyError{"E1002", "challenge mismatch", 400, "challenge"}
	ErrSignatureInvalid  = &PasskeyError{"E1003", "signature verification failed", 400, ""}
	ErrAuthDataInvalid   = &PasskeyError{"E1004", "invalid authenticator data", 400, "authenticatorData"}
	ErrSignCountReplay   = &PasskeyError{"E1005", "replay attack detected", 400, ""}
	ErrChallengeDecode   = &PasskeyError{"E1006", "failed to decode challenge", 400, "challenge"}
	ErrChallengeGen      = &PasskeyError{"E1007", "failed to generate challenge", 500, ""}
)

--- Assertion Errors ---

View Source
var (
	ErrCredentialNotFound     = &PasskeyError{"E3001", "credential not found", 404, "credentialID"}
	ErrCredentialIDMalformed  = &PasskeyError{"E3002", "credential ID malformed", 400, "credentialID"}
	ErrNotECDSAPublicKey      = &PasskeyError{"E3003", "not an ECDSA public key", 400, "publicKey"}
	ErrPublicKeyMarshalFailed = &PasskeyError{"E3004", "failed to marshal public key", 500, "publicKey"}
)

--- Storage / Credential Errors ---

View Source
var (
	ErrCreateOptionsMarshal = &PasskeyError{"E4001", "failed to generate credential creation options", 500, ""}
)

--- Credential Creation Errors ---

View Source
var (
	ErrInternal = &PasskeyError{"E9001", "internal server error", 500, ""}
)

--- Generic ---

Functions ΒΆ

func CheckChallenge ΒΆ

func CheckChallenge(expectedB64, receivedB64 string) error

CheckChallenge compares two base64url-encoded challenge strings for exact match. If decoding fails or the decoded values do not match, an error is returned.

func CheckSignCount ΒΆ

func CheckSignCount(old, new uint32) error

CheckSignCount verifies that the new signCount value is strictly greater than the old one. This helps detect replay attacks or cloned authenticators.

Behavior: - If both old and new are 0, the authenticator likely does not support signCount and no error is returned. - If the new value is less than or equal to the stored old value, this indicates a possible replay or cloned device.

func ConvertCOSEKeyToECDSA ΒΆ

func ConvertCOSEKeyToECDSA(coseKey map[int]any) (*ecdsa.PublicKey, error)

ConvertCOSEKeyToECDSA converts a COSE-formatted public key (used in WebAuthn and Passkeys) into a standard Go ecdsa.PublicKey, performing full validation of key parameters.

func CreateOptions ΒΆ

func CreateOptions(rpID, rpName, userID, userName, displayName, challenge string) ([]byte, error)

CreateOptions is a simplified wrapper for legacy usage. Use CreateOptionsWithParams for more control.

func CreateOptionsWithParams ΒΆ

func CreateOptionsWithParams(p CreateOptionsParams) ([]byte, error)

CreateOptionsWithParams constructs and serializes the credential creation options into JSON. These options are then sent to the client browser for initiating passkey registration.

func GenerateChallenge ΒΆ

func GenerateChallenge() (string, error)

GenerateChallenge creates a secure random challenge string, encoded in URL-safe Base64.

func MarshalPublicKey ΒΆ added in v1.0.2

func MarshalPublicKey(pkr PublicKeyRecord) (b []byte, err error)

ParsePublicKey converts a byte slice containing an ASN.1 DER-encoded public key

func ToLowS ΒΆ added in v1.1.1

func ToLowS(s *big.Int, curveN *big.Int) *big.Int

ToLowS normalizes an ECDSA S value to a canonical low-S form as recommended by FIDO/WebAuthn specifications. If S is greater than half the curve order, it is replaced with N - S.

func VerifyAssertion ΒΆ

func VerifyAssertion(
	jsonBody []byte,
	expectedOrigin string,
	expectedRPID string,
	expectedChallenge string,
	storedSignCount uint32,
	pubKey *ecdsa.PublicKey,
) (uint32, error)

VerifyAssertion verifies a WebAuthn assertion response.

Parameters:

  • jsonBody: Raw JSON request body received from the client.
  • expectedOrigin: Origin string the server expects (e.g. "https://example.com").
  • expectedRPID: Relying Party ID string (e.g. "example.com") used to hash and verify authenticator data.
  • expectedChallenge: The base64url-encoded challenge that was originally issued to the client. This must match the 'challenge' field found in the decoded clientDataJSON. Verifying the challenge ensures that the response corresponds to an ongoing authentication session, and prevents replay attacks or cross-site request forgery.
  • storedSignCount: The signCount previously stored for this credential, used to detect cloned authenticators.
  • pubKey: The public key that was originally registered with the credential.

Returns: - newSignCount: The updated signCount to store on success. - error: nil if valid, or a detailed structured PasskeyError on failure.

func VerifyAssertionSignature ΒΆ

func VerifyAssertionSignature(authData, clientDataJSON, signature []byte, pubKey *ecdsa.PublicKey) error

VerifyAssertionSignature verifies an ECDSA signature for WebAuthn assertion responses. It checks that the signature was generated over the concatenation of authenticator data and the SHA-256 hash of the clientDataJSON, using the given public key.

Parameters: - authData: Raw authenticator data received from the authenticator. - clientDataJSON: JSON-encoded client data from the browser. - signature: ASN.1 DER encoded ECDSA signature. - pubKey: The public key that should have been used to sign the data.

Returns: - nil if the signature is valid. - ErrSignatureInvalid if the signature is malformed or does not verify.

Types ΒΆ

type AssertionResponse ΒΆ

type AssertionResponse struct {
	ID       string `json:"id"`    // Credential ID
	Type     string `json:"type"`  // Must be "public-key"
	RawID    string `json:"rawId"` // Base64url-encoded credential ID
	Response struct {
		AuthenticatorData string `json:"authenticatorData"` // Base64url-encoded authenticator data
		ClientDataJSON    string `json:"clientDataJSON"`    // Base64url-encoded client data JSON
		Signature         string `json:"signature"`         // Base64url-encoded signature
		UserHandle        string `json:"userHandle"`        // Optional base64url-encoded user handle
	} `json:"response"`
}

AssertionResponse represents the structure of an assertion response sent by a client (browser or authenticator device).

type AttestationObject ΒΆ

type AttestationObject struct {
	Format               string         `cbor:"fmt"`      // Format specifies the attestation format (e.g., "packed", "fido-u2f").
	AuthData             []byte         `cbor:"authData"` // AuthData contains authenticator data, including the credential public key and metadata.
	AttestationStatement map[string]any `cbor:"attStmt"`  // AttestationStatement includes the attestation statement specific to the format.
}

AttestationObject represents the parsed CBOR-encoded attestation object returned from the authenticator.

func ParseAttestationObject ΒΆ

func ParseAttestationObject(attestationB64 string) (*AttestationObject, error)

ParseAttestationObject decodes a base64url-encoded CBOR attestation object string and performs minimal structural validation.

type AuthenticatorSelection ΒΆ

type AuthenticatorSelection struct {
	UserVerification string `json:"userVerification"`      // Requirement level for user verification ("required", "preferred", "discouraged").
	ResidentKey      string `json:"residentKey,omitempty"` // Optional: whether a resident key is required.
}

AuthenticatorSelection specifies authenticator requirements for registration.

type ClientData ΒΆ

type ClientData struct {
	Type      string `json:"type"`      // Operation type: "webauthn.create" or "webauthn.get"
	Challenge string `json:"challenge"` // Base64url-encoded challenge string
	Origin    string `json:"origin"`    // Origin of the request (e.g., "https://example.com")
}

ClientData represents the parsed client-side data from a WebAuthn or Passkey authentication process.

func ParseClientDataJSON ΒΆ

func ParseClientDataJSON(data []byte, expectedOrigin string) (*ClientData, error)

ParseClientDataJSON parses the input JSON and validates its contents against expectedOrigin. It returns a parsed ClientData or an error if parsing or validation fails.

type CreateOptionsParams ΒΆ

type CreateOptionsParams struct {
	RPID             string
	RPName           string
	UserID           []byte // raw user ID (will be base64url-encoded internally)
	UserName         string
	DisplayName      string
	Challenge        string
	Attestation      string // "none", "direct", etc.
	UserVerification string // "preferred", etc.
	ResidentKey      string // optional
	TimeoutMs        int    // optional
}

CreateOptionsParams allows flexible creation of credential creation options.

type ParsedAssertion ΒΆ

type ParsedAssertion struct {
	Raw        AssertionResponse
	AuthData   []byte
	ClientData []byte
	Signature  []byte
	UserHandle []byte // may be nil
}

ParsedAssertion contains all decoded fields from an assertion response.

func ParseAssertion ΒΆ

func ParseAssertion(jsonBody []byte) (*ParsedAssertion, error)

ParseAssertion parses a JSON-formatted assertion response from a client. It returns a ParsedAssertion struct containing decoded data.

type ParsedAuthData ΒΆ

type ParsedAuthData struct {
	RPIDHash  []byte      // SHA-256 hash of the relying party ID.
	Flags     byte        // Flags indicating the state of the authenticator data.
	SignCount uint32      // Signature counter, used to detect cloned authenticators.
	AAGUID    []byte      // Authenticator Attestation GUID (globally unique identifier).
	CredID    []byte      // Credential ID assigned by the authenticator.
	PublicKey map[int]any // Parsed COSE public key.
}

ParsedAuthData represents the parsed WebAuthn authenticator data.

func ParseAuthData ΒΆ

func ParseAuthData(authData []byte) (*ParsedAuthData, error)

ParseAuthData parses raw authenticator data bytes into a structured ParsedAuthData object.

type PasskeyError ΒΆ

type PasskeyError struct {
	Code       string // e.g. "E1001"
	Message    string // human-readable message
	HTTPStatus int    // HTTP status code to return
	Field      string // optional: field name for validation errors
}

PasskeyError is a structured error with code and field info

func (*PasskeyError) Error ΒΆ

func (e *PasskeyError) Error() string

func (*PasskeyError) Is ΒΆ

func (e *PasskeyError) Is(target error) bool

func (*PasskeyError) ToJSON ΒΆ

func (e *PasskeyError) ToJSON() map[string]any

type PublicKeyCredentialCreationOptions ΒΆ

type PublicKeyCredentialCreationOptions struct {
	Challenge              string                          `json:"challenge"`                        // A unique, random challenge generated by the server.
	RP                     RelyingPartyEntity              `json:"rp"`                               // Information about the relying party (server).
	User                   UserEntity                      `json:"user"`                             // Information about the user creating the credential.
	PubKeyCredParams       []PublicKeyCredentialParameters `json:"pubKeyCredParams"`                 // List of supported public key credential parameters.
	Timeout                int                             `json:"timeout,omitempty"`                // Optional timeout in milliseconds for the credential creation.
	Attestation            string                          `json:"attestation,omitempty"`            // Attestation preference ("none", "indirect", "direct").
	AuthenticatorSelection AuthenticatorSelection          `json:"authenticatorSelection,omitempty"` // Preferences for selecting an authenticator.
}

PublicKeyCredentialCreationOptions represents the options required for creating a new public key credential.

type PublicKeyCredentialParameters ΒΆ

type PublicKeyCredentialParameters struct {
	Type string `json:"type"` // Credential type, typically "public-key".
	Alg  int    `json:"alg"`  // Cryptographic algorithm identifier (-7: ES256, -257: RS256).
}

PublicKeyCredentialParameters defines acceptable algorithms for public key credentials.

type PublicKeyRecord ΒΆ

type PublicKeyRecord struct {
	Key *ecdsa.PublicKey // ECDSA public key used for authentication.
}

PublicKeyRecord stores an ECDSA public key associated with a user's passkey. This structure typically represents the credential stored on the server side, used for verifying signatures during authentication.

func UnmarshalPublicKey ΒΆ added in v1.0.2

func UnmarshalPublicKey(data []byte) (pkr PublicKeyRecord, err error)

ParsePublicKey converts a byte slice containing an ASN.1 DER-encoded public key

type RelyingPartyEntity ΒΆ

type RelyingPartyEntity struct {
	Name string `json:"name"` // Human-readable name of the relying party.
	ID   string `json:"id"`   // Relying party identifier (usually the domain).
}

RelyingPartyEntity describes the relying party (typically the server).

type UserEntity ΒΆ

type UserEntity struct {
	ID          string `json:"id"`          // User identifier (must be base64url-encoded binary ID).
	Name        string `json:"name"`        // User handle, typically the username.
	DisplayName string `json:"displayName"` // User's display name.
}

UserEntity describes the user who is registering the credential.

Directories ΒΆ

Path Synopsis

Jump to

Keyboard shortcuts

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