diff --git a/NEWS b/NEWS
index 56f953e95..bdd4220cc 100644
--- a/NEWS
+++ b/NEWS
@@ -1,9 +1,33 @@
-== Version 0.6.0 (unreleased) ==
+== Version 0.7.0 ==
+
+=== `webauthn-server-attestation` ===
+
+* Added attestation metadata for Security Key NFC by Yubico
=== `webauthn-server-core` ===
Breaking changes:
+* Deleted parameter `RelyingParty.verifyTypeAttribute`. This was added as a
+ workaround while browser implementations were incomplete, and should never be
+ used in production.
+* Replaced field `RegisteredCredential.publicKey: PublicKey` with
+ `publicKeyCose: ByteArray`. This means the library user no longer needs to
+ parse the public key before passing it back into the library.
+* `RelyingParty.finishAssertion` now throws `InvalidSignatureCountException`
+ instead of its supertype `AssertionFailedException` when signature count
+ validation is enabled and the received signature count is invalid.
+
+New features:
+
+* New parameter `StartAssertionOptions.userVerification` which is forwarded into
+ `PublicKeyCredentialRequestOptions` by `RelyingParty.startAssertion`
+
+
+== Version 0.6.0 ==
+
+Breaking changes:
+
* Classes moved from package `com.yubico.webauthn.data` to `com.yubico.webauthn`:
** `AssertionRequest`
** `AssertionResult`
diff --git a/README b/README
index d0b0d2378..23ebfcd0e 100644
--- a/README
+++ b/README
@@ -16,11 +16,7 @@ authenticators and authenticating registered authenticators.
=== Planned breaking changes
-* Update spec version from Candidate Recommendation 2018-03-20 to Proposed
- Recommendation 2018-12-??. This will include at least:
- ** Renaming class `AttestationData` to `AttestedCredentialData`
- ** Deleting enum value `TokenBindingStatus.NOT_SUPPORTED` and constructor
- `TokenBindingInfo.notSupported()`
+None.
=== Example Usage
diff --git a/webauthn-server-attestation/src/main/resources/metadata.json b/webauthn-server-attestation/src/main/resources/metadata.json
index 35087f54a..889662828 100755
--- a/webauthn-server-attestation/src/main/resources/metadata.json
+++ b/webauthn-server-attestation/src/main/resources/metadata.json
@@ -10,6 +10,26 @@
"-----BEGIN CERTIFICATE-----\nMIIDHjCCAgagAwIBAgIEG1BT9zANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZ\ndWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAw\nMDBaGA8yMDUwMDkwNDAwMDAwMFowLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290\nIENBIFNlcmlhbCA0NTcyMDA2MzEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\nAoIBAQC/jwYuhBVlqaiYWEMsrWFisgJ+PtM91eSrpI4TK7U53mwCIawSDHy8vUmk\n5N2KAj9abvT9NP5SMS1hQi3usxoYGonXQgfO6ZXyUA9a+KAkqdFnBnlyugSeCOep\n8EdZFfsaRFtMjkwz5Gcz2Py4vIYvCdMHPtwaz0bVuzneueIEz6TnQjE63Rdt2zbw\nnebwTG5ZybeWSwbzy+BJ34ZHcUhPAY89yJQXuE0IzMZFcEBbPNRbWECRKgjq//qT\n9nmDOFVlSRCt2wiqPSzluwn+v+suQEBsUjTGMEd25tKXXTkNW21wIWbxeSyUoTXw\nLvGS6xlwQSgNpk2qXYwf8iXg7VWZAgMBAAGjQjBAMB0GA1UdDgQWBBQgIvz0bNGJ\nhjgpToksyKpP9xv9oDAPBgNVHRMECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAN\nBgkqhkiG9w0BAQsFAAOCAQEAjvjuOMDSa+JXFCLyBKsycXtBVZsJ4Ue3LbaEsPY4\nMYN/hIQ5ZM5p7EjfcnMG4CtYkNsfNHc0AhBLdq45rnT87q/6O3vUEtNMafbhU6kt\nhX7Y+9XFN9NpmYxr+ekVY5xOxi8h9JDIgoMP4VB1uS0aunL1IGqrNooL9mmFnL2k\nLVVee6/VR6C5+KSTCMCWppMuJIZII2v9o4dkoZ8Y7QRjQlLfYzd3qGtKbw7xaF1U\nsG/5xUb/Btwb2X2g4InpiB/yt/3CpQXpiWX/K4mBvUKiGn05ZsqeY1gx4g0xLBqc\nU9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw==\n-----END CERTIFICATE-----"
],
"devices": [
+ {
+ "deviceId": "1.3.6.1.4.1.41482.1.1",
+ "displayName": "Security Key NFC by Yubico",
+ "transports": 12,
+ "deviceUrl": "https://www.yubico.com/product/security-key-nfc-by-yubico/",
+ "imageUrl": "https://developers.yubico.com/U2F/Images/SKY-NFC.png",
+ "selectors": [
+ {
+ "type": "x509Extension",
+ "parameters": {
+ "key": "1.3.6.1.4.1.45724.1.1.4",
+ "value": {
+ "type": "hex",
+ "value": "6d44ba9bf6ec2e49b9300c8fe920cb73"
+ }
+ }
+ }
+ ]
+ },
+
{
"deviceId": "1.3.6.1.4.1.41482.1.1",
"displayName": "Security Key by Yubico",
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 513bc3283..b98ec55c3 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
@@ -25,14 +25,20 @@
package com.yubico.webauthn;
+import COSE.CoseException;
+import com.fasterxml.jackson.core.JsonProcessingException;
import com.yubico.internal.util.CollectionUtil;
+import com.yubico.internal.util.WebAuthnCodecs;
import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
import com.yubico.webauthn.data.ByteArray;
import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
import com.yubico.webauthn.data.CollectedClientData;
import com.yubico.webauthn.data.PublicKeyCredential;
import com.yubico.webauthn.data.UserVerificationRequirement;
+import com.yubico.webauthn.exception.InvalidSignatureCountException;
import com.yubico.webauthn.extension.appid.AppId;
+import java.io.IOException;
+import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
@@ -60,8 +66,6 @@ final class FinishAssertionSteps {
private final String rpId;
private final CredentialRepository credentialRepository;
- @Builder.Default
- private final boolean validateTypeAttribute = true;
@Builder.Default
private final boolean validateSignatureCounter = true;
@Builder.Default
@@ -71,14 +75,14 @@ public Step0 begin() {
return new Step0();
}
- public AssertionResult run() {
+ public AssertionResult run() throws InvalidSignatureCountException {
return begin().run();
}
private interface Step, B extends Step, ?>> {
B nextStep();
- void validate();
+ void validate() throws InvalidSignatureCountException;
List getPrevWarnings();
@@ -101,12 +105,12 @@ default List allWarnings() {
return CollectionUtil.immutableList(result);
}
- default B next() {
+ default B next() throws InvalidSignatureCountException {
validate();
return nextStep();
}
- default AssertionResult run() {
+ default AssertionResult run() throws InvalidSignatureCountException {
if (isFinished()) {
return result().get();
} else {
@@ -330,18 +334,10 @@ public List getWarnings() {
@Override
public void validate() {
- if (!
- CLIENT_DATA_TYPE.equals(clientData.getType())
- ) {
- final String message = String.format(
- "The \"type\" in the client data must be exactly \"%s\", was: %s", CLIENT_DATA_TYPE, clientData.getType()
- );
- if (validateTypeAttribute) {
- throw new IllegalArgumentException(message);
- } else {
- warnings.add(message);
- }
- }
+ assure(CLIENT_DATA_TYPE.equals(clientData.getType()),
+ "The \"type\" in the client data must be exactly \"%s\", was: %s",
+ CLIENT_DATA_TYPE, clientData.getType()
+ );
}
@Override
@@ -553,9 +549,29 @@ public class Step16 implements Step {
@Override
public void validate() {
+ final ByteArray cose = credential.getPublicKeyCose();
+ final PublicKey key;
+
+ try {
+ key = WebAuthnCodecs.importCoseP256PublicKey(cose);
+ } catch (CoseException | IOException e) {
+ String coseString;
+ try {
+ coseString = WebAuthnCodecs.json().writeValueAsString(cose.getBytes());
+ } catch (JsonProcessingException e2) {
+ coseString = "(Failed to write as string)";
+ }
+
+ throw new IllegalArgumentException(String.format(
+ "Failed to decode public key: Credential ID: {} COSE: {}",
+ credential.getCredentialId().getBase64Url(),
+ coseString
+ ));
+ }
+
if (!
crypto.verifySignature(
- credential.getPublicKey(),
+ key,
signedBytes(),
response.getResponse().getSignature()
)
@@ -581,13 +597,15 @@ public class Step17 implements Step {
private final List prevWarnings;
@Override
- public void validate() {
+ public void validate() throws InvalidSignatureCountException {
if (validateSignatureCounter) {
- assure(
- signatureCounterValid(),
- "Signature counter must increase. Stored value: %s, received value: %s",
- storedSignatureCountBefore(), assertionSignatureCount()
- );
+ if (!signatureCounterValid()) {
+ throw new InvalidSignatureCountException(
+ response.getId(),
+ storedSignatureCountBefore() + 1,
+ assertionSignatureCount()
+ );
+ }
}
}
diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/FinishRegistrationSteps.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/FinishRegistrationSteps.java
index bcd28b664..8d9f9f069 100644
--- a/webauthn-server-core/src/main/java/com/yubico/webauthn/FinishRegistrationSteps.java
+++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/FinishRegistrationSteps.java
@@ -68,14 +68,12 @@ final class FinishRegistrationSteps {
private final Optional callerTokenBindingId;
private final Set origins;
private final String rpId;
- private final Boolean allowUntrustedAttestation;
+ private final boolean allowUntrustedAttestation;
private final Optional metadataService;
private final CredentialRepository credentialRepository;
@Builder.Default
- private final Boolean allowUnrequestedExtensions = false;
- @Builder.Default
- private final Boolean validateTypeAttribute = true;
+ private final boolean allowUnrequestedExtensions = false;
public Step1 begin() {
@@ -172,20 +170,10 @@ public class Step3 implements Step {
@Override
public void validate() {
- final String type = clientData.getType();
-
- if (!CLIENT_DATA_TYPE.equals(type)) {
- final String message = String.format(
- "The \"type\" in the client data must be exactly \"%s\", was: %s",
- CLIENT_DATA_TYPE, clientData.getType()
- );
-
- if (validateTypeAttribute) {
- throw new IllegalArgumentException(message);
- } else {
- warnings.add(message);
- }
- }
+ assure(CLIENT_DATA_TYPE.equals(clientData.getType()),
+ "The \"type\" in the client data must be exactly \"%s\", was: %s",
+ CLIENT_DATA_TYPE, clientData.getType()
+ );
}
@Override
diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/RegisteredCredential.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/RegisteredCredential.java
index b03ea7c1b..d849cb0d9 100644
--- a/webauthn-server-core/src/main/java/com/yubico/webauthn/RegisteredCredential.java
+++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/RegisteredCredential.java
@@ -24,12 +24,12 @@
package com.yubico.webauthn;
+import com.yubico.webauthn.data.AttestedCredentialData;
import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
import com.yubico.webauthn.data.AuthenticatorData;
import com.yubico.webauthn.data.ByteArray;
import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
import com.yubico.webauthn.data.UserIdentity;
-import java.security.PublicKey;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
@@ -71,17 +71,19 @@ public class RegisteredCredential {
private final ByteArray userHandle;
/**
- * The public key of the credential.
+ * The credential public key encoded in COSE_Key format, as defined in Section 7 of RFC 8152.
*
*
* This is used to verify the {@link AuthenticatorAssertionResponse#getSignature() signature} in authentication
* assertions.
*
*
+ * @see AttestedCredentialData#getCredentialPublicKey()
* @see RegistrationResult#getPublicKeyCose()
*/
@NonNull
- private final PublicKey publicKey;
+ private final ByteArray publicKeyCose;
/**
* The stored signature count of the credential.
@@ -116,8 +118,8 @@ public Step3 userHandle(ByteArray userHandle) {
}
}
public class Step3 {
- public RegisteredCredentialBuilder publicKey(PublicKey publicKey) {
- return builder.publicKey(publicKey);
+ public RegisteredCredentialBuilder publicKeyCose(ByteArray publicKeyCose) {
+ return builder.publicKeyCose(publicKeyCose);
}
}
}
diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/RegistrationResult.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/RegistrationResult.java
index f2001b909..dea59b9d2 100644
--- a/webauthn-server-core/src/main/java/com/yubico/webauthn/RegistrationResult.java
+++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/RegistrationResult.java
@@ -87,7 +87,7 @@ public class RegistrationResult {
* signatures.
*
*
- * @see RegisteredCredential#getPublicKey()
+ * @see RegisteredCredential#getPublicKeyCose()
*/
@NonNull
private final ByteArray publicKeyCose;
diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/RelyingParty.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/RelyingParty.java
index 6cde9c750..1c1c0e438 100644
--- a/webauthn-server-core/src/main/java/com/yubico/webauthn/RelyingParty.java
+++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/RelyingParty.java
@@ -24,6 +24,7 @@
package com.yubico.webauthn;
+import com.yubico.webauthn.AssertionRequest.AssertionRequestBuilder;
import com.yubico.webauthn.attestation.MetadataService;
import com.yubico.webauthn.data.AssertionExtensionInputs;
import com.yubico.webauthn.data.AttestationConveyancePreference;
@@ -39,8 +40,10 @@
import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions.PublicKeyCredentialCreationOptionsBuilder;
import com.yubico.webauthn.data.PublicKeyCredentialParameters;
import com.yubico.webauthn.data.PublicKeyCredentialRequestOptions;
+import com.yubico.webauthn.data.PublicKeyCredentialRequestOptions.PublicKeyCredentialRequestOptionsBuilder;
import com.yubico.webauthn.data.RelyingPartyIdentity;
import com.yubico.webauthn.exception.AssertionFailedException;
+import com.yubico.webauthn.exception.InvalidSignatureCountException;
import com.yubico.webauthn.exception.RegistrationFailedException;
import com.yubico.webauthn.extension.appid.AppId;
import java.security.SecureRandom;
@@ -227,11 +230,11 @@ public class RelyingParty {
* attestation and none attestation.
*
*
- * The default is false
.
+ * The default is true
.
*
*/
@Builder.Default
- private final boolean allowUntrustedAttestation = false;
+ private final boolean allowUntrustedAttestation = true;
/**
* If true
, {@link #finishAssertion(FinishAssertionOptions) finishAssertion} will fail if the {@link
@@ -245,19 +248,6 @@ public class RelyingParty {
@Builder.Default
private final boolean validateSignatureCounter = true;
- /**
- * If true
, {@link #finishRegistration(FinishRegistrationOptions) finishRegistration} and {@link
- * #finishAssertion(FinishAssertionOptions) finishAssertion} will fail if the {@link CollectedClientData#getType()}
- * type attribute} in the client data is not exactly equal to "webauthn.create"
or
- * "webauthn.get"
, respectively.
- *
- *
- * The default is true
.
- *
- */
- @Builder.Default
- private final boolean validateTypeAttribute = true;
-
private RelyingParty(
@NonNull RelyingPartyIdentity identity,
Set origins,
@@ -267,8 +257,7 @@ private RelyingParty(
@NonNull Optional metadataService, List preferredPubkeyParams,
boolean allowUnrequestedExtensions,
boolean allowUntrustedAttestation,
- boolean validateSignatureCounter,
- boolean validateTypeAttribute
+ boolean validateSignatureCounter
) {
this.identity = identity;
this.origins = origins != null ? origins : Collections.singleton("https://" + identity.getId());
@@ -280,7 +269,6 @@ private RelyingParty(
this.allowUnrequestedExtensions = allowUnrequestedExtensions;
this.allowUntrustedAttestation = allowUntrustedAttestation;
this.validateSignatureCounter = validateSignatureCounter;
- this.validateTypeAttribute = validateTypeAttribute;
}
private static ByteArray generateChallenge() {
@@ -335,32 +323,44 @@ FinishRegistrationSteps _finishRegistration(
.allowUnrequestedExtensions(allowUnrequestedExtensions)
.allowUntrustedAttestation(allowUntrustedAttestation)
.metadataService(metadataService)
- .validateTypeAttribute(validateTypeAttribute)
.build();
}
public AssertionRequest startAssertion(StartAssertionOptions startAssertionOptions) {
+ PublicKeyCredentialRequestOptionsBuilder pkcro = PublicKeyCredentialRequestOptions.builder()
+ .challenge(generateChallenge())
+ .rpId(Optional.of(identity.getId()))
+ .allowCredentials(
+ startAssertionOptions.getUsername().map(un ->
+ new ArrayList<>(credentialRepository.getCredentialIdsForUsername(un)))
+ )
+ .extensions(
+ startAssertionOptions.getExtensions()
+ .toBuilder()
+ .appid(appId)
+ .build()
+ )
+ ;
+
+ startAssertionOptions.getUserVerification().ifPresent(pkcro::userVerification);
+
return AssertionRequest.builder()
.publicKeyCredentialRequestOptions(
- PublicKeyCredentialRequestOptions.builder()
- .challenge(generateChallenge())
- .rpId(Optional.of(identity.getId()))
- .allowCredentials(
- startAssertionOptions.getUsername().map(un ->
- new ArrayList<>(credentialRepository.getCredentialIdsForUsername(un)))
- )
- .extensions(
- startAssertionOptions.getExtensions()
- .toBuilder()
- .appid(appId)
- .build()
- )
- .build()
+ pkcro.build()
)
.username(startAssertionOptions.getUsername())
.build();
}
+ /**
+ * @throws InvalidSignatureCountException
+ * if {@link RelyingPartyBuilder#validateSignatureCounter(boolean) validateSignatureCounter} is
+ * true
, the {@link AuthenticatorData#getSignatureCounter() signature count} in the response is
+ * less than or equal to the {@link RegisteredCredential#getSignatureCount() stored signature count}, and at
+ * least one of the signature count values is nonzero.
+ * @throws AssertionFailedException
+ * if validation fails for any other reason.
+ */
public AssertionResult finishAssertion(FinishAssertionOptions finishAssertionOptions) throws AssertionFailedException {
try {
return _finishAssertion(finishAssertionOptions.getRequest(), finishAssertionOptions.getResponse(), finishAssertionOptions.getCallerTokenBindingId()).run();
@@ -389,7 +389,6 @@ FinishAssertionSteps _finishAssertion(
.credentialRepository(credentialRepository)
.allowUnrequestedExtensions(allowUnrequestedExtensions)
.validateSignatureCounter(validateSignatureCounter)
- .validateTypeAttribute(validateTypeAttribute)
.build();
}
diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/StartAssertionOptions.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/StartAssertionOptions.java
index 3d3c6ee81..4840df357 100644
--- a/webauthn-server-core/src/main/java/com/yubico/webauthn/StartAssertionOptions.java
+++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/StartAssertionOptions.java
@@ -25,6 +25,8 @@
package com.yubico.webauthn;
import com.yubico.webauthn.data.AssertionExtensionInputs;
+import com.yubico.webauthn.data.PublicKeyCredentialRequestOptions;
+import com.yubico.webauthn.data.UserVerificationRequirement;
import java.util.Optional;
import lombok.Builder;
import lombok.NonNull;
@@ -44,6 +46,10 @@ public class StartAssertionOptions {
* deferred until after receiving the response from the client.
*
*
+ *
+ * The default is empty (absent).
+ *
+ *
* @see Client-side-resident
* credential
*/
@@ -57,10 +63,23 @@ public class StartAssertionOptions {
* If {@link RelyingParty#getAppId()} is set, {@link RelyingParty#startAssertion(StartAssertionOptions)} will
* overwrite any {@link AssertionExtensionInputs#getAppid() appId} extension input set herein.
*
+ *
+ *
+ * The default specifies no extension inputs.
+ *
*/
@NonNull
@Builder.Default
private final AssertionExtensionInputs extensions = AssertionExtensionInputs.builder().build();
+ /**
+ * The value for {@link PublicKeyCredentialRequestOptions#getUserVerification()} for this authentication operation.
+ *
+ * The default is {@link UserVerificationRequirement#PREFERRED}.
+ *
+ */
+ @NonNull
+ @Builder.Default
+ private final Optional userVerification = Optional.empty();
}
diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/exception/AssertionFailedException.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/exception/AssertionFailedException.java
index 24812d80b..180d70f68 100644
--- a/webauthn-server-core/src/main/java/com/yubico/webauthn/exception/AssertionFailedException.java
+++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/exception/AssertionFailedException.java
@@ -24,10 +24,14 @@
package com.yubico.webauthn.exception;
-public final class AssertionFailedException extends Exception {
+public class AssertionFailedException extends Exception {
public AssertionFailedException(IllegalArgumentException e) {
super(e);
}
+ public AssertionFailedException(String message) {
+ super(message);
+ }
+
}
diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/exception/InvalidSignatureCountException.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/exception/InvalidSignatureCountException.java
new file mode 100644
index 000000000..4d997ef48
--- /dev/null
+++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/exception/InvalidSignatureCountException.java
@@ -0,0 +1,24 @@
+package com.yubico.webauthn.exception;
+
+import com.yubico.webauthn.data.ByteArray;
+import lombok.Value;
+
+@Value
+public class InvalidSignatureCountException extends AssertionFailedException {
+
+ private final ByteArray credentialId;
+ private final long expectedMinimum;
+ private final long received;
+
+ public InvalidSignatureCountException(ByteArray credentialId, long expectedMinimum, long received) {
+ super(String.format(
+ "Signature counter must increase. Expected minimum: %s, received value: %s",
+ expectedMinimum,
+ received
+ ));
+ this.credentialId = credentialId;
+ this.expectedMinimum = expectedMinimum;
+ this.received = received;
+ }
+
+}
diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/RegistrationTestData.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/RegistrationTestData.scala
index 75fa071b8..243e8212a 100644
--- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/RegistrationTestData.scala
+++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/RegistrationTestData.scala
@@ -58,9 +58,9 @@ object RegistrationTestDataGenerator extends App {
caCert: Option[X509Certificate]
): Unit = {
for { caCert <- caCert } {
- println(s"""attestationCaCert = Some(CertificateParser.parseDer(BinaryUtil.fromHex("${BinaryUtil.toHex(caCert.getEncoded)}").get.toArray)),""")
+ println(s"""attestationCaCert = Some(CertificateParser.parseDer(BinaryUtil.fromHex("${BinaryUtil.toHex(caCert.getEncoded)}"))),""")
}
- println(s"""attestationObject = BinaryUtil.fromHex("${credential.getResponse.getAttestationObject.getHex}").get,
+ println(s"""attestationObject = new ByteArray(BinaryUtil.fromHex("${credential.getResponse.getAttestationObject.getHex}")),
|clientDataJson = \"\"\"${new String(credential.getResponse.getClientDataJSON.getBytes, "UTF-8")}\"\"\"
|
|
@@ -97,49 +97,50 @@ object RegistrationTestData {
object FidoU2f {
val BasicAttestation: RegistrationTestData = new RegistrationTestData(
- attestationCaCert = Some(CertificateParser.parseDer(BinaryUtil.fromHex("308201d83082017da00302010202020539300a06082a8648ce3d040302306a3126302406035504030c1d59756269636f20576562417574686e20756e6974207465737473204341310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b3009060355040613025345301e170d3138303930363137343230305a170d3138303930363137343230305a306a3126302406035504030c1d59756269636f20576562417574686e20756e6974207465737473204341310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b30090603550406130253453059301306072a8648ce3d020106082a8648ce3d03010703420004cfe9bbc171191c5d3f5237d9ccea472297c97372be0e6559ea4f229f799019889d0ac8a94b21b03db7726486e9ca7df9c913cd4597550aff452004371551e42ca3133011300f0603551d130101ff040530030101ff300a06082a8648ce3d04030203490030460221008c6f70a3f00ce2ea5936cccedfc39edcda300fdf0a2b218be5f1e78bcd1c09c3022100ffb9a8e3f91362df6289b5f7376fd626d7a2c7fe94bb9e5fefb42ea8a1d4cd33"))),
- attestationObject = ByteArray.fromHex("bf68617574684461746158a449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97634100000539000102030405060708090a0b0c0d0e0f002088e3863ea71f7a8bd90177b64528822945ca4b9a5e3253c37e4ee26fb43525f4a52258200e4db7c79890b728f8d41f28f5da03a743f547d7fd21da628f8bf9841befc1ec03260102215820ebb3465d68d477a6da35516218e46d2788c9d86a2e59fd8a5b4082c0d88d7453200163666d74686669646f2d7532666761747453746d74bf637835639f5901e9308201e53082018ca00302010202020539300a06082a8648ce3d040302306a3126302406035504030c1d59756269636f20576562417574686e20756e6974207465737473204341310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b3009060355040613025345301e170d3138303930363137343230305a170d3138303930363137343230305a30673123302106035504030c1a59756269636f20576562417574686e20756e6974207465737473310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b30090603550406130253453059301306072a8648ce3d020106082a8648ce3d0301070342000427ef1b1440fd4e7161680aee8e00747e835a57881022e94fd660b90ced70bd2072c3878ec97ba3a4f1e44f0f72e5ee49d89dcdf5ca1245e649801469bc5cd80fa32530233021060b2b0601040182e51c01010404120410000102030405060708090a0b0c0d0e0f300a06082a8648ce3d04030203470030440220786cbdb9107d3abcf04e56745e39b5eb4bdd7401154e2c93cf9ca36865b7bae202205f5460e68c9d1f8e7310c1150e334f4adc2fe99de713f8ac31e63eb3395086fdff6373696758473045022100c1b359db4aeb8a43aca81cc428469f1e093fc766f478e45fda54feaa157ff02a02203022f9f1bbfe72b90bc13c2210bf626c25789c360cf8366e5b9fc114225b7319ffff"),
- clientDataJson = """{"challenge":"AAEBAgMFCA0VIjdZEGl5Yls","origin":"localhost","type":"webauthn.create","tokenBinding":{"status":"supported"}}"""
+ attestationCaCert = Some(CertificateParser.parseDer(BinaryUtil.fromHex("308201d63082017da00302010202020539300a06082a8648ce3d040302306a3126302406035504030c1d59756269636f20576562417574686e20756e6974207465737473204341310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b3009060355040613025345301e170d3138303930363137343230305a170d3138303930363137343230305a306a3126302406035504030c1d59756269636f20576562417574686e20756e6974207465737473204341310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b30090603550406130253453059301306072a8648ce3d020106082a8648ce3d030107034200048a551913988ee12c2330b4d3a503607defd0ca1eb5f44edf8a4cee2d48df692efbeeb3e9749bbddd960483b6fa930f49ee45318f0de4e014ad07b54b5d88a862a3133011300f0603551d130101ff040530030101ff300a06082a8648ce3d040302034700304402202ab9e33dd3fcaee34bfe44f370656b73ccd591bbf1a41b01ee3fb83a3b8fd83f022036b95c02cfa90b751c93612f487a3773fc2b85276de059bc972ad0a47ed3304d"))),
+ attestationObject = new ByteArray(BinaryUtil.fromHex("bf68617574684461746158a449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97634100000539000102030405060708090a0b0c0d0e0f0020e50fe8ab67d1e773463decf62cfe9a9d5928ece4fd98a013b80478301bb8e29ea5225820d06403b07cf09311ca10b2478979deaaad9c65751e749c503fe9fb935686fcae03260102215820bfa61c3ae256f6a887d2ae9b2075b5246896ba9f44a2a6874ab746acfe7db9e3200163666d74686669646f2d7532666761747453746d74bf63783563815901eb308201e73082018ca00302010202020539300a06082a8648ce3d040302306a3126302406035504030c1d59756269636f20576562417574686e20756e6974207465737473204341310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b3009060355040613025345301e170d3138303930363137343230305a170d3138303930363137343230305a30673123302106035504030c1a59756269636f20576562417574686e20756e6974207465737473310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b30090603550406130253453059301306072a8648ce3d020106082a8648ce3d030107034200040bd659232377a4f910fdcfccaec55511d00beacbdf417f49c9de938137f98df03971b3553bc11a2bd4ef5089ed290d15cc84e005443c794b13dc5e230916c591a32530233021060b2b0601040182e51c01010404120410000102030405060708090a0b0c0d0e0f300a06082a8648ce3d04030203490030460221008546464190caa7a603cd5c8dd60f30a23a9d227ca69603c1421c179092d8e4a1022100891b766c83b9def81518e354db14068d0ade9c8651927b347f4a63454b12add36373696758473045022100c88c93d88194e183f5522ec471a77f8a78d82fa7f99292f8d5f0c20cec6277d702203e289df8dd0568d9bd0b7d294fd30afcf3b264f5fb63f3163b46bb725c8fb31fffff")),
+ clientDataJson = """{"challenge":"AAEBAgMFCA0VIjdZEGl5Yls","origin":"https://localhost","type":"webauthn.create","tokenBinding":{"status":"supported"},"clientExtensions":{}}"""
) { override def regenerate() = TestAuthenticator.createBasicAttestedCredential(attestationStatementFormat = "fido-u2f") }
val SelfAttestation: RegistrationTestData = new RegistrationTestData(
- attestationObject = ByteArray.fromHex("bf68617574684461746158a449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97634100000539000102030405060708090a0b0c0d0e0f00206df43eaa938b658c53fa71eeb6e89459530e1c7243d17c5a24f98f0e9a523784a5225820c2bdd306fa7bf28dd7b5d3cff0dbc64765f5270df99afc97100927a5a714832a032601022158207216bab0f8646f5fc2b247e203e991e51873fc6ad4d582a07108cb36ba812498200163666d74686669646f2d7532666761747453746d74bf637835639f5901e5308201e130820187a00302010202020539300a06082a8648ce3d04030230673123302106035504030c1a59756269636f20576562417574686e20756e6974207465737473310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b3009060355040613025345301e170d3138303930363137343230305a170d3138303930363137343230305a30673123302106035504030c1a59756269636f20576562417574686e20756e6974207465737473310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b30090603550406130253453059301306072a8648ce3d020106082a8648ce3d030107034200047216bab0f8646f5fc2b247e203e991e51873fc6ad4d582a07108cb36ba812498c2bdd306fa7bf28dd7b5d3cff0dbc64765f5270df99afc97100927a5a714832aa3233021301f060b2b0601040182e51c0101040410000102030405060708090a0b0c0d0e0f300a06082a8648ce3d0403020348003045022100c63552850e2515899dfeda270a4c90212e99f103717d775b411153a3ce23d65e022036710be80f3914b62334beaf834cabceeedf273295d09e42472bd558767f3e9aff637369675847304502207d63e85990c2b2c1768b7fc17a46e32a90535699435ecc8b877da2678e42a73d0221009b17cd3c06dcc846a2f1bb83008ded7be4cf79a04f527a797ce1755cdad9c9e8ffff"),
- clientDataJson = """{"challenge":"AAEBAgMFCA0VIjdZEGl5Yls","origin":"localhost","type":"webauthn.create","tokenBinding":{"status":"supported"}}"""
+ attestationObject = new ByteArray(BinaryUtil.fromHex("bf68617574684461746158a449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97634100000539000102030405060708090a0b0c0d0e0f00205558386f4ed61a6c98a3fed94060fff66808947953754a0dff2aea9ae2164635a52258208d05cb87cec921d5e6fbc22c32a07fb35ed89c19a3f0a2866fcf4a248194e650032601022158202bb1c0846fca809059b41272f0c2953d733b31b50c14453b7a9855b7bfc98229200163666d74686669646f2d7532666761747453746d74bf63783563815901e7308201e330820189a00302010202020539300a06082a8648ce3d04030230673123302106035504030c1a59756269636f20576562417574686e20756e6974207465737473310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b3009060355040613025345301e170d3138303930363137343230305a170d3138303930363137343230305a30673123302106035504030c1a59756269636f20576562417574686e20756e6974207465737473310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b30090603550406130253453059301306072a8648ce3d020106082a8648ce3d030107034200042bb1c0846fca809059b41272f0c2953d733b31b50c14453b7a9855b7bfc982298d05cb87cec921d5e6fbc22c32a07fb35ed89c19a3f0a2866fcf4a248194e650a32530233021060b2b0601040182e51c01010404120410000102030405060708090a0b0c0d0e0f300a06082a8648ce3d0403020348003045022100a91c5499a6518bc59648bde7e7467488736e1ae82b5eb85c14957a0f82d23dfc02205a4b9963f88dbabaa0fa298eae6f0876b9f5e65650c4bd29f1f3f7eeb1312c24637369675847304502205af7085152ec65cc5ee097c5890316e6cac286379c32925a969ab414b013aa59022100b9b9d56cf4314e10c13caa57fb1fb0a01e87ffdec623c62637fddf56a8c4c62cffff")),
+ clientDataJson = """{"challenge":"AAEBAgMFCA0VIjdZEGl5Yls","origin":"https://localhost","type":"webauthn.create","tokenBinding":{"status":"supported"},"clientExtensions":{}}"""
) { override def regenerate() = TestAuthenticator.createSelfAttestedCredential(attestationStatementFormat = "fido-u2f") }
}
object NoneAttestation {
val Default = new RegistrationTestData(
- attestationObject = ByteArray.fromHex("bf68617574684461746158a449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97634100000539000102030405060708090a0b0c0d0e0f0020f7c49900dc6073fb5fd1c10ebcd53904c581f7fc989080dc87a30e5701488a79a5225820d5fe91bb518f13b9c484a9d81a14fc400cc1a2ce0151bd415f3190e620a30a85032601022158205e3057e50fb81f4f6a887c94fb9148d2f7e4c688d9c9a54d89465f5618c6cf0a200163666d74646e6f6e656761747453746d74bfffff"),
- clientDataJson = """{"challenge":"AAEBAgMFCA0VIjdZEGl5Yls","origin":"localhost","type":"webauthn.create","tokenBinding":{"status":"supported"}}"""
+ attestationObject = new ByteArray(BinaryUtil.fromHex("bf68617574684461746158a449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97634100000539000102030405060708090a0b0c0d0e0f002082e7622c8c35a5786e66815f44a82b954628df497361169e77af23bb9bea1b69a5225820ae947a15818d883351ac00b957ad794c4b0206e2df34ec7b52969016a215800e03260102215820763f33278817151fad81d172493b8826c3a736cb1acf884e38c26fbe65c2438a200163666d74646e6f6e656761747453746d74bfffff")),
+ clientDataJson = """{"challenge":"AAEBAgMFCA0VIjdZEGl5Yls","origin":"https://localhost","type":"webauthn.create","tokenBinding":{"status":"supported"},"clientExtensions":{}}"""
) { override def regenerate() = TestAuthenticator.createUnattestedCredential() }
}
object Packed {
val BasicAttestation: RegistrationTestData = new RegistrationTestData(
- attestationCaCert = Some(CertificateParser.parseDer(BinaryUtil.fromHex("308201d83082017da00302010202020539300a06082a8648ce3d040302306a3126302406035504030c1d59756269636f20576562417574686e20756e6974207465737473204341310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b3009060355040613025345301e170d3138303930363137343230305a170d3138303930363137343230305a306a3126302406035504030c1d59756269636f20576562417574686e20756e6974207465737473204341310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b30090603550406130253453059301306072a8648ce3d020106082a8648ce3d030107034200040394c5fa42d59c832d6c8e4783eaac8971fdac20105f046a773da02f4d460a9be64df86e87a45124d21c30bf3361ae96840a598a2f2b019ec25b3f8004623b79a3133011300f0603551d130101ff040530030101ff300a06082a8648ce3d0403020349003046022100af8544c2c6b656513b5074ba1fc90d0b78b7710501e75fc3f3531d15103f3843022100b8ef7dac90e9168e012be191e0c210de79aec3a4584f0de2f8c6a3e645658cc0"))),
- attestationObject = ByteArray.fromHex("bf68617574684461746158a449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97634100000539000102030405060708090a0b0c0d0e0f0020cd51f2253872fad05f2b5307b0df89d47cadb2ace87e518916545900ad5f4eada522582089d3e7e7f696e009be5471fd85378e0072bf61fd5b33de6a6b6880256793148703260102215820925956d46552e8366d2acd2a57a2ed17b3e5f663cd194c56e6554c2f2b8f26de200163666d74667061636b65646761747453746d74bf637369675846304402201c564842e0415dcb4fe08874ea4e05e0f48309cc8ded393c5455e6fe2bd1375302202bb4762e4a4c49fd82f330c4cd4105be16175ee8ae3608989835e3ab50d643a2637835639f5901ea308201e63082018ca00302010202020539300a06082a8648ce3d040302306a3126302406035504030c1d59756269636f20576562417574686e20756e6974207465737473204341310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b3009060355040613025345301e170d3138303930363137343230305a170d3138303930363137343230305a30673123302106035504030c1a59756269636f20576562417574686e20756e6974207465737473310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b30090603550406130253453059301306072a8648ce3d020106082a8648ce3d030107034200046b06cc7af7fe4b180a251bef098174dabb011f6e72cea398fb6124aed056e7108c01efdf5aacf71e66c42110cf038d36c92faafba32e5ce97a451117341d1fe8a32530233021060b2b0601040182e51c01010404120410000102030405060708090a0b0c0d0e0f300a06082a8648ce3d0403020348003045022100d64bf76adeaa2d44bcc7a1f68da98830382ef3a6b6493205c57d4007b151fcf8022012f4568c72be6c76e66c524b8e0cefe529bb385732bffe43672d2709448e3220ffffff"),
- clientDataJson = """{"challenge":"AAEBAgMFCA0VIjdZEGl5Yls","origin":"localhost","type":"webauthn.create","tokenBinding":{"status":"supported"}}"""
+ attestationCaCert = Some(CertificateParser.parseDer(BinaryUtil.fromHex("308201d63082017da00302010202020539300a06082a8648ce3d040302306a3126302406035504030c1d59756269636f20576562417574686e20756e6974207465737473204341310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b3009060355040613025345301e170d3138303930363137343230305a170d3138303930363137343230305a306a3126302406035504030c1d59756269636f20576562417574686e20756e6974207465737473204341310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b30090603550406130253453059301306072a8648ce3d020106082a8648ce3d0301070342000474d182bbb3aaab864ac3e0c7e93d3f3eb65299cf36ed0ea7795d4da0246f517bd3d6ef2a8a359246ea78734f6bd71c4bd6394e499e658815415edc0d14b43735a3133011300f0603551d130101ff040530030101ff300a06082a8648ce3d040302034700304402206429f7885dc57981fba4e12a1e4e415cb27c0228dc824231b123bc7cbb3ee0ae02202b89913cacff206d9ea7d6246c6b5fa8d7949bc157ad364d9f754b783d660109"))),
+ attestationObject = new ByteArray(BinaryUtil.fromHex("bf68617574684461746158a449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97634100000539000102030405060708090a0b0c0d0e0f00203479233993ce33d113bca341cbff7b17f8da6477f4f052067eee7431a741cf33a5225820930d76c61326ca11ebac918dd4374a652177739519b45e1d12484d1c815714690326010221582077c8934918d445fb70c2371f5d132693adf2ac90ba7609809e53ee24efaff148200163666d74667061636b65646761747453746d74bf63736967584730450220192f0fb8fa4488bb62f1712f0cb35b2e27cecedae5c81fbb220989c851ced05a022100bcb5d4dcccd30360490bcbd843fdb1cdd5d2e5acd78665c87e46bac865b9543063783563815901ea308201e63082018ca00302010202020539300a06082a8648ce3d040302306a3126302406035504030c1d59756269636f20576562417574686e20756e6974207465737473204341310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b3009060355040613025345301e170d3138303930363137343230305a170d3138303930363137343230305a30673123302106035504030c1a59756269636f20576562417574686e20756e6974207465737473310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b30090603550406130253453059301306072a8648ce3d020106082a8648ce3d03010703420004f09bbbfa47bfaf423f143154763b25373f3d9d94af225a1a1629df5a5ff75034d23e015902e0c97dfc1ace1c2821907d8fe090b50c39aad032596b88fd6068efa32530233021060b2b0601040182e51c01010404120410000102030405060708090a0b0c0d0e0f300a06082a8648ce3d04030203480030450220186f6aba1f39dd8431f566e7993ee8928ac365f88475bfad2c783da69d93b59402210091f8b72046427284ad51ef1068b4892795d21bdcdc14e625b4ecfccb166f4172ffff")),
+ clientDataJson = """{"challenge":"AAEBAgMFCA0VIjdZEGl5Yls","origin":"https://localhost","type":"webauthn.create","tokenBinding":{"status":"supported"},"clientExtensions":{}}"""
) { override def regenerate() = TestAuthenticator.createBasicAttestedCredential(attestationStatementFormat = "packed") }
val BasicAttestationWithoutAaguidExtension: RegistrationTestData = new RegistrationTestData(
- attestationObject = ByteArray.fromHex("bf68617574684461746158a449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97634100000539000102030405060708090a0b0c0d0e0f0020c8d081ed22eb272f43c965cceed9a0598c444ba849410ffe464d4c9527cc3b1ba5225820cfae9dcf781519dcf49eac78ce98f3895329af911ec64bfd9433a19dff7e382903260102215820fa99b52c5c6392d8d96c9a46c5c382bd5483cf23413174653fa653b46e24694f200163666d74667061636b65646761747453746d74bf6373696758483046022100fae904dc12f8322dfab8df9a7f5285701df72888d90f232f3433fb384ffa557c022100cef097751b78aa2abca73340072d5069e897b157985d2548a16526d691f282c1637835639f5901c0308201bc30820162a00302010202020539300a06082a8648ce3d04030230673123302106035504030c1a59756269636f20576562417574686e20756e6974207465737473310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b3009060355040613025345301e170d3138303930363137343230305a170d3138303930363137343230305a30673123302106035504030c1a59756269636f20576562417574686e20756e6974207465737473310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b30090603550406130253453059301306072a8648ce3d020106082a8648ce3d0301070342000455aa22ee12d518248bf6fb97c6923d2d94fc39215ce872cf4f08547261c56dab099a229c09148f78f7fb705a25ede498c484122bb0e1eb8276b833c96f6de786300a06082a8648ce3d040302034800304502205c6eac1497c1520bf1a6050cbcd16ce59727ec240319874dadddc5b3f758fabc0221008358c611c21dd085dca675df9c82437d9835fc71866d1b4635653e91ba913067ffffff"),
- clientDataJson = """{"challenge":"AAEBAgMFCA0VIjdZEGl5Yls","origin":"localhost","type":"webauthn.create","tokenBinding":{"status":"supported"}}"""
+ attestationObject = new ByteArray(BinaryUtil.fromHex("bf68617574684461746158a449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97634100000539000102030405060708090a0b0c0d0e0f0020b5d7943ea57b200148e0d87b735269ee2c09108087916b0dab1aabb0f78599cda5225820619f68d30b6c4dddf73f4dbb86d4585f06d0b0d2c8978b5d351ffa2e5c060d54032601022158208c68e6bc94460133d137d0bd11eea5067512ed470f6f479f0ba699052959d822200163666d74667061636b65646761747453746d74bf637369675846304402205f52f52e3f44618945f542646a3c459e6438abe2ef036ed8daef223d164ab338022077cdb39f441957215d8b7d68c7f697d0a121bd7e17ddbc341cad2d713bbbe25d63783563815901bf308201bb30820162a00302010202020539300a06082a8648ce3d04030230673123302106035504030c1a59756269636f20576562417574686e20756e6974207465737473310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b3009060355040613025345301e170d3138303930363137343230305a170d3138303930363137343230305a30673123302106035504030c1a59756269636f20576562417574686e20756e6974207465737473310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b30090603550406130253453059301306072a8648ce3d020106082a8648ce3d03010703420004bc0bd10e9f28f94715aca7dc586100a5fb6fa442ae2038a5f6f3667d5f9a134d8e1dc2aa55a6f56dfb44b2456028d64540ec2aaba78226593fe544884f4d7c65300a06082a8648ce3d0403020347003044022028f69c5ab6cb118296305743d3781840552eaf54bd01803cb857e6b07ed77ec402201a3efb279c314b5eba4635d0c62ba3d81703b63061c5b603df80b39ac4088218ffff")),
+ clientDataJson = """{"challenge":"AAEBAgMFCA0VIjdZEGl5Yls","origin":"https://localhost","type":"webauthn.create","tokenBinding":{"status":"supported"},"clientExtensions":{}}"""
) { override def regenerate() = TestAuthenticator.createBasicAttestedCredential(attestationCertAndKey = Some(TestAuthenticator.generateAttestationCertificate(extensions = Nil)), attestationStatementFormat = "packed") }
val BasicAttestationWithWrongAaguidExtension: RegistrationTestData = new RegistrationTestData(
- attestationObject = ByteArray.fromHex("bf68617574684461746158a449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976341000005390f0e0d0c0b0a090807060504030201000020c9e56ad018fca003474751aed40576504d1222308672b09770a3ea11d5af1b7ea522582018af6924402fa6a0756cd534b0740f0a324683a03ea741e92d592ab1e2b78d5a03260102215820c68f6bd3a9fb06fcec89872b518bf31c06d691e6c2b3bb80407517aed270765d200163666d74667061636b65646761747453746d74bf6373696758473045022100d6c52fbb9785b2e4056608523781e3acf9b5beb792474f76819002032873030202204c59e70c8dde385aae4c1b1897292bd804e0f6edc1235e393e9bb9ffe927351e637835639f5901e6308201e230820189a00302010202020539300a06082a8648ce3d04030230673123302106035504030c1a59756269636f20576562417574686e20756e6974207465737473310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b3009060355040613025345301e170d3138303930363137343230305a170d3138303930363137343230305a30673123302106035504030c1a59756269636f20576562417574686e20756e6974207465737473310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b30090603550406130253453059301306072a8648ce3d020106082a8648ce3d03010703420004784204aef94ff04c0d464ee4dda4c97dca2314f151c718ce9619cc06741c4ccdbf7bb9643096c1f14cd6f51c0b5312dcc31424c73cf4396494be8d75608a5ac5a32530233021060b2b0601040182e51c01010404120410000102030405060708090a0b0c0d0e0f300a06082a8648ce3d04030203470030440220396024ca74d621b2e5c5c1350ddbfd41acde0c95e4c82353de5a7d01d3eb563502201030b24397dce0ca92a0a06ad2ebaef0a281208e6ba44806c5d62aa3b0653c97ffffff"),
- clientDataJson = """{"challenge":"AAEBAgMFCA0VIjdZEGl5Yls","origin":"localhost","type":"webauthn.create","tokenBinding":{"status":"supported"}}"""
+ attestationCaCert = Some(CertificateParser.parseDer(BinaryUtil.fromHex("308201d63082017da00302010202020539300a06082a8648ce3d040302306a3126302406035504030c1d59756269636f20576562417574686e20756e6974207465737473204341310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b3009060355040613025345301e170d3138303930363137343230305a170d3138303930363137343230305a306a3126302406035504030c1d59756269636f20576562417574686e20756e6974207465737473204341310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b30090603550406130253453059301306072a8648ce3d020106082a8648ce3d03010703420004caa58b4a5bbcca24c5e398e4653dafb882a327960ccb72963bf62fa2a1c03f82671b11f0cccb1e1c476125f04afae64b5d1f4f7a6fb5bd1abecd18eeab9d5126a3133011300f0603551d130101ff040530030101ff300a06082a8648ce3d0403020347003044022072f6ae460c82ffe89f9ad1f1bd188ba0c3b50540e02edda0a99c37c6efe2fab5022038754e4e088f0e749946975eab9017eeebb621e830ab853119aae1998e750ab2"))),
+ attestationObject = new ByteArray(BinaryUtil.fromHex("bf68617574684461746158a449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976341000005390f0e0d0c0b0a09080706050403020100002028856c20f5018aaffce3835765cc58ae6c73e37d37acde80f0aa1611602fa815a52258200348c1c7f2fd5dc658252f7865ebbde62ce968c03e9d97988612809f5abbbe7503260102215820a9342b20b06f71fd8b7b0e7ade89ab438c0d9f541edacedbf0d43f9494d06874200163666d74667061636b65646761747453746d74bf637369675846304402205b1ee9a9def2fb631423fec4c02fa132a5562ad1a32ea4b3edaf8300fd920bd1022069799c95206c10a7a50a36a14bc990a25a54c48ba62c73af55ec8c117329170b63783563815901e9308201e53082018ca00302010202020539300a06082a8648ce3d040302306a3126302406035504030c1d59756269636f20576562417574686e20756e6974207465737473204341310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b3009060355040613025345301e170d3138303930363137343230305a170d3138303930363137343230305a30673123302106035504030c1a59756269636f20576562417574686e20756e6974207465737473310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b30090603550406130253453059301306072a8648ce3d020106082a8648ce3d03010703420004ecc94d2f374915a217b9558ed6746f40ebc3de98e953742d2a7963288cbaf017506988d24b8caf1ec728008c70b749f513007106913c39828772d75b0591db03a32530233021060b2b0601040182e51c01010404120410000102030405060708090a0b0c0d0e0f300a06082a8648ce3d0403020347003044022013c65c197c02e710acca16da432659c16313a1e19f2d8a3e9d47ee22cedc57a702205373349d9a58e8d7170032a2b64dd056d24d84dded75fac0002375a27037ee73ffff")),
+ clientDataJson = """{"challenge":"AAEBAgMFCA0VIjdZEGl5Yls","origin":"https://localhost","type":"webauthn.create","tokenBinding":{"status":"supported"},"clientExtensions":{}}"""
) { override def regenerate() = TestAuthenticator.createBasicAttestedCredential(aaguid = new ByteArray(Array(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)), attestationStatementFormat = "packed") }
val SelfAttestation: RegistrationTestData = new RegistrationTestData(
- attestationObject = ByteArray.fromHex("bf68617574684461746158a449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97634100000539000102030405060708090a0b0c0d0e0f0020bd3fe2dff3d47319e56a3a31482632e879f009952d0e96ee6518a7619f017f13a522582056f7131370c5b3b6cffa4e8d7e43a82af3e3ccd3269f09708fd8867766e364e403260102215820a4af49910476085c820d376a0d510c207a0becbbcb72a3d006d71b3c3691b352200163666d74667061636b65646761747453746d74bf6373696758473045022100b128aec3a5f86c809c7401cfbb8fa3ffcbd96c7f79ef5d7d841fc98293d4d3ff02207668604dbac157e01f761459f88fe50497cfd906394ed2d6d61c2c5f91a55e3263616c6726ffff"),
- clientDataJson = """{"challenge":"AAEBAgMFCA0VIjdZEGl5Yls","origin":"localhost","type":"webauthn.create","tokenBinding":{"status":"supported"}}"""
+ attestationObject = new ByteArray(BinaryUtil.fromHex("bf68617574684461746158a449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97634100000539000102030405060708090a0b0c0d0e0f0020fa616cbe1c046d224524e773b386f9f3fd0d0fb6d4c20700023288034e48f093a52258208b02052aeec1d7cfaf1244d9b72296a6bfaf9542c132273c4be8fc01388ee8f30326010221582081906607ef7095eaa3dea2517cfc5a7c0c9768685e30ddb5865f2ada0f5cc63c200163666d74667061636b65646761747453746d74bf6373696758473045022010511b27bd566c7bcdf6e4f08ef2fe4ea20a56826b76761253bbcc31b0be1fa2022100b2659e3efc858fd4389dc48cd0651487f2e7bc4f5eba59db154bdcd0ae60c9d163616c6726ffff")),
+ clientDataJson = """{"challenge":"AAEBAgMFCA0VIjdZEGl5Yls","origin":"https://localhost","type":"webauthn.create","tokenBinding":{"status":"supported"},"clientExtensions":{}}"""
) { override def regenerate() = TestAuthenticator.createSelfAttestedCredential(attestationStatementFormat = "packed") }
val SelfAttestationWithWrongAlgValue = new RegistrationTestData(
- attestationObject = ByteArray.fromHex("bf68617574684461746158a449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97634100000539000102030405060708090a0b0c0d0e0f0020924bdf8eb58991edbae660c21ac2bf2c71a70c711b354ffabd4e59105c3db9e7a5225820d8ac44b2ebaa675781265b232a73387916181bdc13bc4dd0d9adaa7f85747d6503260102215820afdd3af235ce10ea6c777a1c1d121135d42ec72f914d7209988e272e45709095200163666d74667061636b65646761747453746d74bf6373696758473045022100a7ab74706daed3107824e529b79e067dc63f6dbce777453258592cba2e2822ff02201aa7016a3a8eed64bc9c73be41593a3895f4edb6ad421abd883e23968eb64e1463616c6727ffff"),
- clientDataJson = """{"challenge":"AAEBAgMFCA0VIjdZEGl5Yls","origin":"localhost","type":"webauthn.create","tokenBinding":{"status":"supported"}}"""
+ attestationObject = new ByteArray(BinaryUtil.fromHex("bf68617574684461746158a449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97634100000539000102030405060708090a0b0c0d0e0f00203022c626739f52e583ac292c31b80b80759d546f9956a5baf65216faae61313da52258202f752354da475fb5f6c0d35fef2ed8eea3e6dbf225c08b7fed567e813ae41402032601022158207fc7d8d3d5e8dce8bbfda0395f89f0d3c9ea0d9de1d6e62d0f0df9db7661cb9b200163666d74667061636b65646761747453746d74bf6373696758463044022078cf79efde68909ee2518b8feeb727b17a689db2e4b9d13dc3a34e9c46b9390002201e94861f46b7f19f5df5bedef08f91fb862e5eb07c23e6c3b28151917f3e5c2963616c67390100ffff")),
+ clientDataJson = """{"challenge":"AAEBAgMFCA0VIjdZEGl5Yls","origin":"https://localhost","type":"webauthn.create","tokenBinding":{"status":"supported"},"clientExtensions":{}}"""
) { override def regenerate() = TestAuthenticator.createSelfAttestedCredential(attestationStatementFormat = "packed", alg = Some(COSEAlgorithmIdentifier.RS256)) }
}
object Tpm {
diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyAssertionSpec.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyAssertionSpec.scala
index 323128043..46c7c5fa4 100644
--- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyAssertionSpec.scala
+++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyAssertionSpec.scala
@@ -28,6 +28,7 @@ import java.io.IOException
import java.nio.charset.Charset
import java.security.MessageDigest
import java.security.KeyPair
+import java.security.interfaces.ECPublicKey
import java.util.Optional
import com.fasterxml.jackson.databind.node.JsonNodeFactory
@@ -45,6 +46,7 @@ import com.yubico.webauthn.data.ByteArray
import com.yubico.webauthn.data.AssertionExtensionInputs
import com.yubico.webauthn.data.ClientAssertionExtensionOutputs
import com.yubico.webauthn.data.Generators._
+import com.yubico.webauthn.exception.InvalidSignatureCountException
import com.yubico.webauthn.extension.appid.AppId
import com.yubico.webauthn.test.Util.toStepWithUtilities
import org.junit.runner.RunWith
@@ -68,6 +70,14 @@ class RelyingPartyAssertionSpec extends FunSpec with Matchers with GeneratorDriv
private def sha256(bytes: ByteArray): ByteArray = crypto.hash(bytes)
private def sha256(data: String): ByteArray = sha256(new ByteArray(data.getBytes(Charset.forName("UTF-8"))))
+ private val emptyCredentialRepository = new CredentialRepository {
+ override def getCredentialIdsForUsername(username: String): java.util.Set[PublicKeyCredentialDescriptor] = Set.empty.asJava
+ override def getUserHandleForUsername(username: String): Optional[ByteArray] = None.asJava
+ override def getUsernameForUserHandle(userHandle: ByteArray): Optional[String] = None.asJava
+ override def lookup(credentialId: ByteArray, userHandle: ByteArray): Optional[RegisteredCredential] = None.asJava
+ override def lookupAll(credentialId: ByteArray): java.util.Set[RegisteredCredential] = Set.empty.asJava
+ }
+
private object Defaults {
val rpId = RelyingPartyIdentity.builder().id("localhost").name("Test party").build()
@@ -107,6 +117,8 @@ class RelyingPartyAssertionSpec extends FunSpec with Matchers with GeneratorDriv
else
???
+ private def getPublicKeyBytes(credentialKey: KeyPair): ByteArray = WebAuthnCodecs.ecPublicKeyToCose(credentialKey.getPublic.asInstanceOf[ECPublicKey])
+
def finishAssertion(
allowCredentials: Option[java.util.List[PublicKeyCredentialDescriptor]] = Some(List(PublicKeyCredentialDescriptor.builder().id(Defaults.credentialId).build()).asJava),
authenticatorData: ByteArray = Defaults.authenticatorData,
@@ -129,6 +141,7 @@ class RelyingPartyAssertionSpec extends FunSpec with Matchers with GeneratorDriv
validateSignatureCounter: Boolean = true
): FinishAssertionSteps = {
val clientDataJsonBytes: ByteArray = if (clientDataJson == null) null else new ByteArray(clientDataJson.getBytes("UTF-8"))
+ val credentialPublicKeyBytes = getPublicKeyBytes(credentialKey)
val request = AssertionRequest.builder()
.publicKeyCredentialRequestOptions(
@@ -166,7 +179,7 @@ class RelyingPartyAssertionSpec extends FunSpec with Matchers with GeneratorDriv
Some(RegisteredCredential.builder()
.credentialId(credId)
.userHandle(userHandleForUser)
- .publicKey(credentialKey.getPublic)
+ .publicKeyCose(credentialPublicKeyBytes)
.signatureCount(0)
.build()
)
@@ -186,6 +199,40 @@ class RelyingPartyAssertionSpec extends FunSpec with Matchers with GeneratorDriv
._finishAssertion(request, response, callerTokenBindingId.asJava)
}
+ describe("RelyingParty.startAssertion") {
+
+ describe("respects the userVerification parameter in StartAssertionOptions.") {
+
+ val default = UserVerificationRequirement.PREFERRED
+
+ it(s"If the parameter is not set, or set to empty, the default of ${default} is used.") {
+ val rp = RelyingParty.builder()
+ .identity(Defaults.rpId)
+ .credentialRepository(emptyCredentialRepository)
+ .build()
+ val request1 = rp.startAssertion(StartAssertionOptions.builder().build())
+ val request2 = rp.startAssertion(StartAssertionOptions.builder().userVerification(None.asJava).build())
+
+ request1.getPublicKeyCredentialRequestOptions.getUserVerification should equal (default)
+ request2.getPublicKeyCredentialRequestOptions.getUserVerification should equal (default)
+ }
+
+ it(s"If the parameter is set, that value is used.") {
+ val rp = RelyingParty.builder()
+ .identity(Defaults.rpId)
+ .credentialRepository(emptyCredentialRepository)
+ .build()
+
+ forAll { uv: UserVerificationRequirement =>
+ val request = rp.startAssertion(StartAssertionOptions.builder().userVerification(Some(uv).asJava).build())
+
+ request.getPublicKeyCredentialRequestOptions.getUserVerification should equal (uv)
+ }
+ }
+ }
+
+ }
+
describe("§7.2. Verifying an authentication assertion") {
describe("When verifying a given PublicKeyCredential structure (credential) and an AuthenticationExtensionsClientOutputs structure clientExtensionResults, as part of an authentication ceremony, the Relying Party MUST proceed as follows:") {
@@ -244,7 +291,7 @@ class RelyingPartyAssertionSpec extends FunSpec with Matchers with GeneratorDriv
RegisteredCredential.builder()
.credentialId(new ByteArray(Array(0, 1, 2, 3)))
.userHandle(owner.userHandle)
- .publicKey(Defaults.credentialKey.getPublic)
+ .publicKeyCose(getPublicKeyBytes(Defaults.credentialKey))
.signatureCount(0)
.build()
).asJava
@@ -283,13 +330,7 @@ class RelyingPartyAssertionSpec extends FunSpec with Matchers with GeneratorDriv
describe("3. Using credential’s id attribute (or the corresponding rawId, if base64url encoding is inappropriate for your use case), look up the corresponding credential public key.") {
it("Fails if the credential ID is unknown.") {
val steps = finishAssertion(
- credentialRepository = Some(new CredentialRepository {
- override def lookup(id: ByteArray, uh: ByteArray) = None.asJava
- override def lookupAll(id: ByteArray) = Set.empty.asJava
- override def getCredentialIdsForUsername(username: String) = ???
- override def getUserHandleForUsername(username: String): Optional[ByteArray] = ???
- override def getUsernameForUserHandle(userHandle: ByteArray): Optional[String] = ???
- })
+ credentialRepository = Some(emptyCredentialRepository)
)
val step: steps.Step3 = new steps.Step3(Defaults.username, Defaults.userHandle, Nil.asJava)
@@ -304,7 +345,7 @@ class RelyingPartyAssertionSpec extends FunSpec with Matchers with GeneratorDriv
RegisteredCredential.builder()
.credentialId(id)
.userHandle(uh)
- .publicKey(Defaults.credentialKey.getPublic)
+ .publicKeyCose(getPublicKeyBytes(Defaults.credentialKey))
.signatureCount(0)
.build()
).asJava
@@ -316,7 +357,7 @@ class RelyingPartyAssertionSpec extends FunSpec with Matchers with GeneratorDriv
val step: FinishAssertionSteps#Step3 = steps.begin.next.next.next
step.validations shouldBe a [Success[_]]
- step.credential.getPublicKey should equal (Defaults.credentialKey.getPublic)
+ step.credential.getPublicKeyCose should equal (getPublicKeyBytes(Defaults.credentialKey))
step.tryNext shouldBe a [Success[_]]
}
}
@@ -763,7 +804,7 @@ class RelyingPartyAssertionSpec extends FunSpec with Matchers with GeneratorDriv
describe("client extension outputs in clientExtensionResults are as expected, considering the client extension input values that were given as the extensions option in the get() call. In particular, any extension identifier values in the clientExtensionResults MUST be also be present as extension identifier values in the extensions member of options, i.e., no extensions are present that were not requested. In the general case, the meaning of \"are as expected\" is specific to the Relying Party and which extensions are in use.") {
it("Fails if clientExtensionResults is not a subset of the extensions requested by the Relying Party.") {
- forAll(unrequestedAssertionExtensions) { case (extensionInputs, clientExtensionOutputs) =>
+ forAll(unrequestedAssertionExtensions, minSuccessful(5)) { case (extensionInputs, clientExtensionOutputs) =>
val steps = finishAssertion(
requestedExtensions = extensionInputs,
clientExtensionResults = clientExtensionOutputs
@@ -905,7 +946,7 @@ class RelyingPartyAssertionSpec extends FunSpec with Matchers with GeneratorDriv
RegisteredCredential.builder()
.credentialId(id)
.userHandle(uh)
- .publicKey(Defaults.credentialKey.getPublic)
+ .publicKeyCose(getPublicKeyBytes(Defaults.credentialKey))
.signatureCount(1336)
.build()
).asJava
@@ -937,7 +978,7 @@ class RelyingPartyAssertionSpec extends FunSpec with Matchers with GeneratorDriv
RegisteredCredential.builder()
.credentialId(id)
.userHandle(uh)
- .publicKey(Defaults.credentialKey.getPublic)
+ .publicKeyCose(getPublicKeyBytes(Defaults.credentialKey))
.signatureCount(1337)
.build()
).asJava
@@ -967,10 +1008,17 @@ class RelyingPartyAssertionSpec extends FunSpec with Matchers with GeneratorDriv
validateSignatureCounter = true
)
val step: FinishAssertionSteps#Step17 = steps.begin.next.next.next.next.next.next.next.next.next.next.next.next.next.next.next.next.next
+ val result = Try(step.run())
step.validations shouldBe a [Failure[_]]
- step.validations.failed.get shouldBe an [IllegalArgumentException]
+ step.validations.failed.get shouldBe an [InvalidSignatureCountException]
step.tryNext shouldBe a [Failure[_]]
+
+ result shouldBe a [Failure[_]]
+ result.failed.get shouldBe an [InvalidSignatureCountException]
+ result.failed.get.asInstanceOf[InvalidSignatureCountException].getExpectedMinimum should equal (1338)
+ result.failed.get.asInstanceOf[InvalidSignatureCountException].getReceived should equal (1337)
+ result.failed.get.asInstanceOf[InvalidSignatureCountException].getCredentialId should equal (Defaults.credentialId)
}
}
}
diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyRegistrationSpec.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyRegistrationSpec.scala
index 8537f57c3..c5b23b477 100644
--- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyRegistrationSpec.scala
+++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyRegistrationSpec.scala
@@ -51,6 +51,7 @@ import com.yubico.webauthn.data.CollectedClientData
import com.yubico.webauthn.data.ByteArray
import com.yubico.webauthn.data.RegistrationExtensionInputs
import com.yubico.webauthn.data.PublicKeyCredentialDescriptor
+import com.yubico.webauthn.data.UserIdentity
import com.yubico.webauthn.data.Generators._
import com.yubico.webauthn.test.Util.toStepWithUtilities
import javax.security.auth.x500.X500Principal
@@ -80,10 +81,10 @@ class RelyingPartyRegistrationSpec extends FunSpec with Matchers with GeneratorD
private def sha256(bytes: ByteArray): ByteArray = crypto.hash(bytes)
private val emptyCredentialRepository = new CredentialRepository {
- override def getCredentialIdsForUsername(username: String) = ???
- override def getUserHandleForUsername(username: String): Optional[ByteArray] = ???
- override def getUsernameForUserHandle(userHandleBase64: ByteArray): Optional[String] = ???
- override def lookup(credentialId: ByteArray, userHandle: ByteArray): Optional[RegisteredCredential] = ???
+ override def getCredentialIdsForUsername(username: String): java.util.Set[PublicKeyCredentialDescriptor] = Set.empty.asJava
+ override def getUserHandleForUsername(username: String): Optional[ByteArray] = None.asJava
+ override def getUsernameForUserHandle(userHandle: ByteArray): Optional[String] = None.asJava
+ override def lookup(credentialId: ByteArray, userHandle: ByteArray): Optional[RegisteredCredential] = None.asJava
override def lookupAll(credentialId: ByteArray): java.util.Set[RegisteredCredential] = Set.empty.asJava
}
@@ -108,7 +109,7 @@ class RelyingPartyRegistrationSpec extends FunSpec with Matchers with GeneratorD
.identity(rp)
.credentialRepository(credentialRepository.getOrElse(unimplementedCredentialRepository))
.preferredPubkeyParams(Nil.asJava)
- .origins(Set(rp.getId).asJava)
+ .origins(Set("https://" + rp.getId).asJava)
.allowUntrustedAttestation(allowUntrustedAttestation)
.metadataService(metadataService.asJava)
.build()
@@ -207,7 +208,7 @@ class RelyingPartyRegistrationSpec extends FunSpec with Matchers with GeneratorD
it("5. Verify that the value of C.origin matches the Relying Party's origin.") {
val steps = finishRegistration(
- testData = RegistrationTestData.FidoU2f.BasicAttestation.editClientData("origin", "root.evil")
+ testData = RegistrationTestData.FidoU2f.BasicAttestation.editClientData("origin", "https://root.evil")
)
val step: FinishRegistrationSteps#Step5 = steps.begin.next.next.next.next
@@ -1076,7 +1077,7 @@ class RelyingPartyRegistrationSpec extends FunSpec with Matchers with GeneratorD
))
CBORObject.DecodeFromBytes(new AttestationObject(testData.attestationObject).getAuthenticatorData.getAttestedCredentialData.get.getCredentialPublicKey.getBytes).get(CBORObject.FromObject(3)).AsInt64 should equal (-7)
- new AttestationObject(testData.attestationObject).getAttestationStatement.get("alg").longValue should equal (-8)
+ new AttestationObject(testData.attestationObject).getAttestationStatement.get("alg").longValue should equal (-257)
result shouldBe a [Failure[_]]
result.failed.get shouldBe an [IllegalArgumentException]
}
@@ -1451,7 +1452,7 @@ class RelyingPartyRegistrationSpec extends FunSpec with Matchers with GeneratorD
RegisteredCredential.builder()
.credentialId(id)
.userHandle(uh)
- .publicKey(WebAuthnCodecs.importCoseP256PublicKey(testData.response.getResponse.getAttestation.getAuthenticatorData.getAttestedCredentialData.get.getCredentialPublicKey))
+ .publicKeyCose(testData.response.getResponse.getAttestation.getAuthenticatorData.getAttestedCredentialData.get.getCredentialPublicKey)
.signatureCount(1337)
.build()
).asJava
@@ -1462,7 +1463,7 @@ class RelyingPartyRegistrationSpec extends FunSpec with Matchers with GeneratorD
RegisteredCredential.builder()
.credentialId(id)
.userHandle(testData.request.getUser.getId)
- .publicKey(WebAuthnCodecs.importCoseP256PublicKey(testData.response.getResponse.getAttestation.getAuthenticatorData.getAttestedCredentialData.get.getCredentialPublicKey))
+ .publicKeyCose(testData.response.getResponse.getAttestation.getAuthenticatorData.getAttestedCredentialData.get.getCredentialPublicKey)
.signatureCount(1337)
.build()
).asJava
@@ -1551,6 +1552,33 @@ class RelyingPartyRegistrationSpec extends FunSpec with Matchers with GeneratorD
Try(steps.run).failed.get shouldBe an [IllegalArgumentException]
}
+ describe("The default RelyingParty settings") {
+
+ it("accept registrations with no attestation.") {
+ val rp = RelyingParty.builder()
+ .identity(RelyingPartyIdentity.builder().id("localhost").name("Test party").build())
+ .credentialRepository(emptyCredentialRepository)
+ .build()
+
+ val request = rp.startRegistration(StartRegistrationOptions.builder()
+ .user(UserIdentity.builder().name("test").displayName("Test Testsson").id(new ByteArray(Array())).build())
+ .build()
+ ).toBuilder()
+ .challenge(RegistrationTestData.NoneAttestation.Default.clientData.getChallenge)
+ .build()
+
+ val result = rp.finishRegistration(FinishRegistrationOptions.builder()
+ .request(request)
+ .response(RegistrationTestData.NoneAttestation.Default.response)
+ .build()
+ )
+
+ result.isAttestationTrusted should be (false)
+ result.getKeyId.getId should equal (RegistrationTestData.NoneAttestation.Default.response.getId)
+ }
+
+ }
+
}
}
diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyUserIdentificationSpec.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyUserIdentificationSpec.scala
index 0e008e7af..49d47cc99 100644
--- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyUserIdentificationSpec.scala
+++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyUserIdentificationSpec.scala
@@ -25,10 +25,12 @@
package com.yubico.webauthn
import java.security.KeyPair
+import java.security.interfaces.ECPublicKey
import java.util.Optional
import com.fasterxml.jackson.databind.node.JsonNodeFactory
import com.fasterxml.jackson.databind.node.ObjectNode
+import com.yubico.internal.util.WebAuthnCodecs
import com.yubico.internal.util.scala.JavaConverters._
import com.yubico.webauthn.data.CollectedClientData
import com.yubico.webauthn.data.PublicKeyCredentialDescriptor
@@ -132,7 +134,7 @@ class RelyingPartyUserIdentificationSpec extends FunSpec with Matchers {
Some(RegisteredCredential.builder()
.credentialId(Defaults.credentialId)
.userHandle(Defaults.userHandle)
- .publicKey(Defaults.credentialKey.getPublic)
+ .publicKeyCose(WebAuthnCodecs.ecPublicKeyToCose(Defaults.credentialKey.getPublic.asInstanceOf[ECPublicKey]))
.signatureCount(0)
.build()
).asJava
diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/TestAuthenticator.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/TestAuthenticator.scala
index 2bf21dc7d..d0a667ef9 100644
--- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/TestAuthenticator.scala
+++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/TestAuthenticator.scala
@@ -76,14 +76,17 @@ import org.bouncycastle.asn1.x509.BasicConstraints
import org.bouncycastle.asn1.x509.Extension
import org.bouncycastle.cert.X509v3CertificateBuilder
import org.bouncycastle.cert.jcajce.JcaX500NameUtil
+import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey
import org.bouncycastle.jce.ECNamedCurveTable
import org.bouncycastle.jce.spec.ECNamedCurveSpec
+import org.bouncycastle.math.ec.custom.sec.SecP256R1Curve
import org.bouncycastle.openssl.PEMKeyPair
import org.bouncycastle.openssl.PEMParser
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
import scala.collection.JavaConverters._
+import scala.util.Try
object TestAuthenticator {
@@ -129,6 +132,7 @@ object TestAuthenticator {
val challenge: ByteArray = new ByteArray(Array(0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 16, 105, 121, 98, 91))
val credentialId: ByteArray = new ByteArray(((0 to 31).toVector map { _.toByte }).toArray)
val rpId = "localhost"
+ val origin = "https://" + rpId
object TokenBinding {
val status = "supported"
val id = None
@@ -158,7 +162,7 @@ object TestAuthenticator {
clientData: Option[JsonNode] = None,
clientExtensions: ClientRegistrationExtensionOutputs = ClientRegistrationExtensionOutputs.builder().build(),
credentialKeypair: Option[KeyPair] = None,
- origin: String = Defaults.rpId,
+ origin: String = Defaults.origin,
rpId: String = Defaults.rpId,
tokenBindingStatus: String = Defaults.TokenBinding.status,
tokenBindingId: Option[String] = Defaults.TokenBinding.id,
@@ -288,7 +292,7 @@ object TestAuthenticator {
clientExtensions: ClientAssertionExtensionOutputs = ClientAssertionExtensionOutputs.builder().build(),
credentialId: ByteArray = Defaults.credentialId,
credentialKey: KeyPair = Defaults.credentialKey,
- origin: String = Defaults.rpId,
+ origin: String = Defaults.origin,
rpId: String = Defaults.rpId,
tokenBindingStatus: String = Defaults.TokenBinding.status,
tokenBindingId: Option[String] = Defaults.TokenBinding.id,
@@ -434,7 +438,7 @@ object TestAuthenticator {
Map("sig" -> f.binaryNode(signature.getBytes))
++ (
selfAttestationKey match {
- case Some(key) => Map("alg" -> f.numberNode((alg getOrElse COSEAlgorithmIdentifier.valueOf(key.getAlgorithm)).getId))
+ case Some(key) => Map("alg" -> f.numberNode((alg getOrElse coseAlgorithmOfJavaKey(key)).getId))
case None => Map("x5c" -> f.arrayNode().add(f.binaryNode(cert.getEncoded)))
}
)
@@ -634,4 +638,12 @@ object TestAuthenticator {
+ "\n-----END CERTIFICATE-----\n"
)
+ def coseAlgorithmOfJavaKey(key: PrivateKey): COSEAlgorithmIdentifier =
+ Try(COSEAlgorithmIdentifier.valueOf(key.getAlgorithm)) getOrElse
+ key match {
+ case key: BCECPrivateKey => key.getParameters.getCurve match {
+ case _: SecP256R1Curve => COSEAlgorithmIdentifier.valueOf("ES256")
+ }
+ }
+
}
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 25e00fcd1..25503e28d 100644
--- a/webauthn-server-demo/src/main/java/demo/webauthn/InMemoryRegistrationStorage.java
+++ b/webauthn-server-demo/src/main/java/demo/webauthn/InMemoryRegistrationStorage.java
@@ -24,20 +24,15 @@
package demo.webauthn;
-import COSE.CoseException;
-import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.yubico.internal.util.CollectionUtil;
-import com.yubico.internal.util.WebAuthnCodecs;
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 demo.webauthn.data.CredentialRegistration;
-import java.io.IOException;
-import java.security.PublicKey;
import java.util.Collection;
import java.util.HashSet;
import java.util.NoSuchElementException;
@@ -160,33 +155,16 @@ public Optional lookup(ByteArray credentialId, ByteArray u
.findAny();
logger.debug("lookup credential ID: {}, user handle: {}; result: {}", credentialId, userHandle, registrationMaybe);
- return registrationMaybe.flatMap(registration -> {
- final ByteArray cose = registration.getRegistration().getPublicKeyCose();
- final PublicKey key;
-
- try {
- key = WebAuthnCodecs.importCoseP256PublicKey(cose);
- } catch (CoseException | IOException e) {
- String coseString;
- try {
- coseString = WebAuthnCodecs.json().writeValueAsString(cose.getBytes());
- } catch (JsonProcessingException e2) {
- coseString = "(Failed to write as string)";
- }
-
- logger.error("Failed to decode public key in storage: ID: {} COSE: {}", credentialId, coseString);
- return Optional.empty();
- }
-
- return Optional.of(
+ return registrationMaybe.flatMap(registration ->
+ Optional.of(
RegisteredCredential.builder()
.credentialId(registration.getRegistration().getKeyId().getId())
.userHandle(registration.getUserIdentity().getId())
- .publicKey(key)
+ .publicKeyCose(registration.getRegistration().getPublicKeyCose())
.signatureCount(registration.getSignatureCount())
.build()
- );
- });
+ )
+ );
}
@Override
@@ -195,19 +173,13 @@ public Set lookupAll(ByteArray credentialId) {
storage.asMap().values().stream()
.flatMap(Collection::stream)
.filter(reg -> reg.getRegistration().getKeyId().getId().equals(credentialId))
- .map(reg -> {
- try {
- return RegisteredCredential.builder()
- .credentialId(reg.getRegistration().getKeyId().getId())
- .userHandle(reg.getUserIdentity().getId())
- .publicKey(WebAuthnCodecs.importCoseP256PublicKey(reg.getRegistration().getPublicKeyCose()))
- .signatureCount(reg.getSignatureCount())
- .build();
- } catch (CoseException | IOException e) {
- log.error("Failed to read public key {} from storage", reg.getRegistration().getKeyId().getId(), e);
- throw new RuntimeException(e);
- }
- })
+ .map(reg -> RegisteredCredential.builder()
+ .credentialId(reg.getRegistration().getKeyId().getId())
+ .userHandle(reg.getUserIdentity().getId())
+ .publicKeyCose(reg.getRegistration().getPublicKeyCose())
+ .signatureCount(reg.getSignatureCount())
+ .build()
+ )
.collect(Collectors.toSet()));
}
diff --git a/webauthn-server-demo/src/main/java/demo/webauthn/WebAuthnServer.java b/webauthn-server-demo/src/main/java/demo/webauthn/WebAuthnServer.java
index 902629a69..e4c2fd70b 100644
--- a/webauthn-server-demo/src/main/java/demo/webauthn/WebAuthnServer.java
+++ b/webauthn-server-demo/src/main/java/demo/webauthn/WebAuthnServer.java
@@ -136,7 +136,6 @@ public WebAuthnServer(RegistrationStorage userStorage, Cache