File: attestation_androidkey.go

package info (click to toggle)
golang-github-duo-labs-webauthn 0.0~git20220815.00c9fb5-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 908 kB
  • sloc: makefile: 5
file content (227 lines) | stat: -rw-r--r-- 11,153 bytes parent folder | download
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
package protocol

import (
	"bytes"
	"crypto/x509"
	"encoding/asn1"
	"fmt"

	"github.com/duo-labs/webauthn/protocol/webauthncose"
)

var androidAttestationKey = "android-key"

func init() {
	RegisterAttestationFormat(androidAttestationKey, verifyAndroidKeyFormat)
}

// From §8.4. https://www.w3.org/TR/webauthn/#android-key-attestation
// The android-key attestation statement looks like:
// $$attStmtType //= (
// 	fmt: "android-key",
// 	attStmt: androidStmtFormat
// )
// androidStmtFormat = {
// 		alg: COSEAlgorithmIdentifier,
// 		sig: bytes,
// 		x5c: [ credCert: bytes, * (caCert: bytes) ]
//   }
func verifyAndroidKeyFormat(att AttestationObject, clientDataHash []byte) (string, []interface{}, error) {
	// Given the verification procedure inputs attStmt, authenticatorData and clientDataHash, the verification procedure is as follows:
	// §8.4.1. Verify that attStmt is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract
	// the contained fields.

	// Get the alg value - A COSEAlgorithmIdentifier containing the identifier of the algorithm
	// used to generate the attestation signature.
	alg, present := att.AttStatement["alg"].(int64)
	if !present {
		return androidAttestationKey, nil, ErrAttestationFormat.WithDetails("Error retreiving alg value")
	}

	// Get the sig value - A byte string containing the attestation signature.
	sig, present := att.AttStatement["sig"].([]byte)
	if !present {
		return androidAttestationKey, nil, ErrAttestationFormat.WithDetails("Error retreiving sig value")
	}

	// If x5c is not present, return an error
	x5c, x509present := att.AttStatement["x5c"].([]interface{})
	if !x509present {
		// Handle Basic Attestation steps for the x509 Certificate
		return androidAttestationKey, nil, ErrAttestationFormat.WithDetails("Error retreiving x5c value")
	}

	// §8.4.2. Verify that sig is a valid signature over the concatenation of authenticatorData and clientDataHash
	// using the public key in the first certificate in x5c with the algorithm specified in alg.
	attCertBytes, valid := x5c[0].([]byte)
	if !valid {
		return androidAttestationKey, nil, ErrAttestation.WithDetails("Error getting certificate from x5c cert chain")
	}

	signatureData := append(att.RawAuthData, clientDataHash...)

	attCert, err := x509.ParseCertificate(attCertBytes)
	if err != nil {
		return androidAttestationKey, nil, ErrAttestationFormat.WithDetails(fmt.Sprintf("Error parsing certificate from ASN.1 data: %+v", err))
	}

	coseAlg := webauthncose.COSEAlgorithmIdentifier(alg)
	sigAlg := webauthncose.SigAlgFromCOSEAlg(coseAlg)
	err = attCert.CheckSignature(x509.SignatureAlgorithm(sigAlg), signatureData, sig)
	if err != nil {
		return androidAttestationKey, nil, ErrInvalidAttestation.WithDetails(fmt.Sprintf("Signature validation error: %+v\n", err))
	}
	// Verify that the public key in the first certificate in x5c matches the credentialPublicKey in the attestedCredentialData in authenticatorData.
	pubKey, err := webauthncose.ParsePublicKey(att.AuthData.AttData.CredentialPublicKey)
	if err != nil {
		return androidAttestationKey, nil, ErrInvalidAttestation.WithDetails(fmt.Sprintf("Error parsing public key: %+v\n", err))
	}
	e := pubKey.(webauthncose.EC2PublicKeyData)
	valid, err = e.Verify(signatureData, sig)
	if err != nil || valid != true {
		return androidAttestationKey, nil, ErrInvalidAttestation.WithDetails(fmt.Sprintf("Error parsing public key: %+v\n", err))
	}
	// §8.4.3. Verify that the attestationChallenge field in the attestation certificate extension data is identical to clientDataHash.
	// attCert.Extensions
	var attExtBytes []byte
	for _, ext := range attCert.Extensions {
		if ext.Id.Equal([]int{1, 3, 6, 1, 4, 1, 11129, 2, 1, 17}) {
			attExtBytes = ext.Value
		}
	}
	if len(attExtBytes) == 0 {
		return androidAttestationKey, nil, ErrAttestationFormat.WithDetails("Attestation certificate extensions missing 1.3.6.1.4.1.11129.2.1.17")
	}
	// As noted in §8.4.1 (https://w3c.github.io/webauthn/#key-attstn-cert-requirements) the Android Key Attestation attestation certificate's
	// android key attestation certificate extension data is identified by the OID "1.3.6.1.4.1.11129.2.1.17".
	decoded := keyDescription{}
	_, err = asn1.Unmarshal([]byte(attExtBytes), &decoded)
	if err != nil {
		return androidAttestationKey, nil, ErrAttestationFormat.WithDetails("Unable to parse Android key attestation certificate extensions")
	}
	// Verify that the attestationChallenge field in the attestation certificate extension data is identical to clientDataHash.
	if 0 != bytes.Compare(decoded.AttestationChallenge, clientDataHash) {
		return androidAttestationKey, nil, ErrAttestationFormat.WithDetails("Attestation challenge not equal to clientDataHash")
	}
	// The AuthorizationList.allApplications field is not present on either authorization list (softwareEnforced nor teeEnforced), since PublicKeyCredential MUST be scoped to the RP ID.
	if nil != decoded.SoftwareEnforced.AllApplications || nil != decoded.TeeEnforced.AllApplications {
		return androidAttestationKey, nil, ErrAttestationFormat.WithDetails("Attestation certificate extensions contains all applications field")
	}
	// For the following, use only the teeEnforced authorization list if the RP wants to accept only keys from a trusted execution environment, otherwise use the union of teeEnforced and softwareEnforced.
	// The value in the AuthorizationList.origin field is equal to KM_ORIGIN_GENERATED.  (which == 0)
	if KM_ORIGIN_GENERATED != decoded.SoftwareEnforced.Origin || KM_ORIGIN_GENERATED != decoded.TeeEnforced.Origin {
		return androidAttestationKey, nil, ErrAttestationFormat.WithDetails("Attestation certificate extensions contains authorization list with origin not equal KM_ORIGIN_GENERATED")
	}
	// The value in the AuthorizationList.purpose field is equal to KM_PURPOSE_SIGN.  (which == 2)
	if !contains(decoded.SoftwareEnforced.Purpose, KM_PURPOSE_SIGN) && !contains(decoded.TeeEnforced.Purpose, KM_PURPOSE_SIGN) {
		return androidAttestationKey, nil, ErrAttestationFormat.WithDetails("Attestation certificate extensions contains authorization list with purpose not equal KM_PURPOSE_SIGN")
	}
	return androidAttestationKey, x5c, err
}

func contains(s []int, e int) bool {
	for _, a := range s {
		if a == e {
			return true
		}
	}
	return false
}

type keyDescription struct {
	AttestationVersion       int
	AttestationSecurityLevel asn1.Enumerated
	KeymasterVersion         int
	KeymasterSecurityLevel   asn1.Enumerated
	AttestationChallenge     []byte
	UniqueID                 []byte
	SoftwareEnforced         authorizationList
	TeeEnforced              authorizationList
}

type authorizationList struct {
	Purpose                     []int       `asn1:"tag:1,explicit,set,optional"`
	Algorithm                   int         `asn1:"tag:2,explicit,optional"`
	KeySize                     int         `asn1:"tag:3,explicit,optional"`
	Digest                      []int       `asn1:"tag:5,explicit,set,optional"`
	Padding                     []int       `asn1:"tag:6,explicit,set,optional"`
	EcCurve                     int         `asn1:"tag:10,explicit,optional"`
	RsaPublicExponent           int         `asn1:"tag:200,explicit,optional"`
	RollbackResistance          interface{} `asn1:"tag:303,explicit,optional"`
	ActiveDateTime              int         `asn1:"tag:400,explicit,optional"`
	OriginationExpireDateTime   int         `asn1:"tag:401,explicit,optional"`
	UsageExpireDateTime         int         `asn1:"tag:402,explicit,optional"`
	NoAuthRequired              interface{} `asn1:"tag:503,explicit,optional"`
	UserAuthType                int         `asn1:"tag:504,explicit,optional"`
	AuthTimeout                 int         `asn1:"tag:505,explicit,optional"`
	AllowWhileOnBody            interface{} `asn1:"tag:506,explicit,optional"`
	TrustedUserPresenceRequired interface{} `asn1:"tag:507,explicit,optional"`
	TrustedConfirmationRequired interface{} `asn1:"tag:508,explicit,optional"`
	UnlockedDeviceRequired      interface{} `asn1:"tag:509,explicit,optional"`
	AllApplications             interface{} `asn1:"tag:600,explicit,optional"`
	ApplicationID               interface{} `asn1:"tag:601,explicit,optional"`
	CreationDateTime            int         `asn1:"tag:701,explicit,optional"`
	Origin                      int         `asn1:"tag:702,explicit,optional"`
	RootOfTrust                 rootOfTrust `asn1:"tag:704,explicit,optional"`
	OsVersion                   int         `asn1:"tag:705,explicit,optional"`
	OsPatchLevel                int         `asn1:"tag:706,explicit,optional"`
	AttestationApplicationID    []byte      `asn1:"tag:709,explicit,optional"`
	AttestationIDBrand          []byte      `asn1:"tag:710,explicit,optional"`
	AttestationIDDevice         []byte      `asn1:"tag:711,explicit,optional"`
	AttestationIDProduct        []byte      `asn1:"tag:712,explicit,optional"`
	AttestationIDSerial         []byte      `asn1:"tag:713,explicit,optional"`
	AttestationIDImei           []byte      `asn1:"tag:714,explicit,optional"`
	AttestationIDMeid           []byte      `asn1:"tag:715,explicit,optional"`
	AttestationIDManufacturer   []byte      `asn1:"tag:716,explicit,optional"`
	AttestationIDModel          []byte      `asn1:"tag:717,explicit,optional"`
	VendorPatchLevel            int         `asn1:"tag:718,explicit,optional"`
	BootPatchLevel              int         `asn1:"tag:719,explicit,optional"`
}

type rootOfTrust struct {
	verifiedBootKey   []byte
	deviceLocked      bool
	verifiedBootState verifiedBootState
	verifiedBootHash  []byte
}

type verifiedBootState int

const (
	Verified verifiedBootState = iota
	SelfSigned
	Unverified
	Failed
)

/**
 * The origin of a key (or pair), i.e. where it was generated.  Note that KM_TAG_ORIGIN can be found
 * in either the hardware-enforced or software-enforced list for a key, indicating whether the key
 * is hardware or software-based.  Specifically, a key with KM_ORIGIN_GENERATED in the
 * hardware-enforced list is guaranteed never to have existed outide the secure hardware.
 */
type KM_KEY_ORIGIN int

const (
	KM_ORIGIN_GENERATED = iota /* Generated in keymaster.  Should not exist outside the TEE. */
	KM_ORIGIN_DERIVED          /* Derived inside keymaster.  Likely exists off-device. */
	KM_ORIGIN_IMPORTED         /* Imported into keymaster.  Existed as cleartext in Android. */
	KM_ORIGIN_UNKNOWN          /* Keymaster did not record origin.  This value can only be seen on
	 * keys in a keymaster0 implementation.  The keymaster0 adapter uses
	 * this value to document the fact that it is unkown whether the key
	 * was generated inside or imported into keymaster. */
)

/**
 * Possible purposes of a key (or pair).
 */
type KM_PURPOSE int

const (
	KM_PURPOSE_ENCRYPT    = iota /* Usable with RSA, EC and AES keys. */
	KM_PURPOSE_DECRYPT           /* Usable with RSA, EC and AES keys. */
	KM_PURPOSE_SIGN              /* Usable with RSA, EC and HMAC keys. */
	KM_PURPOSE_VERIFY            /* Usable with RSA, EC and HMAC keys. */
	KM_PURPOSE_DERIVE_KEY        /* Usable with EC keys. */
	KM_PURPOSE_WRAP              /* Usable with wrapped keys. */
)