From 21339c8c59500fb713f3c9d2869f6bd003ac016b Mon Sep 17 00:00:00 2001 From: rmartinc Date: Mon, 27 Oct 2025 09:05:18 +0100 Subject: [PATCH] Only add the none verifier when attestation conveyance preference is none Closes #43723 Signed-off-by: rmartinc (cherry picked from commit 1bd9a3f4733f80f30111a5e2bad973b85530dc16) --- .../requiredactions/WebAuthnRegister.java | 31 ++++++++++------ .../AttestationConveyanceRegisterTest.java | 36 +++++++++++++++++++ 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/WebAuthnRegister.java b/services/src/main/java/org/keycloak/authentication/requiredactions/WebAuthnRegister.java index 9bfa00eaf7dc..67c356961151 100644 --- a/services/src/main/java/org/keycloak/authentication/requiredactions/WebAuthnRegister.java +++ b/services/src/main/java/org/keycloak/authentication/requiredactions/WebAuthnRegister.java @@ -61,6 +61,7 @@ import org.keycloak.utils.StringUtil; import com.webauthn4j.converter.util.ObjectConverter; +import com.webauthn4j.data.AttestationConveyancePreference; import com.webauthn4j.data.attestation.authenticator.AttestedCredentialData; import com.webauthn4j.data.attestation.statement.AttestationStatement; import com.webauthn4j.data.attestation.statement.COSEAlgorithmIdentifier; @@ -72,6 +73,7 @@ import com.webauthn4j.data.RegistrationParameters; import com.webauthn4j.server.ServerProperty; import com.webauthn4j.util.exception.WebAuthnException; +import com.webauthn4j.verifier.attestation.statement.AttestationStatementVerifier; import com.webauthn4j.verifier.attestation.statement.androidkey.AndroidKeyAttestationStatementVerifier; import com.webauthn4j.verifier.attestation.statement.androidsafetynet.AndroidSafetyNetAttestationStatementVerifier; import com.webauthn4j.verifier.attestation.statement.none.NoneAttestationStatementVerifier; @@ -264,7 +266,7 @@ public void processAction(RequiredActionContext context) { AuthenticatorUtil.logoutOtherSessions(context); } - WebAuthnRegistrationManager webAuthnRegistrationManager = createWebAuthnRegistrationManager(); + WebAuthnRegistrationManager webAuthnRegistrationManager = createWebAuthnRegistrationManager(policy.getAttestationConveyancePreference()); try { // parse RegistrationData registrationData = webAuthnRegistrationManager.parse(registrationRequest); @@ -314,22 +316,29 @@ public void processAction(RequiredActionContext context) { * Create WebAuthnRegistrationManager instance * Can be overridden in subclasses to customize the used attestation validators * + * @param attestationPreference The attestation selected in the policy * @return webauthn4j WebAuthnRegistrationManager instance */ - protected WebAuthnRegistrationManager createWebAuthnRegistrationManager() { + protected WebAuthnRegistrationManager createWebAuthnRegistrationManager(String attestationPreference) { + List verifiers = new ArrayList<>(6); + if (attestationPreference == null + || Constants.DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED.equals(attestationPreference) + || AttestationConveyancePreference.NONE.getValue().equals(attestationPreference)) { + verifiers.add(new NoneAttestationStatementVerifier()); + } + verifiers.add(new PackedAttestationStatementVerifier()); + verifiers.add(new TPMAttestationStatementVerifier()); + verifiers.add(new AndroidKeyAttestationStatementVerifier()); + verifiers.add(new AndroidSafetyNetAttestationStatementVerifier()); + verifiers.add(new FIDOU2FAttestationStatementVerifier()); + return new WebAuthnRegistrationManager( - Arrays.asList( - new NoneAttestationStatementVerifier(), - new PackedAttestationStatementVerifier(), - new TPMAttestationStatementVerifier(), - new AndroidKeyAttestationStatementVerifier(), - new AndroidSafetyNetAttestationStatementVerifier(), - new FIDOU2FAttestationStatementVerifier() - ), this.certPathtrustVerifier, + verifiers, + this.certPathtrustVerifier, new DefaultSelfAttestationTrustworthinessVerifier(), Collections.emptyList(), // Custom Registration Verifier is not supported new ObjectConverter() - ); + ); } /** diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/webauthn/registration/AttestationConveyanceRegisterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/webauthn/registration/AttestationConveyanceRegisterTest.java index dc8c919bf65a..d7198b146681 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/webauthn/registration/AttestationConveyanceRegisterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/webauthn/registration/AttestationConveyanceRegisterTest.java @@ -35,7 +35,10 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertTrue; import static org.keycloak.models.Constants.DEFAULT_WEBAUTHN_POLICY_NOT_SPECIFIED; +import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad; import static org.keycloak.testsuite.webauthn.authenticators.DefaultVirtualAuthOptions.DEFAULT; /** @@ -92,6 +95,39 @@ public void attestationConveyancePreferenceDirect() { } } + @Test + public void attestationConveyancePreferenceNoneToDirect() throws IOException { + oauth.openLoginForm(); + waitForPageToLoad(); + loginPage.assertCurrent(); + loginPage.clickRegister(); + + waitForPageToLoad(); + registerPage.assertCurrent(); + registerPage.register("firstName", "lastName", EMAIL, USERNAME, generatePassword(USERNAME)); + + // User was registered. Now he needs to register WebAuthn credential + waitForPageToLoad(); + webAuthnRegisterPage.assertCurrent(); + webAuthnRegisterPage.clickRegister(); + + try (AbstractWebAuthnRealmUpdater updater = getWebAuthnRealmUpdater() + .setWebAuthnPolicyAttestationConveyancePreference(AttestationConveyancePreference.DIRECT.getValue()) + .update()) { + + testingClient.testing().disableTruststoreSpi(); + + assertTrue(webAuthnRegisterPage.isRegisterAlertPresent()); + webAuthnRegisterPage.registerWebAuthnCredential("new webauth credential"); + + // should fail because none is not allowed + webAuthnErrorPage.isCurrent(); + assertThat(webAuthnErrorPage.getError(), containsString("AttestationVerifier is not configured to handle the supplied AttestationStatement format 'none'.")); + } finally { + testingClient.testing().reenableTruststoreSpi(); + } + } + protected void assertAttestationConveyance(boolean shouldSuccess, AttestationConveyancePreference attestation) { Credential credential = getDefaultResidentKeyCredential();