KEYCLOAK-12176 WebAuthn: show the attestation statement format in the admin console

This commit is contained in:
Takashi Norimatsu 2020-02-25 12:40:50 +09:00 committed by Marek Posolda
parent 1db87acc98
commit 8513760e25
6 changed files with 58 additions and 5 deletions

View file

@ -48,8 +48,8 @@ public class WebAuthnCredentialModel extends CredentialModel {
} }
public static WebAuthnCredentialModel create(String credentialType, String userLabel, String aaguid, String credentialId, public static WebAuthnCredentialModel create(String credentialType, String userLabel, String aaguid, String credentialId,
String attestationStatement, String credentialPublicKey, long counter) { String attestationStatement, String credentialPublicKey, long counter, String attestationStatementFormat) {
WebAuthnCredentialData credentialData = new WebAuthnCredentialData(aaguid, credentialId, counter, attestationStatement, credentialPublicKey); WebAuthnCredentialData credentialData = new WebAuthnCredentialData(aaguid, credentialId, counter, attestationStatement, credentialPublicKey, attestationStatementFormat);
WebAuthnSecretData secretData = new WebAuthnSecretData(); WebAuthnSecretData secretData = new WebAuthnSecretData();
WebAuthnCredentialModel credentialModel = new WebAuthnCredentialModel(credentialType, credentialData, secretData); WebAuthnCredentialModel credentialModel = new WebAuthnCredentialModel(credentialType, credentialData, secretData);

View file

@ -31,18 +31,21 @@ public class WebAuthnCredentialData {
private long counter; private long counter;
private String attestationStatement; private String attestationStatement;
private String credentialPublicKey; private String credentialPublicKey;
private String attestationStatementFormat;
@JsonCreator @JsonCreator
public WebAuthnCredentialData(@JsonProperty("aaguid") String aaguid, public WebAuthnCredentialData(@JsonProperty("aaguid") String aaguid,
@JsonProperty("credentialId") String credentialId, @JsonProperty("credentialId") String credentialId,
@JsonProperty("counter") long counter, @JsonProperty("counter") long counter,
@JsonProperty("attestationStatement") String attestationStatement, @JsonProperty("attestationStatement") String attestationStatement,
@JsonProperty("credentialPublicKey") String credentialPublicKey ) { @JsonProperty("credentialPublicKey") String credentialPublicKey,
@JsonProperty("attestationStatementFormat") String attestationStatementFormat) {
this.aaguid = aaguid; this.aaguid = aaguid;
this.credentialId = credentialId; this.credentialId = credentialId;
this.counter = counter; this.counter = counter;
this.attestationStatement = attestationStatement; this.attestationStatement = attestationStatement;
this.credentialPublicKey = credentialPublicKey; this.credentialPublicKey = credentialPublicKey;
this.attestationStatementFormat = attestationStatementFormat;
} }
public String getAaguid() { public String getAaguid() {
@ -69,6 +72,14 @@ public class WebAuthnCredentialData {
this.counter = counter; this.counter = counter;
} }
public String getAttestationStatementFormat() {
return attestationStatementFormat;
}
public void setAttestationStatementFormat(String attestationStatementFormat) {
this.attestationStatementFormat = attestationStatementFormat;
}
@Override @Override
public String toString() { public String toString() {
return "WebAuthnCredentialData { " + return "WebAuthnCredentialData { " +
@ -78,6 +89,7 @@ public class WebAuthnCredentialData {
", credentialPublicKey=" + credentialPublicKey + ", credentialPublicKey=" + credentialPublicKey +
", attestationStatement='" + attestationStatement + '\'' + ", attestationStatement='" + attestationStatement + '\'' +
", credentialPublicKey='" + credentialPublicKey + '\'' + ", credentialPublicKey='" + credentialPublicKey + '\'' +
", attestationStatementFormat='" + attestationStatementFormat + '\'' +
" }"; " }";
} }
} }

View file

@ -223,6 +223,7 @@ public class WebAuthnRegister implements RequiredActionProvider, CredentialRegis
credential.setAttestedCredentialData(registrationData.getAttestationObject().getAuthenticatorData().getAttestedCredentialData()); credential.setAttestedCredentialData(registrationData.getAttestationObject().getAuthenticatorData().getAttestedCredentialData());
credential.setCount(registrationData.getAttestationObject().getAuthenticatorData().getSignCount()); credential.setCount(registrationData.getAttestationObject().getAuthenticatorData().getSignCount());
credential.setAttestationStatementFormat(registrationData.getAttestationObject().getFormat());
// Save new webAuthn credential // Save new webAuthn credential
WebAuthnCredentialProvider webAuthnCredProvider = (WebAuthnCredentialProvider) this.session.getProvider(CredentialProvider.class, getCredentialProviderId()); WebAuthnCredentialProvider webAuthnCredProvider = (WebAuthnCredentialProvider) this.session.getProvider(CredentialProvider.class, getCredentialProviderId());

View file

@ -33,6 +33,7 @@ public class WebAuthnCredentialModelInput implements CredentialInput {
private long count; private long count;
private String credentialDBId; private String credentialDBId;
private final String credentialType; private final String credentialType;
private String attestationStatementFormat;
public WebAuthnCredentialModelInput(String credentialType) { public WebAuthnCredentialModelInput(String credentialType) {
this.credentialType = credentialType; this.credentialType = credentialType;
@ -106,16 +107,29 @@ public class WebAuthnCredentialModelInput implements CredentialInput {
return credentialType; return credentialType;
} }
public String getAttestationStatementFormat() {
return attestationStatementFormat;
}
public void setAttestationStatementFormat(String attestationStatementFormat) {
this.attestationStatementFormat = attestationStatementFormat;
}
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder("Credential Type = " + credentialType + ","); StringBuilder sb = new StringBuilder("Credential Type = " + credentialType + ",");
if (credentialDBId != null) if (credentialDBId != null)
sb.append("Credential DB Id = ") sb.append("Credential DB Id = ")
.append(credentialDBId) .append(credentialDBId)
.append(","); .append(",");
if (attestationStatement != null) if (attestationStatement != null) {
sb.append("Attestation Statement Format = ") sb.append("Attestation Statement Format = ")
.append(attestationStatement.getFormat()) .append(attestationStatement.getFormat())
.append(","); .append(",");
} else if (attestationStatementFormat != null) {
sb.append("Attestation Statement Format = ")
.append(attestationStatementFormat)
.append(",");
}
if (attestedCredentialData != null) { if (attestedCredentialData != null) {
sb.append("AAGUID = ") sb.append("AAGUID = ")
.append(attestedCredentialData.getAaguid().toString()) .append(attestedCredentialData.getAaguid().toString())

View file

@ -102,8 +102,9 @@ public class WebAuthnCredentialProvider implements CredentialProvider<WebAuthnCr
String credentialId = Base64.encodeBytes(webAuthnModel.getAttestedCredentialData().getCredentialId()); String credentialId = Base64.encodeBytes(webAuthnModel.getAttestedCredentialData().getCredentialId());
String credentialPublicKey = credentialPublicKeyConverter.convertToDatabaseColumn(webAuthnModel.getAttestedCredentialData().getCOSEKey()); String credentialPublicKey = credentialPublicKeyConverter.convertToDatabaseColumn(webAuthnModel.getAttestedCredentialData().getCOSEKey());
long counter = webAuthnModel.getCount(); long counter = webAuthnModel.getCount();
String attestationStatementFormat = webAuthnModel.getAttestationStatementFormat();
WebAuthnCredentialModel model = WebAuthnCredentialModel.create(getType(), userLabel, aaguid, credentialId, null, credentialPublicKey, counter); WebAuthnCredentialModel model = WebAuthnCredentialModel.create(getType(), userLabel, aaguid, credentialId, null, credentialPublicKey, counter, attestationStatementFormat);
model.setId(webAuthnModel.getCredentialDBId()); model.setId(webAuthnModel.getCredentialDBId());
@ -141,6 +142,8 @@ public class WebAuthnCredentialProvider implements CredentialProvider<WebAuthnCr
auth.setCredentialDBId(credential.getId()); auth.setCredentialDBId(credential.getId());
auth.setAttestationStatementFormat(credData.getAttestationStatementFormat());
return auth; return auth;
} }

View file

@ -31,6 +31,7 @@ import org.keycloak.common.util.RandomString;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
import org.keycloak.models.credential.WebAuthnCredentialModel; import org.keycloak.models.credential.WebAuthnCredentialModel;
import org.keycloak.models.credential.dto.WebAuthnCredentialData;
import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
@ -47,12 +48,14 @@ import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.RegisterPage; import org.keycloak.testsuite.pages.RegisterPage;
import org.keycloak.testsuite.pages.webauthn.WebAuthnLoginPage; import org.keycloak.testsuite.pages.webauthn.WebAuthnLoginPage;
import org.keycloak.testsuite.pages.webauthn.WebAuthnRegisterPage; import org.keycloak.testsuite.pages.webauthn.WebAuthnRegisterPage;
import org.keycloak.util.JsonSerialization;
import org.keycloak.testsuite.WebAuthnAssume; import org.keycloak.testsuite.WebAuthnAssume;
import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.AppPage.RequestType; import org.keycloak.testsuite.pages.AppPage.RequestType;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -166,6 +169,7 @@ public class WebAuthnRegisterAndLoginTest extends AbstractTestRealmKeycloakTest
.assertEvent().getSessionId(); .assertEvent().getSessionId();
// confirm user registered // confirm user registered
assertUserRegistered(userId, username.toLowerCase(), email.toLowerCase()); assertUserRegistered(userId, username.toLowerCase(), email.toLowerCase());
assertRegisteredCredentials(userId, ALL_ZERO_AAGUID, "none");
// logout by user // logout by user
appPage.logout(); appPage.logout();
@ -304,10 +308,29 @@ public class WebAuthnRegisterAndLoginTest extends AbstractTestRealmKeycloakTest
assertEquals("lastName", user.getLastName()); assertEquals("lastName", user.getLastName());
} }
private void assertRegisteredCredentials(String userId, String aaguid, String attestationStatementFormat) {
List<CredentialRepresentation> credentials = getCredentials(userId);
credentials.stream().forEach(i -> {
if (WebAuthnCredentialModel.TYPE_TWOFACTOR.equals(i.getType())) {
try {
WebAuthnCredentialData data = JsonSerialization.readValue(i.getCredentialData(), WebAuthnCredentialData.class);
assertEquals(aaguid, data.getAaguid());
assertEquals(attestationStatementFormat, data.getAttestationStatementFormat());
} catch (IOException e) {
Assert.fail();
}
}
});
}
protected UserRepresentation getUser(String userId) { protected UserRepresentation getUser(String userId) {
return testRealm().users().get(userId).toRepresentation(); return testRealm().users().get(userId).toRepresentation();
} }
protected List<CredentialRepresentation> getCredentials(String userId) {
return testRealm().users().get(userId).credentials();
}
private RealmRepresentation backupWebAuthnRealmSettings() { private RealmRepresentation backupWebAuthnRealmSettings() {
RealmRepresentation rep = testRealm().toRepresentation(); RealmRepresentation rep = testRealm().toRepresentation();
signatureAlgorithms = rep.getWebAuthnPolicySignatureAlgorithms(); signatureAlgorithms = rep.getWebAuthnPolicySignatureAlgorithms();