From 500e4ff52e5110abb5add51dba8ecaa10bd25e7a Mon Sep 17 00:00:00 2001 From: James Elliott Date: Fri, 18 Feb 2022 19:49:58 +1100 Subject: [PATCH] feat: parse transports data from credential attestation This parses the credential attestation and obtains the transports if provided. This must be manually done at the client level using AuthenticatorAttestationResponse.getTransports(). See the MDN for more information: https://developer.mozilla.org/en-US/docs/Web/API/AuthenticatorAttestationResponse. As per the spec this allows any values from the client. See https://w3c.github.io/webauthn/#dom-authenticatorattestationresponse-transports-slot. --- protocol/credential.go | 10 ++++++++-- protocol/credential_test.go | 19 +++++++++++++------ webauthn/credential.go | 12 ++++++++++++ webauthn/login.go | 5 +---- 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/protocol/credential.go b/protocol/credential.go index a43369c..9967904 100644 --- a/protocol/credential.go +++ b/protocol/credential.go @@ -45,12 +45,14 @@ type ParsedPublicKeyCredential struct { type CredentialCreationResponse struct { PublicKeyCredential AttestationResponse AuthenticatorAttestationResponse `json:"response"` + Transports []string `json:"transports,omitempty"` } type ParsedCredentialCreationData struct { ParsedPublicKeyCredential - Response ParsedAttestationResponse - Raw CredentialCreationResponse + Response ParsedAttestationResponse + Transports []AuthenticatorTransport + Raw CredentialCreationResponse } func ParseCredentialCreationResponse(response *http.Request) (*ParsedCredentialCreationData, error) { @@ -88,6 +90,10 @@ func ParseCredentialCreationResponseBody(body io.Reader) (*ParsedCredentialCreat pcc.ID, pcc.RawID, pcc.Type = ccr.ID, ccr.RawID, ccr.Type pcc.Raw = ccr + for _, t := range ccr.Transports { + pcc.Transports = append(pcc.Transports, AuthenticatorTransport(t)) + } + parsedAttestationResponse, err := ccr.AttestationResponse.Parse() if err != nil { return nil, ErrParsingData.WithDetails("Error parsing attestation response") diff --git a/protocol/credential_test.go b/protocol/credential_test.go index a5396b5..f80932b 100644 --- a/protocol/credential_test.go +++ b/protocol/credential_test.go @@ -3,11 +3,12 @@ package protocol import ( "bytes" "encoding/base64" - "github.com/duo-labs/webauthn/protocol/webauthncbor" "io/ioutil" "net/http" "reflect" "testing" + + "github.com/duo-labs/webauthn/protocol/webauthncbor" ) func TestParseCredentialCreationResponse(t *testing.T) { @@ -43,6 +44,7 @@ func TestParseCredentialCreationResponse(t *testing.T) { }, RawID: byteID, }, + Transports: []AuthenticatorTransport{USB, NFC, "fake"}, Response: ParsedAttestationResponse{ CollectedClientData: CollectedClientData{ Type: CeremonyType("webauthn.create"), @@ -78,6 +80,7 @@ func TestParseCredentialCreationResponse(t *testing.T) { }, AttestationObject: byteAttObject, }, + Transports: []string{"usb", "nfc", "fake"}, }, }, wantErr: false, @@ -92,22 +95,25 @@ func TestParseCredentialCreationResponse(t *testing.T) { return } if !reflect.DeepEqual(got.ClientExtensionResults, tt.want.ClientExtensionResults) { - t.Errorf("Extensions = %v \n want: %v", got, tt.want) + t.Errorf("Extensions = %v \n want: %v", got.ClientExtensionResults, tt.want.ClientExtensionResults) + } + if !reflect.DeepEqual(got.Transports, tt.want.Transports) { + t.Errorf("Transports = %v \n want: %v", got.Transports, tt.want.Transports) } if !reflect.DeepEqual(got.ID, tt.want.ID) { t.Errorf("ID = %v \n want: %v", got, tt.want) } if !reflect.DeepEqual(got.ParsedCredential, tt.want.ParsedCredential) { - t.Errorf("ParsedCredential = %v \n want: %v", got, tt.want) + t.Errorf("ParsedCredential = %v \n want: %v", got.ParsedCredential, tt.want.ParsedCredential) } if !reflect.DeepEqual(got.ParsedPublicKeyCredential, tt.want.ParsedPublicKeyCredential) { - t.Errorf("ParsedPublicKeyCredential = %v \n want: %v", got, tt.want) + t.Errorf("ParsedPublicKeyCredential = %v \n want: %v", got.ParsedPublicKeyCredential, tt.want.ParsedPublicKeyCredential) } if !reflect.DeepEqual(got.Raw, tt.want.Raw) { - t.Errorf("Raw = %v \n want: %v", got, tt.want) + t.Errorf("Raw = %v \n want: %v", got.Raw, tt.want.Raw) } if !reflect.DeepEqual(got.RawID, tt.want.RawID) { - t.Errorf("RawID = %v \n want: %v", got, tt.want) + t.Errorf("RawID = %v \n want: %v", got.RawID, tt.want.RawID) } // Unmarshall CredentialPublicKey var pkWant interface{} @@ -235,6 +241,7 @@ var testCredentialRequestBody = `{ "id":"6xrtBhJQW6QU4tOaB4rrHaS2Ks0yDDL_q8jDC16DEjZ-VLVf4kCRkvl2xp2D71sTPYns-exsHQHTy3G-zJRK8g", "rawId":"6xrtBhJQW6QU4tOaB4rrHaS2Ks0yDDL_q8jDC16DEjZ-VLVf4kCRkvl2xp2D71sTPYns-exsHQHTy3G-zJRK8g", "type":"public-key", + "transports":["usb","nfc","fake"], "response":{ "attestationObject":"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjEdKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQOsa7QYSUFukFOLTmgeK6x2ktirNMgwy_6vIwwtegxI2flS1X-JAkZL5dsadg-9bEz2J7PnsbB0B08txvsyUSvKlAQIDJiABIVggLKF5xS0_BntttUIrm2Z2tgZ4uQDwllbdIfrrBMABCNciWCDHwin8Zdkr56iSIh0MrB5qZiEzYLQpEOREhMUkY6q4Vw", "clientDataJSON":"eyJjaGFsbGVuZ2UiOiJXOEd6RlU4cEdqaG9SYldyTERsYW1BZnFfeTRTMUNaRzFWdW9lUkxBUnJFIiwib3JpZ2luIjoiaHR0cHM6Ly93ZWJhdXRobi5pbyIsInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ" diff --git a/webauthn/credential.go b/webauthn/credential.go index 224bb7f..2953981 100644 --- a/webauthn/credential.go +++ b/webauthn/credential.go @@ -15,16 +15,28 @@ type Credential struct { PublicKey []byte // The attestation format used (if any) by the authenticator when creating the credential. AttestationType string + // Transport types this credential supports. + Transport []protocol.AuthenticatorTransport // The Authenticator information for a given certificate Authenticator Authenticator } +// Descriptor provides the relevant protocol.CredentialDescriptor of this webauthn.Credential. +func (c Credential) Descriptor() protocol.CredentialDescriptor { + return protocol.CredentialDescriptor{ + Type: protocol.PublicKeyCredentialType, + CredentialID: c.ID, + Transport: c.Transport, + } +} + // MakeNewCredential will return a credential pointer on successful validation of a registration response func MakeNewCredential(c *protocol.ParsedCredentialCreationData) (*Credential, error) { newCredential := &Credential{ ID: c.Response.AttestationObject.AuthData.AttData.CredentialID, PublicKey: c.Response.AttestationObject.AuthData.AttData.CredentialPublicKey, AttestationType: c.Response.AttestationObject.Format, + Transport: c.Transports, Authenticator: Authenticator{ AAGUID: c.Response.AttestationObject.AuthData.AttData.AAGUID, SignCount: c.Response.AttestationObject.AuthData.Counter, diff --git a/webauthn/login.go b/webauthn/login.go index 4ce3a88..a2e4185 100644 --- a/webauthn/login.go +++ b/webauthn/login.go @@ -36,10 +36,7 @@ func (webauthn *WebAuthn) BeginLogin(user User, opts ...LoginOption) (*protocol. var allowedCredentials = make([]protocol.CredentialDescriptor, len(credentials)) for i, credential := range credentials { - var credentialDescriptor protocol.CredentialDescriptor - credentialDescriptor.CredentialID = credential.ID - credentialDescriptor.Type = protocol.PublicKeyCredentialType - allowedCredentials[i] = credentialDescriptor + allowedCredentials[i] = credential.Descriptor() } requestOptions := protocol.PublicKeyCredentialRequestOptions{