diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,3 +1,27 @@
+### 0.12.0.0
+
+* [#196](https://github.com/tweag/webauthn/pull/196) Add the WebAuthn Level 3
+  backup eligibility (BE) and backup state (BS) authenticator data flags.
+* [#197](https://github.com/tweag/webauthn/pull/197) Add the WebAuthn Level 3
+  credential hints field to registration and authentication options.
+* [#198](https://github.com/tweag/webauthn/pull/198) Add the WebAuthn Level 3
+  `authenticatorAttachment` field to registration and authentication credentials.
+* [#207](https://github.com/tweag/webauthn/pull/207) Require `crypton >= 1.1`,
+  `crypton-x509 >= 1.9.1`, `crypton-x509-store >= 1.9`,
+  `crypton-x509-validation >= 1.9.1` and `jose >= 0.13`, and switch from `memory` to `ram`.
+  `crypton-x509` and `crypton-x509-validation` below 1.9.1 do not enforce the X.509 Name
+  Constraints extension. See
+  [HSEC-2026-0008](https://haskell.github.io/security-advisories/advisory/HSEC-2026-0008.html).
+  `X509.UnknownCriticalExtension` in `uaFailures` now carries an OID, and the
+  `VerificationKeyStore` instances use `JWSHeader RequiredProtection` instead of
+  `JWSHeader ()`.
+* `crypton-x509-validation` reports every critical certificate extension outside its
+  recognized set as a validation failure. An attestation chain with such an extension now
+  results in `UnverifiedAuthenticator`. Microsoft TPM certificates mark the Certificate
+  Policies extension (OID 2.5.29.32) critical. A relying party that accepts TPM attestation
+  must decide whether that failure is acceptable.
+* Raise the `aeson` upper bound to `< 2.4`.
+
 ### 0.11.0.0
 
 * [#195](https://github.com/tweag/webauthn/pull/195) Allow for conditional mediation during registration.
diff --git a/src/Crypto/WebAuthn/AttestationStatementFormat/AndroidSafetyNet.hs b/src/Crypto/WebAuthn/AttestationStatementFormat/AndroidSafetyNet.hs
--- a/src/Crypto/WebAuthn/AttestationStatementFormat/AndroidSafetyNet.hs
+++ b/src/Crypto/WebAuthn/AttestationStatementFormat/AndroidSafetyNet.hs
@@ -147,7 +147,7 @@
   deriving newtype (IsString)
 
 -- | This instance doesn't actually perform any validation
-instance (MonadError JOSE.Error m) => JOSE.VerificationKeyStore m (JOSE.JWSHeader ()) p VerificationHostName where
+instance (MonadError JOSE.Error m) => JOSE.VerificationKeyStore m (JOSE.JWSHeader JOSE.RequiredProtection) p VerificationHostName where
   getVerificationKeys header _ hostName = do
     chain <- case header ^? JOSE.x5c . _Just . JOSE.param of
       Nothing -> throwError JOSE.JWSInvalidSignature
@@ -196,7 +196,7 @@
         sig <- case jws ^? JOSE.signatures of
           Nothing -> Left "Can't extract x5c because the JWT contains no signatures"
           Just res -> pure res
-        JOSE.HeaderParam () x5c <- case sig ^. JOSE.header . JOSE.x5c of
+        JOSE.HeaderParam JOSE.RequiredProtection x5c <- case sig ^. JOSE.header . JOSE.x5c of
           Nothing -> Left "No x5c in the header of the first JWT signature"
           Just res -> pure res
         pure x5c
diff --git a/src/Crypto/WebAuthn/Encoding/Binary.hs b/src/Crypto/WebAuthn/Encoding/Binary.hs
--- a/src/Crypto/WebAuthn/Encoding/Binary.hs
+++ b/src/Crypto/WebAuthn/Encoding/Binary.hs
@@ -408,16 +408,20 @@
     bytes :: BS.ByteString
     bytes = LBS.toStrict $ toLazyByteString builder
 
-    -- https://www.w3.org/TR/webauthn-2/#flags
+    -- https://www.w3.org/TR/webauthn-3/#flags
     flags :: Word8
     flags =
       userPresentFlag
         .|. userVerifiedFlag
+        .|. backupEligibleFlag
+        .|. backupStateFlag
         .|. attestedCredentialDataPresentFlag
         .|. extensionsPresentFlag
       where
         userPresentFlag = if M.adfUserPresent adFlags then Bits.bit 0 else 0
         userVerifiedFlag = if M.adfUserVerified adFlags then Bits.bit 2 else 0
+        backupEligibleFlag = if M.adfBackupEligible adFlags then Bits.bit 3 else 0
+        backupStateFlag = if M.adfBackupState adFlags then Bits.bit 4 else 0
         attestedCredentialDataPresentFlag = case sing @c of
           K.SRegistration -> Bits.bit 6
           K.SAuthentication -> 0
@@ -472,12 +476,14 @@
     M.RpIdHash . fromJust . Hash.digestFromByteString
       <$> runBinary (Binary.getByteString 32)
 
-  -- https://www.w3.org/TR/webauthn-2/#flags
+  -- https://www.w3.org/TR/webauthn-3/#flags
   bitFlags <- runBinary Binary.getWord8
   let adFlags =
         M.AuthenticatorDataFlags
           { M.adfUserPresent = Bits.testBit bitFlags 0,
-            M.adfUserVerified = Bits.testBit bitFlags 2
+            M.adfUserVerified = Bits.testBit bitFlags 2,
+            M.adfBackupEligible = Bits.testBit bitFlags 3,
+            M.adfBackupState = Bits.testBit bitFlags 4
           }
 
   -- https://www.w3.org/TR/webauthn-2/#signcount
diff --git a/src/Crypto/WebAuthn/Encoding/Internal/WebAuthnJson.hs b/src/Crypto/WebAuthn/Encoding/Internal/WebAuthnJson.hs
--- a/src/Crypto/WebAuthn/Encoding/Internal/WebAuthnJson.hs
+++ b/src/Crypto/WebAuthn/Encoding/Internal/WebAuthnJson.hs
@@ -207,6 +207,13 @@
 instance Decode m T.AttestationConveyancePreference where
   decode = liftEither . S.decodeAttestationConveyancePreference
 
+instance Encode T.PublicKeyCredentialHint where
+  type JSON T.PublicKeyCredentialHint = Text
+  encode = S.encodePublicKeyCredentialHint
+
+instance Decode m T.PublicKeyCredentialHint where
+  decode = pure . S.decodePublicKeyCredentialHint
+
 instance Encode T.AuthenticatorTransport where
   type JSON T.AuthenticatorTransport = Text
   encode = S.encodeAuthenticatorTransport
@@ -348,6 +355,8 @@
     excludeCredentials :: Maybe [PublicKeyCredentialDescriptor],
     -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialcreationoptions-authenticatorselection)
     authenticatorSelection :: Maybe AuthenticatorSelectionCriteria,
+    -- | [(spec)](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-hints)
+    hints :: Maybe [Text],
     -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialcreationoptions-attestation)
     attestation :: Maybe Text,
     -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialcreationoptions-extensions)
@@ -372,6 +381,7 @@
         timeout = encode corTimeout,
         excludeCredentials = Just $ encode corExcludeCredentials,
         authenticatorSelection = encode corAuthenticatorSelection,
+        hints = Just $ encode corHints,
         attestation = Just $ encode corAttestation,
         extensions = encode corExtensions
       }
@@ -385,6 +395,7 @@
     corTimeout <- decode timeout
     corExcludeCredentials <- decodeWithDefault D.corExcludeCredentialsDefault excludeCredentials
     corAuthenticatorSelection <- decode authenticatorSelection
+    corHints <- decodeWithDefault D.corHintsDefault hints
     corAttestation <- decodeWithDefault D.corAttestationDefault attestation
     corExtensions <- decode extensions
     pure $ T.CredentialOptionsRegistration {..}
@@ -401,6 +412,8 @@
     allowCredentials :: Maybe [PublicKeyCredentialDescriptor],
     -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialrequestoptions-userverification)
     userVerification :: Maybe Text,
+    -- | [(spec)](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-hints)
+    hints :: Maybe [Text],
     -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialrequestoptions-extensions)
     extensions :: Maybe AuthenticationExtensionsClientInputs
   }
@@ -421,6 +434,7 @@
         rpId = encode coaRpId,
         allowCredentials = Just $ encode coaAllowCredentials,
         userVerification = Just $ encode coaUserVerification,
+        hints = Just $ encode coaHints,
         extensions = encode coaExtensions
       }
 
@@ -431,6 +445,7 @@
     coaRpId <- decode rpId
     coaAllowCredentials <- decodeWithDefault D.coaAllowCredentialsDefault allowCredentials
     coaUserVerification <- decodeWithDefault D.coaUserVerificationDefault userVerification
+    coaHints <- decodeWithDefault D.coaHintsDefault hints
     coaExtensions <- decode extensions
     pure $ T.CredentialOptionsAuthentication {..}
 
@@ -605,6 +620,8 @@
     rawId :: Base64UrlString,
     -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredential-response)
     response :: response,
+    -- | [(spec)](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-authenticatorattachment)
+    authenticatorAttachment :: Maybe Text,
     -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredential-getclientextensionresults)
     clientExtensionResults :: AuthenticationExtensionsClientOutputs
   }
@@ -622,9 +639,15 @@
     PublicKeyCredential
       { rawId = encode cIdentifier,
         response = encode cResponse,
+        authenticatorAttachment = encode cAuthenticatorAttachment,
         clientExtensionResults = encode cClientExtensionResults
       }
 
+-- | Decodes the authenticatorAttachment field, treating unknown values as Nothing
+-- per the spec: "Relying Parties SHOULD treat unknown values as if the value were null."
+decodeAuthenticatorAttachment :: Maybe Text -> Maybe T.AuthenticatorAttachment
+decodeAuthenticatorAttachment = (>>= either (const Nothing) Just . S.decodeAuthenticatorAttachment)
+
 instance
   (MonadReader T.SupportedAttestationStatementFormats m) =>
   Decode m (T.Credential 'K.Registration 'True)
@@ -632,6 +655,7 @@
   decode PublicKeyCredential {..} = do
     cIdentifier <- decode rawId
     cResponse <- decode response
+    let cAuthenticatorAttachment = decodeAuthenticatorAttachment authenticatorAttachment
     cClientExtensionResults <- decode clientExtensionResults
     pure $ T.Credential {..}
 
@@ -641,6 +665,7 @@
     PublicKeyCredential
       { rawId = encode cIdentifier,
         response = encode cResponse,
+        authenticatorAttachment = encode cAuthenticatorAttachment,
         clientExtensionResults = encode cClientExtensionResults
       }
 
@@ -648,6 +673,7 @@
   decode PublicKeyCredential {..} = do
     cIdentifier <- decode rawId
     cResponse <- decode response
+    let cAuthenticatorAttachment = decodeAuthenticatorAttachment authenticatorAttachment
     cClientExtensionResults <- decode clientExtensionResults
     pure $ T.Credential {..}
 
diff --git a/src/Crypto/WebAuthn/Encoding/Strings.hs b/src/Crypto/WebAuthn/Encoding/Strings.hs
--- a/src/Crypto/WebAuthn/Encoding/Strings.hs
+++ b/src/Crypto/WebAuthn/Encoding/Strings.hs
@@ -15,6 +15,8 @@
     decodeAttestationConveyancePreference,
     encodeAuthenticatorTransport,
     decodeAuthenticatorTransport,
+    encodePublicKeyCredentialHint,
+    decodePublicKeyCredentialHint,
   )
 where
 
@@ -131,3 +133,22 @@
 -- > The values SHOULD be members of AuthenticatorTransport but Relying
 -- > Parties SHOULD accept and store unknown values.
 decodeAuthenticatorTransport str = T.AuthenticatorTransportUnknown str
+
+-- | [(spec)](https://www.w3.org/TR/webauthn-3/#enumdef-publickeycredentialhint)
+-- Encodes a 'T.PublicKeyCredentialHint' to a string.
+encodePublicKeyCredentialHint :: T.PublicKeyCredentialHint -> Text
+encodePublicKeyCredentialHint T.PublicKeyCredentialHintSecurityKey = "security-key"
+encodePublicKeyCredentialHint T.PublicKeyCredentialHintClientDevice = "client-device"
+encodePublicKeyCredentialHint T.PublicKeyCredentialHintHybrid = "hybrid"
+encodePublicKeyCredentialHint (T.PublicKeyCredentialHintUnknown str) = str
+
+-- | [(spec)](https://www.w3.org/TR/webauthn-3/#enumdef-publickeycredentialhint)
+-- Decodes a string into a 'T.PublicKeyCredentialHint', returning
+-- 'T.PublicKeyCredentialHintUnknown' when the string isn't a known enum value.
+-- This is required for forward compatibility when new hint values are added
+-- to the specification.
+decodePublicKeyCredentialHint :: Text -> T.PublicKeyCredentialHint
+decodePublicKeyCredentialHint "security-key" = T.PublicKeyCredentialHintSecurityKey
+decodePublicKeyCredentialHint "client-device" = T.PublicKeyCredentialHintClientDevice
+decodePublicKeyCredentialHint "hybrid" = T.PublicKeyCredentialHintHybrid
+decodePublicKeyCredentialHint str = T.PublicKeyCredentialHintUnknown str
diff --git a/src/Crypto/WebAuthn/Metadata/Service/Processing.hs b/src/Crypto/WebAuthn/Metadata/Service/Processing.hs
--- a/src/Crypto/WebAuthn/Metadata/Service/Processing.hs
+++ b/src/Crypto/WebAuthn/Metadata/Service/Processing.hs
@@ -34,6 +34,8 @@
     HasX5u (x5u),
     JWSHeader,
     JWTError,
+    RequiredProtection,
+    SignedJWT,
     decodeCompact,
     defaultJWTValidationSettings,
     param,
@@ -114,7 +116,7 @@
       Left err -> error err
       Right cert -> cert
 
-instance (MonadError ProcessingError m, MonadReader DateTime m) => VerificationKeyStore m (JWSHeader ()) p RootCertificate where
+instance (MonadError ProcessingError m, MonadReader DateTime m) => VerificationKeyStore m (JWSHeader RequiredProtection) p RootCertificate where
   getVerificationKeys header _ (RootCertificate rootStore hostName) = do
     -- TODO: Implement step 4 of the spec, which says to try to get the chain
     -- from x5u first before trying x5c. See:
@@ -164,7 +166,7 @@
   DateTime ->
   Either ProcessingError addData
 jwtToAdditionalData blob rootCert now = runExcept $ do
-  jwt <- decodeCompact $ LBS.fromStrict blob
+  jwt <- decodeCompact @SignedJWT $ LBS.fromStrict blob
   payload <- runReaderT (verifyJWT (defaultJWTValidationSettings (const True)) rootCert jwt) now
   return $ Service.additionalData payload
 
diff --git a/src/Crypto/WebAuthn/Model/Defaults.hs b/src/Crypto/WebAuthn/Model/Defaults.hs
--- a/src/Crypto/WebAuthn/Model/Defaults.hs
+++ b/src/Crypto/WebAuthn/Model/Defaults.hs
@@ -14,8 +14,10 @@
     ascResidentKeyDefault,
     corAttestationDefault,
     corExcludeCredentialsDefault,
+    corHintsDefault,
     coaUserVerificationDefault,
     coaAllowCredentialsDefault,
+    coaHintsDefault,
   )
 where
 
@@ -43,6 +45,11 @@
 corExcludeCredentialsDefault :: [M.CredentialDescriptor]
 corExcludeCredentialsDefault = []
 
+-- | [(spec)](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-hints)
+-- Returns the default of the field 'M.corHints'
+corHintsDefault :: [M.PublicKeyCredentialHint]
+corHintsDefault = []
+
 -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialrequestoptions-userverification)
 -- Returns the default of the field 'M.coaUserVerification'
 coaUserVerificationDefault :: M.UserVerificationRequirement
@@ -52,3 +59,8 @@
 -- Returns the default of the field 'M.coaAllowCredentials'
 coaAllowCredentialsDefault :: [M.CredentialDescriptor]
 coaAllowCredentialsDefault = []
+
+-- | [(spec)](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-hints)
+-- Returns the default of the field 'M.coaHints'
+coaHintsDefault :: [M.PublicKeyCredentialHint]
+coaHintsDefault = []
diff --git a/src/Crypto/WebAuthn/Model/Types.hs b/src/Crypto/WebAuthn/Model/Types.hs
--- a/src/Crypto/WebAuthn/Model/Types.hs
+++ b/src/Crypto/WebAuthn/Model/Types.hs
@@ -61,6 +61,7 @@
     ResidentKeyRequirement (..),
     UserVerificationRequirement (..),
     AttestationConveyancePreference (..),
+    PublicKeyCredentialHint (..),
     AttestationChain (..),
     AttestationKind (..),
     AttestationType (..),
@@ -386,6 +387,46 @@
 -- "Crypto.WebAuthn.Encoding" modules
 deriving instance ToJSON AttestationConveyancePreference
 
+-- | [(spec)](https://www.w3.org/TR/webauthn-3/#enumdef-publickeycredentialhint)
+-- [WebAuthn Relying Parties](https://www.w3.org/TR/webauthn-3/#webauthn-relying-party) may use
+-- this enumeration to communicate hints to the user-agent about how a request may be best completed.
+-- These hints are not requirements, and do not bind the user-agent, but may guide it in providing
+-- the best experience by using contextual information that the Relying Party has about the request.
+-- Hints are provided in order of decreasing preference so, if two hints are contradictory,
+-- the first one controls.
+--
+-- Note: It is important for backwards compatibility that client platforms and Relying Parties
+-- handle unknown values, which is why 'PublicKeyCredentialHintUnknown' exists.
+--
+-- To decode\/encode this type from\/to its standard string, use
+-- 'Crypto.WebAuthn.Encoding.Strings.decodePublicKeyCredentialHint'/'Crypto.WebAuthn.Encoding.Strings.encodePublicKeyCredentialHint'.
+data PublicKeyCredentialHint
+  = -- | [(spec)](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialhint-security-key)
+    -- Indicates that the Relying Party believes that users will satisfy this request with a physical
+    -- security key. For example, an enterprise Relying Party may set this hint if they have issued
+    -- security keys to their employees and will only accept those authenticators for registration
+    -- and authentication.
+    PublicKeyCredentialHintSecurityKey
+  | -- | [(spec)](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialhint-client-device)
+    -- Indicates that the Relying Party believes that users will satisfy this request with a platform
+    -- authenticator attached to the client device.
+    PublicKeyCredentialHintClientDevice
+  | -- | [(spec)](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialhint-hybrid)
+    -- Indicates that the Relying Party believes that users will satisfy this request with general-purpose
+    -- authenticators such as smartphones. For example, a consumer Relying Party may believe that only
+    -- a small fraction of their customers possesses dedicated security keys. This option also implies
+    -- that the local platform authenticator should not be promoted in the UI.
+    PublicKeyCredentialHintHybrid
+  | -- | An unknown credential hint value. This allows for forward compatibility
+    -- when new hint values are added to the specification.
+    PublicKeyCredentialHintUnknown Text
+  deriving (Eq, Show, Ord, Generic)
+
+-- | An arbitrary and potentially unstable JSON encoding, only intended for
+-- logging purposes. To actually encode and decode structures, use the
+-- "Crypto.WebAuthn.Encoding" modules
+deriving instance ToJSON PublicKeyCredentialHint
+
 -- | An X.509 certificate chain that can be used to verify an attestation
 -- statement
 data AttestationChain (p :: ProtocolKind) where
@@ -943,16 +984,29 @@
 -- "Crypto.WebAuthn.Encoding" modules
 deriving instance ToJSON AuthenticatorSelectionCriteria
 
--- | [(spec)](https://www.w3.org/TR/webauthn-2/#flags)
+-- | [(spec)](https://www.w3.org/TR/webauthn-3/#flags)
 data AuthenticatorDataFlags = AuthenticatorDataFlags
-  { -- | [(spec)](https://www.w3.org/TR/webauthn-2/#concept-user-present)
-    -- Upon successful completion of a [user presence test](https://www.w3.org/TR/webauthn-2/#test-of-user-presence),
-    -- the user is said to be "[present](https://www.w3.org/TR/webauthn-2/#concept-user-present)".
+  { -- | [(spec)](https://www.w3.org/TR/webauthn-3/#concept-user-present)
+    -- Upon successful completion of a [user presence test](https://www.w3.org/TR/webauthn-3/#test-of-user-presence),
+    -- the user is said to be "[present](https://www.w3.org/TR/webauthn-3/#concept-user-present)".
     adfUserPresent :: Bool,
-    -- | [(spec)](https://www.w3.org/TR/webauthn-2/#concept-user-verified)
-    -- Upon successful completion of a [user verification](https://www.w3.org/TR/webauthn-2/#user-verification) process,
-    -- the user is said to be "[verified](https://www.w3.org/TR/webauthn-2/#concept-user-verified)".
-    adfUserVerified :: Bool
+    -- | [(spec)](https://www.w3.org/TR/webauthn-3/#concept-user-verified)
+    -- Upon successful completion of a [user verification](https://www.w3.org/TR/webauthn-3/#user-verification) process,
+    -- the user is said to be "[verified](https://www.w3.org/TR/webauthn-3/#concept-user-verified)".
+    adfUserVerified :: Bool,
+    -- | [(spec)](https://www.w3.org/TR/webauthn-3/#backup-eligibility)
+    -- The backup eligibility (BE) flag. If set, the credential is a
+    -- [multi-device credential](https://www.w3.org/TR/webauthn-3/#multi-device-credential).
+    -- If not set, the credential is a
+    -- [single-device credential](https://www.w3.org/TR/webauthn-3/#single-device-credential).
+    -- This value is set during registration and MUST NOT change.
+    adfBackupEligible :: Bool,
+    -- | [(spec)](https://www.w3.org/TR/webauthn-3/#backup-state)
+    -- The backup state (BS) flag. If set, the credential is currently backed up.
+    -- This flag can change over time based on the current state of the
+    -- public key credential source. This flag can only be set if 'adfBackupEligible'
+    -- is also set; the combination @(BE=False, BS=True)@ is not allowed.
+    adfBackupState :: Bool
   }
   deriving (Eq, Show, Generic)
 
@@ -1031,6 +1085,12 @@
       -- that wish to select the appropriate authenticators to participate in the
       -- [create()](https://w3c.github.io/webappsec-credential-management/#dom-credentialscontainer-create) operation.
       corAuthenticatorSelection :: Maybe AuthenticatorSelectionCriteria,
+      -- | [(spec)](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-hints)
+      -- This OPTIONAL member contains zero or more elements from 'PublicKeyCredentialHint' to
+      -- guide the user agent in interacting with the user. Hints are provided in order of
+      -- decreasing preference so, if two hints are contradictory, the first one controls.
+      -- The default value of this field is 'Crypto.WebAuthn.Model.Defaults.corHintsDefault'.
+      corHints :: [PublicKeyCredentialHint],
       -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialcreationoptions-attestation)
       -- This member is intended for use by [Relying Parties](https://www.w3.org/TR/webauthn-2/#relying-party)
       -- that wish to express their preference for [attestation conveyance](https://www.w3.org/TR/webauthn-2/#attestation-conveyance).
@@ -1080,6 +1140,12 @@
       -- `[get()](https://w3c.github.io/webappsec-credential-management/#dom-credentialscontainer-get)` operation.
       -- The default value of this field is 'Crypto.WebAuthn.Model.Defaults.coaUserVerificationDefault'.
       coaUserVerification :: UserVerificationRequirement,
+      -- | [(spec)](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-hints)
+      -- This OPTIONAL member contains zero or more elements from 'PublicKeyCredentialHint' to
+      -- guide the user agent in interacting with the user. Hints are provided in order of
+      -- decreasing preference so, if two hints are contradictory, the first one controls.
+      -- The default value of this field is 'Crypto.WebAuthn.Model.Defaults.coaHintsDefault'.
+      coaHints :: [PublicKeyCredentialHint],
       -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialrequestoptions-extensions)
       -- This OPTIONAL member contains additional parameters requesting additional processing by the client and authenticator.
       -- For example, if transaction confirmation is sought from the user, then the prompt string might be included as an extension.
@@ -1106,6 +1172,7 @@
         "corTimeout" .= corTimeout,
         "corExcludeCredentials" .= corExcludeCredentials,
         "corAuthenticatorSelection" .= corAuthenticatorSelection,
+        "corHints" .= corHints,
         "corAttestation" .= corAttestation,
         "corExtensions" .= corExtensions
       ]
@@ -1117,6 +1184,7 @@
         "coaRpId" .= coaRpId,
         "coaAllowCredentials" .= coaAllowCredentials,
         "coaUserVerification" .= coaUserVerification,
+        "coaHints" .= coaHints,
         "coaExtensions" .= coaExtensions
       ]
 
@@ -1432,7 +1500,8 @@
 -- data, specifically 'aoAttStmt'. The
 -- 'Crypto.WebAuthn.Encoding.Binary.encodeAttestationObject' can be used to get
 -- the binary encoding of this type when @raw ~ 'True'@.
-data AttestationObject raw = forall a.
+data AttestationObject raw
+  = forall a.
   (AttestationStatementFormat a) =>
   AttestationObject
   { -- | [(spec)](https://www.w3.org/TR/webauthn-2/#authenticator-data)
@@ -1654,6 +1723,11 @@
     -- was created in response to `[get()](https://w3c.github.io/webappsec-credential-management/#dom-credentialscontainer-get)`,
     -- and this attribute’s value will be an `[AuthenticatorAssertionResponse](https://www.w3.org/TR/webauthn-2/#authenticatorassertionresponse)`.
     cResponse :: AuthenticatorResponse c raw,
+    -- | [(spec)](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-authenticatorattachment)
+    -- This attribute reports the [authenticator attachment modality](https://www.w3.org/TR/webauthn-3/#authenticator-attachment-modality)
+    -- in effect at the time the navigator.credentials.create() or navigator.credentials.get() methods successfully complete.
+    -- The attribute's value SHOULD be a member of 'AuthenticatorAttachment'. Relying Parties SHOULD treat unknown values as if the value were null.
+    cAuthenticatorAttachment :: Maybe AuthenticatorAttachment,
     -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredential-getclientextensionresults)
     -- This operation returns the value of `[[[clientExtensionsResults]]](https://www.w3.org/TR/webauthn-2/#dom-publickeycredential-clientextensionsresults-slot)`,
     -- which is a [map](https://infra.spec.whatwg.org/#ordered-map) containing
@@ -1673,9 +1747,9 @@
 -- The 'CredentialMediationRequirement' enum defines the requirements for
 -- [user mediation](https://www.w3.org/TR/credential-management-1/#user-mediation).
 -- Currently only `CredentialMediationRequirementConditional` is supported during credential creation.
-data CredentialMediationRequirement = 
-  CredentialMediationRequirementSilent |
-  CredentialMediationRequirementOptional |
-  CredentialMediationRequirementConditional |
-  CredentialMediationRequirementRequired
+data CredentialMediationRequirement
+  = CredentialMediationRequirementSilent
+  | CredentialMediationRequirementOptional
+  | CredentialMediationRequirementConditional
+  | CredentialMediationRequirementRequired
   deriving (Eq, Show)
diff --git a/src/Crypto/WebAuthn/Operation/Registration.hs b/src/Crypto/WebAuthn/Operation/Registration.hs
--- a/src/Crypto/WebAuthn/Operation/Registration.hs
+++ b/src/Crypto/WebAuthn/Operation/Registration.hs
@@ -209,7 +209,8 @@
 -- that was returned along with information about the [authenticator](https://www.w3.org/TR/webauthn-2/#authenticator)
 -- model that created it. This result may be inspected to enforce relying party
 -- policy, see the individual fields for more information.
-data SomeAttestationStatement = forall k.
+data SomeAttestationStatement
+  = forall k.
   SomeAttestationStatement
   { -- | The [attestation type](https://www.w3.org/TR/webauthn-2/#sctn-attestation-types)
     -- of the attestation statement. This could be used to only allow specific
@@ -310,15 +311,14 @@
   rpIdHash
   registry
   currentTime
-  options
-  =
-      verifyRegistrationResponseL3
-        origins
-        rpIdHash
-        registry
-        currentTime
-        options
-        M.CredentialMediationRequirementOptional
+  options =
+    verifyRegistrationResponseL3
+      origins
+      rpIdHash
+      registry
+      currentTime
+      options
+      M.CredentialMediationRequirementOptional
 
 -- | Like 'verifyRegistrationResponse', but allows passing the credential mediation requirement
 -- If you don't need conditional create functionality, use 'verifyRegistrationResponse' instead.
diff --git a/tests/Emulation.hs b/tests/Emulation.hs
--- a/tests/Emulation.hs
+++ b/tests/Emulation.hs
@@ -20,7 +20,6 @@
 import qualified Data.List.NonEmpty as NE
 import qualified Data.Set as Set
 import Data.Text.Encoding (encodeUtf8)
-import Data.Validation (toEither)
 import Emulation.Authenticator
   ( Authenticator (AuthenticatorNone, aAuthenticatorDataFlags, aConformance, aSignatureCounter),
     AuthenticatorNonConformingBehaviour (RandomPrivateKey, RandomSignatureData, StaticCounter),
@@ -35,7 +34,7 @@
     clientAttestation,
   )
 import Emulation.Client.Arbitrary ()
-import Spec.Util (predeterminedDateTime)
+import Spec.Util (predeterminedDateTime, toEither)
 import Test.Hspec (SpecWith, describe, it, shouldSatisfy)
 import Test.QuickCheck (property, (==>))
 
@@ -289,6 +288,7 @@
               M.ascResidentKey = M.ResidentKeyRequirementDiscouraged,
               M.ascUserVerification = M.UserVerificationRequirementPreferred
             },
+      M.corHints = [],
       M.corAttestation = M.AttestationConveyancePreferenceDirect,
       M.corExtensions = Nothing
     }
@@ -303,5 +303,6 @@
       -- We currently only support client-side discoverable credentials
       M.coaAllowCredentials = [],
       M.coaUserVerification = M.UserVerificationRequirementPreferred,
+      M.coaHints = [],
       M.coaExtensions = Nothing
     }
diff --git a/tests/Emulation/Client.hs b/tests/Emulation/Client.hs
--- a/tests/Emulation/Client.hs
+++ b/tests/Emulation/Client.hs
@@ -92,6 +92,7 @@
                   M.arrAttestationObject = attestationObject,
                   M.arrTransports = []
                 },
+            M.cAuthenticatorAttachment = Nothing,
             M.cClientExtensionResults = M.AuthenticationExtensionsClientOutputs {aecoCredProps = Nothing}
           }
   pure (response, authenticator')
@@ -145,6 +146,7 @@
                   M.araSignature = M.AssertionSignature signature,
                   M.araUserHandle = userHandle
                 },
+            M.cAuthenticatorAttachment = Nothing,
             M.cClientExtensionResults = M.AuthenticationExtensionsClientOutputs {aecoCredProps = Nothing}
           }
   pure (response, authenticator')
diff --git a/tests/Main.hs b/tests/Main.hs
--- a/tests/Main.hs
+++ b/tests/Main.hs
@@ -30,13 +30,13 @@
 import qualified Data.Text as Text
 import Data.Text.Encoding (encodeUtf8)
 import Data.These (These (That, These, This))
-import Data.Validation (toEither)
+import qualified Data.X509.Validation as X509
 import qualified Emulation
 import qualified Encoding
 import GHC.Stack (HasCallStack)
 import qualified MetadataSpec
 import qualified PublicKeySpec
-import Spec.Util (decodeFile, predeterminedDateTime, timeZero)
+import Spec.Util (decodeFile, predeterminedDateTime, timeZero, toEither)
 import qualified System.Directory as Directory
 import System.FilePath ((</>))
 import Test.Hspec (Spec, describe, it, shouldSatisfy)
@@ -73,15 +73,21 @@
 -- This is because some of our tests cannot be verfied (for different reasons).
 registerTestFromFile :: FilePath -> M.Origin -> M.RpId -> Bool -> Service.MetadataServiceRegistry -> HG.DateTime -> IO ()
 registerTestFromFile fp origin rpId verifiable service now = do
-  registerTestFromFile' fp origin rpId verifiable service now M.CredentialMediationRequirementOptional
+  registerTestFromFile' fp origin rpId verifiable Nothing service now M.CredentialMediationRequirementOptional
 
+-- | Like 'registerTestFromFile', but expects chain validation to fail for the
+-- specified reasons.
+registerTestFromFileWithExpectedValidationFailures :: FilePath -> M.Origin -> M.RpId -> NonEmpty X509.FailedReason -> Service.MetadataServiceRegistry -> HG.DateTime -> IO ()
+registerTestFromFileWithExpectedValidationFailures fp origin rpId expectedFailures service now = do
+  registerTestFromFile' fp origin rpId True (Just expectedFailures) service now M.CredentialMediationRequirementOptional
+
 -- | Same as 'registerTestFromFile', but allows to specifies the mediation requirement as conditional.
 registerTestFromFileConditional :: FilePath -> M.Origin -> M.RpId -> Bool -> Service.MetadataServiceRegistry -> HG.DateTime -> IO ()
 registerTestFromFileConditional fp origin rpId verifiable service now = do
-  registerTestFromFile' fp origin rpId verifiable service now M.CredentialMediationRequirementConditional
+  registerTestFromFile' fp origin rpId verifiable Nothing service now M.CredentialMediationRequirementConditional
 
-registerTestFromFile' :: FilePath -> M.Origin -> M.RpId -> Bool -> Service.MetadataServiceRegistry -> HG.DateTime -> M.CredentialMediationRequirement -> IO ()
-registerTestFromFile' fp origin rpId verifiable service now mediation = do
+registerTestFromFile' :: FilePath -> M.Origin -> M.RpId -> Bool -> Maybe (NonEmpty X509.FailedReason) -> Service.MetadataServiceRegistry -> HG.DateTime -> M.CredentialMediationRequirement -> IO ()
+registerTestFromFile' fp origin rpId verifiable expectedFailures service now mediation = do
   pkCredential <-
     either (error . show) id . WJ.wjDecodeCredentialRegistration
       <$> decodeFile fp
@@ -96,7 +102,7 @@
             options
             mediation
             pkCredential
-  registerResult `shouldSatisfy` isExpectedAttestationResponse pkCredential options verifiable
+  registerResult `shouldSatisfy` isExpectedAttestationResponse pkCredential options verifiable expectedFailures
 
 main :: IO ()
 main = Hspec.hspec $ do
@@ -141,7 +147,7 @@
                   predeterminedDateTime
                   options
                   pkCredential
-        registerResult `shouldSatisfy` isExpectedAttestationResponse pkCredential options False
+        registerResult `shouldSatisfy` isExpectedAttestationResponse pkCredential options False Nothing
         let Right O.RegistrationResult {O.rrEntry = credentialEntry} = registerResult
         loginReq <-
           either (error . show) id . WJ.wjDecodeCredentialAuthentication
@@ -160,6 +166,7 @@
                   M.Credential
                     { M.cIdentifier = O.ceCredentialId credentialEntry,
                       M.cResponse = cResponse,
+                      M.cAuthenticatorAttachment = Nothing,
                       M.cClientExtensionResults = M.AuthenticationExtensionsClientOutputs Nothing
                     }
         signInResult `shouldSatisfy` isRight
@@ -179,7 +186,7 @@
                   predeterminedDateTime
                   options
                   pkCredential
-        registerResult `shouldSatisfy` isExpectedAttestationResponse pkCredential options False
+        registerResult `shouldSatisfy` isExpectedAttestationResponse pkCredential options False Nothing
         let Right O.RegistrationResult {O.rrEntry = credentialEntry} = registerResult
         loginReq <-
           either (error . show) id . WJ.wjDecodeCredentialAuthentication
@@ -198,6 +205,7 @@
                   M.Credential
                     { M.cIdentifier = O.ceCredentialId credentialEntry,
                       M.cResponse = cResponse,
+                      M.cAuthenticatorAttachment = Nothing,
                       M.cClientExtensionResults = M.AuthenticationExtensionsClientOutputs Nothing
                     }
         signInResult `shouldSatisfy` isRight
@@ -351,15 +359,19 @@
         registry
         HG.DateTime {dtDate = HG.Date {dateYear = 2021, dateMonth = HG.September, dateDay = 1}, dtTime = timeZero}
   describe "TPM register" $ do
-    it "tests whether the fixed TPM-SHA1 register has a valid attestation" $
-      registerTestFromFile
+    it "reports the expected validation failure for the fixed TPM-RS1 registration" $
+      registerTestFromFileWithExpectedValidationFailures
         "tests/responses/attestation/tpm-rs1-01.json"
         "https://webauthntest.azurewebsites.net"
         "webauthntest.azurewebsites.net"
-        True
+        -- crypton-x509-validation >= 1.9.1 reports a failure for every
+        -- critical extension outside crypton-x509's recognizedOIDs. This
+        -- certificate marks Certificate Policies critical, but that extension
+        -- is irrelevant to attestation trust.
+        (NE.singleton $ X509.UnknownCriticalExtension [2, 5, 29, 32])
         registry
         predeterminedDateTime
-    it "tests whether the fixed TPM-SHA1 register has a valid attestation" $
+    it "tests whether the fixed TPM-ES256 register has a valid attestation" $
       registerTestFromFile
         "tests/responses/attestation/tpm-es256-01.json"
         "https://localhost:44329"
@@ -378,14 +390,15 @@
         predeterminedDateTime
 
 -- | Checks if the received attestation response if one we expect
-isExpectedAttestationResponse :: M.Credential 'M.Registration 'True -> M.CredentialOptions 'M.Registration -> Bool -> Either (NonEmpty O.RegistrationError) O.RegistrationResult -> Bool
-isExpectedAttestationResponse _ _ _ (Left _) = False -- We should never receive errors
-isExpectedAttestationResponse M.Credential {..} M.CredentialOptionsRegistration {..} verifiable (Right O.RegistrationResult {..}) =
+isExpectedAttestationResponse :: M.Credential 'M.Registration 'True -> M.CredentialOptions 'M.Registration -> Bool -> Maybe (NonEmpty X509.FailedReason) -> Either (NonEmpty O.RegistrationError) O.RegistrationResult -> Bool
+isExpectedAttestationResponse _ _ _ _ (Left _) = False -- We should never receive errors
+isExpectedAttestationResponse M.Credential {..} M.CredentialOptionsRegistration {..} verifiable expectedFailures (Right O.RegistrationResult {..}) =
   rrEntry == expectedCredentialEntry
-    && not verifiable
-    || ( case rrAttestationStatement of
-           O.SomeAttestationStatement _ O.VerifiedAuthenticator {} -> True
-           _ -> False
+    && ( not verifiable
+           || case (rrAttestationStatement, expectedFailures) of
+             (O.SomeAttestationStatement _ O.VerifiedAuthenticator {}, Nothing) -> True
+             (O.SomeAttestationStatement _ O.UnverifiedAuthenticator {O.uaFailures = failures}, Just expected) -> failures == expected
+             _ -> False
        )
   where
     expectedCredentialEntry :: O.CredentialEntry
@@ -436,6 +449,7 @@
       M.corTimeout = Nothing,
       M.corExcludeCredentials = [],
       M.corAuthenticatorSelection = Nothing,
+      M.corHints = [],
       M.corAttestation = M.AttestationConveyancePreferenceNone,
       M.corExtensions = Nothing
     }
@@ -448,5 +462,6 @@
       M.coaRpId = Just "localhost",
       M.coaAllowCredentials = [],
       M.coaUserVerification = M.UserVerificationRequirementPreferred,
+      M.coaHints = [],
       M.coaExtensions = Nothing
     }
diff --git a/tests/Spec/Types.hs b/tests/Spec/Types.hs
--- a/tests/Spec/Types.hs
+++ b/tests/Spec/Types.hs
@@ -98,6 +98,17 @@
 instance Arbitrary M.AttestationConveyancePreference where
   arbitrary = arbitraryBoundedEnum
 
+instance Arbitrary M.PublicKeyCredentialHint where
+  arbitrary = elements publicKeyCredentialHintsList
+
+publicKeyCredentialHintsList :: [M.PublicKeyCredentialHint]
+publicKeyCredentialHintsList =
+  [ M.PublicKeyCredentialHintSecurityKey,
+    M.PublicKeyCredentialHintClientDevice,
+    M.PublicKeyCredentialHintHybrid,
+    M.PublicKeyCredentialHintUnknown "unknown"
+  ]
+
 instance Arbitrary (M.AuthenticatorResponse 'M.Registration 'False) where
   arbitrary = M.AuthenticatorResponseRegistration <$> arbitrary <*> arbitrary <*> arbitrary
 
@@ -164,7 +175,7 @@
     pure $ M.RpIdHash $ hash rpId
 
 instance Arbitrary M.AuthenticatorDataFlags where
-  arbitrary = M.AuthenticatorDataFlags <$> arbitrary <*> arbitrary
+  arbitrary = M.AuthenticatorDataFlags <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
 
 instance (SingI c) => Arbitrary (M.AttestedCredentialData c 'False) where
   arbitrary = case sing @c of
@@ -248,6 +259,7 @@
       <*> arbitrary
       <*> resize 4 arbitrary
       <*> arbitrary
+      <*> resize 3 arbitrary
       <*> arbitrary
       <*> arbitrary
 
@@ -259,6 +271,7 @@
       <*> arbitrary
       <*> resize 4 arbitrary
       <*> arbitrary
+      <*> resize 3 arbitrary
       <*> arbitrary
 
 instance Arbitrary M.CredentialPropertiesOutput where
@@ -273,11 +286,13 @@
       <$> arbitrary
       <*> arbitrary
       <*> arbitrary
+      <*> arbitrary
 
 instance Arbitrary (M.Credential 'M.Authentication 'False) where
   arbitrary =
     M.Credential
       <$> arbitrary
+      <*> arbitrary
       <*> arbitrary
       <*> arbitrary
 
diff --git a/tests/Spec/Util.hs b/tests/Spec/Util.hs
--- a/tests/Spec/Util.hs
+++ b/tests/Spec/Util.hs
@@ -1,10 +1,16 @@
-module Spec.Util (decodeFile, runSeededMonadRandom, timeZero, predeterminedDateTime) where
+module Spec.Util (decodeFile, runSeededMonadRandom, timeZero, predeterminedDateTime, toEither) where
 
 import qualified Crypto.Random as Random
 import Data.Aeson (FromJSON)
 import qualified Data.Aeson as Aeson
 import qualified Data.ByteString.Lazy as ByteString
 import qualified Data.Hourglass as HG
+import Data.Validation (Validation (Failure, Success))
+
+-- | validation 1.2 dropped 'Data.Validation.toEither'.
+toEither :: Validation e a -> Either e a
+toEither (Failure e) = Left e
+toEither (Success a) = Right a
 
 decodeFile :: (FromJSON a, Show a) => FilePath -> IO a
 decodeFile filePath = do
diff --git a/webauthn.cabal b/webauthn.cabal
--- a/webauthn.cabal
+++ b/webauthn.cabal
@@ -1,6 +1,6 @@
 cabal-version: 2.4
 name: webauthn
-version: 0.11.0.0
+version: 0.12.0.0
 license: Apache-2.0
 license-file: LICENSE
 copyright:
@@ -35,7 +35,7 @@
   however follow the [PVP](https://pvp.haskell.org/) and properly label changes
   with the appropriate version increase.
 category: Web, Authentication
-tested-with: GHC == 9.4.7
+tested-with: GHC == 9.10.3
 extra-source-files:
   README.md,
   changelog.md,
@@ -69,27 +69,27 @@
   import: sanity
   hs-source-dirs: src
   build-depends:
-    base                    >= 4.13.0 && < 4.20,
-    aeson                   >= 2.0.1 && < 2.3,
-    asn1-encoding           >= 0.9.6 && < 0.10,
-    asn1-parse              >= 0.9.5 && < 0.10,
-    asn1-types              >= 0.3.4 && < 0.4,
+    base                    >= 4.13.0 && < 4.22,
+    aeson                   >= 2.0.1 && < 2.4,
     base16-bytestring       >= 1.0.0 && < 1.1,
     base64-bytestring       >= 1.2.1 && < 1.3,
     binary                  >= 0.8.7 && < 0.9,
     bytestring              >= 0.10.10 && < 0.13,
     cborg                   >= 0.2.4 && < 0.3,
-    containers              >= 0.6.2.1 && < 0.7,
-    crypton                 >= 0.32 && < 0.35,
-    crypton-x509            >= 1.7.6 && < 1.8,
-    crypton-x509-store      >= 1.6.9 && < 1.7,
-    crypton-x509-validation >= 1.6.12 && < 1.7,
+    containers              >= 0.6.2.1 && < 0.9,
+    crypton                 >= 1.1 && < 1.2,
+    crypton-asn1-encoding   >= 0.9 && < 0.11,
+    crypton-asn1-parse      >= 0.9 && < 0.11,
+    crypton-asn1-types      >= 0.4.1 && < 0.5,
+    crypton-x509            >= 1.9.1 && < 1.10,
+    crypton-x509-store      >= 1.9 && < 1.10,
+    crypton-x509-validation >= 1.9.1 && < 1.10,
     file-embed              >= 0.0.11 && < 0.1,
-    hashable                >= 1.3.2 && < 1.5,
-    hourglass               >= 0.2.12 && < 0.3,
-    jose                    >= 0.11 && < 0.12,
-    lens                    >= 4.18.1 && < 5.3,
-    memory                  >= 0.15.0 && < 0.19,
+    hashable                >= 1.3.2 && < 1.6,
+    time-hourglass          >= 0.2 && < 0.4,
+    jose                    >= 0.13 && < 0.14,
+    lens                    >= 4.18.1 && < 5.4,
+    ram                     >= 0.22 && < 0.23,
     monad-time              >= 0.4.0 && < 0.5,
     mtl                     >= 2.2.2 && < 2.4,
     serialise               >= 0.2.3 && < 0.3,
@@ -97,6 +97,7 @@
     text                    >= 1.2.4 && < 2.2,
     these                   >= 1.1.1.1 && < 1.3,
     time                    >= 1.9.3 && < 1.14,
+    time-hourglass          >= 0.2 && < 0.4,
     unordered-containers    >= 0.2.12 && < 0.3,
     uuid                    >= 1.3.13 && < 1.4,
     validation              >= 1.1 && < 1.3,
@@ -167,20 +168,21 @@
     base,
     QuickCheck,
     aeson,
-    asn1-encoding,
+    crypton-asn1-encoding,
     bytestring,
     containers,
     crypton,
     crypton-x509,
     crypton-x509-store,
+    crypton-x509-validation,
     directory,
     filepath,
-    hourglass,
+    time-hourglass,
     hspec,
     hspec-expectations-json,
-    memory,
+    ram,
     mtl,
-    pem,
+    crypton-pem,
     quickcheck-instances,
     serialise,
     singletons,
