This commit is contained in:
@@ -0,0 +1,342 @@
|
||||
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"
|
||||
}`,
|
||||
}
|
||||
Reference in New Issue
Block a user