Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: CI
on: push

env:
GO_VERSION: 1.21
GO_VERSION: 1.24

jobs:
build:
Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Check the [test](test/webauthn_test.go) for a working example on how to use this
- Generate [attestation](https://www.w3.org/TR/webauthn-2/#authenticatorattestationresponse) and [assertion](https://www.w3.org/TR/webauthn-2/#authenticatorassertionresponse) responses
- Supports `EC2` and `RSA` keys with `SHA256`
- Supports `packed` attestation format
- Supports WebAuthn Level 3 compatible responses with `clientExtensionResults`
- Supports WebAuthn Level 3 compatible responses with `clientExtensionResults` and `transports`

## Usage

Expand All @@ -24,9 +24,15 @@ First we create mock entities to work with for running tests.
// The relying party settings should mirror those on the actual WebAuthn server
rp := virtualwebauthn.RelyingParty{Name: "Example Corp", ID: "example.com", Origin: "https://example.com"}

// A mock authenticator that represents a security key or biometrics module
// A mock authenticator that represents a platform authenticator (defaults to internal transport)
authenticator := virtualwebauthn.NewAuthenticator()

// Optionally configure authenticator transports and client extension results
authenticator = virtualwebauthn.NewAuthenticatorWithOptions(virtualwebauthn.AuthenticatorOptions{
Transports: []virtualwebauthn.Transport{virtualwebauthn.TransportUSB, virtualwebauthn.TransportInternal},
ClientExtensionResults: map[string]any{"credProps": map[string]any{"rk": true}},
})

// Create a new credential that we'll try to register with the relying party
credential := virtualwebauthn.NewCredential(virtualwebauthn.KeyTypeEC2)
```
Expand Down
21 changes: 10 additions & 11 deletions assertion.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,7 @@ func ParseAssertionOptions(str string) (assertionOptions *AssertionOptions, err

/// Response

// CreateAssertionResponse creates an assertion response with default empty clientExtensionResults
func CreateAssertionResponse(rp RelyingParty, auth Authenticator, cred Credential, options AssertionOptions) string {
return CreateAssertionResponseWithExtensions(rp, auth, cred, options, map[string]interface{}{})
}

// CreateAssertionResponseWithExtensions creates an assertion response with custom clientExtensionResults
func CreateAssertionResponseWithExtensions(rp RelyingParty, auth Authenticator, cred Credential, options AssertionOptions, clientExtensionResults map[string]interface{}) string {
clientData := clientData{
Type: "webauthn.get",
Challenge: base64.RawURLEncoding.EncodeToString(options.Challenge),
Expand Down Expand Up @@ -99,6 +93,11 @@ func CreateAssertionResponseWithExtensions(rp RelyingParty, auth Authenticator,

credIDEncoded := base64.RawURLEncoding.EncodeToString(cred.ID)

clientExtensionResults := auth.Options.ClientExtensionResults
if clientExtensionResults == nil {
clientExtensionResults = map[string]any{}
}

assertionResponse := assertionResponse{
AuthenticatorData: authDataEncoded,
ClientDataJSON: clientDataJSONEncoded,
Expand Down Expand Up @@ -144,9 +143,9 @@ type assertionResponse struct {
}

type assertionResult struct {
Type string `json:"type"`
ID string `json:"id"`
RawID string `json:"rawId"`
Response assertionResponse `json:"response"`
ClientExtensionResults map[string]interface{} `json:"clientExtensionResults"`
Type string `json:"type"`
ID string `json:"id"`
RawID string `json:"rawId"`
Response assertionResponse `json:"response"`
ClientExtensionResults map[string]any `json:"clientExtensionResults"`
}
25 changes: 14 additions & 11 deletions attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,7 @@ func ParseAttestationOptions(str string) (attestationOptions *AttestationOptions

/// Response

// CreateAttestationResponse creates an attestation response with default empty clientExtensionResults and Default Transports (Hybrid and Internal)
func CreateAttestationResponse(rp RelyingParty, auth Authenticator, cred Credential, options AttestationOptions) string {
return CreateAttestationResponseWithExtensions(rp, auth, cred, options, map[string]interface{}{}, []Transport{TransportHybrid, TransportInternal})
}

// CreateAttestationResponseWithExtensions creates an attestation response with custom clientExtensionResults and transports
func CreateAttestationResponseWithExtensions(rp RelyingParty, auth Authenticator, cred Credential, options AttestationOptions, clientExtensionResults map[string]interface{}, transports []Transport) string {
clientData := clientData{
Type: "webauthn.create",
Challenge: base64.RawURLEncoding.EncodeToString(options.Challenge),
Expand Down Expand Up @@ -139,8 +133,17 @@ func CreateAttestationResponseWithExtensions(rp RelyingParty, auth Authenticator

credIDEncoded := base64.RawURLEncoding.EncodeToString(cred.ID)

transports := auth.Options.Transports
if len(transports) == 0 {
transports = []Transport{TransportInternal}
}
translatedTransports := translateTransports(transports)

clientExtensionResults := auth.Options.ClientExtensionResults
if clientExtensionResults == nil {
clientExtensionResults = map[string]any{}
}

attestationResponse := attestationResponse{
AttestationObject: attestationObjectEncoded,
ClientDataJSON: clientDataJSONEncoded,
Expand Down Expand Up @@ -207,9 +210,9 @@ type attestationResponse struct {
}

type attestationResult struct {
Type string `json:"type"`
ID string `json:"id"`
RawID string `json:"rawId"`
Response attestationResponse `json:"response"`
ClientExtensionResults map[string]interface{} `json:"clientExtensionResults"`
Type string `json:"type"`
ID string `json:"id"`
RawID string `json:"rawId"`
Response attestationResponse `json:"response"`
ClientExtensionResults map[string]any `json:"clientExtensionResults"`
}
12 changes: 7 additions & 5 deletions authenticator.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package virtualwebauthn

type AuthenticatorOptions struct {
UserHandle []byte
UserNotPresent bool
UserNotVerified bool
BackupEligible bool
BackupState bool
UserHandle []byte
UserNotPresent bool
UserNotVerified bool
BackupEligible bool
BackupState bool
Transports []Transport
ClientExtensionResults map[string]any
}

type Authenticator struct {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module github.com/descope/virtualwebauthn

go 1.24.0

toolchain go1.24.5
toolchain go1.24.6

require (
github.com/fxamacker/cbor/v2 v2.9.0
Expand Down
144 changes: 0 additions & 144 deletions test/client_extension_results_test.go

This file was deleted.

Loading