Files
web/third_party/go-webauthn/protocol/attestation_fido_u2f_test.go
T
gamertan bfe6cfd29e
verify / verify (push) Successful in 3m40s
auth: publish passkey foundations preview
2026-08-21 17:33:00 -04:00

343 lines
11 KiB
Go
Vendored

package protocol
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"math/big"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/go-webauthn/webauthn/metadata"
)
func TestVerifyU2FFormat(t *testing.T) {
successAttResponse := attestationTestUnpackResponse(t, u2fTestResponse["success"]).Response.AttestationObject
successClientDataHash := sha256.Sum256(attestationTestUnpackResponse(t, u2fTestResponse["success"]).Raw.AttestationResponse.ClientDataJSON)
testCases := []struct {
name string
att AttestationObject
clientDataHash []byte
attestationType string
err string
}{
{
name: "ShouldSuccessfullyVerifyU2FFormat",
att: successAttResponse,
clientDataHash: successClientDataHash[:],
attestationType: string(metadata.BasicFull),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
attestationType, _, err := attestationFormatValidationHandlerFIDOU2F(tc.att, tc.clientDataHash, nil)
if tc.err != "" {
require.EqualError(t, err, tc.err)
} else {
require.NoError(t, err)
}
assert.Equal(t, tc.attestationType, attestationType)
})
}
}
func TestVerifyU2FFormat_Errors(t *testing.T) {
zeroAAGUID := make([]byte, 16)
es256Key := []byte{
0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01,
0x21, 0x58, 0x20,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x22, 0x58, 0x20,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
}
testCases := []struct {
name string
att AttestationObject
err string
}{
{
name: "ShouldFailInvalidPublicKey",
att: AttestationObject{
AuthData: AuthenticatorData{
AttData: AttestedCredentialData{
AAGUID: zeroAAGUID,
CredentialPublicKey: []byte("not-cbor"),
},
},
},
err: "Error parsing public key",
},
{
name: "ShouldFailNonES256Algorithm",
att: AttestationObject{
AuthData: AuthenticatorData{
AttData: AttestedCredentialData{
AAGUID: zeroAAGUID,
CredentialPublicKey: []byte{
0xa3, 0x01, 0x02, 0x03, 0x39, 0x01, 0x00, 0x20, 0x01,
},
},
},
},
err: "Non-ES256 Public Key algorithm used",
},
{
name: "ShouldFailMissingX5C",
att: AttestationObject{
AuthData: AuthenticatorData{
AttData: AttestedCredentialData{
AAGUID: zeroAAGUID,
CredentialPublicKey: es256Key,
},
},
AttStatement: map[string]any{},
},
err: "Missing properly formatted x5c data",
},
{
name: "ShouldFailMissingSig",
att: AttestationObject{
AuthData: AuthenticatorData{
AttData: AttestedCredentialData{
AAGUID: zeroAAGUID,
CredentialPublicKey: es256Key,
},
},
AttStatement: map[string]any{
stmtX5C: []any{[]byte("cert")},
},
},
err: "Missing sig data",
},
{
name: "ShouldFailX5CNotExactlyOne",
att: AttestationObject{
AuthData: AuthenticatorData{
AttData: AttestedCredentialData{
AAGUID: zeroAAGUID,
CredentialPublicKey: es256Key,
},
},
AttStatement: map[string]any{
stmtX5C: []any{[]byte("cert1"), []byte("cert2")},
stmtSignature: []byte("sig"),
},
},
err: "x5c must contain exactly one element",
},
{
name: "ShouldFailX5CElementNotBytes",
att: AttestationObject{
AuthData: AuthenticatorData{
AttData: AttestedCredentialData{
AAGUID: zeroAAGUID,
CredentialPublicKey: es256Key,
},
},
AttStatement: map[string]any{
stmtX5C: []any{"not-bytes"},
stmtSignature: []byte("sig"),
},
},
err: "Error decoding ASN.1 data from x5c",
},
{
name: "ShouldFailX5CInvalidCert",
att: AttestationObject{
AuthData: AuthenticatorData{
AttData: AttestedCredentialData{
AAGUID: zeroAAGUID,
CredentialPublicKey: es256Key,
},
},
AttStatement: map[string]any{
stmtX5C: []any{[]byte("not-a-cert")},
stmtSignature: []byte("sig"),
},
},
err: "Error parsing certificate from ASN.1 data into certificate",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, _, err := attestationFormatValidationHandlerFIDOU2F(tc.att, []byte("hash"), nil)
require.EqualError(t, err, tc.err)
})
}
}
func TestVerifyU2FFormat_CertificateErrors(t *testing.T) {
zeroAAGUID := make([]byte, 16)
es256Key := []byte{
0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01,
0x21, 0x58, 0x20,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x22, 0x58, 0x20,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
}
shortCoordKey := []byte{
0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01,
0x21, 0x58, 0x10,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x22, 0x58, 0x10,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
}
rsaCertDER := u2fTestGenerateRSACert(t)
p384CertDER := u2fTestGenerateP384Cert(t)
p256CertDER := u2fTestGenerateP256Cert(t)
testCases := []struct {
name string
att AttestationObject
err string
}{
{
name: "ShouldFailNonECDSACert",
att: AttestationObject{
AuthData: AuthenticatorData{
AttData: AttestedCredentialData{
AAGUID: zeroAAGUID,
CredentialPublicKey: es256Key,
},
},
AttStatement: map[string]any{
stmtX5C: []any{rsaCertDER},
stmtSignature: []byte("sig"),
},
},
err: "Attestation certificate public key algorithm is not ECDSA",
},
{
name: "ShouldFailNonP256Curve",
att: AttestationObject{
AuthData: AuthenticatorData{
AttData: AttestedCredentialData{
AAGUID: zeroAAGUID,
CredentialPublicKey: es256Key,
},
},
AttStatement: map[string]any{
stmtX5C: []any{p384CertDER},
stmtSignature: []byte("sig"),
},
},
err: "Attestation certificate does not contain a P-256 ECDSA public key",
},
{
name: "ShouldFailShortCoordinates",
att: AttestationObject{
AuthData: AuthenticatorData{
AttData: AttestedCredentialData{
AAGUID: zeroAAGUID,
CredentialPublicKey: shortCoordKey,
},
},
AttStatement: map[string]any{
stmtX5C: []any{p256CertDER},
stmtSignature: []byte("sig"),
},
},
err: "X or Y Coordinate for key is invalid length",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, _, err := attestationFormatValidationHandlerFIDOU2F(tc.att, []byte("hash"), nil)
require.EqualError(t, err, tc.err)
})
}
}
// Supporting functions.
func u2fTestGenerateRSACert(t *testing.T) []byte {
t.Helper()
key, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)
template := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "Test RSA"},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(time.Hour),
}
der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
require.NoError(t, err)
return der
}
func u2fTestGenerateP384Cert(t *testing.T) []byte {
t.Helper()
key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
require.NoError(t, err)
template := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "Test P384"},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(time.Hour),
}
der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
require.NoError(t, err)
return der
}
func u2fTestGenerateP256Cert(t *testing.T) []byte {
t.Helper()
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
template := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "Test P256"},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(time.Hour),
}
der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
require.NoError(t, err)
return der
}
var u2fTestResponse = map[string]string{
`success`: `{
"rawId": "7nJsttr4dLSsmrWnaHB3espJ0ua9rsJ2ws-93BFcNOP64g_s_4wLFDvklrNYcg0BCN6ddUjJLxDfDSBreKQLAw",
"id": "7nJsttr4dLSsmrWnaHB3espJ0ua9rsJ2ws-93BFcNOP64g_s_4wLFDvklrNYcg0BCN6ddUjJLxDfDSBreKQLAw",
"response": {
"clientDataJSON": "eyJjaGFsbGVuZ2UiOiJhTDJ1d0FwZ3d1bUJ6VFlDY29MMF80RFJ2X21mWXlremdxSkJGb0pqX1dDS05aT3B2VVFueWpkd01XSVdLY1k4NDR0eUROTE81cFFQQk1KckhQel8zZyIsImNsaWVudEV4dGVuc2lvbnMiOnt9LCJoYXNoQWxnb3JpdGhtIjoiU0hBLTI1NiIsIm9yaWdpbiI6Imh0dHBzOi8vbG9jYWxob3N0OjQ0MzI5IiwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9",
"attestationObject": "o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEcwRQIgRMxowC__Z-mgVR6netL6C7Q15weqiTCPwwq1EaeJVqMCIQCHb9cCad1VloGhQ60mw7KTJhkx61mfgKKwHUVZf1wR6mN4NWOBWQLCMIICvjCCAaagAwIBAgIEdIb9wjANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowbzELMAkGA1UEBhMCU0UxEjAQBgNVBAoMCVl1YmljbyBBQjEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjEoMCYGA1UEAwwfWXViaWNvIFUyRiBFRSBTZXJpYWwgMTk1NTAwMzg0MjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJVd8633JH0xde_9nMTzGk6HjrrhgQlWYVD7OIsuX2Unv1dAmqWBpQ0KxS8YRFwKE1SKE1PIpOWacE5SO8BN6-2jbDBqMCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0ODIuMS4xMBMGCysGAQQBguUcAgEBBAQDAgUgMCEGCysGAQQBguUcAQEEBBIEEPigEfOMCk0VgAYXER-e3H0wDAYDVR0TAQH_BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAMVxIgOaaUn44Zom9af0KqG9J655OhUVBVW-q0As6AIod3AH5bHb2aDYakeIyyBCnnGMHTJtuekbrHbXYXERIn4aKdkPSKlyGLsA_A-WEi-OAfXrNVfjhrh7iE6xzq0sg4_vVJoywe4eAJx0fS-Dl3axzTTpYl71Nc7p_NX6iCMmdik0pAuYJegBcTckE3AoYEg4K99AM_JaaKIblsbFh8-3LxnemeNf7UwOczaGGvjS6UzGVI0Odf9lKcPIwYhuTxM5CaNMXTZQ7xq4_yTfC3kPWtE4hFT34UJJflZBiLrxG4OsYxkHw_n5vKgmpspB3GfYuYTWhkDKiE8CYtyg87mhhdXRoRGF0YVjESZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQO5ybLba-HS0rJq1p2hwd3rKSdLmva7CdsLPvdwRXDTj-uIP7P-MCxQ75JazWHINAQjenXVIyS8Q3w0ga3ikCwOlAQIDJiABIVggUOAo5xqsJoPfJWsU50h7c2S7_llP0KwGI6vJkEj1N48iWCA2TMSeBfhJ84HyMQQgjJvBiA6JnHA0chxSlmuZeT9Xgg"
},
"type": "public-key"
}`,
}