-
Notifications
You must be signed in to change notification settings - Fork 59
/
attestation_apple.go
110 lines (90 loc) · 3.63 KB
/
attestation_apple.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
package protocol
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/sha256"
"crypto/x509"
"encoding/asn1"
"fmt"
"math/big"
"github.com/go-webauthn/webauthn/metadata"
"github.com/go-webauthn/webauthn/protocol/webauthncose"
)
func init() {
RegisterAttestationFormat(AttestationFormatApple, verifyAppleFormat)
}
// The apple attestation statement looks like:
// $$attStmtType //= (
//
// fmt: "apple",
// attStmt: appleStmtFormat
//
// )
//
// appleStmtFormat = {
// x5c: [ credCert: bytes, * (caCert: bytes) ]
// }
//
// Specification: §8.8. Apple Anonymous Attestation Statement Format (https://www.w3.org/TR/webauthn/#sctn-apple-anonymous-attestation)
func verifyAppleFormat(att AttestationObject, clientDataHash []byte) (string, []any, error) {
// Step 1. Verify that attStmt is valid CBOR conforming to the syntax defined
// above and perform CBOR decoding on it to extract the contained fields.
// If x5c is not present, return an error
x5c, x509present := att.AttStatement["x5c"].([]any)
if !x509present {
// Handle Basic Attestation steps for the x509 Certificate
return "", nil, ErrAttestationFormat.WithDetails("Error retrieving x5c value")
}
credCertBytes, valid := x5c[0].([]byte)
if !valid {
return "", nil, ErrAttestation.WithDetails("Error getting certificate from x5c cert chain")
}
credCert, err := x509.ParseCertificate(credCertBytes)
if err != nil {
return "", nil, ErrAttestationFormat.WithDetails(fmt.Sprintf("Error parsing certificate from ASN.1 data: %+v", err))
}
// Step 2. Concatenate authenticatorData and clientDataHash to form nonceToHash.
nonceToHash := append(att.RawAuthData, clientDataHash...)
// Step 3. Perform SHA-256 hash of nonceToHash to produce nonce.
nonce := sha256.Sum256(nonceToHash)
// Step 4. Verify that nonce equals the value of the extension with OID 1.2.840.113635.100.8.2 in credCert.
var attExtBytes []byte
for _, ext := range credCert.Extensions {
if ext.Id.Equal([]int{1, 2, 840, 113635, 100, 8, 2}) {
attExtBytes = ext.Value
}
}
if len(attExtBytes) == 0 {
return "", nil, ErrAttestationFormat.WithDetails("Attestation certificate extensions missing 1.2.840.113635.100.8.2")
}
decoded := AppleAnonymousAttestation{}
if _, err = asn1.Unmarshal(attExtBytes, &decoded); err != nil {
return "", nil, ErrAttestationFormat.WithDetails("Unable to parse apple attestation certificate extensions")
}
if !bytes.Equal(decoded.Nonce, nonce[:]) || err != nil {
return "", nil, ErrInvalidAttestation.WithDetails("Attestation certificate does not contain expected nonce")
}
// Step 5. Verify that the credential public key equals the Subject Public Key of credCert.
// TODO: Probably move this part to webauthncose.go
pubKey, err := webauthncose.ParsePublicKey(att.AuthData.AttData.CredentialPublicKey)
if err != nil {
return "", nil, ErrInvalidAttestation.WithDetails(fmt.Sprintf("Error parsing public key: %+v\n", err))
}
credPK := pubKey.(webauthncose.EC2PublicKeyData)
subjectPK := credCert.PublicKey.(*ecdsa.PublicKey)
credPKInfo := &ecdsa.PublicKey{
Curve: elliptic.P256(),
X: big.NewInt(0).SetBytes(credPK.XCoord),
Y: big.NewInt(0).SetBytes(credPK.YCoord),
}
if !credPKInfo.Equal(subjectPK) {
return "", nil, ErrInvalidAttestation.WithDetails("Certificate public key does not match public key in authData")
}
// Step 6. If successful, return implementation-specific values representing attestation type Anonymization CA and attestation trust path x5c.
return string(metadata.AnonCA), x5c, nil
}
// Apple has not yet publish schema for the extension(as of JULY 2021.)
type AppleAnonymousAttestation struct {
Nonce []byte `asn1:"tag:1,explicit"`
}