-
Notifications
You must be signed in to change notification settings - Fork 2
/
attestation_statement_fido_u2f.go
97 lines (86 loc) · 4.25 KB
/
attestation_statement_fido_u2f.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package webauthn
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/x509"
"fmt"
"github.com/pomerium/webauthn/cose"
)
// VerifyFIDOU2FAttestationStatement verifies a fido-u2f formatted attestation statement according to procedure
// documented here: https://www.w3.org/TR/webauthn-2/#sctn-fido-u2f-attestation.
func VerifyFIDOU2FAttestationStatement(
attestationObject *AttestationObject,
clientDataJSONHash ClientDataJSONHash,
) (*VerifyAttestationStatementResult, error) {
// 1. Verify that attStmt is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to
// extract the contained fields.
// - by this point the attestation statement has already been CBOR decoded
// 2. Check that x5c has exactly one element and let attCert be that element. Let certificate public key be the
// public key conveyed by attCert. If certificate public key is not an Elliptic Curve (EC) public key over the
// P-256 curve, terminate this algorithm and return an appropriate error.
certificates, err := attestationObject.Statement.UnmarshalCertificates()
if err != nil {
return nil, fmt.Errorf("%w: invalid x5c certificate: %s", ErrInvalidAttestationStatement, err)
} else if len(certificates) != 1 {
return nil, fmt.Errorf("%w: expected exactly 1 x5c certificate", ErrInvalidAttestationStatement)
}
certificate := certificates[0]
publicKey, ok := certificate.PublicKey.(*ecdsa.PublicKey)
if !ok {
return nil, fmt.Errorf("%w: invalid x5c certificate, only ECDSA keys are supported", ErrInvalidAttestationStatement)
}
if publicKey.Curve != elliptic.P256() {
return nil, fmt.Errorf("%w: invalid x5c certificate, only the P-256 curve is supported",
ErrInvalidAttestationStatement)
}
// 3. Extract the claimed rpIdHash from authenticatorData, and the claimed credentialId and credentialPublicKey from
// authenticatorData.attestedCredentialData.
authenticatorData, err := attestationObject.UnmarshalAuthenticatorData()
if err != nil {
return nil, fmt.Errorf("%w: %s", ErrInvalidAttestationStatement, err)
}
rpIDHash := authenticatorData.RPIDHash
credentialID := authenticatorData.AttestedCredentialData.CredentialID
// 4. Convert the COSE_KEY formatted credentialPublicKey (see Section 7 of [RFC8152]) to Raw ANSI X9.62 public key
// format (see ALG_KEY_ECC_X962_RAW in Section 3.6.2 Public Key Representation Formats of [FIDO-Registry]).
credentialPublicKey, _, err := cose.UnmarshalPublicKey(authenticatorData.AttestedCredentialData.CredentialPublicKey)
if err != nil {
return nil, fmt.Errorf("%w: %s", ErrInvalidAttestationStatement, err)
}
// 4a. Let x be the value corresponding to the "-2" key (representing x coordinate) in credentialPublicKey, and
// confirm its size to be of 32 bytes. If size differs or "-2" key is not found, terminate this algorithm and
// return an appropriate error.
// 4b. Let y be the value corresponding to the "-3" key (representing y coordinate) in credentialPublicKey, and
// confirm its size to be of 32 bytes. If size differs or "-3" key is not found, terminate this algorithm and
// return an appropriate error.
ecdsaKey, ok := credentialPublicKey.(*cose.ECDSAPublicKey)
if !ok {
return nil, fmt.Errorf("%w: only ECDSA keys are supported", ErrInvalidAttestationStatement)
}
// 4c. Let publicKeyU2F be the concatenation 0x04 || x || y.
publicKeyU2F := ecdsaKey.RawX962ECC()
// 5. Let verificationData be the concatenation of (0x00 || rpIdHash || clientDataHash || credentialId ||
// publicKeyU2F) (see Section 4.3 of [FIDO-U2F-Message-Formats]).
verificationData := concat(
[]byte{0x00},
rpIDHash[:],
clientDataJSONHash[:],
credentialID,
publicKeyU2F[:],
)
// 6. Verify the sig using verificationData and the certificate public key per section 4.1.4 of [SEC1] with SHA-256
// as the hash function used in step two.
signature := attestationObject.Statement.GetSignature()
err = certificate.CheckSignature(
credentialPublicKey.Algorithm().X509SignatureAlgorithm(),
verificationData,
signature,
)
if err != nil {
return nil, fmt.Errorf("%w: invalid signature, %s", ErrInvalidAttestationStatement, err)
}
return &VerifyAttestationStatementResult{
Type: AttestationTypeUnknown,
TrustPaths: [][]*x509.Certificate{certificates},
}, nil
}