8000 [CVE-2026-118030] Concurrent credential updates by mabartos · Pull Request #55105 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,7 @@ public boolean isValid(RealmModel realm, UserModel user, CredentialInput credent
return false;
}
otpCredentialModel.updateCounter(counter);
user.credentialManager().updateStoredCredential(otpCredentialModel);
return true;
return user.credentialManager().updateStoredCredential(credential, otpCredentialModel);
} else if (OTPCredentialModel.TOTP.equals(credentialData.getSubType())) {
TimeBasedOTP validator = new TimeBasedOTP(credentialData.getAlgorithm(), credentialData.getDigits(), credentialData.getPeriod(), policy.getLookAheadWindow());
final boolean isValid = validator.validateTOTP(challengeResponse, otpCredentialModel.getDecodedSecret());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,7 @@ public boolean isValid(RealmModel realm, UserModel user, CredentialInput credent
String nextRecoveryCode = nextRecoveryAuthnCode.get().getEncodedHashedValue();
if (RecoveryAuthnCodesUtils.verifyRecoveryCodeInput(rawInputRecoveryAuthnCode, nextRecoveryCode)) {
credentialModel.removeRecoveryAuthnCode();
user.credentialManager().updateStoredCredential(credentialModel);
return true;
return user.credentialManager().updateStoredCredential(credential.get(), credentialModel);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,8 @@ public WebAuthnCredentialModel getCredentialModelFromCredentialInput(CredentialI
/**
* Convert WebAuthnCredentialModel, which was usually retrieved from DB, to the CredentialInput, which contains data in the webauthn4j specific format
*/
private WebAuthnCredentialModelInput getCredentialInputFromCredentialModel(CredentialModel credential) {
WebAuthnCredentialModel webAuthnCredential = getCredentialFromModel(credential);

WebAuthnCredentialData credData = webAuthnCredential.getWebAuthnCredentialData();
private WebAuthnCredentialModelInput getCredentialInputFromCredentialModel(WebAuthnCredentialModel credential) {
WebAuthnCredentialData credData = credential.getWebAuthnCredentialData();

WebAuthnCredentialModelInput auth = new WebAuthnCredentialModelInput(getType());

Expand Down Expand Up @@ -209,13 +207,16 @@ public boolean isValid(RealmModel realm, UserModel user, CredentialInput input)
if (!WebAuthnCredentialModelInput.class.isInstance(input)) return false;

WebAuthnCredentialModelInput context = WebAuthnCredentialModelInput.class.cast(input);
List<WebAuthnCredentialModelInput> auths = getWebAuthnCredentialModelList(realm, user);
List<CredentialModel> credentials = user.credentialManager().getStoredCredentialsByTypeStream(getType())
.collect(Collectors.toList());

WebAuthnAuthenticationManager webAuthnAuthenticationManager = getWebAuthnAuthenticationManager();
AuthenticationData authenticationData = null;

try {
for (WebAuthnCredentialModelInput auth : auths) {
for (CredentialModel credModel : credentials) {
WebAuthnCredentialModel webAuthnCredModel = getCredentialFromModel(credModel);
WebAuthnCredentialModelInput auth = getCredentialInputFromCredentialModel(webAuthnCredModel);

byte[] credentialId = auth.getAttestedCredentialData().getCredentialId();
if (Arrays.equals(credentialId, context.getAuthenticationRequest().getCredentialId())) {
Expand All @@ -239,9 +240,6 @@ public boolean isValid(RealmModel realm, UserModel user, CredentialInput input)

logger.debugv("response.getAuthenticatorData().getFlags() = {0}", authenticationData.getAuthenticatorData().getFlags());

CredentialModel credModel = user.credentialManager().getStoredCredentialById(auth.getCredentialDBId());
WebAuthnCredentialModel webAuthnCredModel = getCredentialFromModel(credModel);

// update authenticator counter
// counters are an optional feature of the spec - if an authenticator does not support them, it
// will always send zero. MacOS/iOS does this for keys stored in the secure enclave (TouchID/FaceID)
Expand All @@ -254,7 +252,10 @@ public boolean isValid(RealmModel realm, UserModel user, CredentialInput input)
}

webAuthnCredModel.updateCounter(clientCounter);
user.credentialManager().updateStoredCredential(webAuthnCredModel);
if (!user.credentialManager().updateStoredCredential(credModel, webAuthnCredModel)) {
logger.debugf("WebAuthn credential changed while validating user %s", user.getUsername());
return false;
}
}

logger.debugf("Successfully validated WebAuthn credential for user %s", user.getUsername());
Expand Down Expand Up @@ -303,12 +304,6 @@ public String getType() {
}


private List<WebAuthnCredentialModelInput> getWebAuthnCredentialModelList(RealmModel realm, UserModel user) {
return user.credentialManager().getStoredCredentialsByTypeStream(getType())
.map(this::getCredentialInputFromCredentialModel)
.collect(Collectors.toList());
}

public void dumpCredentialModel(WebAuthnCredentialModel credential, WebAuthnCredentialModelInput auth) {
if(logger.isDebugEnabled()) {
logger.debug(" Persisted Credential Info::");
Expand Down
Loading
Loading
0