diff --git a/lib/auth/webauthn/attestation.go b/lib/auth/webauthn/attestation.go index fa14c23a21ed1..3a408cf33d996 100644 --- a/lib/auth/webauthn/attestation.go +++ b/lib/auth/webauthn/attestation.go @@ -22,6 +22,7 @@ import ( "context" "crypto/x509" "encoding/pem" + "errors" "log/slog" "slices" @@ -68,6 +69,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. @@ -78,11 +91,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 = `o2NmbXRjdHBtZ2F0dFN0bXSmY2FsZzn//mNzaWdZAQCmmJfj0phUlYcI/mDHiUEBLbBaMwJye5cfk/zumldAQg0NqsjTWPPp5Fr3YSPJqO7qVLn2/44Q9+Pu7qKlRVyAQc4YGbKwGSgttPwjKQmwgaRgkNC3buWguFq4+0tl/IibDEO9RP0qv9aNrRNVRkuBy3MLpw6mGA/lKUMtqBWhn/YzrNvXjdKgj0EQrt+cl8z/a7HJNEvpWtng7xex8uLnKF0QSJNt1V1y9z8RBu2w06yiNLlWJLT38LzVdCgCEGWaUIMBn2mL4ieBUhhSkADsgm9XBCAcPSBBRcrwYqHu5YUe43DzwBWNMkouDpcceGtqrCJeUd5cN3WDbMTfPPR3Y3ZlcmMyLjBjeDVjglkFvzCCBbswggOjoAMCAQICEA4Ad3KqOEPYppYBtxlnw54wDQYJKoZIhvcNAQELBQAwQTE/MD0GA1UEAxM2RVVTLU5UQy1LRVlJRC02NjdEMTU0NjY1Q0FDMDFGNzBDQjQwRDhEQjMzNTk0QzkwQjREOTExMB4XDTIzMDkxNDE5NTgzOFoXDTI4MTAyNTE4MDYzNVowADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMz1fK7CFQY4/c0V01o4Hzx1KAIVQRSw7yiv5jGJIG+7ngEg3+3Bql8wKZZntSbTT0oE/tOM5VBJ2JU/FJRiKBJshxziPlGk9qlr6xLcTxnZ7mHQYylvtJ36Pm1WwzqSOH0lQYutdu9PLkuQe/kccYB8rSStGrIXlA4fvcQZrMNRb4p1LBtYTJY9pI4223BqUjCteZIsQbOO9m0gouxU1LvciydpSlv4FKU3ir1EtcANHoK1/m43WrtfHqU1uhpyBXqGWsN3ckyXC9/Tn90ujQeIRSggAL2qXy3FgXXaDLcCXTIKAdk0FfGz5ND4WYuwZV1ddxwaF8ieeJHPCWY3iVkCAwEAAaOCAe4wggHqMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMG0GA1UdIAEB/wRjMGEwXwYJKwYBBAGCNxUfMFIwUAYIKwYBBQUHAgIwRB5CAFQAQwBQAEEAIAAgAFQAcgB1AHMAdABlAGQAIAAgAFAAbABhAHQAZgBvAHIAbQAgACAASQBkAGUAbgB0AGkAdAB5MBAGA1UdJQQJMAcGBWeBBQgDMFQGA1UdEQEB/wRKMEikRjBEMRYwFAYFZ4EFAgEMC2lkOjRFNTQ0MzAwMRIwEAYFZ4EFAgIMB05QQ1Q3NXgxFjAUBgVngQUCAwwLaWQ6MDAwNzAwMDIwHwYDVR0jBBgwFoAUpttMbYCPYRK2xQElKALtii/cnvwwHQYDVR0OBBYEFOtd8e5Oy0BLPCK2PKJzyTAuSj4wMIGyBggrBgEFBQcBAQSBpTCBojCBnwYIKwYBBQUHMAKGgZJodHRwOi8vYXpjc3Byb2RldXNhaWtwdWJsaXNoLmJsb2IuY29yZS53aW5kb3dzLm5ldC9ldXMtbnRjLWtleWlkLTY2N2QxNTQ2NjVjYWMwMWY3MGNiNDBkOGRiMzM1OTRjOTBiNGQ5MTEvN2E0ZjBmZjktNzQwYy00YmM2LWE5MzktMmM3N2YxNWNlNzUzLmNlcjANBgkqhkiG9w0BAQsFAAOCAgEAWqxa3+4jOPVA3nYCgE6vhGRV6u2AJpkjrZHT5ENwHLuBJ0frSkyHgrOtHvfj0czGq5cEoODErfn+6ptjokQhihKMB8SeEx9Q3tubolp772kxUysk0msNDOj+RgWNE301ylp0RuiZ6TSTuulKYO86XY0aM3nGiEgHzQQ8sH/3KCjPGdH+zDyA8uPucNAc17992X4DFW+7sqa+Ggf/yVL8EIzyAoMosAuLmD1hqClQJ5Do5N/nid5Ms9CIUpC3zPVWaeae/uGt2vFD9CjpQDQypEuYW9gP098YZ9ytGIiLfsTc+/UhTK1zTc/iJv0PVgJMxC6lDwxAoiajk5cBSHjV7Iv4nii7Zv7AZoRGXMhDDETk7FgTmC4E8L2IMF/9JyRBwrHMXFUS+/bOHazNOeUvYuGzEd1CTB+HMhQZwoFAIMOYnwmUTnfln9ynpBtoMaUnNdpXj6xVO2AupBLqDKsOCs+yFlHYsZSc0/dsWxl0YVFWD/WjTBjRaGAutquEEowGs38og4zMQpIkS+rOLWAPYoUWo0oV9WKvunis8le2/1CdTk4uIdWuKOlCm6K+u1cpME85DNZCRn7rsvueD/6gPJCiOkzwi+yv5dcoFTUHeP1xKlHLx7ZLmb9/ExxT9Sboj+IvgCz1+1H3KSxLBT9+x5FNjMfeNMT7jJHaYDt2LhFZBu8wggbrMIIE06ADAgECAhMzAAAHzN2zKq4gCErFAAAAAAfMMA0GCSqGSIb3DQEBCwUAMIGMMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQgVFBNIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTQwHhcNMjIxMDI1MTgwNjM1WhcNMjgxMDI1MTgwNjM1WjBBMT8wPQYDVQQDEzZFVVMtTlRDLUtFWUlELTY2N0QxNTQ2NjVDQUMwMUY3MENCNDBEOERCMzM1OTRDOTBCNEQ5MTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDPHSG2PggOCw6zIxnuGZ3yllijkfL38GzX20/2T1PlVwlgcqoFRSCMKeANeXPkKCmgbmknKOm5k7XMq0dZGMks89YcZltnNMx0W9ULfkgJx3zIs8yUirEHEhooqXn429gfIjgC7GSwGJ4RcaMQc1pztpQGUwIKo6oXsmauvnMx+ZSJagyw5ztGCvEYTO5YqT9nwNPydbVZpo1FPrKiKdqAXLbQJxRPT1+4DoP/kYlm1pvQg/bADl23wRLI9gtkY/A+iM6t6ByQnuMYtXkbn0JCmNlkrOqDG7s4cYWMp2rWw6/CbwuOUyc6BENNwfcqURlHHKdUBC4v0qiXHl/5ahrByL0vnm8eeGJKjhMcPSElb7j26p17JP+U1iCGMsh3wV5C3mEf+/rfMNinK868KGpl/5O3tfOwKEpjbdVrPTAooxpIV875CoWHS2D91U5z6Pe/i5oy53W6pN5TwJd56Zp9E7inyVKkAPLEjYlZgiCRoaJyQf4RnwI374bXEyLQomA0FbXLnaA1hXu3J7IUHtCU5JXV1nwvcVgiOAhWnY6axVKaQ56y3Qz79+g/5CbPgks3LaBWFb4xrbSqGnk6CQqWGSXlXFKlBz9usGut91odMpbazud7ki3SH45K7HbYh/ax3XUR8ePRYFv1nLpMj85mzYwtyFcFABodRtfWCwGocwIDAQABo4IBjjCCAYowDgYDVR0PAQH/BAQDAgKEMBsGA1UdJQQUMBIGCSsGAQQBgjcVJAYFZ4EFCAMwFgYDVR0gBA8wDTALBgkrBgEEAYI3FR8wEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUpttMbYCPYRK2xQElKALtii/cnvwwHwYDVR0jBBgwFoAUeowKzi9IYhfilNGuVcFS7HF0pFYwcAYDVR0fBGkwZzBloGOgYYZfaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0JTIwVFBNJTIwUm9vdCUyMENlcnRpZmljYXRlJTIwQXV0aG9yaXR5JTIwMjAxNC5jcmwwfQYIKwYBBQUHAQEEcTBvMG0GCCsGAQUFBzAChmFodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRQTSUyMFJvb3QlMjBDZXJ0aWZpY2F0ZSUyMEF1dGhvcml0eSUyMDIwMTQuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQBjFHCmCR55Tyj/Be2yrbcFznEdVtOtoeOqJ1jwf6a0UL6YDSqSyk3VHGDgm3Gv0G65Jx4GF5ciLKij/DZJQvImYFnluf7UcHu0YGsWzTqSdQ3iwhzpVDpPZQpC984Ph+trDdTH76UzRuTWnePAPzeG5bOlLJwbMTUdvFv1+4aHn7GQdfS0wLvOw7xduaOAN4U3uRAymuDilnSrsotJKvoAV49j3PM+taKvuE8TIF/9CLFc4jtizahvbmbv01c8z3cY9i2Xj2mw0LsNkq2nYrmfSOPt0T7YvN/aPMHLtrGphb6ZswE6r4w5eScZVwnCs/6kRzADIqoo55iIoBjx249NPSPHURWCkSzCFXOKGFAvv/Ipdg2Qa+h0z+hAtFyMgHItYo5gOCeVoTrcDUZNvftLfsF4sg+R7KXFnDu2MBdJJRmOMSkmLY8AJF8ScTUKtdY2BklN/hmi/gp/PWqtuwqirF1fFWJ1sVTXUt2v9G5qpsxfRpu5NjBhWcMoDbf0TdpsssuZ1+ZcZaBP9QspKgxLOFMSL5rYDzjAi6L4zebHVJmMz5wjAlCyjIk30/1/7AG0nj+A0vSPaEZD+VmXxnnAJ/J2wraos8lYglIdItivW1P2d5nxwtGqF02T8y/2mhEdXXXWiVjp/KnQc4/VihBjHrlFr+rwgLXpndZEk+QmPWdwdWJBcmVhWQE2AAEACwAGBHIAIJ3/y/NsODrmmfuYaNxty4nXFTiEvigDkiwSQVi/rSKuABAAEAgAAAAAAAEA6kMWEIz2l1xATPBPJjJ7H4F6qSxuRk6kOcDxZuKW5wfmMmB9f2AfUDIZdRGHuUGSmN9fD3TvctTk7UZXdZtWWmvkbhh3BV97vo7s/Yk0WvdEktK83fb7ygnR0Zm4I9HG8Vo9zMbEFkAxgnodplS8fOFBpZM6FJKvrOa0XDehUt8Gi7haDuWK07+LHp/b16GK/mZh9VAdq1Sgl3HsKOUBSOJnxuk33EOqOw2olM4J3NSAYPoj3gzdBIHw8urZ2r2ejHXPPeDYB24Q/lex4sBa/DlmgpwXCh9pfov575agQ+dRcALSJKMQLfw6S4y57wdoUIP0KPy+Rmibr+GQey6IsWhjZXJ0SW5mb1ih/1RDR4AXACIACybN/dr9E5N+bk/Pn6YtACjcb8a/todiSaGlnHdniqmOABSN+Dyya5wGkxvet+d+EErwjQUjOAAAAABhnJUX1g166cntrz4BXuIEzRk3lnEAIgALZTXCaZy81mG1xxFBxGXnywV1iA85TWte8eCLLatT3ssAIgAL3wWp9IeXhyIqMRB+qprqjjZAXzItcKM99N3fakpUcdloYXV0aERhdGFZAWfaDQPzgyylduMhBmhxflmFQZ6OWjOtHshbgeAu9Yb1XEUAAAAACJhwWMrcS4G24TDeUNy+lgAg/z7Y+PDiOCxLKhhTeO60lF+cZmoHjomGXCG9caLnqXekAQMDOQEAIFkBAOpDFhCM9pdcQEzwTyYyex+BeqksbkZOpDnA8WbilucH5jJgfX9gH1AyGXURh7lBkpjfXw9073LU5O1GV3WbVlpr5G4YdwVfe76O7P2JNFr3RJLSvN32+8oJ0dGZuCPRxvFaPczGxBZAMYJ6HaZUvHzhQaWTOhSSr6zmtFw3oVLfBou4Wg7litO/ix6f29ehiv5mYfVQHatUoJdx7CjlAUjiZ8bpN9xDqjsNqJTOCdzUgGD6I94M3QSB8PLq2dq9nox1zz3g2AduEP5XseLAWvw5ZoKcFwofaX6L+e+WoEPnUXAC0iSjEC38OkuMue8HaFCD9Cj8vkZom6/hkHsuiLEhQwEAAQ==` + + // 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-----`