From cae03c3a6dcddeb434f57f12762037259eee55e1 Mon Sep 17 00:00:00 2001
From: Alan Parra <alan.parra@goteleport.com>
Date: Mon, 20 Jan 2025 14:47:28 -0300
Subject: [PATCH] Allow ExtKeyUsageAny for Webauthn attestation certificates
 (#51201)

---
 lib/auth/webauthn/attestation.go      | 35 +++++++++++++-
 lib/auth/webauthn/attestation_test.go | 70 +++++++++++++++++++++++++++
 2 files changed, 103 insertions(+), 2 deletions(-)

diff --git a/lib/auth/webauthn/attestation.go b/lib/auth/webauthn/attestation.go
index a382229dce048..6aeb2320fbbc3 100644
--- a/lib/auth/webauthn/attestation.go
+++ b/lib/auth/webauthn/attestation.go
@@ -19,8 +19,10 @@
 package webauthn
 
 import (
+	"context"
 	"crypto/x509"
 	"encoding/pem"
+	"errors"
 	"slices"
 
 	"github.com/go-webauthn/webauthn/protocol"
@@ -63,6 +65,18 @@ func verifyAttestation(cfg *types.Webauthn, obj protocol.AttestationObject) erro
 		return trace.Wrap(err, "invalid webauthn attestation_denied_ca")
 	}
 
+	verifyOptsBase := x509.VerifyOptions{
+		// TPM-bound certificates, like those issued for Windows Hello, set
+		// ExtKeyUsage OID 2.23.133.8.3, aka "AIK (Attestation Identity Key)
+		// certificate".
+		//
+		// There isn't an ExtKeyUsage constant for that, so we allow any.
+		//
+		// - https://learn.microsoft.com/en-us/windows/apps/develop/security/windows-hello#attestation
+		// - https://oid-base.com/get/2.23.133.8.3
+		KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
+	}
+
 	// Attestation check works as follows:
 	// 1. At least one certificate must belong to the allowed pool.
 	// 2. No certificates may belong to the denied pool.
@@ -73,11 +87,28 @@ func verifyAttestation(cfg *types.Webauthn, obj protocol.AttestationObject) erro
 	// so both checks (allowed and denied) may be true for the same cert.
 	allowed := len(cfg.AttestationAllowedCAs) == 0
 	for _, cert := range attestationChain {
-		if _, err := cert.Verify(x509.VerifyOptions{Roots: allowedPool}); err == nil {
+		opts := verifyOptsBase // take copy
+		opts.Roots = allowedPool
+		if _, err := cert.Verify(opts); err == nil {
 			allowed = true // OK, but keep checking
+		} else {
+			log.DebugContext(context.Background(),
+				"Attestation check for allowed CAs failed",
+				"subject", cert.Subject,
+				"error", err,
+			)
 		}
-		if _, err := cert.Verify(x509.VerifyOptions{Roots: deniedPool}); err == nil {
+
+		opts = verifyOptsBase // take copy
+		opts.Roots = deniedPool
+		if _, err := cert.Verify(opts); err == nil {
 			return trace.BadParameter("attestation certificate %q from issuer %q not allowed", cert.Subject, cert.Issuer)
+		} else if !errors.As(err, new(x509.UnknownAuthorityError)) {
+			log.DebugContext(context.Background(),
+				"Attestation check for denied CAs failed",
+				"subject", cert.Subject,
+				"error", err,
+			)
 		}
 	}
 	if !allowed {
diff --git a/lib/auth/webauthn/attestation_test.go b/lib/auth/webauthn/attestation_test.go
index 92ab5c6658f49..68da6a85539a2 100644
--- a/lib/auth/webauthn/attestation_test.go
+++ b/lib/auth/webauthn/attestation_test.go
@@ -24,6 +24,7 @@ import (
 	"crypto/rand"
 	"crypto/x509"
 	"crypto/x509/pkix"
+	"encoding/base64"
 	"fmt"
 	"math"
 	"math/big"
@@ -31,8 +32,10 @@ import (
 	"time"
 
 	"github.com/go-webauthn/webauthn/protocol"
+	"github.com/go-webauthn/webauthn/protocol/webauthncbor"
 	"github.com/go-webauthn/webauthn/protocol/webauthncose"
 	"github.com/gravitational/trace"
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
 	"github.com/gravitational/teleport/api/types"
@@ -382,3 +385,70 @@ func makeCertificate(template, parent *x509.Certificate, signingKey *ecdsa.Priva
 	cert, err := x509.ParseCertificate(certBytes)
 	return cert, certKey, trace.Wrap(err)
 }
+
+func TestVerifyAttestation_windowsHello(t *testing.T) {
+	// Attestation object captured from a Windows Hello registration.
+	// Holds 2 certificates in its x5c chain, the second of which chains to
+	// microsoftTPMRootCA2014
+	// - x5c[0]: subject=""
+	//   issuer="EUS-NTC-KEYID-667D154665CAC01F70CB40D8DB33594C90B4D911"
+	// - x5c[1]: subject="EUS-NTC-KEYID-667D154665CAC01F70CB40D8DB33594C90B4D911"
+	//   issuer="Microsoft TPM Root Certificate Authority 2014"
+	const rawAttObjB64 = `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`
+
+	// Decode and unmarshal attestation object.
+	rawAttObj, err := base64.StdEncoding.DecodeString(rawAttObjB64)
+	require.NoError(t, err, "Decode B64 attestation object")
+	obj := &protocol.AttestationObject{}
+	require.NoError(t,
+		webauthncbor.Unmarshal(rawAttObj, obj),
+		"Unmarshal CBOR attestation object",
+	)
+
+	webConfig := &types.Webauthn{
+		RPID: "localhost", // unimportant for the test
+		AttestationAllowedCAs: []string{
+			microsoftTPMRootCA2014,
+		},
+	}
+	assert.NoError(t,
+		wanlib.VerifyAttestation(webConfig, *obj),
+		"VerifyAttestation failed unexpectedly",
+	)
+}
+
+// http://www.microsoft.com/pkiops/certs/Microsoft%20TPM%20Root%20Certificate%20Authority%202014.crt
+const microsoftTPMRootCA2014 = `-----BEGIN CERTIFICATE-----
+MIIF9TCCA92gAwIBAgIQXbYwTgy/J79JuMhpUB5dyzANBgkqhkiG9w0BAQsFADCB
+jDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
+ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjE2MDQGA1UEAxMt
+TWljcm9zb2Z0IFRQTSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDE0MB4X
+DTE0MTIxMDIxMzExOVoXDTM5MTIxMDIxMzkyOFowgYwxCzAJBgNVBAYTAlVTMRMw
+EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
+aWNyb3NvZnQgQ29ycG9yYXRpb24xNjA0BgNVBAMTLU1pY3Jvc29mdCBUUE0gUm9v
+dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxNDCCAiIwDQYJKoZIhvcNAQEBBQAD
+ggIPADCCAgoCggIBAJ+n+bnKt/JHIRC/oI/xgkgsYdPzP0gpvduDA2GbRtth+L4W
+UyoZKGBw7uz5bjjP8Aql4YExyjR3EZQ4LqnZChMpoCofbeDR4MjCE1TGwWghGpS0
+mM3GtWD9XiME4rE2K0VW3pdN0CLzkYbvZbs2wQTFfE62yNQiDjyHFWAZ4BQH4eWa
+8wrDMUxIAneUCpU6zCwM+l6Qh4ohX063BHzXlTSTc1fDsiPaKuMMjWjK9vp5UHFP
+a+dMAWr6OljQZPFIg3aZ4cUfzS9y+n77Hs1NXPBn6E4Db679z4DThIXyoKeZTv1a
+aWOWl/exsDLGt2mTMTyykVV8uD1eRjYriFpmoRDwJKAEMOfaURarzp7hka9TOElG
+yD2gOV4Fscr2MxAYCywLmOLzA4VDSYLuKAhPSp7yawET30AvY1HRfMwBxetSqWP2
++yZRNYJlHpor5QTuRDgzR+Zej+aWx6rWNYx43kLthozeVJ3QCsD5iEI/OZlmWn5W
+Yf7O8LB/1A7scrYv44FD8ck3Z+hxXpkklAsjJMsHZa9mBqh+VR1AicX4uZG8m16x
+65ZU2uUpBa3rn8CTNmw17ZHOiuSWJtS9+PrZVA8ljgf4QgA1g6NPOEiLG2fn8Gm+
+r5Ak+9tqv72KDd2FPBJ7Xx4stYj/WjNPtEUhW4rcLK3ktLfcy6ea7Rocw5y5AgMB
+AAGjUTBPMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR6
+jArOL0hiF+KU0a5VwVLscXSkVjAQBgkrBgEEAYI3FQEEAwIBADANBgkqhkiG9w0B
+AQsFAAOCAgEAW4ioo1+J9VWC0UntSBXcXRm1ePTVamtsxVy/GpP4EmJd3Ub53JzN
+BfYdgfUL51CppS3ZY6BoagB+DqoA2GbSL+7sFGHBl5ka6FNelrwsH6VVw4xV/8kl
+IjmqOyfatPYsz0sUdZev+reeiGpKVoXrK6BDnUU27/mgPtem5YKWvHB/soofUrLK
+zZV3WfGdx9zBr8V0xW6vO3CKaqkqU9y6EsQw34n7eJCbEVVQ8VdFd9iV1pmXwaBA
+fBwkviPTKEP9Cm+zbFIOLr3V3CL9hJj+gkTUuXWlJJ6wVXEG5i4rIbLAV59UrW4L
+onP+seqvWMJYUFxu/niF0R3fSGM+NU11DtBVkhRZt1u0kFhZqjDz1dWyfT/N7Hke
+3WsDqUFsBi+8SEw90rWx2aUkLvKo83oU4Mx4na+2I3l9F2a2VNGk4K7l3a00g51m
+iPiq0Da0jqw30PaLluTMTGY5+RnZVh50JD6nk+Ea3wRkU8aiYFnpIxfKBZ72whmY
+Ya/egj9IKeqpR0vuLebbU0fJBf880K1jWD3Z5SFyJXo057Mv0OPw5mttytE585ZI
+y5JsaRXlsOoWGRXE3kUT/MKR1UoAgR54c8Bsh+9Dq2wqIK9mRn15zvBDeyHG6+cz
+urLopziOUeWokxZN1syrEdKlhFoPYavm6t+PzIcpdxZwHA+V3jLJPfI=
+-----END CERTIFICATE-----`