forked from duo-labs/webauthn
/
attestation.go
156 lines (133 loc) · 7.37 KB
/
attestation.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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package protocol
import (
"crypto/sha256"
"encoding/json"
"fmt"
"github.com/yongjacky/webauthn/protocol/webauthncbor"
)
// From §5.2.1 (https://www.w3.org/TR/webauthn/#authenticatorattestationresponse)
// "The authenticator's response to a client’s request for the creation
// of a new public key credential. It contains information about the new credential
// that can be used to identify it for later use, and metadata that can be used by
// the WebAuthn Relying Party to assess the characteristics of the credential
// during registration."
// The initial unpacked 'response' object received by the relying party. This
// contains the clientDataJSON object, which will be marshalled into
// CollectedClientData, and the 'attestationObject', which contains
// information about the authenticator, and the newly minted
// public key credential. The information in both objects are used
// to verify the authenticity of the ceremony and new credential
type AuthenticatorAttestationResponse struct {
// The byte slice of clientDataJSON, which becomes CollectedClientData
AuthenticatorResponse
// The byte slice version of AttestationObject
// This attribute contains an attestation object, which is opaque to, and
// cryptographically protected against tampering by, the client. The
// attestation object contains both authenticator data and an attestation
// statement. The former contains the AAGUID, a unique credential ID, and
// the credential public key. The contents of the attestation statement are
// determined by the attestation statement format used by the authenticator.
// It also contains any additional information that the Relying Party's server
// requires to validate the attestation statement, as well as to decode and
// validate the authenticator data along with the JSON-serialized client data.
AttestationObject URLEncodedBase64 `json:"attestationObject"`
}
// The parsed out version of AuthenticatorAttestationResponse.
type ParsedAttestationResponse struct {
CollectedClientData CollectedClientData
AttestationObject AttestationObject
}
// From §6.4. Authenticators MUST also provide some form of attestation. The basic requirement is that the
// authenticator can produce, for each credential public key, an attestation statement verifiable by the
// WebAuthn Relying Party. Typically, this attestation statement contains a signature by an attestation
// private key over the attested credential public key and a challenge, as well as a certificate or similar
// data providing provenance information for the attestation public key, enabling the Relying Party to make
// a trust decision. However, if an attestation key pair is not available, then the authenticator MUST
// perform self attestation of the credential public key with the corresponding credential private key.
// All this information is returned by authenticators any time a new public key credential is generated, in
// the overall form of an attestation object. (https://www.w3.org/TR/webauthn/#attestation-object)
//
type AttestationObject struct {
// The authenticator data, including the newly created public key. See AuthenticatorData for more info
AuthData AuthenticatorData
// The byteform version of the authenticator data, used in part for signature validation
RawAuthData []byte `json:"authData"`
// The format of the Attestation data.
Format string `json:"fmt"`
// The attestation statement data sent back if attestation is requested.
AttStatement map[string]interface{} `json:"attStmt,omitempty"`
}
type attestationFormatValidationHandler func(AttestationObject, []byte) (string, []interface{}, error)
var attestationRegistry = make(map[string]attestationFormatValidationHandler)
// Using one of the locally registered attestation formats, handle validating the attestation
// data provided by the authenticator (and in some cases its manufacturer)
func RegisterAttestationFormat(format string, handler attestationFormatValidationHandler) {
attestationRegistry[format] = handler
}
// Parse the values returned in the authenticator response and perform attestation verification
// Step 8. This returns a fully decoded struct with the data put into a format that can be
// used to verify the user and credential that was created
func (ccr *AuthenticatorAttestationResponse) Parse() (*ParsedAttestationResponse, error) {
var p ParsedAttestationResponse
err := json.Unmarshal(ccr.ClientDataJSON, &p.CollectedClientData)
if err != nil {
return nil, ErrParsingData.WithInfo(err.Error())
}
err = webauthncbor.Unmarshal(ccr.AttestationObject, &p.AttestationObject)
if err != nil {
return nil, ErrParsingData.WithInfo(err.Error())
}
// Step 8. Perform CBOR decoding on the attestationObject field of the AuthenticatorAttestationResponse
// structure to obtain the attestation statement format fmt, the authenticator data authData, and
// the attestation statement attStmt.
err = p.AttestationObject.AuthData.Unmarshal(p.AttestationObject.RawAuthData)
if err != nil {
return nil, fmt.Errorf("error decoding auth data: %v", err)
}
if !p.AttestationObject.AuthData.Flags.HasAttestedCredentialData() {
return nil, ErrAttestationFormat.WithInfo("Attestation missing attested credential data flag")
}
return &p, nil
}
// Verify - Perform Steps 9 through 14 of registration verification, delegating Steps
func (attestationObject *AttestationObject) Verify(relyingPartyID string, clientDataHash []byte, verificationRequired bool) error {
// Steps 9 through 12 are verified against the auth data.
// These steps are identical to 11 through 14 for assertion
// so we handle them with AuthData
// Begin Step 9. Verify that the rpIdHash in authData is
// the SHA-256 hash of the RP ID expected by the RP.
rpIDHash := sha256.Sum256([]byte(relyingPartyID))
// Handle Steps 9 through 12
authDataVerificationError := attestationObject.AuthData.Verify(rpIDHash[:], nil, verificationRequired)
if authDataVerificationError != nil {
return authDataVerificationError
}
// Step 13. Determine the attestation statement format by performing a
// USASCII case-sensitive match on fmt against the set of supported
// WebAuthn Attestation Statement Format Identifier values. The up-to-date
// list of registered WebAuthn Attestation Statement Format Identifier
// values is maintained in the IANA registry of the same name
// [WebAuthn-Registries] (https://www.w3.org/TR/webauthn/#biblio-webauthn-registries).
// Since there is not an active registry yet, we'll check it against our internal
// Supported types.
// But first let's make sure attestation is present. If it isn't, we don't need to handle
// any of the following steps
if attestationObject.Format == "none" {
if len(attestationObject.AttStatement) != 0 {
return ErrAttestationFormat.WithInfo("Attestation format none with attestation present")
}
return nil
}
formatHandler, valid := attestationRegistry[attestationObject.Format]
if !valid {
return ErrAttestationFormat.WithInfo(fmt.Sprintf("Attestation format %s is unsupported", attestationObject.Format))
}
// Step 14. Verify that attStmt is a correct attestation statement, conveying a valid attestation signature, by using
// the attestation statement format fmt’s verification procedure given attStmt, authData and the hash of the serialized
// client data computed in step 7.
attestationType, _, err := formatHandler(*attestationObject, clientDataHash)
if err != nil {
return err.(*Error).WithInfo(attestationType)
}
return nil
}