Skip to content

Commit ad0f7e2

Browse files
fix(protocol): ensure attca is parsed correctly (#280)
This fixes an issue where the attca attestation (TPM 2.0) was not properly parsed due to a nuance with how the AIK certificates conform to the spec and the critical extension parsing.
1 parent e5657ab commit ad0f7e2

File tree

2 files changed

+76
-43
lines changed

2 files changed

+76
-43
lines changed

protocol/attestation.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,12 @@ func (a *AttestationObject) VerifyAttestation(clientDataHash []byte, mds metadat
249249
return ErrInvalidAttestation.WithDetails("Unable to parse attestation certificate from x5c during attestation validation").WithInfo(fmt.Sprintf("Error returned from x509.ParseCertificate: %+v", err))
250250
}
251251

252+
if attestationType == string(metadata.AttCA) {
253+
if err = tpmParseSANExtension(x5c); err != nil {
254+
return err
255+
}
256+
}
257+
252258
if x5c.Subject.CommonName != x5c.Issuer.CommonName {
253259
if !entry.MetadataStatement.AttestationTypes.HasBasicFull() {
254260
return ErrInvalidAttestation.WithDetails("Unable to validate attestation statement signature during attestation validation: attestation with full attestation from authenticator that does not support full attestation")

protocol/attestation_tpm.go

Lines changed: 70 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,13 @@ func verifyTPMFormat(att AttestationObject, clientDataHash []byte, _ metadata.Pr
145145
return "", nil, ErrAttestation.WithDetails("Error getting certificate from x5c cert chain")
146146
}
147147

148-
aikCert, err := x509.ParseCertificate(aikCertBytes)
149-
if err != nil {
148+
var aikCert *x509.Certificate
149+
150+
if aikCert, err = x509.ParseCertificate(aikCertBytes); err != nil {
150151
return "", nil, ErrAttestationFormat.WithDetails("Error parsing certificate from ASN.1")
151152
}
152153

153-
err = aikCert.CheckSignature(webauthncose.SigAlgFromCOSEAlg(coseAlg), certInfoBytes, sigBytes)
154-
if err != nil {
154+
if err = aikCert.CheckSignature(webauthncose.SigAlgFromCOSEAlg(coseAlg), certInfoBytes, sigBytes); err != nil {
155155
return "", nil, ErrAttestationFormat.WithDetails(fmt.Sprintf("Signature validation error: %+v\n", err))
156156
}
157157
// Verify that aikCert meets the requirements in §8.3.1 TPM Attestation Statement Certificate Requirements
@@ -160,23 +160,41 @@ func verifyTPMFormat(att AttestationObject, clientDataHash []byte, _ metadata.Pr
160160
if aikCert.Version != 3 {
161161
return "", nil, ErrAttestationFormat.WithDetails("AIK certificate version must be 3")
162162
}
163+
163164
// 2/6 Subject field MUST be set to empty.
164165
if aikCert.Subject.String() != "" {
165166
return "", nil, ErrAttestationFormat.WithDetails("AIK certificate subject must be empty")
166167
}
167168

168-
// 3/6 The Subject Alternative Name extension MUST be set as defined in [TPMv2-EK-Profile] section 3.2.9{}
169-
var manufacturer, model, version string
169+
var (
170+
manufacturer, model, version string
171+
ekuValid = false
172+
eku []asn1.ObjectIdentifier
173+
constraints tpmBasicConstraints
174+
rest []byte
175+
)
170176

171177
for _, ext := range aikCert.Extensions {
172-
if ext.Id.Equal([]int{2, 5, 29, 17}) {
173-
manufacturer, model, version, err = parseSANExtension(ext.Value)
174-
if err != nil {
178+
if ext.Id.Equal(oidExtensionSubjectAltName) {
179+
if manufacturer, model, version, err = parseSANExtension(ext.Value); err != nil {
175180
return "", nil, err
176181
}
182+
} else if ext.Id.Equal(oidExtensionExtendedKeyUsage) {
183+
if rest, err = asn1.Unmarshal(ext.Value, &eku); len(rest) != 0 || err != nil || !eku[0].Equal(tcgKpAIKCertificate) {
184+
return "", nil, ErrAttestationFormat.WithDetails("AIK certificate EKU missing 2.23.133.8.3")
185+
}
186+
187+
ekuValid = true
188+
} else if ext.Id.Equal(oidExtensionBasicConstraints) {
189+
if rest, err = asn1.Unmarshal(ext.Value, &constraints); err != nil {
190+
return "", nil, ErrAttestationFormat.WithDetails("AIK certificate basic constraints malformed")
191+
} else if len(rest) != 0 {
192+
return "", nil, ErrAttestationFormat.WithDetails("AIK certificate basic constraints contains extra data")
193+
}
177194
}
178195
}
179196

197+
// 3/6 The Subject Alternative Name extension MUST be set as defined in [TPMv2-EK-Profile] section 3.2.9{}
180198
if manufacturer == "" || model == "" || version == "" {
181199
return "", nil, ErrAttestationFormat.WithDetails("Invalid SAN data in AIK certificate")
182200
}
@@ -186,44 +204,10 @@ func verifyTPMFormat(att AttestationObject, clientDataHash []byte, _ metadata.Pr
186204
}
187205

188206
// 4/6 The Extended Key Usage extension MUST contain the "joint-iso-itu-t(2) internationalorganizations(23) 133 tcg-kp(8) tcg-kp-AIKCertificate(3)" OID.
189-
var (
190-
ekuValid = false
191-
eku []asn1.ObjectIdentifier
192-
)
193-
194-
for _, ext := range aikCert.Extensions {
195-
if ext.Id.Equal([]int{2, 5, 29, 37}) {
196-
rest, err := asn1.Unmarshal(ext.Value, &eku)
197-
if len(rest) != 0 || err != nil || !eku[0].Equal(tcgKpAIKCertificate) {
198-
return "", nil, ErrAttestationFormat.WithDetails("AIK certificate EKU missing 2.23.133.8.3")
199-
}
200-
201-
ekuValid = true
202-
}
203-
}
204-
205207
if !ekuValid {
206208
return "", nil, ErrAttestationFormat.WithDetails("AIK certificate missing EKU")
207209
}
208210

209-
// 5/6 The Basic Constraints extension MUST have the CA component set to false.
210-
type basicConstraints struct {
211-
IsCA bool `asn1:"optional"`
212-
MaxPathLen int `asn1:"optional,default:-1"`
213-
}
214-
215-
var constraints basicConstraints
216-
217-
for _, ext := range aikCert.Extensions {
218-
if ext.Id.Equal([]int{2, 5, 29, 19}) {
219-
if rest, err := asn1.Unmarshal(ext.Value, &constraints); err != nil {
220-
return "", nil, ErrAttestationFormat.WithDetails("AIK certificate basic constraints malformed")
221-
} else if len(rest) != 0 {
222-
return "", nil, ErrAttestationFormat.WithDetails("AIK certificate basic constraints contains extra data")
223-
}
224-
}
225-
}
226-
227211
// 6/6 An Authority Information Access (AIA) extension with entry id-ad-ocsp and a CRL Distribution Point
228212
// extension [RFC5280] are both OPTIONAL as the status of many attestation certificates is available
229213
// through metadata services. See, for example, the FIDO Metadata Service.
@@ -374,3 +358,46 @@ func isValidTPMManufacturer(id string) bool {
374358

375359
return false
376360
}
361+
362+
func tpmParseSANExtension(attestation *x509.Certificate) (err error) {
363+
var (
364+
manufacturer, model, version string
365+
)
366+
367+
for _, ext := range attestation.Extensions {
368+
if ext.Id.Equal(oidExtensionSubjectAltName) {
369+
if manufacturer, model, version, err = parseSANExtension(ext.Value); err != nil {
370+
return ErrInvalidAttestation.WithDetails("Authenticator with invalid AIK SAN data encountered during attestation validation.").WithInfo(fmt.Sprintf("Error occurred parsing SAN extension: %s", err.Error()))
371+
}
372+
}
373+
}
374+
375+
if manufacturer == "" || model == "" || version == "" {
376+
return ErrAttestationFormat.WithDetails("Invalid SAN data in AIK certificate")
377+
}
378+
379+
var unhandled []asn1.ObjectIdentifier
380+
381+
for _, uce := range attestation.UnhandledCriticalExtensions {
382+
if uce.Equal(oidExtensionSubjectAltName) {
383+
continue
384+
}
385+
386+
unhandled = append(unhandled, uce)
387+
}
388+
389+
attestation.UnhandledCriticalExtensions = unhandled
390+
391+
return nil
392+
}
393+
394+
var (
395+
oidExtensionSubjectAltName = []int{2, 5, 29, 17}
396+
oidExtensionExtendedKeyUsage = []int{2, 5, 29, 37}
397+
oidExtensionBasicConstraints = []int{2, 5, 29, 19}
398+
)
399+
400+
type tpmBasicConstraints struct {
401+
IsCA bool `asn1:"optional"`
402+
MaxPathLen int `asn1:"optional,default:-1"`
403+
}

0 commit comments

Comments
 (0)