Repository: go-webauthn/webauthn
Pull request #15 (2022-03-01 06:12:09)

fix: parse all transports even if unknown
The spec requires RP's to accept all transports even unregistered/unknown ones. See https://w3c.github.io/webauthn/#dom-authenticatorattestationresponse-transports-slot.


Patch:
diff --git a/protocol/credential.go b/protocol/credential.go
index c1e3ff9..6d04aae 100644
--- a/protocol/credential.go
+++ b/protocol/credential.go
@@ -45,14 +45,14 @@ type ParsedPublicKeyCredential struct {
 type CredentialCreationResponse struct {
 	PublicKeyCredential
 	AttestationResponse AuthenticatorAttestationResponse `json:"response"`
-	Transport           []string                         `json:"transports,omitempty"`
+	Transports          []string                         `json:"transports,omitempty"`
 }
 
 type ParsedCredentialCreationData struct {
 	ParsedPublicKeyCredential
-	Response  ParsedAttestationResponse
-	Raw       CredentialCreationResponse
-	Transport []AuthenticatorTransport `json:"transports,omitempty"`
+	Response   ParsedAttestationResponse
+	Raw        CredentialCreationResponse
+	Transports []AuthenticatorTransport `json:"transports,omitempty"`
 }
 
 func ParseCredentialCreationResponse(response *http.Request) (*ParsedCredentialCreationData, error) {
@@ -90,12 +90,8 @@ func ParseCredentialCreationResponseBody(body io.Reader) (*ParsedCredentialCreat
 	pcc.ID, pcc.RawID, pcc.Type, pcc.ClientExtensionResults = ccr.ID, ccr.RawID, ccr.Type, ccr.ClientExtensionResults
 	pcc.Raw = ccr
 
-	for _, t := range ccr.Transport {
-		transport := AuthenticatorTransport(t)
-		switch transport {
-		case USB, BLE, NFC, Internal:
-			pcc.Transport = append(pcc.Transport, transport)
-		}
+	for _, t := range ccr.Transports {
+		pcc.Transports = append(pcc.Transports, AuthenticatorTransport(t))
 	}
 
 	parsedAttestationResponse, err := ccr.AttestationResponse.Parse()
diff --git a/webauthn/credential.go b/webauthn/credential.go
index 899c3db..af76538 100644
--- a/webauthn/credential.go
+++ b/webauthn/credential.go
@@ -27,7 +27,7 @@ func MakeNewCredential(c *protocol.ParsedCredentialCreationData) (*Credential, e
 		ID:              c.Response.AttestationObject.AuthData.AttData.CredentialID,
 		PublicKey:       c.Response.AttestationObject.AuthData.AttData.CredentialPublicKey,
 		AttestationType: c.Response.AttestationObject.Format,
-		Transport:       c.Transport,
+		Transport:       c.Transports,
 		Authenticator: Authenticator{
 			AAGUID:    c.Response.AttestationObject.AuthData.AttData.AAGUID,
 			SignCount: c.Response.AttestationObject.AuthData.Counter,


Test patch:
diff --git a/protocol/credential_test.go b/protocol/credential_test.go
index 28e65e2..157286f 100644
--- a/protocol/credential_test.go
+++ b/protocol/credential_test.go
@@ -43,7 +43,11 @@ func TestParseCredentialCreationResponse(t *testing.T) {
 						Type: "public-key",
 					},
 					RawID: byteID,
+					ClientExtensionResults: AuthenticationExtensionsClientOutputs{
+						"appid": true,
+					},
 				},
+				Transports: []AuthenticatorTransport{USB, NFC, "fake"},
 				Response: ParsedAttestationResponse{
 					CollectedClientData: CollectedClientData{
 						Type:      CeremonyType("webauthn.create"),
@@ -72,6 +76,9 @@ func TestParseCredentialCreationResponse(t *testing.T) {
 							ID:   "6xrtBhJQW6QU4tOaB4rrHaS2Ks0yDDL_q8jDC16DEjZ-VLVf4kCRkvl2xp2D71sTPYns-exsHQHTy3G-zJRK8g",
 						},
 						RawID: byteID,
+						ClientExtensionResults: AuthenticationExtensionsClientOutputs{
+							"appid": true,
+						},
 					},
 					AttestationResponse: AuthenticatorAttestationResponse{
 						AuthenticatorResponse: AuthenticatorResponse{
@@ -79,6 +86,7 @@ func TestParseCredentialCreationResponse(t *testing.T) {
 						},
 						AttestationObject: byteAttObject,
 					},
+					Transports: []string{"usb", "nfc", "fake"},
 				},
 			},
 			wantErr: false,
@@ -93,22 +101,25 @@ func TestParseCredentialCreationResponse(t *testing.T) {
 				return
 			}
 			if !reflect.DeepEqual(got.ClientExtensionResults, tt.want.ClientExtensionResults) {
-				t.Errorf("ClientExtensionResults = %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{}
@@ -236,6 +247,10 @@ var testCredentialRequestBody = `{
 	"id":"6xrtBhJQW6QU4tOaB4rrHaS2Ks0yDDL_q8jDC16DEjZ-VLVf4kCRkvl2xp2D71sTPYns-exsHQHTy3G-zJRK8g",
 	"rawId":"6xrtBhJQW6QU4tOaB4rrHaS2Ks0yDDL_q8jDC16DEjZ-VLVf4kCRkvl2xp2D71sTPYns-exsHQHTy3G-zJRK8g",
 	"type":"public-key",
+	"transports":["usb","nfc","fake"],
+	"clientExtensionResults":{
+		"appid":true
+	},
 	"response":{
 		"attestationObject":"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjEdKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQOsa7QYSUFukFOLTmgeK6x2ktirNMgwy_6vIwwtegxI2flS1X-JAkZL5dsadg-9bEz2J7PnsbB0B08txvsyUSvKlAQIDJiABIVggLKF5xS0_BntttUIrm2Z2tgZ4uQDwllbdIfrrBMABCNciWCDHwin8Zdkr56iSIh0MrB5qZiEzYLQpEOREhMUkY6q4Vw",
 		"clientDataJSON":"eyJjaGFsbGVuZ2UiOiJXOEd6RlU4cEdqaG9SYldyTERsYW1BZnFfeTRTMUNaRzFWdW9lUkxBUnJFIiwib3JpZ2luIjoiaHR0cHM6Ly93ZWJhdXRobi5pbyIsInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ"
diff --git a/protocol/options_test.go b/protocol/options_test.go
index 8c83d59..e5d0e81 100644
--- a/protocol/options_test.go
+++ b/protocol/options_test.go
@@ -26,7 +26,7 @@ func TestPublicKeyCredentialRequestOptions_GetAllowedCredentialIDs(t *testing.T)
 				Timeout:   60,
 				AllowedCredentials: []CredentialDescriptor{
 					{
-						"public-key", []byte("1234"), []AuthenticatorTransport{"usb"},
+						Type: "public-key", CredentialID: []byte("1234"), Transport: []AuthenticatorTransport{"usb"},
 					},
 				},
 				RelyingPartyID:   "test.org",