[java-idp-plugin-webauthn] branch main updated: Update views. Improve flow and flow actions.
Phil Smart
philip.smart at jisc.ac.uk
Fri Dec 8 17:23:09 UTC 2023
This is an automated email from the git hooks/post-receive script.
philsmart pushed a commit to branch main
in repository java-idp-plugin-webauthn.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-webauthn.git;a=commit;h=6b13fef558a74b7c43b71b34a6ce84b033f8bac7
The following commit(s) were added to refs/heads/main by this push:
new 6b13fef Update views. Improve flow and flow actions.
6b13fef is described below
commit 6b13fef558a74b7c43b71b34a6ce84b033f8bac7
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Dec 8 17:23:05 2023 +0000
Update views. Improve flow and flow actions.
- Add better storage records to display key information
- Complete basic registration flow other than remove key
- Add more key creation and authentication request options
---
.../authn/webauthn/AbstractWebAuthnBaseAction.java | 9 +-
.../AbstractWebAuthnRegistrationAction.java | 9 +-
.../webauthn/WebAuthnAuthenticationClient.java | 5 +-
.../webauthn/context/BaseWebAuthnContext.java | 28 ++--
.../context/WebAuthnRegistrationContext.java | 54 +++----
.../logic/IsDiscoverableCredentialRequired.java | 61 ++++++++
.../webauthn/storage}/CredentialRegistration.java | 26 +++-
.../StorageServiceCredentialRepository.java | 32 ++++
.../CreatePublicKeyCredentialCreationOptions.java | 19 ++-
...actAuthenticatorAttestationFromFormRequest.java | 59 +++++--
.../impl/PopulateWebAuthnRegistrationContext.java | 2 +-
.../admin/impl/StorePublicKeyCredential.java | 29 ++--
.../impl/YubicoWebauthnAuthenticationClient.java | 23 ++-
.../CreatePublicKeyCredentialRequestOptions.java | 18 ++-
.../impl/LookupRegisteredCredentials.java | 21 +--
.../PopulateWebAuthnAuthenticationContext.java | 16 +-
.../storage/impl/InMemoryRegistrationStorage.java | 6 +-
.../webauthn-registration-beans.xml | 2 +-
.../webauthn-registration-flow.xml | 36 +++--
.../idp/flows/authn/WebAuthn/webauthn-beans.xml | 7 +
.../idp/flows/authn/WebAuthn/webauthn-flow.xml | 25 ++-
.../idp/plugin/authn/webauthn/css/webauthn.css | 81 ++++++++++
.../plugin/authn/webauthn/js/webauthn-support.js | 16 ++
.../idp/plugin/authn/webauthn/js/webauthn.js | 171 +++++++++++++++++++++
.../plugin/authn/webauthn/views/webauthn-authn.vm | 27 ++--
.../authn/webauthn/views/webauthn-register.vm | 78 ++++++----
.../authn/webauthn/views/webauthn-registered.vm | 95 ++++++++----
.../YubicoWebauthnAuthenticationClientTest.java | 94 +++++------
.../authn/webauthn/impl/MockAuthenticator.java | 25 ++-
.../impl/ValidatePublicKeyCredentialTest.java | 1 +
30 files changed, 815 insertions(+), 260 deletions(-)
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/AbstractWebAuthnBaseAction.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/AbstractWebAuthnBaseAction.java
index a1034d1..92b2c34 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/AbstractWebAuthnBaseAction.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/AbstractWebAuthnBaseAction.java
@@ -27,10 +27,9 @@ import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
-import com.yubico.webauthn.CredentialRepository;
-
import net.shibboleth.idp.authn.context.AuthenticationContext;
import net.shibboleth.idp.plugin.authn.webauthn.context.BaseWebAuthnContext;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.StorageServiceCredentialRepository;
import net.shibboleth.idp.profile.AbstractProfileAction;
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
@@ -70,7 +69,7 @@ public abstract class AbstractWebAuthnBaseAction extends AbstractProfileAction {
/** The credential repository to store valid credentials in.*/
// TODO replace with an adaptor to the storage service?
- @NonnullAfterInit private CredentialRepository credentialRepository;
+ @NonnullAfterInit private StorageServiceCredentialRepository credentialRepository;
/**
@@ -121,7 +120,7 @@ public abstract class AbstractWebAuthnBaseAction extends AbstractProfileAction {
*
* @param repository The respository to set.
*/
- public void setCredentialRepository(@Nonnull final CredentialRepository repository) {
+ public void setCredentialRepository(@Nonnull final StorageServiceCredentialRepository repository) {
checkSetterPreconditions();
credentialRepository = Constraint.isNotNull(repository, "Credential respository can not be null");
}
@@ -131,7 +130,7 @@ public abstract class AbstractWebAuthnBaseAction extends AbstractProfileAction {
*
* @return the credential repository.
*/
- public CredentialRepository getCredentialRepository() {
+ public StorageServiceCredentialRepository getCredentialRepository() {
return credentialRepository;
}
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/AbstractWebAuthnRegistrationAction.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/AbstractWebAuthnRegistrationAction.java
index 3eb3f2e..e3981c5 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/AbstractWebAuthnRegistrationAction.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/AbstractWebAuthnRegistrationAction.java
@@ -27,9 +27,8 @@ import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
-import com.yubico.webauthn.CredentialRepository;
-
import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.StorageServiceCredentialRepository;
import net.shibboleth.idp.profile.AbstractProfileAction;
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
@@ -68,7 +67,7 @@ public abstract class AbstractWebAuthnRegistrationAction extends AbstractProfile
/** The credential respository to store valid credentials in.*/
// TODO replace with an adaptor to the storage service?
- @NonnullAfterInit private CredentialRepository credentialRepository;
+ @NonnullAfterInit private StorageServiceCredentialRepository credentialRepository;
/**
@@ -118,7 +117,7 @@ public abstract class AbstractWebAuthnRegistrationAction extends AbstractProfile
*
* @param repository The respository to set.
*/
- public void setCredentialRepository(@Nonnull final CredentialRepository repository) {
+ public void setCredentialRepository(@Nonnull final StorageServiceCredentialRepository repository) {
checkSetterPreconditions();
credentialRepository = Constraint.isNotNull(repository, "Credential respository can not be null");
}
@@ -128,7 +127,7 @@ public abstract class AbstractWebAuthnRegistrationAction extends AbstractProfile
*
* @return the credential repository.
*/
- public CredentialRepository getCredentialRepository() {
+ public StorageServiceCredentialRepository getCredentialRepository() {
return credentialRepository;
}
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/WebAuthnAuthenticationClient.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/WebAuthnAuthenticationClient.java
index d44024d..35ffa11 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/WebAuthnAuthenticationClient.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/WebAuthnAuthenticationClient.java
@@ -1,5 +1,6 @@
package net.shibboleth.idp.plugin.authn.webauthn;
+import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
@@ -45,7 +46,7 @@ public interface WebAuthnAuthenticationClient {
*
*/
@Nonnull PublicKeyCredentialRequestOptions createAuthenticationRequest(@Nullable final String username,
- @Nullable final Set<PublicKeyCredentialDescriptor> allowCredentials, @Nonnull final byte[] challenge)
+ @Nonnull final List<PublicKeyCredentialDescriptor> allowCredentials, @Nonnull final byte[] challenge)
throws WebAuthnAuthenticationClientException;
/**
@@ -63,7 +64,7 @@ public interface WebAuthnAuthenticationClient {
* @throws WebAuthnAuthenticationClientException if there is an error generating the creation request
*/
@Nonnull PublicKeyCredentialCreationOptions createRegistrationRequest(
- @Nullable final Set<PublicKeyCredentialDescriptor> excludeCredentials, @Nullable final String username,
+ @Nonnull final Set<PublicKeyCredentialDescriptor> excludeCredentials, @Nullable final String username,
@Nullable final byte[] userHandle, @Nonnull final byte[] challenge)
throws WebAuthnAuthenticationClientException;
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/BaseWebAuthnContext.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/BaseWebAuthnContext.java
index 3632834..4fdb824 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/BaseWebAuthnContext.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/BaseWebAuthnContext.java
@@ -14,17 +14,19 @@
package net.shibboleth.idp.plugin.authn.webauthn.context;
-import java.util.Set;
+import java.util.Collection;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.opensaml.messaging.context.BaseContext;
-import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
-
+import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
import net.shibboleth.shared.codec.Base64Support;
import net.shibboleth.shared.codec.EncodingException;
+import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.logic.Constraint;
/**
@@ -32,14 +34,17 @@ import net.shibboleth.shared.logic.Constraint;
*/
public class BaseWebAuthnContext extends BaseContext {
- /** The original username. */
+ /**
+ * The username of the user that is the subject of this authentication. If {@code null} we can not determine
+ * the userHandle (and hence public key) to use, and the flow will require a discoverable credential.
+ */
@Nullable private String username;
/**
* Credentials that have already been registered with the IdP. The authenticator should use these to avoid creating
* duplicate credentials during registration, or to tell the browser which credentials to use during authentication.
*/
- @Nullable private Set<PublicKeyCredentialDescriptor> existingCredentials;
+ @Nullable private Collection<CredentialRegistration> existingCredentials;
/** The challenge sent to the authenticator in both registration and authentication ceremonies.*/
@Nullable private byte[] serverChallenge;
@@ -57,7 +62,7 @@ public class BaseWebAuthnContext extends BaseContext {
}
/**
- * Sets the username and resets the transformed version to be identical.
+ * Sets the username.
*
* @param name the username
*
@@ -65,7 +70,6 @@ public class BaseWebAuthnContext extends BaseContext {
*/
@Nonnull public BaseWebAuthnContext setUsername(@Nullable final String name) {
username = name;
- //transformedUsername = name;
return this;
}
@@ -74,7 +78,7 @@ public class BaseWebAuthnContext extends BaseContext {
*
* @param credentials the set of credentials
*/
- public void setExistingCredentials(@Nullable final Set<PublicKeyCredentialDescriptor> credentials) {
+ public void setExistingCredentials(@Nullable final Collection<CredentialRegistration> credentials) {
existingCredentials = credentials;
}
@@ -84,8 +88,12 @@ public class BaseWebAuthnContext extends BaseContext {
*
* @return the excluded credentials.
*/
- public Set<PublicKeyCredentialDescriptor> getExistingCredentials() {
- return existingCredentials;
+ @Nonnull @Unmodifiable @NotLive public Collection<CredentialRegistration> getExistingCredentials() {
+ final Collection<CredentialRegistration> localExistingCredentials = existingCredentials;
+ if (localExistingCredentials == null) {
+ return CollectionSupport.emptyList();
+ }
+ return CollectionSupport.copyToList(localExistingCredentials);
}
/**
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnRegistrationContext.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnRegistrationContext.java
index e7d0cda..1ef8de2 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnRegistrationContext.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnRegistrationContext.java
@@ -1,6 +1,5 @@
package net.shibboleth.idp.plugin.authn.webauthn.context;
-import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
@@ -11,7 +10,10 @@ import com.yubico.webauthn.data.PublicKeyCredential;
import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
-/** Registration context for processing WebAuthn Registration Ceremonies. */
+/**
+ * Registration context for processing WebAuthn Registration Ceremonies. This context is intended for registration of
+ * a single credential only. A context would be needed per credential registration.
+ */
@NotThreadSafe
public final class WebAuthnRegistrationContext extends BaseWebAuthnContext {
@@ -42,6 +44,8 @@ public final class WebAuthnRegistrationContext extends BaseWebAuthnContext {
*/
@Nullable private RegistrationResult registrationResult;
+ /** A display friendly nickname for the credential that is to be registered.*/
+ @Nullable private String credentialNickname;
/**
* Get the attestation response as a result of creating a new credential.
@@ -65,33 +69,6 @@ public final class WebAuthnRegistrationContext extends BaseWebAuthnContext {
return this;
}
- /**
- * Gets the username.
- *
- * @return the username
- */
- @Override
- @Nullable public String getUsername() {
- return username;
- }
-
- /**
- * Sets the username and resets the transformed version to be identical.
- *
- * @param name the username
- *
- * @return this context
- */
- @Override
- @Nonnull public WebAuthnRegistrationContext setUsername(@Nullable final String name) {
- username = name;
- //transformedUsername = name;
- return this;
- }
-
-
-
-
/**
* Set the options used to create public key credentials.
*
@@ -148,5 +125,24 @@ public final class WebAuthnRegistrationContext extends BaseWebAuthnContext {
return this;
}
+
+ /**
+ * Set the display friendly nickname for this credential.
+ *
+ * @param nickname The credential nickname to set.
+ */
+ public WebAuthnRegistrationContext setCredentialNickname(@Nullable final String nickname) {
+ credentialNickname = nickname;
+ return this;
+ }
+
+ /**
+ * Get the display friendly nickname for this credential.
+ *
+ * @return the credential nickname.
+ */
+ public String getCredentialNickname() {
+ return credentialNickname;
+ }
}
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/logic/IsDiscoverableCredentialRequired.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/logic/IsDiscoverableCredentialRequired.java
new file mode 100644
index 0000000..3db25ca
--- /dev/null
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/logic/IsDiscoverableCredentialRequired.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.webauthn.context.logic;
+
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A predicate that determines if the authentication ceremony requires a discoverable credential supplied by the
+ * authenticator, or if we have a username to determine which credentials to use on from the IdP.
+ */
+public class IsDiscoverableCredentialRequired implements Predicate<ProfileRequestContext> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(IsDiscoverableCredentialRequired.class);
+
+ @Override
+ public boolean test(@Nullable final ProfileRequestContext input) {
+ if (input == null) {
+ log.trace("Profile context was null, can not determine if discoverable credentials are required");
+ return false;
+ }
+ final AuthenticationContext authnContext = input.getSubcontext(AuthenticationContext.class);
+ if (authnContext == null) {
+ log.trace("Authentication context was null, can not determine if discoverable credentials are required");
+ return false;
+ }
+ final WebAuthnAuthenticationContext webauthnContext =
+ authnContext.getSubcontext(WebAuthnAuthenticationContext.class);
+ if (webauthnContext == null) {
+ log.trace("WebAuthn authentication context was null, can not determine if discoverable credentials "
+ + "are required");
+ return false;
+ }
+ final boolean discoverableCredentialRequired = webauthnContext.getUsername() == null;
+ log.debug("{}", discoverableCredentialRequired ? "Usernameless authentication required" :
+ "Passwordless authentication required for '"+webauthnContext.getUsername()+"'");
+ return discoverableCredentialRequired;
+ }
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/CredentialRegistration.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/CredentialRegistration.java
similarity index 78%
rename from webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/CredentialRegistration.java
rename to webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/CredentialRegistration.java
index 1a0a0cc..cc8368d 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/CredentialRegistration.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/CredentialRegistration.java
@@ -15,32 +15,39 @@
* limitations under the License.
*/
-package net.shibboleth.idp.plugin.authn.webauthn.storage.impl;
+package net.shibboleth.idp.plugin.authn.webauthn.storage;
import java.time.Instant;
import java.util.Optional;
import java.util.SortedSet;
+import java.util.TreeSet;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import com.yubico.webauthn.RegisteredCredential;
import com.yubico.webauthn.data.AuthenticatorTransport;
+import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
import com.yubico.webauthn.data.UserIdentity;
/**
* Influenced by the CredentialRegistration class in the Yubico demo libraries.
*/
//TODO need our own storage record, so this should be test only and then replaced with the actual one eventually
+//TODO make this more official and undeprecate
+ at Deprecated
public class CredentialRegistration {
UserIdentity userIdentity;
Optional<String> credentialNickname;
- SortedSet<AuthenticatorTransport> transports;
+ @Nonnull SortedSet<AuthenticatorTransport> transports;
Instant registrationTime;
RegisteredCredential credential;
Optional<Object> attestationMetadata;
public CredentialRegistration(final UserIdentity userIdentity, final Optional<String> credentialNickname,
- final SortedSet<AuthenticatorTransport> transports, final Instant registrationTime,
+ @Nonnull final SortedSet<AuthenticatorTransport> transports, final Instant registrationTime,
final RegisteredCredential credential,
final Optional<Object> attestationMetadata) {
super();
@@ -52,8 +59,12 @@ public class CredentialRegistration {
this.attestationMetadata = attestationMetadata;
}
+ public String getNickname(){
+ return credentialNickname.orElse("");
+ }
+
public CredentialRegistration() {
-
+ transports = new TreeSet<AuthenticatorTransport>();
}
public String getRegistrationTimestamp() {
@@ -76,6 +87,13 @@ public class CredentialRegistration {
return transports;
}
+ @Nullable public PublicKeyCredentialDescriptor toPublicKeyCredentialDescriptor() {
+ return PublicKeyCredentialDescriptor.builder()
+ .id(credential.getCredentialId())
+ .transports(transports)
+ .build();
+ }
+
public CredentialRegistration withCredential(final RegisteredCredential newRegCred) {
final CredentialRegistration newReg = new CredentialRegistration();
newReg.attestationMetadata = attestationMetadata;
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/StorageServiceCredentialRepository.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/StorageServiceCredentialRepository.java
new file mode 100644
index 0000000..69c01c9
--- /dev/null
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/StorageServiceCredentialRepository.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.webauthn.storage;
+
+import java.util.Collection;
+
+import com.yubico.webauthn.CredentialRepository;
+
+
+/**
+ * An IdP extension of the Yubico {@link CredentialRepository} interface to support additional operations required
+ * by the IdP.
+ */
+public interface StorageServiceCredentialRepository extends CredentialRepository {
+
+ Collection<CredentialRegistration> getRegistrationsByUsername(final String username);
+
+ boolean addRegistrationByUsername(final String username, final CredentialRegistration reg);
+
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CreatePublicKeyCredentialCreationOptions.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CreatePublicKeyCredentialCreationOptions.java
index e97ba8c..304d02b 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CreatePublicKeyCredentialCreationOptions.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CreatePublicKeyCredentialCreationOptions.java
@@ -15,6 +15,11 @@
package net.shibboleth.idp.plugin.authn.webauthn.admin.impl;
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
import javax.annotation.Nonnull;
import org.opensaml.profile.action.ActionSupport;
@@ -24,12 +29,14 @@ import org.slf4j.Logger;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
+import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
import net.shibboleth.idp.authn.AuthnEventIds;
import net.shibboleth.idp.plugin.authn.webauthn.AbstractWebAuthnRegistrationAction;
import net.shibboleth.idp.plugin.authn.webauthn.WebAuthnAuthenticationClient;
import net.shibboleth.idp.plugin.authn.webauthn.WebAuthnAuthenticationClientException;
import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.logic.Constraint;
@@ -81,12 +88,18 @@ public class CreatePublicKeyCredentialCreationOptions extends AbstractWebAuthnRe
return;
}
- try {
+ try {
+ final Collection<CredentialRegistration> existingCredentials = context.getExistingCredentials();
+ final Set<PublicKeyCredentialDescriptor> existingCredentialDescriptors = existingCredentials.stream()
+ .map(cred -> cred.toPublicKeyCredentialDescriptor())
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+
final PublicKeyCredentialCreationOptions pkCredCreationOptions =
- client.createRegistrationRequest(context.getExistingCredentials(),context.getUsername(),
+ client.createRegistrationRequest(existingCredentialDescriptors, context.getUsername(),
context.getUserHandle(), challenge);
context.setPublicKeyCredentialCreationOptions(pkCredCreationOptions);
- //convert to JSON
+ //convert to JSON for the JS api to use
context.setPublicKeyCredentialCreationOptionsJSON(
objectMapper.writeValueAsString(pkCredCreationOptions));
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractAuthenticatorAttestationFromFormRequest.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractAuthenticatorAttestationFromFormRequest.java
index dae40fe..5956c7d 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractAuthenticatorAttestationFromFormRequest.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractAuthenticatorAttestationFromFormRequest.java
@@ -48,21 +48,28 @@ import net.shibboleth.shared.primitive.StringSupport;
*/
public class ExtractAuthenticatorAttestationFromFormRequest extends AbstractWebAuthnRegistrationAction {
- /** Default token code field name. */
- @Nonnull @NotEmpty public static final String DEFAULT_FIELD_NAME = "authenticatorAttestation";
+ /** Default assertion field name. */
+ @Nonnull @NotEmpty public static final String DEFAULT_ASSERTION_FIELD_NAME = "authenticatorAttestation";
+
+ /** Default nickname field name. */
+ @Nonnull @NotEmpty public static final String DEFAULT_NICKNAME_FIELD_NAME = "credentialNickname";
/** Class logger. */
@Nonnull private final Logger log = LoggerFactory.getLogger(ExtractAuthenticatorAttestationFromFormRequest.class);
- /** Name of header. */
- @Nonnull @NotEmpty private String fieldName;
+ /** Name of assertion field. */
+ @Nonnull @NotEmpty private String assertionFieldName;
+
+ /** Name of nickname field. */
+ @Nonnull @NotEmpty private String credentialNicknameFieldName;
/** JSON object mapper. */
@NonnullAfterInit private ObjectMapper objectMapper;
/** Constructor. */
public ExtractAuthenticatorAttestationFromFormRequest() {
- fieldName = DEFAULT_FIELD_NAME;
+ assertionFieldName = DEFAULT_ASSERTION_FIELD_NAME;
+ credentialNicknameFieldName = DEFAULT_NICKNAME_FIELD_NAME;
}
@Override protected void doInitialize() throws ComponentInitializationException {
@@ -85,16 +92,28 @@ public class ExtractAuthenticatorAttestationFromFormRequest extends AbstractWebA
}
/**
- * Set the name of the field to examine.
+ * Set the name of the assertion field to examine.
*
* @param field field name
*/
- public void setFieldName(@Nonnull @NotEmpty final String field) {
+ public void setAssertionFieldName(@Nonnull @NotEmpty final String field) {
checkSetterPreconditions();
- fieldName = Constraint.isNotNull(StringSupport.trimOrNull(field), "Field name cannot be null or empty");
+ assertionFieldName = Constraint.isNotNull(StringSupport.trimOrNull(field),
+ "Assertion Field name cannot be null or empty");
}
+ /**
+ * Set the name of the nickname field to examine.
+ *
+ * @param field field name
+ */
+ public void setCredentialNicknameFieldName(@Nonnull @NotEmpty final String field) {
+ checkSetterPreconditions();
+
+ credentialNicknameFieldName = Constraint.isNotNull(StringSupport.trimOrNull(field),
+ "Nickname FieldName can not be null");
+ }
@Override
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
@@ -107,10 +126,18 @@ public class ExtractAuthenticatorAttestationFromFormRequest extends AbstractWebA
return;
}
- final String pkCredAttestationJson = extractPublicKeyCredential(request);
- log.trace("Public Key Credential AuthenticatorAttestationResponse in JSON is '{}'",pkCredAttestationJson);
+ final String pkCredAttestationJson = extractParameter(request, assertionFieldName);
+ log.trace("Public key credential authenticator attestation response in JSON is '{}'",pkCredAttestationJson);
if (StringSupport.trimOrNull(pkCredAttestationJson) == null) {
- log.warn("{} Could not extract AuthenticatorAttestationResponse from form", getLogPrefix());
+ log.warn("{} Could not extract authenticator attestation response from form", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+
+ final String credNickname = extractParameter(request, credentialNicknameFieldName);
+ log.trace("Public key credential nickname is '{}'",pkCredAttestationJson);
+ if (StringSupport.trimOrNull(credNickname) == null) {
+ log.warn("{} Could not extract nickname from form", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
return;
}
@@ -119,6 +146,7 @@ public class ExtractAuthenticatorAttestationFromFormRequest extends AbstractWebA
final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs>
pkCredAttestation = PublicKeyCredential.parseRegistrationResponseJson(pkCredAttestationJson);
context.setAuthenticatorAttestationResponse(pkCredAttestation);
+ context.setCredentialNickname(credNickname);
} catch (final IOException e) {
log.warn("{} Could not convert AuthenticatorAttestationResponse from form", getLogPrefix(), e);
ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
@@ -130,14 +158,15 @@ public class ExtractAuthenticatorAttestationFromFormRequest extends AbstractWebA
}
/**
- * Extract the public key credential from the AuthenticatorAttestationResponse in the form.
+ * Extract the given parameter from the servlet request.
*
* @param httpRequest the http request
*
- * @return the AuthenticationAttestationResponse JSON.
+ * @return the value of the parameter.
*/
- @Nullable private String extractPublicKeyCredential(@Nonnull final HttpServletRequest httpRequest) {
- return httpRequest.getParameter(fieldName);
+ @Nullable private String extractParameter(@Nonnull final HttpServletRequest httpRequest,
+ @Nonnull @NotEmpty final String field) {
+ return httpRequest.getParameter(field);
}
}
\ No newline at end of file
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/PopulateWebAuthnRegistrationContext.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/PopulateWebAuthnRegistrationContext.java
index 588a3b1..a201063 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/PopulateWebAuthnRegistrationContext.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/PopulateWebAuthnRegistrationContext.java
@@ -106,7 +106,7 @@ public class PopulateWebAuthnRegistrationContext extends AbstractProfileAction {
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
return;
}
- context.setUsername(usernameLookupStrategy.apply(profileRequestContext));
+ context.setUsername(username);
log.debug("Created WebAuthn registration context for user '{}'", context.getUsername());
}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/StorePublicKeyCredential.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/StorePublicKeyCredential.java
index 2c1c36c..df35521 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/StorePublicKeyCredential.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/StorePublicKeyCredential.java
@@ -19,6 +19,7 @@ package net.shibboleth.idp.plugin.authn.webauthn.admin.impl;
import java.time.Instant;
import java.util.Optional;
+import java.util.SortedSet;
import java.util.TreeSet;
import javax.annotation.Nonnull;
@@ -41,8 +42,7 @@ import net.shibboleth.idp.authn.AuthnEventIds;
import net.shibboleth.idp.plugin.authn.webauthn.AbstractWebAuthnRegistrationAction;
import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnPublicKeyCredentialRecord;
import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
-import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.CredentialRegistration;
-import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.InMemoryRegistrationStorage;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.WebauthnPublicKeyCredentialStorageSerializer;
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
import net.shibboleth.shared.component.ComponentInitializationException;
@@ -108,7 +108,7 @@ public class StorePublicKeyCredential extends AbstractWebAuthnRegistrationAction
}
final RegistrationResult registrationResult = context.getRegistrationResult();
if (registrationResult == null) {
- log.error("Unable to find registration information in registration context");
+ log.error("Unable to find registration result in registration context");
ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_AUTHN_CTX);
return;
}
@@ -126,19 +126,18 @@ public class StorePublicKeyCredential extends AbstractWebAuthnRegistrationAction
.build();
// TODO fixup the record we will use to store registrations
- final CredentialRegistration registration = new CredentialRegistration(user, Optional.of("Nickname"),
- new TreeSet<AuthenticatorTransport>(), Instant.now(), credential, Optional.empty());
+ final SortedSet<AuthenticatorTransport> transports =
+ registrationResult.getKeyId().getTransports().orElse(new TreeSet<>());
- // TODO should not need a cast here when we sort out the storage
- if (getCredentialRepository() instanceof final InMemoryRegistrationStorage inMemoryRepo) {
- inMemoryRepo.addRegistrationByUsername(username, registration);
- log.debug("{} Added public key credential registration for user '{}' and key '{}' ",
- getLogPrefix(), username, registrationResult.getKeyId().getId().getBase64Url());
- } else {
- log.debug("{} Unsupported credential repository type", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
- return;
- }
+ final CredentialRegistration registration = new CredentialRegistration(user,
+ Optional.of(context.getCredentialNickname()),
+ transports, Instant.now(), credential, Optional.empty());
+
+
+ getCredentialRepository().addRegistrationByUsername(username, registration);
+ log.debug("{} Added public key credential registration for user '{}' and key '{}' ",
+ getLogPrefix(), username, registrationResult.getKeyId().getId().getBase64Url());
+
} catch (final Exception e) {
log.error("{} Unable to store registration for key '{}'",getLogPrefix(),
registrationResult.getKeyId().getId().getBase64Url(), e);
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClient.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClient.java
index fd2f271..f651a4f 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClient.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClient.java
@@ -14,7 +14,6 @@
package net.shibboleth.idp.plugin.authn.webauthn.client.impl;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -101,7 +100,7 @@ public class YubicoWebauthnAuthenticationClient implements WebAuthnAuthenticatio
@Override
public PublicKeyCredentialRequestOptions createAuthenticationRequest(@Nullable final String username,
- @Nullable final Set<PublicKeyCredentialDescriptor> allowCredentials, final byte[] challenge)
+ @Nullable final List<PublicKeyCredentialDescriptor> allowCredentials, @Nonnull final byte[] challenge)
throws WebAuthnAuthenticationClientException {
//set default to preferred.
@@ -109,16 +108,12 @@ public class YubicoWebauthnAuthenticationClient implements WebAuthnAuthenticatio
if (username == null) {
//then require user verification? makes sense, but is that part of the spec?
userVerificationRequirement = UserVerificationRequirement.REQUIRED;
- }
-
- // The order of these credentials becomes important
- final List<PublicKeyCredentialDescriptor> listOfCredentials = allowCredentials == null ? null :
- new ArrayList<>(allowCredentials);
+ }
final PublicKeyCredentialRequestOptions request = PublicKeyCredentialRequestOptions.builder()
.challenge(new ByteArray(challenge))
.rpId(rp.getIdentity().getId())
- .allowCredentials(Optional.ofNullable(listOfCredentials))
+ .allowCredentials(Optional.ofNullable(allowCredentials))
// .extensions(
// startAssertionOptions
// .getExtensions()
@@ -136,16 +131,20 @@ public class YubicoWebauthnAuthenticationClient implements WebAuthnAuthenticatio
/** {@inheritDoc} */
@Override
public PublicKeyCredentialCreationOptions createRegistrationRequest(
- @Nullable final Set<PublicKeyCredentialDescriptor> excludeCredentials, final String username,
+ @Nullable final Set<PublicKeyCredentialDescriptor> excludeCredentials, @Nullable final String username,
final byte[] userHandle, final byte[] challenge) throws WebAuthnAuthenticationClientException {
//set default to preferred.
- UserVerificationRequirement userVerificationRequirement = UserVerificationRequirement.PREFERRED;
+ ResidentKeyRequirement residentKeyRquirement = ResidentKeyRequirement.PREFERRED;
if (username == null) {
//then require user verification? makes sense, but is that part of the spec?
- userVerificationRequirement = UserVerificationRequirement.REQUIRED;
+ residentKeyRquirement = ResidentKeyRequirement.REQUIRED;
+
}
- final ResidentKeyRequirement residentKeyRquirement = ResidentKeyRequirement.REQUIRED;
+
+ // If U2F we do not need UV. So conditional required.
+ final UserVerificationRequirement userVerificationRequirement = UserVerificationRequirement.PREFERRED;
+
final UserIdentity identity =
UserIdentity.builder().name(username).displayName(username).id(new ByteArray(userHandle)).build();
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CreatePublicKeyCredentialRequestOptions.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CreatePublicKeyCredentialRequestOptions.java
index 2a07e07..e1ba200 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CreatePublicKeyCredentialRequestOptions.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CreatePublicKeyCredentialRequestOptions.java
@@ -15,6 +15,10 @@
package net.shibboleth.idp.plugin.authn.webauthn.impl;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
import javax.annotation.Nonnull;
import org.opensaml.profile.action.ActionSupport;
@@ -23,6 +27,7 @@ import org.slf4j.Logger;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
import com.yubico.webauthn.data.PublicKeyCredentialRequestOptions;
import net.shibboleth.idp.authn.AuthnEventIds;
@@ -31,6 +36,7 @@ import net.shibboleth.idp.plugin.authn.webauthn.AbstractWebAuthnAuthenticationAc
import net.shibboleth.idp.plugin.authn.webauthn.WebAuthnAuthenticationClient;
import net.shibboleth.idp.plugin.authn.webauthn.WebAuthnAuthenticationClientException;
import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.logic.Constraint;
@@ -84,11 +90,17 @@ public class CreatePublicKeyCredentialRequestOptions extends AbstractWebAuthnAut
}
try {
- //TODO userhandle needs to be pulled out.
+ final Collection<CredentialRegistration> existingCredentials = context.getExistingCredentials();
+ final List<PublicKeyCredentialDescriptor> existingCredentialDescriptors = existingCredentials.stream()
+ .map(cred -> cred.toPublicKeyCredentialDescriptor())
+ .filter(Objects::nonNull)
+ .toList();
+
final PublicKeyCredentialRequestOptions pkCredRequestOptions =
- client.createAuthenticationRequest(context.getUsername(), null, challenge);
+ client.createAuthenticationRequest(context.getUsername(), existingCredentialDescriptors,
+ challenge);
context.setPublicKeyCredentialRequestOptions(pkCredRequestOptions);
-
+ // Convert to JSON for the view. TODO maybe that should be converted by velocity
context.setPublicKeyCredentialRequestOptionsJSON(objectMapper.writeValueAsString(pkCredRequestOptions));
log.debug("{} Created PublicKeyCredentialRequestOptions: '{}'",getLogPrefix(), pkCredRequestOptions);
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LookupRegisteredCredentials.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java
similarity index 79%
rename from webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LookupRegisteredCredentials.java
rename to webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java
index 35d8c1a..926dc72 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LookupRegisteredCredentials.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java
@@ -12,9 +12,9 @@
* limitations under the License.
*/
-package net.shibboleth.idp.plugin.authn.webauthn.admin.impl;
+package net.shibboleth.idp.plugin.authn.webauthn.impl;
-import java.util.Set;
+import java.util.Collection;
import javax.annotation.Nonnull;
@@ -22,21 +22,21 @@ import org.opensaml.profile.action.ActionSupport;
import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
-import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
-
import net.shibboleth.idp.authn.AuthnEventIds;
import net.shibboleth.idp.plugin.authn.webauthn.AbstractWebAuthnBaseAction;
import net.shibboleth.idp.plugin.authn.webauthn.context.BaseWebAuthnContext;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
import net.shibboleth.shared.primitive.LoggerFactory;
/**
- * Action that lookups existing registered credentials
+ * Action that lookups existing registered credentials.
*/
public class LookupRegisteredCredentials extends AbstractWebAuthnBaseAction {
-
+
/** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(LookupRegisteredCredentials.class);
-
+ @Nonnull
+ private final Logger log = LoggerFactory.getLogger(LookupRegisteredCredentials.class);
+
/** {@inheritDoc} */
@Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
@Nonnull final BaseWebAuthnContext context) {
@@ -49,8 +49,9 @@ public class LookupRegisteredCredentials extends AbstractWebAuthnBaseAction {
return;
}
- final Set<PublicKeyCredentialDescriptor> credentials =
- getCredentialRepository().getCredentialIdsForUsername(username);
+ final Collection<CredentialRegistration> credentials =
+ getCredentialRepository().getRegistrationsByUsername(username);
+
log.debug("{} Found '{}' registered credentials for '{}'", getLogPrefix(),
credentials != null ? credentials.size() : "0", username);
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/PopulateWebAuthnAuthenticationContext.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/PopulateWebAuthnAuthenticationContext.java
index 79bb7e3..17d5f89 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/PopulateWebAuthnAuthenticationContext.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/PopulateWebAuthnAuthenticationContext.java
@@ -88,7 +88,7 @@ public class PopulateWebAuthnAuthenticationContext extends AbstractAuthenticatio
}
/**
- * Set the lookup strategy to use for the username to match against Duo identity.
+ * Set the lookup strategy to use for the username to use if we are not using a discoverable credential.
*
* @param strategy lookup strategy
*/
@@ -112,15 +112,21 @@ public class PopulateWebAuthnAuthenticationContext extends AbstractAuthenticatio
return;
}
- final String username = usernameLookupStrategy.apply(profileRequestContext);
+ String username = usernameLookupStrategy.apply(profileRequestContext);
+ //FIXME: testing
+ username = "philsmart";
if (username == null && usernameRequiredPredicate.test(profileRequestContext)) {
log.error("{} Error creating WebauthnAuthenticationContext, no username found", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
return;
}
- context.setUsername(username);
-
- log.debug("Created Webauthn authentication context");
+ if (username != null) {
+ context.setUsername(username);
+ log.debug("Created Webauthn authentication context for user '{}'", context.getUsername());
+ return;
+ }
+ log.debug("Created Webauthn authentication context for a discoverable credential (no username)'");
+
}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/InMemoryRegistrationStorage.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/InMemoryRegistrationStorage.java
index aa58bec..fe7c09c 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/InMemoryRegistrationStorage.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/InMemoryRegistrationStorage.java
@@ -40,13 +40,15 @@ import org.slf4j.LoggerFactory;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.yubico.webauthn.AssertionResult;
-import com.yubico.webauthn.CredentialRepository;
import com.yubico.webauthn.RegisteredCredential;
import com.yubico.webauthn.data.ByteArray;
import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.StorageServiceCredentialRepository;
-public class InMemoryRegistrationStorage implements CredentialRepository {
+
+public class InMemoryRegistrationStorage implements StorageServiceCredentialRepository {
private final Cache<String, Set<CredentialRegistration>> storage =
CacheBuilder.newBuilder().maximumSize(1000).expireAfterAccess(1, TimeUnit.DAYS).build();
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-beans.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-beans.xml
index da08c57..75bc9df 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-beans.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-beans.xml
@@ -26,7 +26,7 @@
c:type="#{ T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext) }" />
<bean id="LookupRegisteredCredentials" parent="AbstractWebAuthnRegistrationAction"
- class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.LookupRegisteredCredentials"
+ class="net.shibboleth.idp.plugin.authn.webauthn.impl.LookupRegisteredCredentials"
p:webAuthnBaseContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnRegistrationContext"/>
<bean id="GenerateServerChallenge" parent="AbstractWebAuthnBaseAction"
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-flow.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-flow.xml
index 983a7cf..b85d4b3 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-flow.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-flow.xml
@@ -46,6 +46,7 @@
<evaluate expression="flowRequestContext.getExternalContext().getNativeRequest()" result="viewScope.request" />
<evaluate expression="flowRequestContext.getExternalContext().getNativeResponse()" result="viewScope.response" />
</on-render>
+
<transition on="proceed" to="ExtractPublicKeyCredential" />
</view-state>
@@ -59,20 +60,27 @@
<transition on="proceed" to="DisplayWebAuthnSuccessfulRegistration" />
</action-state>
- <end-state id="DisplayWebAuthnSuccessfulRegistration" view="webauthn/webauthn-registered">
- <on-entry>
- <evaluate expression="environment" result="requestScope.environment" />
- <evaluate expression="opensamlProfileRequestContext" result="requestScope.profileRequestContext" />
- <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext))" result="requestScope.authenticationContext" />
- <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).getSubcontext(T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext))" result="requestScope.webauthnContext" />
- <evaluate expression="authenticationContext.getSubcontext(T(net.shibboleth.idp.ui.context.RelyingPartyUIContext))" result="requestScope.rpUIContext" />
- <evaluate expression="authenticationContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationErrorContext))" result="requestScope.authenticationErrorContext" />
- <evaluate expression="authenticationContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationWarningContext))" result="requestScope.authenticationWarningContext" />
- <evaluate expression="T(net.shibboleth.utilities.java.support.codec.HTMLEncoder)" result="requestScope.encoder" />
- <evaluate expression="flowRequestContext.getExternalContext().getNativeRequest()" result="requestScope.request" />
- <evaluate expression="flowRequestContext.getExternalContext().getNativeResponse()" result="requestScope.response" />
- </on-entry>
- </end-state>
+ <view-state id="DisplayWebAuthnSuccessfulRegistration" view="webauthn/webauthn-registered">
+ <on-entry>
+ <evaluate expression="LookupRegisteredCredentials"/>
+ </on-entry>
+ <on-render>
+ <evaluate expression="environment" result="viewScope.environment" />
+ <evaluate expression="opensamlProfileRequestContext" result="viewScope.profileRequestContext" />
+ <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext))" result="viewScope.authenticationContext" />
+ <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext))" result="viewScope.webauthnRegContext" />
+ <evaluate expression="authenticationContext.getSubcontext(T(net.shibboleth.idp.ui.context.RelyingPartyUIContext))" result="viewScope.rpUIContext" />
+ <evaluate expression="authenticationContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationErrorContext))" result="viewScope.authenticationErrorContext" />
+ <evaluate expression="authenticationContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationWarningContext))" result="viewScope.authenticationWarningContext" />
+ <evaluate expression="T(net.shibboleth.utilities.java.support.codec.HTMLEncoder)" result="viewScope.encoder" />
+ <evaluate expression="flowRequestContext.getExternalContext().getNativeRequest()" result="viewScope.request" />
+ <evaluate expression="flowRequestContext.getExternalContext().getNativeResponse()" result="viewScope.response" />
+ </on-render>
+
+ <transition on="proceed" to="RegistrationComplete" />
+ </view-state>
+
+ <end-state id="RegistrationComplete"/>
<bean-import resource="webauthn-registration-beans.xml" />
<bean-import resource="classpath:/META-INF/net/shibboleth/idp/flows/authn/webauthn/webauthn-abstract-beans.xml" />
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-beans.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-beans.xml
index 6f58664..5d3ac72 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-beans.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-beans.xml
@@ -14,6 +14,9 @@
p:usernameRequiredPredicate="false">
</bean>
+ <bean id="IsDiscoverableCredentialRequired" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.webauthn.context.logic.IsDiscoverableCredentialRequired"/>
+
<bean id="shibboleth.ChildLookup.WebAuthnAuthenticationContext"
class="org.opensaml.messaging.context.navigate.ChildContextLookup"
c:type="#{ T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext) }" />
@@ -21,6 +24,10 @@
<bean id ="shibboleth.ChildLookup.WebAuthnAuthenticationContextFromAuthenticationContext"
parent="shibboleth.Functions.Compose" c:f-ref="shibboleth.ChildLookup.AuthenticationContext"
c:g-ref="shibboleth.ChildLookup.WebAuthnAuthenticationContext" />
+
+ <bean id="LookupRegisteredCredentials" parent="AbstractWebAuthnRegistrationAction"
+ class="net.shibboleth.idp.plugin.authn.webauthn.impl.LookupRegisteredCredentials"
+ p:webAuthnBaseContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnAuthenticationContextFromAuthenticationContext"/>
<bean id="GenerateServerChallenge" parent="AbstractWebAuthnBaseAction"
class="net.shibboleth.idp.plugin.authn.webauthn.impl.GenerateServerChallenge"
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-flow.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-flow.xml
index 0eac3c0..a4837d2 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-flow.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-flow.xml
@@ -4,14 +4,35 @@
<action-state id="PopulateWebauthnContext">
<evaluate expression="PopulateWebAuthnAuthenticationContext"/>
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="DetermineUsernamelessOrPasswordlessLogin" />
+ </action-state>
+
+ <decision-state id="DetermineUsernamelessOrPasswordlessLogin">
+ <if test="IsDiscoverableCredentialRequired.test(opensamlProfileRequestContext)"
+ then="UsernamelessLogin"
+ else="PasswordlessLogin" />
+ </decision-state>
+
+ <!-- If passwordless and not usernameless we need a different flow here. Also User verification not required for 2FA-->
+ <action-state id="UsernamelessLogin">
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="GenerateRegistrationCeremonyOptions" />
+ </action-state>
+
+ <action-state id="PasswordlessLogin">
+ <evaluate expression="LookupRegisteredCredentials"/>
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="GenerateRegistrationCeremonyOptions" />
+ </action-state>
+
+ <action-state id="GenerateRegistrationCeremonyOptions">
<evaluate expression="GenerateServerChallenge"/>
<evaluate expression="CreatePublicKeyCredentialRequestOptions"/>
<evaluate expression="'proceed'" />
<transition on="proceed" to="DisplayWebAuthnView" />
</action-state>
- <!-- If passwordless and not usernameless we need a different flow here -->
-
<view-state id="DisplayWebAuthnView" view="webauthn/webauthn-authn">
<on-render>
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/css/webauthn.css b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/css/webauthn.css
new file mode 100644
index 0000000..b9d5cf5
--- /dev/null
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/css/webauthn.css
@@ -0,0 +1,81 @@
+ at charset "UTF-8";
+
+table {
+ border-collapse: collapse;
+ width: 100%;
+}
+
+th,
+td {
+ padding: 8px;
+ text-align: left;
+ border-bottom: 1px solid #ddd;
+}
+
+tr:hover {
+ background-color: coral;
+}
+
+
+/* Style the button that is used to open and close the collapsible content */
+.collapsible {
+ background-color: #eee;
+ color: #444;
+ cursor: pointer;
+ padding: 18px;
+ width: 100%;
+ border: none;
+ text-align: left;
+ outline: none;
+ font-size: 15px;
+}
+
+/* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */
+.active,
+.collapsible:hover {
+ background-color: #ccc;
+}
+
+/* Style the collapsible content. Note: hidden by default */
+.debug {
+ padding: 0 18px;
+ display: none;
+ overflow: hidden;
+ background-color: #f1f1f1;
+}
+
+.collapsible:after {
+ content: '\02795';
+ /* Unicode character for "plus" sign (+) */
+ font-size: 13px;
+ color: white;
+ float: right;
+ margin-left: 5px;
+}
+
+.active:after {
+ content: "\2796";
+ /* Unicode character for "minus" sign (-) */
+}
+
+.hidden {
+ display: none;
+}
+
+.centre {
+ margin-left: auto;
+ margin-right: auto;
+ text-align: center;
+}
+
+.webauthn-table-button {
+ background-color: rgb(221, 5, 0);
+ /* Green */
+ border: none;
+ color: white;
+ padding: 10px 25px;
+ text-align: center;
+ text-decoration: none;
+ display: inline-block;
+ font-size: 16px;
+}
\ No newline at end of file
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/js/webauthn-support.js b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/js/webauthn-support.js
new file mode 100644
index 0000000..006d9c9
--- /dev/null
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/js/webauthn-support.js
@@ -0,0 +1,16 @@
+document.addEventListener('DOMContentLoaded', function() {
+ var coll = document.getElementsByClassName("collapsible");
+ var i;
+
+ for (i = 0; i < coll.length; i++) {
+ coll[i].addEventListener("click", function() {
+ this.classList.toggle("active");
+ var content = this.nextElementSibling;
+ if (content.style.display === "block") {
+ content.style.display = "none";
+ } else {
+ content.style.display = "block";
+ }
+ });
+ }
+});
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/js/webauthn.js b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/js/webauthn.js
new file mode 100644
index 0000000..2cf5335
--- /dev/null
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/js/webauthn.js
@@ -0,0 +1,171 @@
+// Copyright (c) 2018, Yubico AB
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+(function(root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ define(['base64url'], factory);
+ } else if (typeof module === 'object' && module.exports) {
+ module.exports = factory(require('base64url'));
+ } else {
+ root.webauthn = factory(root.base64url);
+ }
+})(this, function(base64url) {
+
+ function extend(obj, more) {
+ return Object.assign({}, obj, more);
+ }
+
+ /**
+ * Create a WebAuthn credential.
+ *
+ * @param request: object - A PublicKeyCredentialCreationOptions object, except
+ * where binary values are base64url encoded strings instead of byte arrays
+ *
+ * @return a PublicKeyCredentialCreationOptions suitable for passing as the
+ * `publicKey` parameter to `navigator.credentials.create()`
+ */
+ function decodePublicKeyCredentialCreationOptions(request) {
+ const excludeCredentials = request.excludeCredentials.map(credential => extend(
+ credential, {
+ id: base64url.toByteArray(credential.id),
+ }));
+
+ const publicKeyCredentialCreationOptions = extend(
+ request, {
+ attestation: 'direct',
+ user: extend(
+ request.user, {
+ id: base64url.toByteArray(request.user.id),
+ }),
+ challenge: base64url.toByteArray(request.challenge),
+ excludeCredentials,
+ });
+
+ return publicKeyCredentialCreationOptions;
+ }
+
+ /**
+ * Create a WebAuthn credential.
+ *
+ * @param request: object - A PublicKeyCredentialCreationOptions object, except
+ * where binary values are base64url encoded strings instead of byte arrays
+ *
+ * @return the Promise returned by `navigator.credentials.create`
+ */
+ function createCredential(request) {
+ return navigator.credentials.create({
+ publicKey: decodePublicKeyCredentialCreationOptions(request),
+ });
+ }
+
+ /**
+ * Perform a WebAuthn assertion.
+ *
+ * @param request: object - A PublicKeyCredentialRequestOptions object,
+ * except where binary values are base64url encoded strings instead of byte
+ * arrays
+ *
+ * @return a PublicKeyCredentialRequestOptions suitable for passing as the
+ * `publicKey` parameter to `navigator.credentials.get()`
+ */
+ function decodePublicKeyCredentialRequestOptions(request) {
+ const allowCredentials = request.allowCredentials && request.allowCredentials.map(credential => extend(
+ credential, {
+ id: base64url.toByteArray(credential.id),
+ }));
+
+ const publicKeyCredentialRequestOptions = extend(
+ request, {
+ allowCredentials,
+ challenge: base64url.toByteArray(request.challenge),
+ });
+
+ return publicKeyCredentialRequestOptions;
+ }
+
+ /**
+ * Perform a WebAuthn assertion.
+ *
+ * @param request: object - A PublicKeyCredentialRequestOptions object,
+ * except where binary values are base64url encoded strings instead of byte
+ * arrays
+ *
+ * @return the Promise returned by `navigator.credentials.get`
+ */
+ function getAssertion(request) {
+ console.log('Get assertion', request);
+ return navigator.credentials.get({
+ publicKey: decodePublicKeyCredentialRequestOptions(request),
+ });
+ }
+
+
+ /** Turn a PublicKeyCredential object into a plain object with base64url encoded binary values */
+ function responseToObject(response) {
+ if (response.u2fResponse) {
+ return response;
+ } else {
+ let clientExtensionResults = {};
+
+ try {
+ clientExtensionResults = response.getClientExtensionResults();
+ } catch (e) {
+ console.error('getClientExtensionResults failed', e);
+ }
+
+ if (response.response.attestationObject) {
+ return {
+ type: response.type,
+ id: response.id,
+ response: {
+ attestationObject: bytesToBase64(response.response.attestationObject),
+ clientDataJSON: bytesToBase64(response.response.clientDataJSON),
+ },
+ clientExtensionResults,
+ };
+ } else {
+ return {
+ type: response.type,
+ id: response.id,
+ response: {
+ authenticatorData: base64url.fromByteArray(response.response.authenticatorData),
+ clientDataJSON: base64url.fromByteArray(response.response.clientDataJSON),
+ signature: base64url.fromByteArray(response.response.signature),
+ userHandle: response.response.userHandle && base64url.fromByteArray(response.response.userHandle),
+ },
+ clientExtensionResults,
+ };
+ }
+ }
+ }
+
+ return {
+ decodePublicKeyCredentialCreationOptions,
+ decodePublicKeyCredentialRequestOptions,
+ createCredential,
+ getAssertion,
+ responseToObject,
+ };
+
+});
\ No newline at end of file
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-authn.vm b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-authn.vm
index fce3aaa..3b6b4e2 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-authn.vm
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-authn.vm
@@ -27,18 +27,19 @@
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0">
+ <script type="text/javascript" src="$request.getContextPath()/js/webauthn-support.js"></script>
<link rel="stylesheet" type="text/css" href="$request.getContextPath()#springMessageText(" idp.css", "/css/placeholder.css" )">
-
+ <link rel="stylesheet" type="text/css" href="$request.getContextPath()/css/webauthn.css">
<script type="module">
import * as webauthnJson from "$request.getContextPath()/js/webauthn-json.js";
async function authenticate() {
- console.log("authenticating");
var pkCredRequestOptions = $webauthnContext.publicKeyCredentialRequestOptionsJSON;
console.log("Raw request options", pkCredRequestOptions);
await webauthnJson.get({ publicKey: pkCredRequestOptions })
.then(function (assertion){
document.getElementById("publicKeyAssertion").value = JSON.stringify(assertion);
+ document.getElementById("authenticationSubmit").click();
}).catch(function (err){console.error});
}
@@ -68,17 +69,23 @@
<div class="content">
<div class="column one">
- <textarea id="publicKeyCredentialCreation" name="publicKeyCredentialCreation" rows="20" cols="50">
- $webauthnContext.publicKeyCredentialRequestOptions</textarea>
- <button id="authenticate" class="form-element form-button">Authenticate</button>
+ <div class="centre">
+ <button id="authenticate" class="form-element form-button">Authenticate</button>
+ </div>
<form id="authn-form" action="$flowExecutionUrl" method="post">
#parse("csrf/csrf.vm")
- <textarea id="publicKeyAssertion" name="publicKeyAssertion" rows="10" cols="50">
- </textarea>
- <button id="authn-submit" type="submit" name="_eventId_proceed">Submit Authentication</button>
+ <input type="hidden" id="publicKeyAssertion" name="publicKeyAssertion"/>
+ <button class="hidden" id="authenticationSubmit" type="submit" name="_eventId_proceed">Submit Authentication</button>
</form>
-
-
+
+ <hr/>
+ <button type="button" class="collapsible">Debugging</button>
+
+ <div class="debug" id="debug-div">
+ <label for="publicKeyCredentialCreation">Request Options</label>
+ <textarea id="publicKeyCredentialRequestOptions" name="publicKeyCredentialRequestOptions" rows="20" cols="50">
+ $webauthnContext.publicKeyCredentialRequestOptions</textarea>
+ </div>
</div>
</div>
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-register.vm b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-register.vm
index a20ddba..c17545c 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-register.vm
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-register.vm
@@ -8,7 +8,7 @@
## profileRequestContext - root of context tree
## authenticationContext - context with authentication request information
## authenticationErrorContext - context with login error state
-## webauthnRegContext = web registration context
+## webauthnRegContext = WebAuthn registration context
## authenticationWarningContext - context with login warning state
## rpUIContext - the context with SP UI information from the metadata
## encoder - HTMLEncoder class
@@ -27,22 +27,25 @@
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0">
+ <script type="text/javascript" src="$request.getContextPath()/js/webauthn-support.js"></script>
<link rel="stylesheet" type="text/css" href="$request.getContextPath()#springMessageText("idp.css", "/css/placeholder.css" )">
-
+ <link rel="stylesheet" type="text/css" href="$request.getContextPath()/css/webauthn.css">
<script type="module">
import * as webauthnJson from "$request.getContextPath()/js/webauthn-json.js";
- async function register() {
- console.log("Register");
- var pkCredOptions = $webauthnRegContext.publicKeyCredentialCreationOptionsJSON;
- console.log("Raw creation options", pkCredOptions);
- const credentialJson = await webauthnJson.create({ publicKey: pkCredOptions }).catch(console.error);
- console.log("Credential: ", JSON.stringify(credentialJson));
- try {
- document.getElementById("publicKeyCredential").value = JSON.stringify(credentialJson);
- } catch (err) {
- console.log(err);
- }
+ async function register() {
+ var pkCredOptions = $webauthnRegContext.publicKeyCredentialCreationOptionsJSON;
+ await webauthnJson.create({ publicKey: pkCredOptions })
+ .then(function (attestation){
+ console.log("attestation: ",attestation);
+ var nickname = prompt("Credential Nickname");
+ document.getElementById("credentialNickname").value = nickname;
+ document.getElementById("authenticatorAttestation").value = JSON.stringify(attestation);
+ document.getElementById("registrationSubmit").click();
+ }).catch(function (err){
+ console.error
+ });
+
};
function init() {
@@ -72,33 +75,48 @@
<section>
<div class="content">
- <div class="column one">
+ <div class="column one centre">
<div>
<h1>Registered Credentials</h1>
#if ($webauthnRegContext.existingCredentials)
- <ul>
- #foreach($cred in $webauthnRegContext.existingCredentials)
- <li>$cred</li>
- #end
- </ul>
+ <table>
+ <tr>
+ <th>Nickname</th>
+ <th>Transports</th>
+ <th>Registration Time</th>
+ <th>Delete</th>
+ </tr>
+ #foreach($cred in $webauthnRegContext.existingCredentials)
+ <tr>
+ <td>$cred.nickname</td>
+ <td>$cred.transports</td>
+ <td>$cred.registrationTimestamp</td>
+ <td>Remove</td>
+ </tr>
+ #end
+ </table>
#else
- <span>You have no registered credentials</span>
+ <div><span>You have no registered credentials</span></div>
#end
- <div>
- <h1>Registered A New Credential</h1>
-
-
+ <br/>
+ <div>
+ <button class="form-element form-button" id="registerButton">Add Key</button>
+ </div>
+ </div>
+ <hr/>
+
+ <button type="button" class="collapsible">Debugging</button>
+
+ <div class="debug" id="debug-div">
<label for="publicKeyCredentialCreation">Registration Options</label>
<textarea id="publicKeyCredentialCreation" name="publicKeyCredentialCreation" rows="20" cols="50">
$webauthnRegContext.publicKeyCredentialCreationOptions</textarea>
- <button class="form-element form-button" id="registerButton">Register</button>
-
- <label for="publicKeyCredentialCreation">Registration Response (Attestation)</label>
- <form action="$flowExecutionUrl" method="post">
+ <form id="authenticatorAttestationForm" action="$flowExecutionUrl" method="post">
#parse("csrf/csrf.vm")
- <textarea id="publicKeyCredential" name="authenticatorAttestation" rows="10" cols="50"></textarea>
- <button id="reg-submit" type="submit" name="_eventId_proceed">Submit Registration</button>
+ <input type="hidden" id="credentialNickname" name="credentialNickname"/>
+ <input type="hidden" id="authenticatorAttestation" name="authenticatorAttestation"/>
+ <button class="hidden" id="registrationSubmit" type="submit" name="_eventId_proceed">Submit Registration</button>
</form>
</div>
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-registered.vm b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-registered.vm
index 2ed681d..33ad029 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-registered.vm
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-registered.vm
@@ -8,7 +8,7 @@
## profileRequestContext - root of context tree
## authenticationContext - context with authentication request information
## authenticationErrorContext - context with login error state
-## webauthnContext = web authentication context
+## webauthnRegContext = WebAuthn registration context
## authenticationWarningContext - context with login warning state
## rpUIContext - the context with SP UI information from the metadata
## encoder - HTMLEncoder class
@@ -21,37 +21,72 @@
##
<!DOCTYPE html>
<html>
- <head>
- <title>#springMessageText("idp.title", "Web Login Service")</title>
- <meta charset="UTF-8" />
- <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0">
- <link rel="stylesheet" type="text/css" href="$request.getContextPath()#springMessageText("idp.css", "/css/placeholder.css")">
- </head>
-
-
-
- <body>
- <main class="main">
- <header>
- <img class="main-logo" src="$request.getContextPath()#springMessageText("idp.logo", "/images/placeholder-logo.png")" alt="#springMessageText("idp.logo.alt-text", "logo")" />
-
- #set ($serviceName = $rpUIContext.serviceName)
- #if ($serviceName && !$rpContext.getRelyingPartyId().contains($serviceName))
- <h1>#springMessageText("idp.login.loginTo", "Login to") $encoder.encodeForHTML($serviceName)</h1>
+
+<head>
+ <title>#springMessageText("idp.title", "Web Login Service")</title>
+ <meta charset="UTF-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0">
+ <link rel="stylesheet" type="text/css" href="$request.getContextPath()#springMessageText("
+ idp.css", "/css/placeholder.css" )">
+ <link rel="stylesheet" type="text/css" href="$request.getContextPath()/css/webauthn.css">
+</head>
+
+
+
+<body>
+ <main class="main">
+ <header>
+ <img class="main-logo" src="$request.getContextPath()#springMessageText("
+ idp.logo", "/images/placeholder-logo.png" )" alt="#springMessageText(" idp.logo.alt-text", "logo" )" />
+
+ #set ($serviceName = $rpUIContext.serviceName)
+ #if ($serviceName && !$rpContext.getRelyingPartyId().contains($serviceName))
+ <h1>#springMessageText("idp.login.loginTo", "Login to") $encoder.encodeForHTML($serviceName)</h1>
+ #end
+ </header>
+ <section>
+ <div class="centre">
+ <p>You have registered your authenticator credentials successfully.</p>
+
+ <hr />
+ <p>Registered credentials</p>
+ #if ($webauthnRegContext.existingCredentials)
+ <table>
+ <tr>
+ <th>Nickname</th>
+ <th>Transports</th>
+ <th>Registration Time</th>
+ </tr>
+ #foreach($cred in $webauthnRegContext.existingCredentials)
+ <tr>
+ <td>$cred.nickname</td>
+ <td>$cred.transports</td>
+ <td>$cred.registrationTimestamp</td>
+ </tr>
+ #end
+ </table>
+ #else
+ <div><span>You have no registered credentials</span></div>
#end
- </header>
- <section>
- You have registered your authenticator credentials succesfully.
- </section>
- </main>
+ <br />
+ <form id="doneButtonForm" action="$flowExecutionUrl" method="post">
+ #parse("csrf/csrf.vm")
+ <button id="doneButton" type="submit" name="_eventId_proceed">Done</button>
+ </form>
+ </div>
+
+
+ </section>
+ </main>
- <footer>
+ <footer>
<div class="container container-footer">
- <p class="footer-text">#springMessageText("idp.footer", "Insert your footer text here.")</p>
+ <p class="footer-text">#springMessageText("idp.footer", "Insert your footer text here.")</p>
</div>
- </footer>
+ </footer>
</div>
-
- </body>
-</html>
\ No newline at end of file
+
+</body>
+
+</html>
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClientTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClientTest.java
index d07a1da..a12e6ea 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClientTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClientTest.java
@@ -33,8 +33,13 @@ import com.yubico.webauthn.AssertionResult;
import com.yubico.webauthn.RegisteredCredential;
import com.yubico.webauthn.RegistrationResult;
import com.yubico.webauthn.RelyingParty;
+import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
+import com.yubico.webauthn.data.AuthenticatorAttestationResponse;
import com.yubico.webauthn.data.AuthenticatorTransport;
import com.yubico.webauthn.data.ByteArray;
+import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
+import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
+import com.yubico.webauthn.data.PublicKeyCredential;
import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
import com.yubico.webauthn.data.PublicKeyCredentialRequestOptions;
import com.yubico.webauthn.data.RelyingPartyIdentity;
@@ -45,9 +50,7 @@ import net.shibboleth.idp.plugin.authn.webauthn.AssertionFailureException;
import net.shibboleth.idp.plugin.authn.webauthn.RegistrationFailureException;
import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnTest;
import net.shibboleth.idp.plugin.authn.webauthn.impl.MockAuthenticator;
-import net.shibboleth.idp.plugin.authn.webauthn.impl.MockAuthenticator.Attestation;
-import net.shibboleth.idp.plugin.authn.webauthn.impl.MockAuthenticator.AuthenticatonExtensionsClientOutputs;
-import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.CredentialRegistration;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.InMemoryRegistrationStorage;
import net.shibboleth.shared.annotation.constraint.NotEmpty;
import net.shibboleth.shared.codec.Base64Support;
@@ -136,13 +139,12 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
final Map<String, String> clientDataCreate = createClientData("webauthn.create", ORIGIN, CHALLENGE_B64);
- final MockAuthenticator.PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs> attestation =
+ final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation =
mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate,
Base64Support.decode(USER_HANDLE_B64));
- final var attestationJson = jsonMapper.writeValueAsString(attestation);
final RegistrationResult registration =
- client.validateAuthenticatorAttestationResponse(credentialCreationOptions, attestationJson);
+ client.validateAuthenticatorAttestationResponse(credentialCreationOptions, attestation);
assertNotNull(registration);
@@ -155,13 +157,12 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
final Map<String, String> clientDataCreate = createClientData("webauthn.create", ORIGIN, CHALLENGE_B64);
- final MockAuthenticator.PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs> attestation =
+ final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation =
mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate,
Base64Support.decode(USER_HANDLE_B64));
- final var attestationJson = jsonMapper.writeValueAsString(attestation);
final RegistrationResult registration =
- client.validateAuthenticatorAttestationResponse(credentialCreationOptions, attestationJson);
+ client.validateAuthenticatorAttestationResponse(credentialCreationOptions, attestation);
assertNotNull(registration);
}
@@ -173,13 +174,12 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
final Map<String, String> clientDataCreate = createClientData("webauthn.create", "wrong-origin", CHALLENGE_B64);
- final MockAuthenticator.PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs> attestation =
+ final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation =
mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate,
Base64Support.decode(USER_HANDLE_B64));
- final var attestationJson = jsonMapper.writeValueAsString(attestation);
final RegistrationResult registration =
- client.validateAuthenticatorAttestationResponse(credentialCreationOptions, attestationJson);
+ client.validateAuthenticatorAttestationResponse(credentialCreationOptions, attestation);
}
@@ -191,14 +191,15 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
final Map<String, String> clientDataCreate = createClientData("webauthn.create", ORIGIN, CHALLENGE_B64);
// Need to register a new credential first
- final MockAuthenticator.PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs> attestation =
+ final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation =
mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate,
Base64Support.decode(USER_HANDLE_B64));
final RegisteredCredential credential = RegisteredCredential.builder()
- .credentialId(new ByteArray(attestation.getRawId()))
+ .credentialId(attestation.getId())
.userHandle(new ByteArray(Base64Support.decode(USER_HANDLE_B64)))
- .publicKeyCose(new ByteArray(attestation.getResponse().getKey().AsCBOR().EncodeToBytes()))
+ .publicKeyCose(attestation.getResponse().getParsedAuthenticatorData()
+ .getAttestedCredentialData().get().getCredentialPublicKey())
.build();
final CredentialRegistration reg = new CredentialRegistration(userIdentity, Optional.of("Nickanme"),
@@ -209,14 +210,13 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
final Map<String, String> clientDataGet = createClientData("webauthn.get", ORIGIN, CHALLENGE_B64);
// Now generate an assertion (authentication) and check it is valid
- final MockAuthenticator.PublicKeyCredential<MockAuthenticator.Assertion, AuthenticatonExtensionsClientOutputs>
- assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getRawId(), clientDataGet);
-
- final var assertionJson = jsonMapper.writeValueAsString(assertion);
- log.debug("AuthenticatorAssertionResponse: '{}'",assertionJson);
+ final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
+ assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getId().getBytes(),
+ clientDataGet);
+
final AssertionResult result =
client.validateAuthenticatorAssertionResponse(USERNAME, Base64Support.decode(USER_HANDLE_B64),
- credentialRequestOptions, assertionJson);
+ credentialRequestOptions, assertion);
assertNotNull(result);
assertTrue(result.isSuccess());
@@ -230,14 +230,15 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
final Map<String, String> clientDataCreate = createClientData("webauthn.create", ORIGIN, CHALLENGE_B64);
// Need to register a new credential first
- final MockAuthenticator.PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs> attestation =
+ final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation =
mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate,
Base64Support.decode(USER_HANDLE_B64));
final RegisteredCredential credential = RegisteredCredential.builder()
- .credentialId(new ByteArray(attestation.getRawId()))
+ .credentialId(attestation.getId())
.userHandle(new ByteArray(Base64Support.decode(USER_HANDLE_B64)))
- .publicKeyCose(new ByteArray(attestation.getResponse().getKey().AsCBOR().EncodeToBytes()))
+ .publicKeyCose(attestation.getResponse().getParsedAuthenticatorData()
+ .getAttestedCredentialData().get().getCredentialPublicKey())
.build();
final CredentialRegistration reg = new CredentialRegistration(userIdentity, Optional.of("Nickanme"),
@@ -249,13 +250,12 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
final Map<String, String> clientDataGet = createClientData("webauthn.get", "wrong", CHALLENGE_B64);
// Now generate an assertion (authentication) and check it is valid
- final MockAuthenticator.PublicKeyCredential<MockAuthenticator.Assertion, AuthenticatonExtensionsClientOutputs>
- assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getRawId(), clientDataGet);
+ final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
+ assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getId().getBytes(),
+ clientDataGet);
- final var assertionJson = jsonMapper.writeValueAsString(assertion);
- log.debug("AuthenticatorAssertionResponse: '{}'",assertionJson);
client.validateAuthenticatorAssertionResponse(USERNAME, Base64Support.decode(USER_HANDLE_B64),
- credentialRequestOptions, assertionJson);
+ credentialRequestOptions, assertion);
}
@@ -267,14 +267,15 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
final Map<String, String> clientDataCreate = createClientData("webauthn.create", ORIGIN, CHALLENGE_B64);
// Need to register a new credential first
- final MockAuthenticator.PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs> attestation =
+ final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation =
mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate,
Base64Support.decode(USER_HANDLE_B64));
final RegisteredCredential credential = RegisteredCredential.builder()
- .credentialId(new ByteArray(attestation.getRawId()))
+ .credentialId(attestation.getId())
.userHandle(new ByteArray(Base64Support.decode(USER_HANDLE_B64)))
- .publicKeyCose(new ByteArray(attestation.getResponse().getKey().AsCBOR().EncodeToBytes()))
+ .publicKeyCose(attestation.getResponse().getParsedAuthenticatorData()
+ .getAttestedCredentialData().get().getCredentialPublicKey())
.build();
final CredentialRegistration reg = new CredentialRegistration(userIdentity, Optional.of("Nickanme"),
@@ -286,13 +287,12 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
final Map<String, String> clientDataGet = createClientData("webauthn.create", ORIGIN, CHALLENGE_B64);
// Now generate an assertion (authentication) and check it is valid
- final MockAuthenticator.PublicKeyCredential<MockAuthenticator.Assertion, AuthenticatonExtensionsClientOutputs>
- assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getRawId(), clientDataGet);
-
- final var assertionJson = jsonMapper.writeValueAsString(assertion);
- log.debug("AuthenticatorAssertionResponse: '{}'",assertionJson);
+ final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
+ assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getId().getBytes(),
+ clientDataGet);
+
client.validateAuthenticatorAssertionResponse(USERNAME, Base64Support.decode(USER_HANDLE_B64),
- credentialRequestOptions, assertionJson);
+ credentialRequestOptions, assertion);
}
@@ -305,14 +305,15 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
final Map<String, String> clientDataCreate = createClientData("webauthn.create", ORIGIN, CHALLENGE_B64);
// Need to register a new credential first
- final MockAuthenticator.PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs> attestation =
+ final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation =
mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate,
Base64Support.decode(USER_HANDLE_B64));
final RegisteredCredential credential = RegisteredCredential.builder()
- .credentialId(new ByteArray(attestation.getRawId()))
+ .credentialId(attestation.getId())
.userHandle(new ByteArray(Base64Support.decode(USER_HANDLE_B64)))
- .publicKeyCose(new ByteArray(attestation.getResponse().getKey().AsCBOR().EncodeToBytes()))
+ .publicKeyCose(attestation.getResponse().getParsedAuthenticatorData()
+ .getAttestedCredentialData().get().getCredentialPublicKey())
.build();
final CredentialRegistration reg = new CredentialRegistration(userIdentity, Optional.of("Nickanme"),
@@ -324,13 +325,12 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
final Map<String, String> clientDataGet = createClientData("webauthn.get", ORIGIN, CHALLENGE_B64);
// Now generate an assertion (authentication) and check it is valid
- final MockAuthenticator.PublicKeyCredential<MockAuthenticator.Assertion, AuthenticatonExtensionsClientOutputs>
- assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getRawId(), clientDataGet);
-
- final var assertionJson = jsonMapper.writeValueAsString(assertion);
- log.debug("AuthenticatorAssertionResponse: '{}'",assertionJson);
+ final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
+ assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getId().getBytes(),
+ clientDataGet);
+
client.validateAuthenticatorAssertionResponse(USERNAME, Base64Support.decode(USER_HANDLE_B64),
- credentialRequestOptions, assertionJson);
+ credentialRequestOptions, assertion);
}
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/MockAuthenticator.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/MockAuthenticator.java
index 51ebed8..c73e677 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/MockAuthenticator.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/MockAuthenticator.java
@@ -48,6 +48,10 @@ import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.dataformat.cbor.CBORFactory;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
+import com.yubico.webauthn.data.AuthenticatorAttestationResponse;
+import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
+import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
import COSE.AlgorithmID;
import COSE.OneKey;
@@ -148,7 +152,7 @@ public class MockAuthenticator {
*
* @throws Exception on error.
*/
- public PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs>
+ public com.yubico.webauthn.data.PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs>
createAuthenticatorAttestationResponse(@Nonnull @NotEmpty final String challenge,
final Map<String, String> clientData, final byte[] userHandle) throws Exception {
@@ -166,8 +170,13 @@ public class MockAuthenticator {
credentialId,
new Attestation(clientDataJsonString.getBytes(), createdKey, attestationObject, userHandle),
new AuthenticatonExtensionsClientOutputs());
+
+ //convert from the test type to the correct type for the rest of the system
+ final var attestationAsJson = jsonMapper.writeValueAsString(publicKeyCredential);
+ final var pkCred =com.yubico.webauthn.data.PublicKeyCredential.parseRegistrationResponseJson(attestationAsJson);
+
createdCredentialsMaps.put(Base64Support.encodeURLSafe(credentialId), publicKeyCredential);
- return publicKeyCredential;
+ return pkCred;
}
@@ -181,8 +190,9 @@ public class MockAuthenticator {
*
* @throws Exception on error
*/
- public PublicKeyCredential<Assertion, AuthenticatonExtensionsClientOutputs> createAuthenticatorAssertionResponse(
- @Nonnull final byte[] credentialId, final Map<String, String> clientData) throws Exception {
+ public com.yubico.webauthn.data.PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
+ createAuthenticatorAssertionResponse(@Nonnull final byte[] credentialId, final Map<String, String> clientData)
+ throws Exception {
final var credentialb64 = Base64Support.encodeURLSafe(credentialId);
final var publicKeyAttestation = createdCredentialsMaps.get(credentialb64);
@@ -198,10 +208,15 @@ public class MockAuthenticator {
sign(authenticatorData, clientDataCompactSerialization,
publicKeyAttestation.getResponse().getKey().AsPrivateKey());
- return new PublicKeyCredential<Assertion, AuthenticatonExtensionsClientOutputs>(rawCredentialIdentifier,
+ final var pkCred = new PublicKeyCredential<Assertion, AuthenticatonExtensionsClientOutputs>(rawCredentialIdentifier,
new Assertion(clientDataCompactSerialization.getBytes(), authenticatorData, signature,
publicKeyAttestation.getResponse().getUserHandle()),
new AuthenticatonExtensionsClientOutputs());
+
+ //convert from the test type to the correct type for the rest of the system
+ final var assertionAsJson = jsonMapper.writeValueAsString(pkCred);
+ return com.yubico.webauthn.data.PublicKeyCredential.parseAssertionResponseJson(assertionAsJson);
+
}
/**
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidatePublicKeyCredentialTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidatePublicKeyCredentialTest.java
index 8b6a85c..8715c85 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidatePublicKeyCredentialTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidatePublicKeyCredentialTest.java
@@ -76,6 +76,7 @@ public class ValidatePublicKeyCredentialTest extends AbstractWebAuthnTest{
final WebAuthnAuthenticationClient client = new YubicoWebauthnAuthenticationClient(rp, jsonMapper);
validator.setWebAuthnClient(client);
+ validator.setCredentialRepository(new InMemoryRegistrationStorage());
validator.initialize();
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list