diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/FinishAssertionSteps.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/FinishAssertionSteps.java index 06bc78462..b720634e2 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/FinishAssertionSteps.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/FinishAssertionSteps.java @@ -55,7 +55,7 @@ final class FinishAssertionSteps { private final AssertionRequest request; private final PublicKeyCredential - response; + response; private final Optional callerTokenBindingId; private final Set origins; private final String rpId; @@ -67,37 +67,37 @@ final class FinishAssertionSteps { private final boolean isSecurePaymentConfirmation; static FinishAssertionSteps fromV1( - RelyingParty rp, FinishAssertionOptions options) { + RelyingParty rp, FinishAssertionOptions options) { final CredentialRepository credRepo = rp.getCredentialRepository(); final CredentialRepositoryV1ToV2Adapter credRepoV2 = - new CredentialRepositoryV1ToV2Adapter(credRepo); + new CredentialRepositoryV1ToV2Adapter(credRepo); return new FinishAssertionSteps<>( - options.getRequest(), - options.getResponse(), - options.getCallerTokenBindingId(), - rp.getOrigins(), - rp.getIdentity().getId(), - credRepoV2, - Optional.of(credRepoV2), - rp.isAllowOriginPort(), - rp.isAllowOriginSubdomain(), - rp.isValidateSignatureCounter(), - options.isSecurePaymentConfirmation()); + options.getRequest(), + options.getResponse(), + options.getCallerTokenBindingId(), + rp.getOrigins(), + rp.getIdentity().getId(), + credRepoV2, + Optional.of(credRepoV2), + rp.isAllowOriginPort(), + rp.isAllowOriginSubdomain(), + rp.isValidateSignatureCounter(), + options.isSecurePaymentConfirmation()); } FinishAssertionSteps(RelyingPartyV2 rp, FinishAssertionOptions options) { this( - options.getRequest(), - options.getResponse(), - options.getCallerTokenBindingId(), - rp.getOrigins(), - rp.getIdentity().getId(), - rp.getCredentialRepository(), - Optional.ofNullable(rp.getUsernameRepository()), - rp.isAllowOriginPort(), - rp.isAllowOriginSubdomain(), - rp.isValidateSignatureCounter(), - options.isSecurePaymentConfirmation()); + options.getRequest(), + options.getResponse(), + options.getCallerTokenBindingId(), + rp.getOrigins(), + rp.getIdentity().getId(), + rp.getCredentialRepository(), + Optional.ofNullable(rp.getUsernameRepository()), + rp.isAllowOriginPort(), + rp.isAllowOriginSubdomain(), + rp.isValidateSignatureCounter(), + options.isSecurePaymentConfirmation()); } private Optional getUsernameForUserHandle(final ByteArray userHandle) { @@ -163,16 +163,16 @@ public Step6 nextStep() { @Override public void validate() { request - .getPublicKeyCredentialRequestOptions() - .getAllowCredentials() - .filter(allowCredentials -> !allowCredentials.isEmpty()) - .ifPresent( - allowed -> { - assertTrue( - allowed.stream().anyMatch(allow -> allow.getId().equals(response.getId())), - "Unrequested credential ID: %s", - response.getId()); - }); + .getPublicKeyCredentialRequestOptions() + .getAllowCredentials() + .filter(allowCredentials -> !allowCredentials.isEmpty()) + .ifPresent( + allowed -> { + assertTrue( + allowed.stream().anyMatch(allow -> allow.getId().equals(response.getId())), + "Unrequested credential ID: %s", + response.getId()); + }); } } @@ -197,29 +197,29 @@ public Step6() { responseUserHandle = response.getResponse().getUserHandle(); effectiveRequestUserHandle = - OptionalUtil.orElseOptional( - requestedUserHandle, - () -> - usernameRepository.flatMap( - unr -> requestedUsername.flatMap(unr::getUserHandleForUsername))); + OptionalUtil.orElseOptional( + requestedUserHandle, + () -> + usernameRepository.flatMap( + unr -> requestedUsername.flatMap(unr::getUserHandleForUsername))); effectiveRequestUsername = - OptionalUtil.orElseOptional( - requestedUsername, - () -> - requestedUserHandle.flatMap(FinishAssertionSteps.this::getUsernameForUserHandle)); + OptionalUtil.orElseOptional( + requestedUsername, + () -> + requestedUserHandle.flatMap(FinishAssertionSteps.this::getUsernameForUserHandle)); userHandleDerivedFromUsername = - !requestedUserHandle.isPresent() && effectiveRequestUserHandle.isPresent(); + !requestedUserHandle.isPresent() && effectiveRequestUserHandle.isPresent(); finalUserHandle = OptionalUtil.orOptional(effectiveRequestUserHandle, responseUserHandle); finalUsername = - OptionalUtil.orElseOptional( - effectiveRequestUsername, - () -> finalUserHandle.flatMap(FinishAssertionSteps.this::getUsernameForUserHandle)); + OptionalUtil.orElseOptional( + effectiveRequestUsername, + () -> finalUserHandle.flatMap(FinishAssertionSteps.this::getUsernameForUserHandle)); registration = - finalUserHandle.flatMap(uh -> credentialRepositoryV2.lookup(response.getId(), uh)); + finalUserHandle.flatMap(uh -> credentialRepositoryV2.lookup(response.getId(), uh)); } @Override @@ -230,43 +230,46 @@ public Step7 nextStep() { @Override public void validate() { assertTrue( - !(request.getUsername().isPresent() && !usernameRepository.isPresent()), - "Cannot set request username when usernameRepository is not configured."); + !(request.getUsername().isPresent() && !usernameRepository.isPresent()), + "Cannot set request username when usernameRepository is not configured."); assertTrue( - finalUserHandle.isPresent(), - "Could not identify user to authenticate: none of requested username, requested user handle or response user handle are set."); + finalUserHandle.isPresent(), + "Could not identify user to authenticate: none of requested username, requested user handle or response user handle are set."); if (requestedUserHandle.isPresent() && responseUserHandle.isPresent()) { assertTrue( - requestedUserHandle.get().equals(responseUserHandle.get()), - "User handle set in request (%s) does not match user handle in response (%s).", - requestedUserHandle.get(), - responseUserHandle.get()); + requestedUserHandle.get().equals(responseUserHandle.get()), + "User handle set in request (%s) does not match user handle in response (%s).", + requestedUserHandle.get(), + responseUserHandle.get()); } if (userHandleDerivedFromUsername && responseUserHandle.isPresent()) { assertTrue( - effectiveRequestUserHandle.get().equals(responseUserHandle.get()), - "User handle in request (%s) (derived from username: %s) does not match user handle in response (%s).", - effectiveRequestUserHandle.get(), - requestedUsername.get(), - responseUserHandle.get()); + effectiveRequestUserHandle.get().equals(responseUserHandle.get()), + "User handle in request (%s) (derived from username: %s) does not match user handle in response (%s).", + effectiveRequestUserHandle.get(), + requestedUsername.get(), + responseUserHandle.get()); } assertTrue(registration.isPresent(), "Unknown credential: %s", response.getId()); + System.out.println("User handle: " + new String(java.util.Base64.getUrlEncoder().encode(finalUserHandle.get().getBytes()))); + System.out.println("User handle 2: " + registration.get().getUserHandle()); + System.out.println("User handle 3: " + finalUserHandle.get()); assertTrue( - finalUserHandle.get().equals(registration.get().getUserHandle()), - "User handle %s does not own credential %s", - finalUserHandle.get(), - response.getId()); + finalUserHandle.get().equals(registration.get().getUserHandle()), + "User handle %s does not own credential %s", + finalUserHandle.get(), + response.getId()); if (usernameRepository.isPresent()) { assertTrue( - finalUsername.isPresent(), - "Unknown username for user handle: %s", - finalUserHandle.get()); + finalUsername.isPresent(), + "Unknown username for user handle: %s", + finalUserHandle.get()); } } } @@ -285,10 +288,10 @@ public Step8 nextStep() { @Override public void validate() { assertTrue( - credential.isPresent(), - "Unknown credential. Credential ID: %s, user handle: %s", - response.getId(), - userHandle); + credential.isPresent(), + "Unknown credential. Credential ID: %s, user handle: %s", + response.getId(), + userHandle); } } @@ -354,12 +357,12 @@ class Step11 implements Step { @Override public void validate() { final String expectedType = - isSecurePaymentConfirmation ? SPC_CLIENT_DATA_TYPE : CLIENT_DATA_TYPE; + isSecurePaymentConfirmation ? SPC_CLIENT_DATA_TYPE : CLIENT_DATA_TYPE; assertTrue( - expectedType.equals(clientData.getType()), - "The \"type\" in the client data must be exactly \"%s\", was: %s", - expectedType, - clientData.getType()); + expectedType.equals(clientData.getType()), + "The \"type\" in the client data must be exactly \"%s\", was: %s", + expectedType, + clientData.getType()); } @Override @@ -376,11 +379,11 @@ class Step12 implements Step { @Override public void validate() { assertTrue( - request - .getPublicKeyCredentialRequestOptions() - .getChallenge() - .equals(response.getResponse().getClientData().getChallenge()), - "Incorrect challenge."); + request + .getPublicKeyCredentialRequestOptions() + .getChallenge() + .equals(response.getResponse().getClientData().getChallenge()), + "Incorrect challenge."); } @Override @@ -398,9 +401,9 @@ class Step13 implements Step { public void validate() { final String responseOrigin = response.getResponse().getClientData().getOrigin(); assertTrue( - OriginMatcher.isAllowed(responseOrigin, origins, allowOriginPort, allowOriginSubdomain), - "Incorrect origin, please see the RelyingParty.origins setting: %s", - responseOrigin); + OriginMatcher.isAllowed(responseOrigin, origins, allowOriginPort, allowOriginSubdomain), + "Incorrect origin, please see the RelyingParty.origins setting: %s", + responseOrigin); } @Override @@ -417,7 +420,7 @@ class Step14 implements Step { @Override public void validate() { TokenBindingValidator.validate( - response.getResponse().getClientData().getTokenBinding(), callerTokenBindingId); + response.getResponse().getClientData().getTokenBinding(), callerTokenBindingId); } @Override @@ -435,17 +438,17 @@ class Step15 implements Step { public void validate() { try { assertTrue( - Crypto.sha256(rpId) - .equals(response.getResponse().getParsedAuthenticatorData().getRpIdHash()), - "Wrong RP ID hash."); + Crypto.sha256(rpId) + .equals(response.getResponse().getParsedAuthenticatorData().getRpIdHash()), + "Wrong RP ID hash."); } catch (IllegalArgumentException e) { Optional appid = - request.getPublicKeyCredentialRequestOptions().getExtensions().getAppid(); + request.getPublicKeyCredentialRequestOptions().getExtensions().getAppid(); if (appid.isPresent()) { assertTrue( - Crypto.sha256(appid.get().getId()) - .equals(response.getResponse().getParsedAuthenticatorData().getRpIdHash()), - "Wrong RP ID hash."); + Crypto.sha256(appid.get().getId()) + .equals(response.getResponse().getParsedAuthenticatorData().getRpIdHash()), + "Wrong RP ID hash."); } else { throw e; } @@ -466,8 +469,8 @@ class Step16 implements Step { @Override public void validate() { assertTrue( - response.getResponse().getParsedAuthenticatorData().getFlags().UP, - "User Presence is required."); + response.getResponse().getParsedAuthenticatorData().getFlags().UP, + "User Presence is required."); } @Override @@ -484,12 +487,12 @@ class Step17 implements Step { @Override public void validate() { if (request - .getPublicKeyCredentialRequestOptions() - .getUserVerification() - .equals(Optional.of(UserVerificationRequirement.REQUIRED))) { + .getPublicKeyCredentialRequestOptions() + .getUserVerification() + .equals(Optional.of(UserVerificationRequirement.REQUIRED))) { assertTrue( - response.getResponse().getParsedAuthenticatorData().getFlags().UV, - "User Verification is required."); + response.getResponse().getParsedAuthenticatorData().getFlags().UV, + "User Verification is required."); } } @@ -500,8 +503,8 @@ public PendingStep16 nextStep() { } @Value - // Step 16 in editor's draft as of 2022-11-09 https://w3c.github.io/webauthn/ - // TODO: Finalize this when spec matures + // Step 16 in editor's draft as of 2022-11-09 https://w3c.github.io/webauthn/ + // TODO: Finalize this when spec matures class PendingStep16 implements Step { private final Optional username; private final C credential; @@ -509,13 +512,13 @@ class PendingStep16 implements Step { @Override public void validate() { assertTrue( - !credential.isBackupEligible().isPresent() - || response.getResponse().getParsedAuthenticatorData().getFlags().BE - == credential.isBackupEligible().get(), - "Backup eligibility must not change; Stored: BE=%s, received: BE=%s for credential: %s", - credential.isBackupEligible(), - response.getResponse().getParsedAuthenticatorData().getFlags().BE, - credential.getCredentialId()); + !credential.isBackupEligible().isPresent() + || response.getResponse().getParsedAuthenticatorData().getFlags().BE + == credential.isBackupEligible().get(), + "Backup eligibility must not change; Stored: BE=%s, received: BE=%s for credential: %s", + credential.isBackupEligible(), + response.getResponse().getParsedAuthenticatorData().getFlags().BE, + credential.getCredentialId()); } @Override @@ -573,20 +576,20 @@ public void validate() { key = WebAuthnCodecs.importCosePublicKey(cose); } catch (IOException | InvalidKeySpecException e) { throw new IllegalArgumentException( - String.format( - "Failed to decode public key: Credential ID: %s COSE: %s", - credential.getCredentialId().getBase64Url(), cose.getBase64Url()), - e); + String.format( + "Failed to decode public key: Credential ID: %s COSE: %s", + credential.getCredentialId().getBase64Url(), cose.getBase64Url()), + e); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } final COSEAlgorithmIdentifier alg = - COSEAlgorithmIdentifier.fromPublicKey(cose) - .orElseThrow( - () -> - new IllegalArgumentException( - String.format("Failed to decode \"alg\" from COSE key: %s", cose))); + COSEAlgorithmIdentifier.fromPublicKey(cose) + .orElseThrow( + () -> + new IllegalArgumentException( + String.format("Failed to decode \"alg\" from COSE key: %s", cose))); if (!Crypto.verifySignature(key, signedBytes(), response.getResponse().getSignature(), alg)) { throw new IllegalArgumentException("Invalid assertion signature."); @@ -614,7 +617,7 @@ public Step21(Optional username, C credential) { this.username = username; this.credential = credential; this.assertionSignatureCount = - response.getResponse().getParsedAuthenticatorData().getSignatureCounter(); + response.getResponse().getParsedAuthenticatorData().getSignatureCounter(); this.storedSignatureCountBefore = credential.getSignatureCount(); } @@ -622,13 +625,13 @@ public Step21(Optional username, C credential) { public void validate() throws InvalidSignatureCountException { if (validateSignatureCounter && !signatureCounterValid()) { throw new InvalidSignatureCountException( - response.getId(), storedSignatureCountBefore + 1, assertionSignatureCount); + response.getId(), storedSignatureCountBefore + 1, assertionSignatureCount); } } private boolean signatureCounterValid() { return (assertionSignatureCount == 0 && storedSignatureCountBefore == 0) - || assertionSignatureCount > storedSignatureCountBefore; + || assertionSignatureCount > storedSignatureCountBefore; } @Override @@ -657,17 +660,17 @@ public Finished nextStep() { @Override public Optional result() { return Optional.of( - new AssertionResult( - true, - response, - (RegisteredCredential) credential, - username.get(), - signatureCounterValid)); + new AssertionResult( + true, + response, + (RegisteredCredential) credential, + username.get(), + signatureCounterValid)); } public Optional> resultV2() { return Optional.of( - new AssertionResultV2(true, response, credential, signatureCounterValid)); + new AssertionResultV2(true, response, credential, signatureCounterValid)); } } } diff --git a/webauthn-server-demo/src/main/java/demo/webauthn/InMemoryRegistrationStorage.java b/webauthn-server-demo/src/main/java/demo/webauthn/InMemoryRegistrationStorage.java index 1a4add942..065a53480 100644 --- a/webauthn-server-demo/src/main/java/demo/webauthn/InMemoryRegistrationStorage.java +++ b/webauthn-server-demo/src/main/java/demo/webauthn/InMemoryRegistrationStorage.java @@ -26,30 +26,86 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -import com.yubico.webauthn.AssertionResultV2; -import com.yubico.webauthn.CredentialRepositoryV2; -import com.yubico.webauthn.UsernameRepository; +import com.google.common.collect.ImmutableSortedSet; +import com.upokecenter.cbor.CBORObject; +import com.yubico.webauthn.*; +import com.yubico.webauthn.data.AuthenticatorTransport; import com.yubico.webauthn.data.ByteArray; +import com.yubico.webauthn.data.COSEAlgorithmIdentifier; +import com.yubico.webauthn.data.UserIdentity; +import com.yubico.webauthn.data.exception.HexException; import demo.webauthn.data.CredentialRegistration; -import java.util.Collection; -import java.util.HashSet; -import java.util.NoSuchElementException; -import java.util.Optional; -import java.util.Set; +import java.time.Clock; +import java.util.*; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; + +import org.bouncycastle.util.encoders.Hex; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class InMemoryRegistrationStorage - implements CredentialRepositoryV2, UsernameRepository { + implements CredentialRepositoryV2, UsernameRepository { private final Cache> storage = - CacheBuilder.newBuilder().maximumSize(1000).expireAfterAccess(1, TimeUnit.DAYS).build(); + CacheBuilder.newBuilder().maximumSize(1000).expireAfterAccess(1, TimeUnit.DAYS).build(); private static final Logger logger = LoggerFactory.getLogger(InMemoryRegistrationStorage.class); + public InMemoryRegistrationStorage() { + ArrayList cards = new ArrayList(); + + try { + + CardData card = parseCard("", "", ""); + cards.add(card); + + + SortedSet transports = new TreeSet<>(AuthenticatorTransport::compareTo); + transports.add(AuthenticatorTransport.NFC); + + + cards.forEach(c -> { + int i = cards.indexOf(c); + + String name ="baz"+i; + + + + RegisteredCredential credential = RegisteredCredential.builder() + .credentialId(c.getCredentialId()) + .userHandle(c.getUserId()) + .publicKeyCose(c.getPbk()) + .build(); + + + UserIdentity userIdentity = UserIdentity.builder() + .name(name) + .displayName(name) + .id(c.userId) + .build(); + + CredentialRegistration reg = + CredentialRegistration.builder() + .userIdentity(userIdentity) + .credentialNickname(Optional.of(name)) + .registrationTime(Clock.systemDefaultZone().instant()) + .credential(credential) + .transports(transports) + .attestationMetadata(Optional.empty()) + .build(); + + addRegistrationByUsername(name, reg); + }); + + } catch (HexException e) { + throw new RuntimeException(e); + } + + + } + //////////////////////////////////////////////////////////////////////////////// // The following methods are required by the CredentialRepositoryV2 interface. //////////////////////////////////////////////////////////////////////////////// @@ -59,22 +115,37 @@ public Set getCredentialDescriptorsForUserHandle(ByteArr return getRegistrationsByUserHandle(userHandle); } + private CardData parseCard(String userId, String pbk, String credentialId) throws HexException { + ByteArray uid = new ByteArray(Hex.decode(userId.replaceAll("\\s", ""))); + ByteArray publicKey = new ByteArray(Hex.decode(pbk.replaceAll("\\s", ""))); + ByteArray credId = new ByteArray(Base64.getUrlDecoder().decode(credentialId)); + + return new CardData(uid, publicKey, credId); + } + @Override public Optional lookup(ByteArray credentialId, ByteArray userHandle) { + System.out.println("Cred: " + credentialId); +// System.out.println("Cred 2: " + new ByteArray(Base64.getUrlDecoder().decode(credentialId.getBytes()))); + System.out.println("User handle: " + userHandle); Optional registrationMaybe = - storage.asMap().values().stream() - .flatMap(Collection::stream) - .filter( - credReg -> - credentialId.equals(credReg.getCredential().getCredentialId()) - && userHandle.equals(credReg.getUserHandle())) - .findAny(); + storage.asMap().values().stream() + .flatMap(Collection::stream) + .filter( + credReg -> { + System.out.println("Cred db: " + credReg.getCredential().getCredentialId()); + return java.util.Arrays.equals(credReg.getCredential().getCredentialId().getBytes(), credentialId.getBytes()); +// credentialId.equals(credReg.getCredential().getCredentialId())) + //&& userHandle.equals(credReg.getUserHandle())) + } + ) + .findAny(); logger.debug( - "lookup credential ID: {}, user handle: {}; result: {}", - credentialId, - userHandle, - registrationMaybe); + "lookup credential ID: {}, user handle: {}; result: {}", + credentialId, + userHandle, + registrationMaybe); return registrationMaybe; } @@ -82,8 +153,8 @@ public Optional lookup(ByteArray credentialId, ByteArray @Override public boolean credentialIdExists(ByteArray credentialId) { return storage.asMap().values().stream() - .flatMap(Collection::stream) - .anyMatch(reg -> reg.getCredential().getCredentialId().equals(credentialId)); + .flatMap(Collection::stream) + .anyMatch(reg -> reg.getCredential().getCredentialId().equals(credentialId)); } //////////////////////////////////////////////////////////////////////////////// @@ -93,15 +164,15 @@ public boolean credentialIdExists(ByteArray credentialId) { @Override public Optional getUserHandleForUsername(String username) { return getRegistrationsByUsername(username).stream() - .findAny() - .map(reg -> reg.getUserIdentity().getId()); + .findAny() + .map(reg -> reg.getUserIdentity().getId()); } @Override public Optional getUsernameForUserHandle(ByteArray userHandle) { return getRegistrationsByUserHandle(userHandle).stream() - .findAny() - .map(CredentialRegistration::getUsername); + .findAny() + .map(CredentialRegistration::getUsername); } //////////////////////////////////////////////////////////////////////////////// @@ -128,40 +199,40 @@ public Collection getRegistrationsByUsername(String user public Set getRegistrationsByUserHandle(ByteArray userHandle) { return storage.asMap().values().stream() - .flatMap(Collection::stream) - .filter( - credentialRegistration -> - userHandle.equals(credentialRegistration.getUserIdentity().getId())) - .collect(Collectors.toSet()); + .flatMap(Collection::stream) + .filter( + credentialRegistration -> + userHandle.equals(credentialRegistration.getUserIdentity().getId())) + .collect(Collectors.toSet()); } public void updateSignatureCount(AssertionResultV2 result) { CredentialRegistration registration = - getRegistrationByUsernameAndCredentialId( - result.getCredential().getUsername(), result.getCredential().getCredentialId()) - .orElseThrow( - () -> - new NoSuchElementException( - String.format( - "Credential \"%s\" is not registered to user \"%s\"", - result.getCredential().getCredentialId(), - result.getCredential().getUsername()))); + getRegistrationByUsernameAndCredentialId( + result.getCredential().getUsername(), result.getCredential().getCredentialId()) + .orElseThrow( + () -> + new NoSuchElementException( + String.format( + "Credential \"%s\" is not registered to user \"%s\"", + result.getCredential().getCredentialId(), + result.getCredential().getUsername()))); Set regs = storage.getIfPresent(result.getCredential().getUsername()); regs.remove(registration); regs.add( - registration.withCredential( - registration.getCredential().toBuilder() - .signatureCount(result.getSignatureCount()) - .build())); + registration.withCredential( + registration.getCredential().toBuilder() + .signatureCount(result.getSignatureCount()) + .build())); } public Optional getRegistrationByUsernameAndCredentialId( - String username, ByteArray id) { + String username, ByteArray id) { try { return storage.get(username, HashSet::new).stream() - .filter(credReg -> id.equals(credReg.getCredential().getCredentialId())) - .findFirst(); + .filter(credReg -> id.equals(credReg.getCredential().getCredentialId())) + .findFirst(); } catch (ExecutionException e) { logger.error("Registration lookup failed", e); throw new RuntimeException(e); @@ -169,7 +240,7 @@ public Optional getRegistrationByUsernameAndCredentialId } public boolean removeRegistrationByUsername( - String username, CredentialRegistration credentialRegistration) { + String username, CredentialRegistration credentialRegistration) { try { return storage.get(username, HashSet::new).remove(credentialRegistration); } catch (ExecutionException e) { @@ -183,7 +254,35 @@ public boolean removeAllRegistrations(String username) { return true; } + public boolean userExists(String username) { return !getRegistrationsByUsername(username).isEmpty(); } + + class CardData { + + private final ByteArray userId; + private final ByteArray pbk; + private final ByteArray credentialId; + + public CardData(ByteArray userId, ByteArray pbk, ByteArray credentialId) { + this.userId = userId; + this.pbk = pbk; + this.credentialId = credentialId; + } + + public ByteArray getCredentialId() { + return credentialId; + } + + public ByteArray getUserId() { + return userId; + } + + public ByteArray getPbk() { + return pbk; + } + } } + + diff --git a/webauthn-server-demo/src/main/java/demo/webauthn/WebAuthnRestResource.java b/webauthn-server-demo/src/main/java/demo/webauthn/WebAuthnRestResource.java index 6d451ee9f..d4eab747f 100644 --- a/webauthn-server-demo/src/main/java/demo/webauthn/WebAuthnRestResource.java +++ b/webauthn-server-demo/src/main/java/demo/webauthn/WebAuthnRestResource.java @@ -84,7 +84,7 @@ public class WebAuthnRestResource { private final JsonNodeFactory jsonFactory = JsonNodeFactory.instance; public WebAuthnRestResource() - throws InvalidAppIdException, + throws InvalidAppIdException, CertificateException, CertPathValidatorException, InvalidAlgorithmParameterException, @@ -122,7 +122,7 @@ public Index() throws MalformedURLException { authenticate = uriInfo.getAbsolutePathBuilder().path("authenticate").build().toURL(); deleteAccount = uriInfo.getAbsolutePathBuilder().path("delete-account").build().toURL(); deregister = - uriInfo.getAbsolutePathBuilder().path("action").path("deregister").build().toURL(); + uriInfo.getAbsolutePathBuilder().path("action").path("deregister").build().toURL(); register = uriInfo.getAbsolutePathBuilder().path("register").build().toURL(); } } @@ -170,39 +170,39 @@ private StartRegistrationActions() throws MalformedURLException {} @Path("register") @POST public Response startRegistration( - @NonNull @FormParam("username") String username, - @NonNull @FormParam("displayName") String displayName, - @FormParam("credentialNickname") String credentialNickname, - @FormParam("requireResidentKey") @DefaultValue("false") boolean requireResidentKey, - @FormParam("sessionToken") String sessionTokenBase64) - throws MalformedURLException, ExecutionException { + @NonNull @FormParam("username") String username, + @NonNull @FormParam("displayName") String displayName, + @FormParam("credentialNickname") String credentialNickname, + @FormParam("requireResidentKey") @DefaultValue("false") boolean requireResidentKey, + @FormParam("sessionToken") String sessionTokenBase64) + throws MalformedURLException, ExecutionException { logger.trace( - "startRegistration username: {}, displayName: {}, credentialNickname: {}, requireResidentKey: {}", - username, - displayName, - credentialNickname, - requireResidentKey); - Either result = - server.startRegistration( + "startRegistration username: {}, displayName: {}, credentialNickname: {}, requireResidentKey: {}", username, displayName, - Optional.ofNullable(credentialNickname), - requireResidentKey - ? ResidentKeyRequirement.REQUIRED - : ResidentKeyRequirement.DISCOURAGED, - Optional.ofNullable(sessionTokenBase64) - .map( - base64 -> { - try { - return ByteArray.fromBase64Url(base64); - } catch (Base64UrlException e) { - throw new RuntimeException(e); - } - })); + credentialNickname, + requireResidentKey); + Either result = + server.startRegistration( + username, + displayName, + Optional.ofNullable(credentialNickname), + requireResidentKey + ? ResidentKeyRequirement.REQUIRED + : ResidentKeyRequirement.DISCOURAGED, + Optional.ofNullable(sessionTokenBase64) + .map( + base64 -> { + try { + return ByteArray.fromBase64Url(base64); + } catch (Base64UrlException e) { + throw new RuntimeException(e); + } + })); if (result.isRight()) { return startResponse( - "startRegistration", new StartRegistrationResponse(result.right().get())); + "startRegistration", new StartRegistrationResponse(result.right().get())); } else { return messagesJson(Response.status(Status.BAD_REQUEST), result.left().get()); } @@ -213,12 +213,12 @@ public Response startRegistration( public Response finishRegistration(@NonNull String responseJson) { logger.trace("finishRegistration responseJson: {}", responseJson); Either, WebAuthnServer.SuccessfulRegistrationResult> result = - server.finishRegistration(responseJson); + server.finishRegistration(responseJson); return finishResponse( - result, - "Attestation verification failed; further error message(s) were unfortunately lost to an internal server error.", - "finishRegistration", - responseJson); + result, + "Attestation verification failed; further error message(s) were unfortunately lost to an internal server error.", + "finishRegistration", + responseJson); } private final class StartAuthenticationResponse { @@ -227,7 +227,7 @@ private final class StartAuthenticationResponse { public final StartAuthenticationActions actions = new StartAuthenticationActions(); private StartAuthenticationResponse(AssertionRequestWrapper request) - throws MalformedURLException { + throws MalformedURLException { this.request = request; } } @@ -241,14 +241,15 @@ private StartAuthenticationActions() throws MalformedURLException {} @Consumes("application/x-www-form-urlencoded") @Path("authenticate") @POST - public Response startAuthentication(@FormParam("username") String username) - throws MalformedURLException { + public Response startAuthentication(@FormParam("username") String username2) + throws MalformedURLException { + String username = null; logger.trace("startAuthentication username: {}", username); Either, AssertionRequestWrapper> request = - server.startAuthentication(Optional.ofNullable(username)); + server.startAuthentication(Optional.ofNullable(username)); if (request.isRight()) { return startResponse( - "startAuthentication", new StartAuthenticationResponse(request.right().get())); + "startAuthentication", new StartAuthenticationResponse(request.right().get())); } else { return messagesJson(Response.status(Status.BAD_REQUEST), request.left().get()); } @@ -260,44 +261,44 @@ public Response finishAuthentication(@NonNull String responseJson) { logger.trace("finishAuthentication responseJson: {}", responseJson); Either, WebAuthnServer.SuccessfulAuthenticationResult> result = - server.finishAuthentication(responseJson); + server.finishAuthentication(responseJson); return finishResponse( - result, - "Authentication verification failed; further error message(s) were unfortunately lost to an internal server error.", - "finishAuthentication", - responseJson); + result, + "Authentication verification failed; further error message(s) were unfortunately lost to an internal server error.", + "finishAuthentication", + responseJson); } @Path("action/deregister") @POST public Response deregisterCredential( - @NonNull @FormParam("sessionToken") String sessionTokenBase64, - @NonNull @FormParam("credentialId") String credentialIdBase64) - throws MalformedURLException, Base64UrlException { + @NonNull @FormParam("sessionToken") String sessionTokenBase64, + @NonNull @FormParam("credentialId") String credentialIdBase64) + throws MalformedURLException, Base64UrlException { logger.trace( - "deregisterCredential sesion: {}, credentialId: {}", - sessionTokenBase64, - credentialIdBase64); + "deregisterCredential sesion: {}, credentialId: {}", + sessionTokenBase64, + credentialIdBase64); final ByteArray credentialId; try { credentialId = ByteArray.fromBase64Url(credentialIdBase64); } catch (Base64UrlException e) { return messagesJson( - Response.status(Status.BAD_REQUEST), - "Credential ID is not valid Base64Url data: " + credentialIdBase64); + Response.status(Status.BAD_REQUEST), + "Credential ID is not valid Base64Url data: " + credentialIdBase64); } Either, DeregisterCredentialResult> result = - server.deregisterCredential(ByteArray.fromBase64Url(sessionTokenBase64), credentialId); + server.deregisterCredential(ByteArray.fromBase64Url(sessionTokenBase64), credentialId); if (result.isRight()) { return finishResponse( - result, - "Failed to deregister credential; further error message(s) were unfortunately lost to an internal server error.", - "deregisterCredential", - ""); + result, + "Failed to deregister credential; further error message(s) were unfortunately lost to an internal server error.", + "deregisterCredential", + ""); } else { return messagesJson(Response.status(Status.BAD_REQUEST), result.left().get()); } @@ -309,12 +310,12 @@ public Response deleteAccount(@NonNull @FormParam("username") String username) { logger.trace("deleteAccount username: {}", username); Either, JsonNode> result = - server.deleteAccount( - username, - () -> - ((ObjectNode) - jsonFactory.objectNode().set("success", jsonFactory.booleanNode(true))) - .set("deletedAccount", jsonFactory.textNode(username))); + server.deleteAccount( + username, + () -> + ((ObjectNode) + jsonFactory.objectNode().set("success", jsonFactory.booleanNode(true))) + .set("deletedAccount", jsonFactory.textNode(username))); if (result.isRight()) { return Response.ok(result.right().get().toString()).build(); @@ -335,10 +336,10 @@ private Response startResponse(String operationName, Object request) { } private Response finishResponse( - Either, ?> result, - String jsonFailMessage, - String methodName, - String responseJson) { + Either, ?> result, + String jsonFailMessage, + String methodName, + String responseJson) { if (result.isRight()) { try { return Response.ok(writeJson(result.right().get())).build(); @@ -354,8 +355,8 @@ private Response finishResponse( private Response jsonFail() { return Response.status(Status.INTERNAL_SERVER_ERROR) - .entity("{\"messages\":[\"Failed to encode response as JSON\"]}") - .build(); + .entity("{\"messages\":[\"Failed to encode response as JSON\"]}") + .build(); } private Response messagesJson(ResponseBuilder response, String message) { @@ -366,19 +367,19 @@ private Response messagesJson(ResponseBuilder response, List messages) { logger.debug("Encoding messages as JSON: {}", messages); try { return response - .entity( - writeJson( - jsonFactory - .objectNode() - .set( - "messages", - jsonFactory - .arrayNode() - .addAll( - messages.stream() - .map(jsonFactory::textNode) - .collect(Collectors.toList()))))) - .build(); + .entity( + writeJson( + jsonFactory + .objectNode() + .set( + "messages", + jsonFactory + .arrayNode() + .addAll( + messages.stream() + .map(jsonFactory::textNode) + .collect(Collectors.toList()))))) + .build(); } catch (JsonProcessingException e) { logger.error("Failed to encode messages as JSON: {}", messages, e); return jsonFail();