user
* @see #username(Optional)
* @see #userHandle(Optional)
* @see Client-side-resident
+ * href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">Client-side-discoverable
* credential
*/
public StartAssertionOptionsBuilder userHandle(ByteArray userHandle) {
diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/TpmAttestationStatementVerifier.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/TpmAttestationStatementVerifier.java
index 2b22fe7d7..ee051e770 100644
--- a/webauthn-server-core/src/main/java/com/yubico/webauthn/TpmAttestationStatementVerifier.java
+++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/TpmAttestationStatementVerifier.java
@@ -82,7 +82,7 @@ static final class Attributes {
| (1 << 3) // 3 Reserved
| (0x3 << 8) // 9:8 Reserved
| (0xF << 12) // 15:12 Reserved
- | ((0xFFFFFFFF << 19) & ((1 << 32) - 1)) // 31:19 Reserved
+ | ((0xFFFFFFFF << 19) & ((1 << 31) | ((1 << 31) - 1))) // 31:19 Reserved
;
}
@@ -101,14 +101,14 @@ public boolean verifyAttestationSignature(
ObjectNode attStmt = attestationObject.getAttestationStatement();
JsonNode verNode = attStmt.get("ver");
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
verNode != null && verNode.isTextual() && verNode.textValue().equals(TPM_VER),
"attStmt.ver must equal \"%s\", was: %s",
TPM_VER,
verNode);
JsonNode algNode = attStmt.get("alg");
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
algNode != null && algNode.canConvertToLong(),
"attStmt.alg must be set to an integer value, was: %s",
algNode);
@@ -119,7 +119,7 @@ public boolean verifyAttestationSignature(
new IllegalArgumentException("Unknown COSE algorithm identifier: " + algNode));
JsonNode x5cNode = attStmt.get("x5c");
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
x5cNode != null && x5cNode.isArray(),
"attStmt.x5c must be set to an array value, was: %s",
x5cNode);
@@ -137,7 +137,7 @@ public boolean verifyAttestationSignature(
final X509Certificate aikCert = x5c.get(0);
JsonNode sigNode = attStmt.get("sig");
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
sigNode != null && sigNode.isBinary(),
"attStmt.sig must be set to a binary value, was: %s",
sigNode);
@@ -149,13 +149,13 @@ public boolean verifyAttestationSignature(
}
JsonNode certInfoNode = attStmt.get("certInfo");
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
certInfoNode != null && certInfoNode.isBinary(),
"attStmt.certInfo must be set to a binary value, was: %s",
certInfoNode);
JsonNode pubAreaNode = attStmt.get("pubArea");
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
pubAreaNode != null && pubAreaNode.isBinary(),
"attStmt.pubArea must be set to a binary value, was: %s",
pubAreaNode);
@@ -217,10 +217,12 @@ private void validateCertInfo(
break;
case ES384:
+ case RS384:
expectedExtraData = Crypto.sha384(attToBeSigned);
break;
case ES512:
+ case RS512:
expectedExtraData = Crypto.sha512(attToBeSigned);
break;
@@ -235,14 +237,14 @@ private void validateCertInfo(
default:
throw new UnsupportedOperationException("Signing algorithm not implemented: " + alg);
}
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
certInfo.extraData.equals(expectedExtraData), "Incorrect certInfo.extraData.");
// Sub-step 4: Verify that attested contains a TPMS_CERTIFY_INFO structure as specified in
// [TPMv2-Part2] section 10.12.3, whose name field contains a valid Name for pubArea, as
// computed using the algorithm in the nameAlg field of pubArea using the procedure specified in
// [TPMv2-Part1] section 16.
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
certInfo.attestedName.equals(pubArea.name()), "Incorrect certInfo.attestedName.");
// Sub-step 5 handled by parsing above
@@ -250,7 +252,7 @@ private void validateCertInfo(
// Sub-step 7: Verify the sig is a valid signature over certInfo using the attestation public
// key in aikCert with the algorithm specified in alg.
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
Crypto.verifySignature(aikCert, certInfo.getRawBytes(), sig, alg),
"Incorrect TPM attestation signature.");
@@ -287,7 +289,7 @@ private void verifyPublicKeysMatch(AttestationObject attestationObject, TpmtPubl
signedCredentialPublicKey = kf.generatePublic(spec);
}
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
Arrays.equals(credentialPubKey.getEncoded(), signedCredentialPublicKey.getEncoded()),
"Signed public key in TPM attestation is not identical to credential public key in authData.");
break;
@@ -333,19 +335,19 @@ private void verifyPublicKeysMatch(AttestationObject attestationObject, TpmtPubl
"Unsupported elliptic curve: " + params.curve_id);
}
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
algId.equals(tpmAlgId),
"Signed public key in TPM attestation is not identical to credential public key in authData; elliptic curve differs: %s != %s",
tpmAlgId,
algId);
byte[] cosePubkeyX = cosePubkey.get(CBORObject.FromObject(-2)).GetByteString();
byte[] cosePubkeyY = cosePubkey.get(CBORObject.FromObject(-3)).GetByteString();
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
new BigInteger(1, unique.x.getBytes()).equals(new BigInteger(1, cosePubkeyX)),
"Signed public key in TPM attestation is not identical to credential public key in authData; EC X coordinate differs: %s != %s",
unique.x,
new ByteArray(cosePubkeyX));
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
new BigInteger(1, unique.y.getBytes()).equals(new BigInteger(1, cosePubkeyY)),
"Signed public key in TPM attestation is not identical to credential public key in authData; EC Y coordinate differs: %s != %s",
unique.y,
@@ -382,7 +384,7 @@ private static TpmtPublic parse(byte[] pubArea) throws IOException {
final int nameAlg = reader.readUnsignedShort();
final int attributes = reader.readInt();
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
(attributes & Attributes.SHALL_BE_ZERO) == 0,
"Attributes contains 1 bits in reserved position(s): 0x%08x",
attributes);
@@ -393,7 +395,7 @@ private static TpmtPublic parse(byte[] pubArea) throws IOException {
final Parameters parameters;
final Unique unique;
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
(attributes & Attributes.SIGN_ENCRYPT) == Attributes.SIGN_ENCRYPT,
"Public key is expected to have the SIGN_ENCRYPT attribute set, attributes were: 0x%08x",
attributes);
@@ -408,7 +410,7 @@ private static TpmtPublic parse(byte[] pubArea) throws IOException {
throw new UnsupportedOperationException("Signing algorithm not implemented: " + signAlg);
}
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
reader.available() == 0,
"%d remaining bytes in TPMT_PUBLIC buffer",
reader.available());
@@ -472,12 +474,12 @@ static class TpmAlgHash {
private void verifyX5cRequirements(X509Certificate cert, ByteArray aaguid)
throws CertificateParsingException {
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
cert.getVersion() == 3,
"Invalid TPM attestation certificate: Version MUST be 3, but was: %s",
cert.getVersion());
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
cert.getSubjectX500Principal().getName().isEmpty(),
"Invalid TPM attestation certificate: subject MUST be empty, but was: %s",
cert.getSubjectX500Principal());
@@ -505,26 +507,26 @@ private void verifyX5cRequirements(X509Certificate cert, ByteArray aaguid)
}
}
}
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
foundManufacturer && foundModel && foundVersion,
"Invalid TPM attestation certificate: The Subject Alternative Name extension MUST be set as defined in [TPMv2-EK-Profile] section 3.2.9.%s%s%s",
foundManufacturer ? "" : " Missing TPM manufacturer.",
foundModel ? "" : " Missing TPM model.",
foundVersion ? "" : " Missing TPM version.");
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
cert.getExtendedKeyUsage() != null && cert.getExtendedKeyUsage().contains("2.23.133.8.3"),
"Invalid TPM attestation certificate: extended key usage extension MUST contain the OID 2.23.133.8.3, but was: %s",
cert.getExtendedKeyUsage());
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
cert.getBasicConstraints() == -1,
"Invalid TPM attestation certificate: MUST NOT be a CA certificate, but was.");
CertificateParser.parseFidoAaguidExtension(cert)
.ifPresent(
extensionAaguid -> {
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
Arrays.equals(aaguid.getBytes(), extensionAaguid),
"Invalid TPM attestation certificate: X.509 extension \"id-fido-gen-ce-aaguid\" is present but does not match the authenticator AAGUID.");
});
@@ -546,13 +548,13 @@ private static class TpmsRsaParms implements Parameters {
private static TpmsRsaParms parse(ByteInputStream reader) throws IOException {
final int symmetric = reader.readUnsignedShort();
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
symmetric == TPM_ALG_NULL,
"RSA key is expected to have \"symmetric\" set to TPM_ALG_NULL, was: 0x%04x",
symmetric);
final int scheme = reader.readUnsignedShort();
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
scheme == TpmRsaScheme.RSASSA || scheme == TPM_ALG_NULL,
"RSA key is expected to have \"scheme\" set to TPM_ALG_RSASSA or TPM_ALG_NULL, was: 0x%04x",
scheme);
@@ -560,7 +562,7 @@ private static TpmsRsaParms parse(ByteInputStream reader) throws IOException {
reader.skipBytes(2); // key_bits is not used by this implementation
int exponent = reader.readInt();
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
exponent >= 0, "Exponent is too large and wrapped around to negative: %d", exponent);
if (exponent == 0) {
// When zero, indicates that the exponent is the default of 2^16 + 1
@@ -587,11 +589,11 @@ private static class TpmsEccParms implements Parameters {
private static TpmsEccParms parse(ByteInputStream reader) throws IOException {
final int symmetric = reader.readUnsignedShort();
final int scheme = reader.readUnsignedShort();
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
symmetric == TPM_ALG_NULL,
"ECC key is expected to have \"symmetric\" set to TPM_ALG_NULL, was: 0x%04x",
symmetric);
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
scheme == TPM_ALG_NULL,
"ECC key is expected to have \"scheme\" set to TPM_ALG_NULL, was: 0x%04x",
scheme);
@@ -663,14 +665,15 @@ private static TpmsAttest parse(byte[] certInfo) throws IOException {
// Verify that magic is set to TPM_GENERATED_VALUE.
// see https://w3c.github.io/webauthn/#sctn-tpm-attestation
// verification procedure
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
magic.equals(TPM_GENERATED_VALUE), "magic field is invalid: %s", magic);
// Verify that type is set to TPM_ST_ATTEST_CERTIFY.
// see https://w3c.github.io/webauthn/#sctn-tpm-attestation
// verification procedure
final ByteArray type = new ByteArray(reader.read(2));
- ExceptionUtil.assure(type.equals(TPM_ST_ATTEST_CERTIFY), "type field is invalid: %s", type);
+ ExceptionUtil.assertTrue(
+ type.equals(TPM_ST_ATTEST_CERTIFY), "type field is invalid: %s", type);
// qualifiedSigner is not used by this implementation
reader.skipBytes(reader.readUnsignedShort());
diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/WebAuthnCodecs.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/WebAuthnCodecs.java
index b5f8e079d..60aacab0d 100644
--- a/webauthn-server-core/src/main/java/com/yubico/webauthn/WebAuthnCodecs.java
+++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/WebAuthnCodecs.java
@@ -184,6 +184,10 @@ static String getJavaAlgorithmName(COSEAlgorithmIdentifier alg) {
return "SHA512withECDSA";
case RS256:
return "SHA256withRSA";
+ case RS384:
+ return "SHA384withRSA";
+ case RS512:
+ return "SHA512withRSA";
case RS1:
return "SHA1withRSA";
default:
diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AttestedCredentialData.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AttestedCredentialData.java
index 1d110d9d1..48d7873a8 100644
--- a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AttestedCredentialData.java
+++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AttestedCredentialData.java
@@ -53,10 +53,7 @@ public class AttestedCredentialData {
* The credential public key encoded in COSE_Key format, as defined in Section 7 of RFC 8152.
*/
- @NonNull
- // TODO: verify requirements
- // https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-attested-credential-data
- private final ByteArray credentialPublicKey;
+ @NonNull private final ByteArray credentialPublicKey;
@JsonCreator
private AttestedCredentialData(
diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorData.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorData.java
index f15c7fbdd..169e4c5d7 100644
--- a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorData.java
+++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorData.java
@@ -100,7 +100,7 @@ public class AuthenticatorData {
/** Decode an {@link AuthenticatorData} object from a raw authenticator data byte array. */
@JsonCreator
public AuthenticatorData(@NonNull ByteArray bytes) {
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
bytes.size() >= FIXED_LENGTH_PART_END_INDEX,
"%s byte array must be at least %d bytes, was %d: %s",
AuthenticatorData.class.getSimpleName(),
@@ -150,12 +150,12 @@ private static VariableLengthParseResult parseAttestedCredentialData(
final int CREDENTIAL_ID_LENGTH_INDEX = AAGUID_END;
final int CREDENTIAL_ID_LENGTH_END = CREDENTIAL_ID_LENGTH_INDEX + 2;
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
bytes.length >= CREDENTIAL_ID_LENGTH_END,
"Attested credential data must contain at least %d bytes, was %d: %s",
CREDENTIAL_ID_LENGTH_END,
bytes.length,
- new ByteArray(bytes).getHex());
+ new ByteArray(bytes));
byte[] credentialIdLengthBytes =
Arrays.copyOfRange(bytes, CREDENTIAL_ID_LENGTH_INDEX, CREDENTIAL_ID_LENGTH_END);
@@ -174,12 +174,12 @@ private static VariableLengthParseResult parseAttestedCredentialData(
final int CREDENTIAL_PUBLIC_KEY_INDEX = CREDENTIAL_ID_END;
final int CREDENTIAL_PUBLIC_KEY_AND_EXTENSION_DATA_END = bytes.length;
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
bytes.length >= CREDENTIAL_ID_END,
"Expected credential ID of length %d, but attested credential data and extension data is only %d bytes: %s",
CREDENTIAL_ID_END,
bytes.length,
- new ByteArray(bytes).getHex());
+ new ByteArray(bytes));
ByteArrayInputStream indefiniteLengthBytes =
new ByteArrayInputStream(
diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorSelectionCriteria.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorSelectionCriteria.java
index ec3566bd0..1a19d4dda 100644
--- a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorSelectionCriteria.java
+++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorSelectionCriteria.java
@@ -100,6 +100,14 @@ public Optional getAuthenticatorAttachment() {
* By default, this is not set. When not set, the default in the browser is {@link
* ResidentKeyRequirement#DISCOURAGED}.
*
+ *
When this is set, {@link PublicKeyCredentialCreationOptions#toCredentialsCreateJson()} will
+ * also emit a
+ * requireResidentKey
member for backwards compatibility with WebAuthn Level 1.
+ * It will be set to true
if this is set to {@link ResidentKeyRequirement#REQUIRED
+ * REQUIRED} and false
if this is set to anything else. When this is not set, a
+ * requireResidentKey
member will not be emitted.
+ *
* @see ResidentKeyRequirement
* @see §5.4.6.
@@ -112,6 +120,19 @@ public Optional getResidentKey() {
return Optional.ofNullable(residentKey);
}
+ /**
+ * For backwards compatibility with requireResidentKey
.
+ *
+ * @see 5.4.4.
+ * Authenticator Selection Criteria (dictionary AuthenticatorSelectionCriteria) member
+ * requireResidentKey
+ */
+ @JsonProperty
+ private Boolean isRequireResidentKey() {
+ return getResidentKey().map(rk -> rk == ResidentKeyRequirement.REQUIRED).orElse(null);
+ }
+
/**
* Describes the Relying Party's requirements regarding user
diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/COSEAlgorithmIdentifier.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/COSEAlgorithmIdentifier.java
index f003121c5..4b9ca5803 100644
--- a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/COSEAlgorithmIdentifier.java
+++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/COSEAlgorithmIdentifier.java
@@ -48,6 +48,8 @@ public enum COSEAlgorithmIdentifier {
ES384(-35),
ES512(-36),
RS256(-257),
+ RS384(-258),
+ RS512(-259),
RS1(-65535);
@JsonValue @Getter private final long id;
diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/CollectedClientData.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/CollectedClientData.java
index f1e7a392f..7a9f89279 100644
--- a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/CollectedClientData.java
+++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/CollectedClientData.java
@@ -81,7 +81,7 @@ public CollectedClientData(@NonNull ByteArray clientDataJSON)
throws IOException, Base64UrlException {
JsonNode clientData = JacksonCodecs.json().readTree(clientDataJSON.getBytes());
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
clientData != null && clientData.isObject(), "Collected client data must be JSON object.");
this.clientDataJson = clientDataJSON;
diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/Extensions.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/Extensions.java
index 4fb9b37c6..db831a05c 100644
--- a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/Extensions.java
+++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/Extensions.java
@@ -428,8 +428,10 @@ static Optional> parseAuthenticatorExtensionOutput(CBORObject cbo
uvmEntry ->
new UvmEntry(
UserVerificationMethod.fromValue(uvmEntry.get(0).AsInt32Value()),
- KeyProtectionType.fromValue(uvmEntry.get(1).AsInt16()),
- MatcherProtectionType.fromValue(uvmEntry.get(2).AsInt16())))
+ KeyProtectionType.fromValue(
+ uvmEntry.get(1).AsNumber().ToInt16IfExact()),
+ MatcherProtectionType.fromValue(
+ uvmEntry.get(2).AsNumber().ToInt16IfExact())))
.collect(Collectors.toList()));
} else {
return Optional.empty();
@@ -470,7 +472,7 @@ private static boolean validateAuthenticatorExtensionOutput(CBORObject extension
}
for (CBORObject i : entry.getValues()) {
- if (!i.isIntegral()) {
+ if (!(i.isNumber() && i.AsNumber().IsInteger())) {
log.debug("Invalid type for uvmEntry element: expected integer, was: {}", i.getType());
return false;
}
diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialCreationOptions.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialCreationOptions.java
index c1ac9517a..a5f252c31 100644
--- a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialCreationOptions.java
+++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialCreationOptions.java
@@ -382,6 +382,8 @@ private static List filterAvailableAlgorithms(
break;
case RS256:
+ case RS384:
+ case RS512:
case RS1:
KeyFactory.getInstance("RSA");
break;
@@ -419,6 +421,14 @@ private static List filterAvailableAlgorithms(
Signature.getInstance("SHA256withRSA");
break;
+ case RS384:
+ Signature.getInstance("SHA384withRSA");
+ break;
+
+ case RS512:
+ Signature.getInstance("SHA512withRSA");
+ break;
+
case RS1:
Signature.getInstance("SHA1withRSA");
break;
diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialParameters.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialParameters.java
index e2e59d7b4..4848bcd45 100644
--- a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialParameters.java
+++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialParameters.java
@@ -100,6 +100,20 @@ private PublicKeyCredentialParameters(
public static final PublicKeyCredentialParameters RS256 =
builder().alg(COSEAlgorithmIdentifier.RS256).build();
+ /**
+ * Algorithm {@link COSEAlgorithmIdentifier#RS384} and type {@link
+ * PublicKeyCredentialType#PUBLIC_KEY}.
+ */
+ public static final PublicKeyCredentialParameters RS384 =
+ builder().alg(COSEAlgorithmIdentifier.RS384).build();
+
+ /**
+ * Algorithm {@link COSEAlgorithmIdentifier#RS512} and type {@link
+ * PublicKeyCredentialType#PUBLIC_KEY}.
+ */
+ public static final PublicKeyCredentialParameters RS512 =
+ builder().alg(COSEAlgorithmIdentifier.RS512).build();
+
public static PublicKeyCredentialParametersBuilder.MandatoryStages builder() {
return new PublicKeyCredentialParametersBuilder.MandatoryStages();
}
diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/TokenBindingInfo.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/TokenBindingInfo.java
index d267b0c42..13e400db2 100644
--- a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/TokenBindingInfo.java
+++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/TokenBindingInfo.java
@@ -24,7 +24,7 @@
package com.yubico.webauthn.data;
-import static com.yubico.internal.util.ExceptionUtil.assure;
+import static com.yubico.internal.util.ExceptionUtil.assertTrue;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -56,12 +56,12 @@ public class TokenBindingInfo {
@NonNull @JsonProperty("status") TokenBindingStatus status,
@NonNull @JsonProperty("id") Optional id) {
if (status == TokenBindingStatus.PRESENT) {
- assure(
+ assertTrue(
id.isPresent(),
"Token binding ID must be present if status is \"%s\".",
TokenBindingStatus.PRESENT);
} else {
- assure(
+ assertTrue(
!id.isPresent(),
"Token binding ID must not be present if status is not \"%s\".",
TokenBindingStatus.PRESENT);
diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/Generators.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/Generators.scala
index 6053501c3..99eab5f0d 100644
--- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/Generators.scala
+++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/Generators.scala
@@ -1,5 +1,6 @@
package com.yubico.webauthn
+import com.yubico.scalacheck.gen.GenUtil.halfsized
import com.yubico.webauthn.data.AssertionExtensionInputs
import com.yubico.webauthn.data.AttestationType
import com.yubico.webauthn.data.AuthenticatorAssertionResponse
@@ -22,78 +23,86 @@ import scala.jdk.OptionConverters.RichOption
object Generators {
implicit val arbitraryAssertionResult: Arbitrary[AssertionResult] = Arbitrary(
- for {
- credentialResponse <-
- arbitrary[PublicKeyCredential[
- AuthenticatorAssertionResponse,
- ClientAssertionExtensionOutputs,
- ]]
- credential <- arbitrary[RegisteredCredential]
- signatureCounterValid <- arbitrary[Boolean]
- success <- arbitrary[Boolean]
- username <- arbitrary[String]
- } yield new AssertionResult(
- success,
- credentialResponse,
- credential,
- username,
- signatureCounterValid,
+ halfsized(
+ for {
+ credentialResponse <-
+ arbitrary[PublicKeyCredential[
+ AuthenticatorAssertionResponse,
+ ClientAssertionExtensionOutputs,
+ ]]
+ credential <- arbitrary[RegisteredCredential]
+ signatureCounterValid <- arbitrary[Boolean]
+ success <- arbitrary[Boolean]
+ username <- arbitrary[String]
+ } yield new AssertionResult(
+ success,
+ credentialResponse,
+ credential,
+ username,
+ signatureCounterValid,
+ )
)
)
implicit val arbitraryRegistrationResult: Arbitrary[RegistrationResult] =
Arbitrary(
- for {
- credential <-
- arbitrary[PublicKeyCredential[
- AuthenticatorAttestationResponse,
- ClientRegistrationExtensionOutputs,
- ]]
- attestationTrusted <- arbitrary[Boolean]
- attestationTrustPath <- generateAttestationCertificateChain
- attestationType <- arbitrary[AttestationType]
- } yield new RegistrationResult(
- credential,
- attestationTrusted,
- attestationType,
- Some(attestationTrustPath.asJava).toJava,
+ halfsized(
+ for {
+ credential <-
+ arbitrary[PublicKeyCredential[
+ AuthenticatorAttestationResponse,
+ ClientRegistrationExtensionOutputs,
+ ]]
+ attestationTrusted <- arbitrary[Boolean]
+ attestationTrustPath <- generateAttestationCertificateChain
+ attestationType <- arbitrary[AttestationType]
+ } yield new RegistrationResult(
+ credential,
+ attestationTrusted,
+ attestationType,
+ Some(attestationTrustPath.asJava).toJava,
+ )
)
)
implicit val arbitraryRegisteredCredential: Arbitrary[RegisteredCredential] =
Arbitrary(
- for {
- credentialId <- arbitrary[ByteArray]
- userHandle <- arbitrary[ByteArray]
- publicKeyCose <- arbitrary[ByteArray]
- signatureCount <- arbitrary[Int]
- } yield RegisteredCredential
- .builder()
- .credentialId(credentialId)
- .userHandle(userHandle)
- .publicKeyCose(publicKeyCose)
- .signatureCount(signatureCount)
- .build()
+ halfsized(
+ for {
+ credentialId <- arbitrary[ByteArray]
+ userHandle <- arbitrary[ByteArray]
+ publicKeyCose <- arbitrary[ByteArray]
+ signatureCount <- arbitrary[Int]
+ } yield RegisteredCredential
+ .builder()
+ .credentialId(credentialId)
+ .userHandle(userHandle)
+ .publicKeyCose(publicKeyCose)
+ .signatureCount(signatureCount)
+ .build()
+ )
)
implicit val arbitraryStartAssertionOptions
: Arbitrary[StartAssertionOptions] = Arbitrary(
- for {
- extensions <- arbitrary[Option[AssertionExtensionInputs]]
- timeout <- Gen.option(Gen.posNum[Long])
- usernameOrUserHandle <- arbitrary[Option[Either[String, ByteArray]]]
- userVerification <- arbitrary[Option[UserVerificationRequirement]]
- } yield {
- val b = StartAssertionOptions.builder()
- extensions.foreach(b.extensions)
- timeout.foreach(b.timeout)
- usernameOrUserHandle.foreach {
- case Left(username) => b.username(username)
- case Right(userHandle) => b.userHandle(userHandle)
+ halfsized(
+ for {
+ extensions <- arbitrary[Option[AssertionExtensionInputs]]
+ timeout <- Gen.option(Gen.posNum[Long])
+ usernameOrUserHandle <- arbitrary[Option[Either[String, ByteArray]]]
+ userVerification <- arbitrary[Option[UserVerificationRequirement]]
+ } yield {
+ val b = StartAssertionOptions.builder()
+ extensions.foreach(b.extensions)
+ timeout.foreach(b.timeout)
+ usernameOrUserHandle.foreach {
+ case Left(username) => b.username(username)
+ case Right(userHandle) => b.userHandle(userHandle)
+ }
+ userVerification.foreach(b.userVerification)
+ b.build()
}
- userVerification.foreach(b.userVerification)
- b.build()
- }
+ )
)
def generateAttestationCertificateChain: Gen[List[X509Certificate]] =
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 ef93443fb..484399d24 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
@@ -140,6 +140,8 @@ object RegistrationTestDataGenerator extends App {
td.Packed.BasicAttestation,
td.Packed.BasicAttestationEdDsa,
td.Packed.BasicAttestationRsa,
+ td.Packed.BasicAttestationRs384,
+ td.Packed.BasicAttestationRs512,
td.Packed.BasicAttestationRs1,
td.Packed.BasicAttestationWithoutAaguidExtension,
td.Packed.BasicAttestationWithWrongAaguidExtension,
@@ -150,6 +152,8 @@ object RegistrationTestDataGenerator extends App {
td.Tpm.ValidEs384,
td.Tpm.ValidEs512,
td.Tpm.ValidRs256,
+ td.Tpm.ValidRs384,
+ td.Tpm.ValidRs512,
td.Tpm.ValidRs1,
).zipWithIndex
} {
@@ -178,6 +182,8 @@ object RegistrationTestData {
Packed.BasicAttestation,
Packed.BasicAttestationEdDsa,
Packed.BasicAttestationRsa,
+ Packed.BasicAttestationRs384,
+ Packed.BasicAttestationRs512,
Packed.BasicAttestationRsaReal,
Packed.SelfAttestation,
Tpm.ValidEs256,
@@ -185,6 +191,8 @@ object RegistrationTestData {
Tpm.ValidEs384,
Tpm.ValidEs512,
Tpm.ValidRs256,
+ Tpm.ValidRs384,
+ Tpm.ValidRs512,
RealExamples.WindowsHelloTpm.asRegistrationTestData,
RealExamples.ThinkpadTpm.asRegistrationTestData,
)
@@ -449,6 +457,56 @@ object RegistrationTestData {
)
}
+ val BasicAttestationRs384: RegistrationTestData = new RegistrationTestData(
+ alg = COSEAlgorithmIdentifier.RS384,
+ attestationObject =
+ ByteArray.fromHex("bf68617574684461746159016849960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97634100000539000102030405060708090a0b0c0d0e0f0020c088fcbe4ba7f49b972e6cb5fc5819ede68bb6e6267b4a1832af45facff40517a40103033901012059010100c5e44335f0b4b5450eeec98576cd39d7b0b87ff76d330f9c0892fa9a1f7d378bdb2d8fb488c6dab3cbeba47a32ef696156fdc5d07f594ce8cd4d15f8053988b453468440fc3419531ff3605a025855497f36bba5d776660bea8d4a6f9c654b78f362be5bd1c669c2b49fd15fa2d751fb95a80138c75a1b3df18fe5ca6ad3185e24e7da6e0405f78a4353c33c9ebd48021dd2e6c2590311a59f8c3b62945aec3a570687e16a83ffed75a8fd2a12126461d70906185396457008cfe46514495875507d5c4f1aa8237b337cb5319ac5bd4808389990cc74281daf9237b49bcfc8bab24fa4953d6dad3347a5c623d9edd105af3e5d6767fde27c38ff06df6b4aee7d214301000163666d74667061636b65646761747453746d74bf63616c67390101637369675901009b18b1991728261445bede30204b7a86f92cfa5584b6c8895f00d92a6157b5b9da4336ff2c72a2a7e26f2ae6cc9f2e32bf4daa4599b116bff0bc8af9e2cd57f3329e1614eaece02d852bf660d3c419d42d7429225f2d6b3e9a4b68376070c17ffcd85cf1430641731bbe49204088779d8e586ebbd1f64a3edad071ade30ae8ecfb90c8b3fb5ff471d9fe71cbc1ea488c1a263ec9d438c505c91cb4a17d8a1ee5ae47c88876cbf20c74e55196b99bec606ade7ba82b64793eaa91f1207d70c80eba19589ec0e423c886cc616457420dc1b3c04f2b5791772a3c023a246c4d3efcca0169933ee6130cc4af37d5456372e7f6553cc07221aa741566af78744f4ab0637835638159034c3082034830820230a003020102020209b0300d06092a864886f70d01010c050030673123302106035504030c1a59756269636f20576562417574686e20756e6974207465737473310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b3009060355040613025345301e170d3138303930363137343230305a170d3138303931333137343230305a30673123302106035504030c1a59756269636f20576562417574686e20756e6974207465737473310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b300906035504061302534530820122300d06092a864886f70d01010105000382010f003082010a0282010100bbf836d1dd6e2627f2a80a457f49adae909846863ef3eba0cc1a605813b93ef2b683d896a1dcaf74a7db3463c9f0d5dbec2ed30f5edb857e0dbd34576caa1716c43910f4325484a1a6dcf3bf63f2343ce46b88f00210e0cb0b2fbc3065ec8ad302807ce431f4edb9b9b07bdee10fe1fbcbf41451e6f6b04ea996215c2667b80a04fa376c5d17e953b70af71fe8be7dd7f4a845bdf4b2fa654531debefa99d66fe0c1a04c924bcc3be146da68c2de3a97dded885277a84f450e6669a95f1b901057f1ac6d91fa701b0a8170fa415fa28aa4fa242fe4eaea8fc8c44c8258301a25cda21bb9ddd37addd7e2154f43811036045e5b856453e85078401f0659593d950203010001300d06092a864886f70d01010c050003820101000514466869a9656754f0f81121dda9efa3723882dc1600d4b2c6516022c4873be034c8ffd939e191da50b4c7d7198e37c7596fa879343cb8406e0c92b8978078e02b0ce3c7e6e4421b665f6b63b0e566dc758e17f5b9cc6c42d58845740857cb5f488f6f6f81bcd71d9f46346f17cf61021275e577c114588a1fbc8de5f5ebfe97c2267f99c437323891efce43e989e0189dfac9dd331103adce542906794cc59e361526d8ca7016652be3843c270673019ecc296072bb763d8d3cd815a5d1185471e363cdd37edbb812219acab23e90cc57002465e8fe4434a33b10b4e116634c94d16455ef5d887cdcfa424f47ff59bc2aef7d0588a3af60c90c823f61d7a6ffff"),
+ clientDataJson = """{"challenge":"AAEBAgMFCA0VIjdZEGl5Yls","origin":"https://localhost","type":"webauthn.create","tokenBinding":{"status":"supported"}}""",
+ privateKey = Some(
+ ByteArray.fromHex("308204be020100300d06092a864886f70d0101010500048204a8308204a40201000282010100c5e44335f0b4b5450eeec98576cd39d7b0b87ff76d330f9c0892fa9a1f7d378bdb2d8fb488c6dab3cbeba47a32ef696156fdc5d07f594ce8cd4d15f8053988b453468440fc3419531ff3605a025855497f36bba5d776660bea8d4a6f9c654b78f362be5bd1c669c2b49fd15fa2d751fb95a80138c75a1b3df18fe5ca6ad3185e24e7da6e0405f78a4353c33c9ebd48021dd2e6c2590311a59f8c3b62945aec3a570687e16a83ffed75a8fd2a12126461d70906185396457008cfe46514495875507d5c4f1aa8237b337cb5319ac5bd4808389990cc74281daf9237b49bcfc8bab24fa4953d6dad3347a5c623d9edd105af3e5d6767fde27c38ff06df6b4aee7d0203010001028201001742701febede18c7f67d3a9eb3fcdf7ab1ed473a99321d78e2e706423255d9d03a3044c0cf38a8b2d81c1f057024ad99516f8e43bc3ac4584b3f5cc1419221747de76f7086dbb3848fe1b2a193276bbcfc708214304f89397fb096fbaeb3106c35cacd13003e93468748c70783c64b7746cadd015a662a3523c3e9f1f1536bccd0dc7139c5c813443f654305924d3296b6fcf01e1539217a1ce0c634164c42d40617048cbb81a401d0a457f1230957f40c3f16f58414d4de96921aa353dfb294b94b65cafbf05bdb46895a423121a7918dbde57a3e1b30c4a40bff4086597b7558f1fd535e5c4cc8e0a46a80700e43d4afada920266d2bf0eb456a4a1fe250502818100ede3f68c1142913405c4a7bdd5550f123c75d29405e342e61d22e40a9c547799f8729f18b3d1066af094178ce4fb2cfd885239487ed96ce94d770018754dc963ef8bb13bbec9b33905f1d030a2b2b7778c326454b3cc7e2c54e46042e50ed7da12da02505ec817dd5889ab3355a6bd406d4d1c0705ffd6ec4c04d88b683887a702818100d4f4cc350d2e2942c28a7b9e3f83a9d2faf22f24d78bc53407d29614365f013470d55b995afa29978c14c786a76f8fee9e2b692ad5e11e57ea349914758025f9316b5f5290e2f12e771cd2917aacd80b76a43a5e7c60d84de67e0b6556da75f2b163f943952262e8c42d659b157a94e0b3905c4ce8a0c943c5a6057c1aa45d3b02818100ac013a510861d34f84242f0d096519229c68acbbae8e25def08e3bc8984452be176ff92d09474796a720ccee68da5c2b6d17d6a75e60a3690543d7e3d75d4912632fe41448dcda238ef2cb0f7f742d47d92cce72981671dc67fd40c4dd8e1ff063d511fb3eacfae46692142167fac9b7fdcfd54616c667862f690991b2e7bcdd02818100b4ff93890bb8ce4cf5b86a35285aa9beae97a546350591091615008611685247d61721918867d36e011bb0325ca14fbe4a252f6fbef565aae75ee935206158e52201d6b5007c42ed7143c81cea1d7a4ad3fde5b6651493043301b281e17e307da4140aca4c393bc406e966d09742e6c2cd1bc7b77e891a4745f843f52557c9fd0281800ff1fabad5587a4089746854833fb3ef15296c15c13959b6bf0d4f01777525f8c5fb87d9c3cd7b0c63fd182930e9ee9eaad35b5c361f3c2553629dc358b1b591651bfc2104f068fa6175044c40b910aebf7ce6f58ce9b24224182059e2838e20e213523d1fe05e2c49f999d355da66107aa398b844c8993c8e1b7f7ae9cc8026")
+ ),
+ attestationCertChain = List(
+ RegistrationTestDataGenerator.importAttestationCa(
+ "MIIDSDCCAjCgAwIBAgICCbAwDQYJKoZIhvcNAQEMBQAwZzEjMCEGA1UEAwwaWXViaWNvIFdlYkF1dGhuIHVuaXQgdGVzdHMxDzANBgNVBAoMBll1YmljbzEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjELMAkGA1UEBhMCU0UwHhcNMTgwOTA2MTc0MjAwWhcNMTgwOTEzMTc0MjAwWjBnMSMwIQYDVQQDDBpZdWJpY28gV2ViQXV0aG4gdW5pdCB0ZXN0czEPMA0GA1UECgwGWXViaWNvMSIwIAYDVQQLDBlBdXRoZW50aWNhdG9yIEF0dGVzdGF0aW9uMQswCQYDVQQGEwJTRTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALv4NtHdbiYn8qgKRX9Jra6QmEaGPvProMwaYFgTuT7ytoPYlqHcr3Sn2zRjyfDV2+wu0w9e24V+Db00V2yqFxbEORD0MlSEoabc879j8jQ85GuI8AIQ4MsLL7wwZeyK0wKAfOQx9O25ubB73uEP4fvL9BRR5vawTqmWIVwmZ7gKBPo3bF0X6VO3Cvcf6L591/SoRb30svplRTHevvqZ1m/gwaBMkkvMO+FG2mjC3jqX3e2IUneoT0UOZmmpXxuQEFfxrG2R+nAbCoFw+kFfooqk+iQv5Orqj8jETIJYMBolzaIbud3Tet3X4hVPQ4EQNgReW4VkU+hQeEAfBllZPZUCAwEAATANBgkqhkiG9w0BAQwFAAOCAQEABRRGaGmpZWdU8PgRId2p76NyOILcFgDUssZRYCLEhzvgNMj/2TnhkdpQtMfXGY43x1lvqHk0PLhAbgySuJeAeOArDOPH5uRCG2Zfa2Ow5WbcdY4X9bnMbELViEV0CFfLX0iPb2+BvNcdn0Y0bxfPYQISdeV3wRRYih+8jeX16/6XwiZ/mcQ3MjiR785D6YngGJ36yd0zEQOtzlQpBnlMxZ42FSbYynAWZSvjhDwnBnMBnswpYHK7dj2NPNgVpdEYVHHjY83Tftu4EiGayrI+kMxXACRl6P5ENKM7ELThFmNMlNFkVe9diHzc+kJPR/9ZvCrvfQWIo69gyQyCP2HXpg==",
+ "RSA",
+ "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7+DbR3W4mJ/KoCkV/Sa2ukJhGhj7z66DMGmBYE7k+8raD2Jah3K90p9s0Y8nw1dvsLtMPXtuFfg29NFdsqhcWxDkQ9DJUhKGm3PO/Y/I0PORriPACEODLCy+8MGXsitMCgHzkMfTtubmwe97hD+H7y/QUUeb2sE6pliFcJme4CgT6N2xdF+lTtwr3H+i+fdf0qEW99LL6ZUUx3r76mdZv4MGgTJJLzDvhRtpowt46l93tiFJ3qE9FDmZpqV8bkBBX8axtkfpwGwqBcPpBX6KKpPokL+Tq6o/IxEyCWDAaJc2iG7nd03rd1+IVT0OBEDYEXluFZFPoUHhAHwZZWT2VAgMBAAECggEBAJkG6EpOhOAXYZugURfheb5GBVJU9GdMCupfBtRtqkAvBJut9mPr8AN+rByoqLyivpo3PKikxv6Ussa4F/xlNMraEMNWqqrYF2prMx07VvFkKWnKX+qupvNmNgR1OmUqV8MPq51zdj0bGKsvDTIY5hdB4YGxc+CdhEzX5mzI72OJjAQHbw4mOUMjepPPU1sdp/DFtwgv5UN+AhFc5fbIkiQv/AROiKXTegi03xuUGzdoarnSw6t6qdiD/JCqj4vW9bQFm4daCqMbC910ngXxpo4HdD4qbGf8U7gUjHIVwXtc7c7UNDe9QL7sKMdbmtqDxj/Yl13AopLXZ1IC5ZUYbsECgYEA/B7m43Fc4aBknSStNZcdOvYV89bxpGZ3gedSsESp1OoSCsP3IyrzmADK3Q88nFa2AyJKQY58vX6h8VRbR6DzN2TpR5Ui7qB2RLRuK9xcIcILxLtAg4KUJovu0VJLNjDlCqrrMzYKWT71I8rgf9/M6rwDYopYGyk7ohoju8EjUY0CgYEAvtyfUKtfLWNcuieAFzPZA0KLLmWI0ng8xWo9LeP6WKc9hYZZMPY8+E3ZS5EcINfIeMuoU/9MMMBDfsgsoOLdQBNsbX8m66h2qt8KQWTLZptBiSOoBRH3rZIf1xuBC5nwD5Loig2CQDjrvv5ccaEO7t9NA4Z7UPXY9HdAYIC/ZikCgYBkVGfNWu97Wjiv0EidauVW8VcLEh5XLe+g4k0lmC19bSiA4DsY457MfoQ8NDQKgvcriBnEvM8nGZ2YS9mHR6WCBcZPlimwjGqELMkq6yY+yNmmEF4791q9fDItWnJTvmFnPV0bpAW6PjOPasysFoOVZfxy2lr1dBMnDv/pV5KWgQKBgQCT1rjg952Fvs14tFgXoOWcDNNaYPOWc+Q+1ogFH+4u9XxGDUbREisv+r0yN3ieSAbU6ou8ZKhTqtmdPtiy1oeitmjqd+9h4t/og1OiS7zyAZjF7YScSMqc++8F5BwVLGwy5AyTwtr9fBm/m69npOW4SeeRr32dvJEM68JF/fRD4QKBgQDzljYrmgfa3yzST7IVoaF5ZmmuoIBKQhsh+tglpYdRBzT0jr3vk0AWdm2DUEPvnSoroqBMA5CYMV248XT0jvyZRFY87HOnJkFdagf5i5bGMbMIEqpuwxU9zMXUM+dnz8+vjmWwHnM/eQy4PWV/FoXG/Ll40bRX6admbJix0Oe/og==",
+ )
+ ),
+ ) {
+ override def regenerate() =
+ TestAuthenticator.createBasicAttestedCredential(
+ keyAlgorithm = COSEAlgorithmIdentifier.RS384,
+ attestationMaker = AttestationMaker.packed(
+ AttestationSigner.selfsigned(COSEAlgorithmIdentifier.RS384)
+ ),
+ )
+ }
+
+ val BasicAttestationRs512: RegistrationTestData = new RegistrationTestData(
+ alg = COSEAlgorithmIdentifier.RS512,
+ attestationObject =
+ ByteArray.fromHex("bf68617574684461746159016849960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97634100000539000102030405060708090a0b0c0d0e0f0020144ff435d36e8b657e2143922addec5614af740a0f7acc1aaa9f46a85dc73df2a40103033901022059010100b4380cf9bb40185d3f5b141c2a8daec8076e91a9c281f3aa7a981f08375f7528bb9d0fd3348b2f00999d515a24ad345491836f2822535e21391feb4a0a948b277a04b8a6af8e89ad437481aa53a376b97e5b11896f75ceaaa032eb985147f2e4bd4a729e9b64d1b863fa852193012184488ae3634322b59fb63ffac42da9786060e6bb6e6878209377e22a9903104743f7ceb35aaaaf6868072368b5cf65d4f78b805f849e249c9c7d1f7751663caa58bbe978c677358a3c1cec5abb21e44b8950416f77ee0afae12968252d49c2a9d16a9844cb4bb111492140517d0aa9214fa85e3012423dd46f8f4d8e649020bbafe8f1e73762ce1cb2e733c51b180cb537214301000163666d74667061636b65646761747453746d74bf63616c673901026373696759010062313cd97a9d81f54fb88b423218f3632897fb833800339cbdb2f153d87033f7ba81d32467f3784af15e5f12633e2fefcded70c3f560922aca9cf9e2978e7b5a13498b4758feb33d621e5fde54f74fa1f26874fdc4e492e12f0ca6f012cce9916023635292ea902d5dbf12cecf29165200e35a2739afd312263a93fa4e0c29454fee6e5c2ed84c9e9cc1c8220d47274648477ea68642d9a0cb385fa132b95dae73ff900f0fc5e08cd584e01855fc36a6fbfc4facfe3fd4afd1418771ac831c204b33c73b1543c1f309597f97c0af9f710ff2ae40c5cf4144b2c5f2e62b59ed3ce04a6a714300ce9ed8343f8f760f3ab415f1e6666bbce50c8e2fdf2b2c9ab6eb637835638159034c3082034830820230a00302010202020cca300d06092a864886f70d01010d050030673123302106035504030c1a59756269636f20576562417574686e20756e6974207465737473310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b3009060355040613025345301e170d3138303930363137343230305a170d3138303931333137343230305a30673123302106035504030c1a59756269636f20576562417574686e20756e6974207465737473310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b300906035504061302534530820122300d06092a864886f70d01010105000382010f003082010a0282010100d19dec1547c6169901207d39de3147951fe95aa932790fd06e87731f0c74c7a30f2ca34b5996f48222eb4813a6b7c06f3920fdec4b27737356160c138091b971ffd11d55f4411f65c5017de8e1ff0792ef326ae9e561ee2bb628aefba26be8af16219b61c9f9c6bf54c6979c8eea63cc41530a2c02065a890f667a685a456f9c1d6a81e64cf42aba1f82503063004cc7d461c205a8307a4dc59c4d3a499081031e4a3d189ec29ae437b68c62db8efdd9f6fd04da29c184acbce1bf093a29111e5eca22175df4535952584b3e909157e7692c76cd106cf9125d001a519285354798ad4e7efa5e45b1f34e5fb869ada9243874cc759fa7a9d584c2025c14c8cb710203010001300d06092a864886f70d01010d050003820101008b7d7fce57afb896444250e8d099f96c84e3363627c938fc15b6d2a1a5514874d39ced9cb1d86be91b53ef139df2953604293aa30a25a387de12efb5d4b877bf7bd1a1e334a848dcceebe679ce05440e6063069b42d8666298389bdc20dd0c5b3afe353723212404eebe2e4b95a9a75dafa9e9dabba5f7fc19d7f2941988a8ac70e1b812f826528dfe7c002275b5765448d2246779d6cf201bb2976df1dd2c1461f268706fe9a5ad33fd65d58c6107b0fbc7d3da2226ee9fb6304ab381f9342223dfed894999313f1c013fdd432a2ade171cb47562d9d4a8a2a6d33eebbab70934e249dce03e0b87fdbf33f1a183fc697c9c843cc949350a547213a87d75d100ffff"),
+ clientDataJson = """{"challenge":"AAEBAgMFCA0VIjdZEGl5Yls","origin":"https://localhost","type":"webauthn.create","tokenBinding":{"status":"supported"}}""",
+ privateKey = Some(
+ ByteArray.fromHex("308204bd020100300d06092a864886f70d0101010500048204a7308204a30201000282010100b4380cf9bb40185d3f5b141c2a8daec8076e91a9c281f3aa7a981f08375f7528bb9d0fd3348b2f00999d515a24ad345491836f2822535e21391feb4a0a948b277a04b8a6af8e89ad437481aa53a376b97e5b11896f75ceaaa032eb985147f2e4bd4a729e9b64d1b863fa852193012184488ae3634322b59fb63ffac42da9786060e6bb6e6878209377e22a9903104743f7ceb35aaaaf6868072368b5cf65d4f78b805f849e249c9c7d1f7751663caa58bbe978c677358a3c1cec5abb21e44b8950416f77ee0afae12968252d49c2a9d16a9844cb4bb111492140517d0aa9214fa85e3012423dd46f8f4d8e649020bbafe8f1e73762ce1cb2e733c51b180cb5370203010001028201006b07e6dce2127ce5d45cb922c93b0014982558a923759e4b1f27fd3619fcbd4e05ae8fd975993bbef57c72f6405605803c337ceeaf0428213f15efdd374f651d7ae016f217cd6582db4ef43b3e1514cbb179507ef90d54420d86705933dcb12a9c28fccda9a844cd67c33f11e386866b53d1f89dd91f62128a609103b5c2b2543939ba84a7bd88f876433754c24b72f3c50cdae49e43283b486515a5ca287ab18b4f68c0e5a0fc5b08ffa619cdb2e4bec2e4aa0884dc27f3e5c2e3d22122e68e0ec5214cf3b5f1ffe4915c692b9ad7e225ce111ed91807b8584d3ff84c66a55471fddde411ddb7cf131e529beee2b38590805db780a599c317e5e4b17d56dd9102818100f7d0541ba1e580a1281f294153095013cad88f9d4d41586ad48849a641349ddd797b6a73c6c690c29570a745ca10c8babdb714671cce3c0b5c1c2e531adc6ee1750c4610b2271547fa87956ae994c53a239ce0fbf62caf9c25003cd7ac9b39bebc93223e0218d532877a56da897beae8542045cc1be8bdbaff5e49bfb44cdf0502818100ba2c18daa8fcabfba1484a19ae000304ec6f3a2f4d4058e6d156cbe7c3a9e0416dbd514027d236c2d613df1fc03c8b9006762f31b37b4167661eeb4c473e88bc9afac1565cfb27ecabcc204c2eaa63ffc0a22edb61d64d710da14dcaca2026d1a8ad42836aeca52d68f9a142a3c533040a10c5518eb65e00e7d073f47eaca00b0281801fcfd98c368b3ca8f37a94943331a5daf4963a516a2272543c7646661646c7e12f801d5941722a112097f69129f05fa441486851184c8d3eb413560b0b0eb319342a6030327e7be7e28c572d03513ac44ce00dadaa9b6febae804a4f317437c47976b5d599f550210d6d320b19cd1389c18ae70adda651fcd85d65403bc8067502818010407336fb537b4bef0b59749e6cdfd69931287a229b40677dd4bede0f858fcf065e656e5d4b8b7e3ca3e571671da1ed43b323718a4273362c82fc755f2ec54ef99474362ecdb9f17e19c6a3ffdaddf9e07e07eb1cc25166521347b0312ed754ac0ddbe58efaf37c6052925237ebaa056b3f858a161433668ed5f29960497f7b02818100a1e9ff8b24499fe1db468cc0377b30bf35788214639eb1ab54801b77d5e9b27ec196cb3cbcaebf9ab57282cdbb811bab96773b40bc5856c3ed6bcd5743cde5fa897e5733939cddc445dc9590a753be7df06dfeaaebc7365758696d1cd2a546a5e828aeda960c812cb41178caa4e0fcf9f295e595b74d3dca4ba742ae6593d666")
+ ),
+ attestationCertChain = List(
+ RegistrationTestDataGenerator.importAttestationCa(
+ "MIIDSDCCAjCgAwIBAgICDMowDQYJKoZIhvcNAQENBQAwZzEjMCEGA1UEAwwaWXViaWNvIFdlYkF1dGhuIHVuaXQgdGVzdHMxDzANBgNVBAoMBll1YmljbzEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjELMAkGA1UEBhMCU0UwHhcNMTgwOTA2MTc0MjAwWhcNMTgwOTEzMTc0MjAwWjBnMSMwIQYDVQQDDBpZdWJpY28gV2ViQXV0aG4gdW5pdCB0ZXN0czEPMA0GA1UECgwGWXViaWNvMSIwIAYDVQQLDBlBdXRoZW50aWNhdG9yIEF0dGVzdGF0aW9uMQswCQYDVQQGEwJTRTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANGd7BVHxhaZASB9Od4xR5Uf6VqpMnkP0G6Hcx8MdMejDyyjS1mW9IIi60gTprfAbzkg/exLJ3NzVhYME4CRuXH/0R1V9EEfZcUBfejh/weS7zJq6eVh7iu2KK77omvorxYhm2HJ+ca/VMaXnI7qY8xBUwosAgZaiQ9memhaRW+cHWqB5kz0KrofglAwYwBMx9RhwgWoMHpNxZxNOkmQgQMeSj0YnsKa5De2jGLbjv3Z9v0E2inBhKy84b8JOikRHl7KIhdd9FNZUlhLPpCRV+dpLHbNEGz5El0AGlGShTVHmK1OfvpeRbHzTl+4aa2pJDh0zHWfp6nVhMICXBTIy3ECAwEAATANBgkqhkiG9w0BAQ0FAAOCAQEAi31/zlevuJZEQlDo0Jn5bITjNjYnyTj8FbbSoaVRSHTTnO2csdhr6RtT7xOd8pU2BCk6owolo4feEu+11Lh3v3vRoeM0qEjczuvmec4FRA5gYwabQthmYpg4m9wg3QxbOv41NyMhJATuvi5LlamnXa+p6dq7pff8GdfylBmIqKxw4bgS+CZSjf58ACJ1tXZUSNIkZ3nWzyAbspdt8d0sFGHyaHBv6aWtM/1l1YxhB7D7x9PaIibun7YwSrOB+TQiI9/tiUmZMT8cAT/dQyoq3hcctHVi2dSooqbTPuu6twk04knc4D4Lh/2/M/Ghg/xpfJyEPMlJNQpUchOofXXRAA==",
+ "RSA",
+ "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDRnewVR8YWmQEgfTneMUeVH+laqTJ5D9Buh3MfDHTHow8so0tZlvSCIutIE6a3wG85IP3sSydzc1YWDBOAkblx/9EdVfRBH2XFAX3o4f8Hku8yaunlYe4rtiiu+6Jr6K8WIZthyfnGv1TGl5yO6mPMQVMKLAIGWokPZnpoWkVvnB1qgeZM9Cq6H4JQMGMATMfUYcIFqDB6TcWcTTpJkIEDHko9GJ7CmuQ3toxi24792fb9BNopwYSsvOG/CTopER5eyiIXXfRTWVJYSz6QkVfnaSx2zRBs+RJdABpRkoU1R5itTn76XkWx805fuGmtqSQ4dMx1n6ep1YTCAlwUyMtxAgMBAAECggEBANBdxSHaOOSZr28WTAG8xsVL9XEzo4KH388fQaZpgWQ5iIn8wJgL4H3ELFF3h1A9L9KAIylSA6NV0QsVcgVp1gemHb6lhKl/hnBw7TIkBJkIzFE3yc1ErbYx2vsmE+xkXjcHrSdl2K5h3umSKARApneRr/P6jwC12my+l4tHwKIRpMEO9Bxj5bTLp81uGTOT68B3nOiB9UJQ3Anb5gDTWalSnRZIZy8oS4cpfFAGrN7NE9Amm4JaK6FRzTAD/Z2nuCmOZBAzlx8qQJhr9bJa3DANwhDJux2lPpqldukpOd2N5eHXXNMQ85qlcber74o2TOqtRXP5fER07GAN99oaImECgYEA/4ca3YzJChGdVCTHTCllzHqpyLAT8+nv4hVjQp+H/RQc6ZkzDDRweMCQlfnvtDywT9H6kRHsZM1Px+GYomEeHaHEMU4xZAaKFuny7ohjx8nxHFdSYCNfnmq9WFowAxN61O2a8o7zXv+A7OpRzrKP+g+hDnrikASyqat211VMQN0CgYEA0gEYkAjr9FJFcQoyly6/sCjIDnWkNyPUa7+4ko1FWB2UDa6eaq/eSMrefg4OcIviije1KcEhXAeU1+ciLrI2jdSxcnIcRikbd8+oAVHzZPTOKy4nP0QKoBulvTN/8/Zu715qF9lmrZfa0jQLYs1dp16ZeRSHcaxYpjUBlLO4oaUCgYBld5jLcSRDw0reJtyc+bNaxzq0XncN3E9NT4Di68ZsUJhKinMi3Y/r40uGwoDU6WR5zb/Z62wbewu7K3IYyMfUrG/jxFEIjzA2eR/maHJ221HLF0G2u1U06t3VP7rg/dNAyjlFKE6r4nmnmkRx96YEfkBOJ63f0n2/sj62s0BcYQKBgQC3zV3CMwzRenBsz5AX4kLD2+29OhnQaPuyksro+dyHktvSXdMpbWQQMf8qNQNOXiCY+MkHEpIwCjKsBRBV7oTw/geRM26rua7g3k8dWKy+38TS5kJTjSn/mDMntbt3u2i8+NXCqfTEWvSaphKRF02w/4sz/lPNmhq83gfULriaQQKBgQCu/gBexP0ImPy7O9MsOE9Zyi4FpOFB4X3KbAUjaev6grxrKo/cxc3M/1GsZ4DNQq4PrmA0l6yEp0FJD70v2OLsxtxNkHTKiMWb2cvNr1IwmYRUW3YGW6N+VCXpXrri5NSNMTeuMvfj2j60Hu64rUJn/MoAo1UGJpJ6FT9vCjvHsQ==",
+ )
+ ),
+ ) {
+ override def regenerate() =
+ TestAuthenticator.createBasicAttestedCredential(
+ keyAlgorithm = COSEAlgorithmIdentifier.RS512,
+ attestationMaker = AttestationMaker.packed(
+ AttestationSigner.selfsigned(COSEAlgorithmIdentifier.RS512)
+ ),
+ )
+ }
+
val BasicAttestationRs1: RegistrationTestData = new RegistrationTestData(
alg = COSEAlgorithmIdentifier.RS1,
attestationObject =
@@ -867,6 +925,74 @@ object RegistrationTestData {
)
}
+ val ValidRs384: RegistrationTestData = new RegistrationTestData(
+ alg = COSEAlgorithmIdentifier.RS384,
+ attestationObject =
+ ByteArray.fromHex("bf68617574684461746159016849960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97634100000539000102030405060708090a0b0c0d0e0f0020c28bda3318d7721b2d62f1ac5bc3ba6cfa634dce5e1613c42c54e9c2920533dda40103033901012059010100a086d9533f5cd8550dd2a5a1207b3c18845cc20dcaeb40c5a148c2a1ab3b1d34c73f7b2069b39de1e22032eb56077bb09f2025d93d6dbdf66cf200227268caf0de22f6d4b68c36aef28df970bba05bfc9c43073dc780d61ce76e55d02810ef9f835988a3e86cce1b9af830497f47176f9fbe1ed924c0ccccbdb582850c4d39a2bbefa9ee59296e247c684a49f1cbc0df144b3cd7355f732b0da221b397438584fd860a8f243595290a2a26ce7244c9d1a52f669a14ed92ac253b68fb37d489c3d0b467e9755a08409c1000afc5b46d71999c9453f3c0a168e28e950f871e67ad977a99fc5c12a6d732d3d646fae44cdb12ba8bec46347a993d9059ebf9ddfdab214301000163666d746374706d6761747453746d74bf63616c6739010163736967590100559308d2dd2e8fa7bb4e0c11f904e3dbe6f5f695dd59a47ede3fc3f4f8c532aa19db498215ea90fa6aa1c383620da9ec321918aacd61df83d1f6602e4369392df2d2763e0895d62fcd62c8c7769cbc3e77c04f46e195e01df35beaa4e760e6806b471d231cde499959c5323fa0fb9c2292fecb9fbce73c2647d8092250f2a03b9c67d5d5bef65240f08b80e0d278d98a6fe1e4738e4e46526aaf48aeac11c91673a37be6f912eae880815ca63f968991692c43f0c668e53608ac1086a8c892915f4541a22e84b9abafedc2e70e9443bdd3375204192d8ab01cfba2652e270672e61ad3a8c274ee9e296c29d514b3f5ff52b23367f52a1429e89aacf6bc0e810963783563825903913082038d30820275a003020102020211f0300d06092a864886f70d01010c0500306a3126302406035504030c1d59756269636f20576562417574686e20756e6974207465737473204341310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b3009060355040613025345301e170d3138303930363137343230305a170d3138303931333137343230305a300030820122300d06092a864886f70d01010105000382010f003082010a028201010080d4fcffce24bcde9b675454a95f42d9de18a98680c8919c388b58510e9412adc58c3576f4604a3cf3a4335bb318e6b2bc7e8f4d314b269948bf2fb76ce698d0b117a059281d8aff63860372111cd27b1b917f6719a5db9d24654fcef58ba10617c47381bc18729a90db38f9722eb326ef59ee9a59ca388409b1413ddb51afe5949ef5b588d7926c40f27fed80e8912b461dcce555726400dd75620da2822975f7e9920e0e711872c333af43429dadde27a3114a6b4d37927846df77cf2fe35dc94851fb753ca3371faab3a33cf6ee573922223863ddc1d5dd89bec4c66ee3cd54837905972632bdad6492262eb815c7579a6f677f878f311aa51278734f1aa70203010001a381a63081a33021060b2b0601040182e51c01010404120410000102030405060708090a0b0c0d0e0f30690603551d110101ff045f305da45b305931573014060567810502010c0b69643a30303030303030303014060567810502030c0b69643a30303030303030303029060567810502020c20544553545f59756269636f5f6a6176612d776562617574686e2d73657276657230130603551d250101ff0409300706056781050803300d06092a864886f70d01010c050003820101008dcb1be0feeafd6d2b06959868df474fee3b78087919163eaf6bfce38143653a4bbbe5ef1634149786d1765a0fd4ca5348fd84198025fcf6442a18e97f9e0b6d669b7f49ba4ade87dc6e2584da9ac1b5072b7f29a211178f5717730d7306a9bdc8dbf0a4a9fbc94b15d39e6b12661ebfac3c29bbe5e0d154fc833f15e41717e49133656fd130bd63bee22c4e7e54474442eed20158d8a98e861cf3e55742696e5d4e899ef7a63b06c75ef1460caac67d2883ff55d1fbf791db1eae3ae2c685c3207dba8fce74a50419c874821d94245bed7c22d84097407f3f74327c155a7fd8caaa33fbceb95a4907315e3bd09599ee682b425130dba69824d5755273a0a069590367308203633082024ba003020102020210cc300d06092a864886f70d01010c0500306a3126302406035504030c1d59756269636f20576562417574686e20756e6974207465737473204341310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b3009060355040613025345301e170d3138303930363137343230305a170d3138303931333137343230305a306a3126302406035504030c1d59756269636f20576562417574686e20756e6974207465737473204341310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b300906035504061302534530820122300d06092a864886f70d01010105000382010f003082010a028201010090bd7c9cfcccffa596fd45a4586d14906a4ff94826756b0c1856ea3f1ae151ae95156c3584c3cc08bdbd866a8291735b27fefec11848766cf964645efc47452ea59a13b6ae2c77a6a2b37e0c74ff9af5226d3a12848e7be84d809cf81cf1b46e0af5d7b1ce8745dc8572c60f79a331405dc98530d62c8be77e14a9da8e440a4a14192b2eae1d4bdf714728c11b9d3eb82b8d6371ab0a0a295d7d691f9432f4ab859133d1cf312949fdcbe2138d2b7e4399b477243eaf84063d16afecaddbaed16d915a8740eace0a88c0faab3db51433187c5fffe3077249dbd8021c0f3e8950cec31d46c7d6c4aa200c00ac70d7ba583ee0dfa9877374c9981fa5d1c3d7c90b0203010001a3133011300f0603551d130101ff040530030101ff300d06092a864886f70d01010c0500038201010007f0ebe81b798b51e85a6e4eae0b725c46fcd7b65bf30322980e39bd0595b84e381413ae51bc9a7072c8944012edaa41a5f40a28e06916bb8cf8946962f240352ed3c57fc03eef87f0d832196677b46b1ee5f40358a9a24637a637078338b333a75f084f8912fd3e4d1a3469934b8510b9e8ed0b5a762e82c1b8d7306208b8a43b8aa4adc15104da5ca45c4de158094d588a600c48642ef6a008aeb1f8c4f36d1067e75fd06c1b568c1d2ab299f0a8cee0092dce2cd8f2bbf1bd06e9fcc429da23bafcbae8e2946ef390532ba1112b46e7d64e2a30706655d815b62da3f3e94bb386ef3d6e57a9df3f154deb87ca588b394cdc6ecf1bb0d5ced92f8382693d736863657274496e666f5889ff5443478017000000304474eddbe2aaad0976688da49bb2427d2d18fd545d7abf10c699ce19c8760a5a3a4e734eb62428defd21b88daca43f8b000000000000000011111111222222223300000000000000000032000c9f655881ca92c820f85e04e5403a5e4e8b72cc3ef68f5d0f35fb5dd47a073160671851f641932b9aa95bc96024156dae00006376657263322e3067707562417265615901170001000c00040000000000100014080000010001010100a086d9533f5cd8550dd2a5a1207b3c18845cc20dcaeb40c5a148c2a1ab3b1d34c73f7b2069b39de1e22032eb56077bb09f2025d93d6dbdf66cf200227268caf0de22f6d4b68c36aef28df970bba05bfc9c43073dc780d61ce76e55d02810ef9f835988a3e86cce1b9af830497f47176f9fbe1ed924c0ccccbdb582850c4d39a2bbefa9ee59296e247c684a49f1cbc0df144b3cd7355f732b0da221b397438584fd860a8f243595290a2a26ce7244c9d1a52f669a14ed92ac253b68fb37d489c3d0b467e9755a08409c1000afc5b46d71999c9453f3c0a168e28e950f871e67ad977a99fc5c12a6d732d3d646fae44cdb12ba8bec46347a993d9059ebf9ddfdabffff"),
+ clientDataJson = """{"challenge":"AAEBAgMFCA0VIjdZEGl5Yls","origin":"https://localhost","type":"webauthn.create","tokenBinding":{"status":"supported"}}""",
+ privateKey = Some(
+ ByteArray.fromHex("308204be020100300d06092a864886f70d0101010500048204a8308204a40201000282010100a086d9533f5cd8550dd2a5a1207b3c18845cc20dcaeb40c5a148c2a1ab3b1d34c73f7b2069b39de1e22032eb56077bb09f2025d93d6dbdf66cf200227268caf0de22f6d4b68c36aef28df970bba05bfc9c43073dc780d61ce76e55d02810ef9f835988a3e86cce1b9af830497f47176f9fbe1ed924c0ccccbdb582850c4d39a2bbefa9ee59296e247c684a49f1cbc0df144b3cd7355f732b0da221b397438584fd860a8f243595290a2a26ce7244c9d1a52f669a14ed92ac253b68fb37d489c3d0b467e9755a08409c1000afc5b46d71999c9453f3c0a168e28e950f871e67ad977a99fc5c12a6d732d3d646fae44cdb12ba8bec46347a993d9059ebf9ddfdab0203010001028201010097b579d47c3091df28362904733f245783586aa9405a3f17c7ca8ceedf75f9af349321194bec4dccf9b936864502c379f3991d4c070b1d19b472ad7fe0a27b1152ceb679e79ff1da3b2fc44b2f776917fed23618c3e055fa711a4c8d72203766886b6880879bb4da500639146cee520ed368899cec682de55d711a4e0587426cb1c20c0d9c019de56317604002bfd501f96218dac96fba922c418fd812c3a3103931ac04db859d1a18125cfa448dc02c83c365ea991ddce45bb1175aa4f348c4a75d24f084f39929a40c91bb2c782681e48679cf0c1e635c588e8139658d74c83a2e5c2a530b7216de6dc0b2398354d3a6547ca7d4ce353d90ecd5fbeb124aa902818100df274427e772a8ecb7c76557575cc744d213d161e4ea396488ae30adfc35eda523f5cc11a9afe05cd6c918820f7ce287d5fea801d70c5d9f1dbb78ef8b495706af247f6ac0a0ce4f2cc189168cf853a41e027e43ceebf4e4c470b66a21b80d5556981163855cba88a6f632e622bcb9043a44521874361673c97a7c4e949cad1d02818100b827b91b4fc55a52fd566106944471ef25369ac26b24e1379d12476cab605db75e2fd9154f86c62ff7cae534039e04b740e232a8f5e3a98eba4b12cd94461121b9a4206f7ce320ea575ebea879ff463073f323b13a8746f2778e6a15d00abd0a13e13d0af80247a445861bb47de95461bdff04f758ff500e3b766d377b7103670281804690b028be33afdf4b2e2e89b4028eb0e08d8bc49d12c41b5a6d5acf69d5d3d448cecd3d389f791f627c2cd7d3f5f5dc667b24bd903744d3b01f3c5ae37cc99c3f7e171cb6d522e83e8ae4c2d0c92609dbc386120338f233f53a7f34887d1f1a414bcd13df7437384733cb5ca2d772da3762ab63383c725522fd2c99dcbcbeb102818069e3155179edbd40e8c0292bf246e4c8203aa483d3bdb1ee1b57ae4ff2be87446f58cdd6ae128d94794365c521ab5384d73ef8e823f292c529a30f1dbbfb09d0bd807cd1fe1a4f0bcfceff8bba122916a52511c9cf20878fd564c2e4e5e9b6c6bba59046e551d245c76014401501fbedf3a45603af5da677788360cb3d243f5302818100d8e0387a2fd83c82190026e8795b6f8834853126bdee577aa896873ec5c4712cf871a9444d3bdd752481fb710b18129b6a2adf30f51c8d2337f58d974cd1a43720dae6b35401a87bc7803d2cf1774981ffdef01419dc1e7eeb65b24e27cd61ff73a27023cba8cec21a3269f327d8de8faf9cf9b4c74e5fe63329e65e602e5eb7")
+ ),
+ attestationCertChain = List(
+ RegistrationTestDataGenerator.importAttestationCa(
+ "MIIDjTCCAnWgAwIBAgICEfAwDQYJKoZIhvcNAQEMBQAwajEmMCQGA1UEAwwdWXViaWNvIFdlYkF1dGhuIHVuaXQgdGVzdHMgQ0ExDzANBgNVBAoMBll1YmljbzEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjELMAkGA1UEBhMCU0UwHhcNMTgwOTA2MTc0MjAwWhcNMTgwOTEzMTc0MjAwWjAAMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgNT8/84kvN6bZ1RUqV9C2d4YqYaAyJGcOItYUQ6UEq3FjDV29GBKPPOkM1uzGOayvH6PTTFLJplIvy+3bOaY0LEXoFkoHYr/Y4YDchEc0nsbkX9nGaXbnSRlT871i6EGF8RzgbwYcpqQ2zj5ci6zJu9Z7ppZyjiECbFBPdtRr+WUnvW1iNeSbEDyf+2A6JErRh3M5VVyZADddWINooIpdffpkg4OcRhywzOvQ0Kdrd4noxFKa003knhG33fPL+NdyUhR+3U8ozcfqrOjPPbuVzkiIjhj3cHV3Ym+xMZu481Ug3kFlyYyva1kkiYuuBXHV5pvZ3+HjzEapRJ4c08apwIDAQABo4GmMIGjMCEGCysGAQQBguUcAQEEBBIEEAABAgMEBQYHCAkKCwwNDg8waQYDVR0RAQH/BF8wXaRbMFkxVzAUBgVngQUCAQwLaWQ6MDAwMDAwMDAwFAYFZ4EFAgMMC2lkOjAwMDAwMDAwMCkGBWeBBQICDCBURVNUX1l1Ymljb19qYXZhLXdlYmF1dGhuLXNlcnZlcjATBgNVHSUBAf8ECTAHBgVngQUIAzANBgkqhkiG9w0BAQwFAAOCAQEAjcsb4P7q/W0rBpWYaN9HT+47eAh5GRY+r2v844FDZTpLu+XvFjQUl4bRdloP1MpTSP2EGYAl/PZEKhjpf54LbWabf0m6St6H3G4lhNqawbUHK38pohEXj1cXcw1zBqm9yNvwpKn7yUsV055rEmYev6w8Kbvl4NFU/IM/FeQXF+SRM2Vv0TC9Y77iLE5+VEdEQu7SAVjYqY6GHPPlV0Jpbl1OiZ73pjsGx17xRgyqxn0og/9V0fv3kdserjrixoXDIH26j850pQQZyHSCHZQkW+18IthAl0B/P3QyfBVaf9jKqjP7zrlaSQcxXjvQlZnuaCtCUTDbppgk1XVSc6CgaQ==",
+ "RSA",
+ "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCA1Pz/ziS83ptnVFSpX0LZ3hiphoDIkZw4i1hRDpQSrcWMNXb0YEo886QzW7MY5rK8fo9NMUsmmUi/L7ds5pjQsRegWSgdiv9jhgNyERzSexuRf2cZpdudJGVPzvWLoQYXxHOBvBhympDbOPlyLrMm71numlnKOIQJsUE921Gv5ZSe9bWI15JsQPJ/7YDokStGHczlVXJkAN11Yg2igil19+mSDg5xGHLDM69DQp2t3iejEUprTTeSeEbfd88v413JSFH7dTyjNx+qs6M89u5XOSIiOGPdwdXdib7Exm7jzVSDeQWXJjK9rWSSJi64FcdXmm9nf4ePMRqlEnhzTxqnAgMBAAECggEAMZtRi+JBjSQeLKxxKQKQSDnXvzcWUaSXxcIKELQPWh3lSjawBispisy59jih/r2eJyyIW03WxRcSxuNFur4UK491LH4ID1AdRKIuo3ZpZbaXh+/JsDuIE7sW86MaM1iecvpnC5Z0x3QywObwTgIjY6OYOmLenhoi5WSGXZ4clyDAjq6lz/f6njX4nLN9biNH0nPwgxapbFaQ2p6Qw49WFztkDXgw4YQA9CALYFcOL/fUauOIaWKAoFSXRwfiLQQUwGaPlGVOGGJnvQDrTSFnUqqNXqH9t/2zqO9xomjCgXRU0b/zba3zsYR0J0hYUh0YoHO/Hk1AFs2VrF/FqKEIsQKBgQC/FCI1hkxvm7C2Rt/ByDVEdSC98Hmoau0N9z6q1qor/EZVeKTKgYVBczbu0QCWfL2WLJ93qGuENZDUOBuh5ueUO/tMXmqfaperUdWWk7L6ElA/inxiNVKq1v6KHFOQqBNVJDNAmkcHZ6gWnFVaHjXmxBEvBKSQGwAWFmJJuhqfOQKBgQCsmrC4HRqybjHsHSzZRd3dcgbmKiLtmdbw032wiyPiv6Vut7nZ0NPmiPWqaxyZYtX16vI4kEVZ8IluasjnsaIPcZKIgAI4NDmkQZGQ4DOgeUcRTpFN2sHufGxmmf+tM7v2fBC7Q0gnAnfM31Hsz8n/dg/qc05jbDiZ5aS/yamo3wKBgQCC8phTGBtv7UGYWU/k7IDczmxG3vNw4P5eMM/Iol5y0GufDZPZmBOre/rshV0ixI/kx+XtSgWM0GzVkzIUrTqNUuHwP1BQuesBJI78p3HjgQNv2EdPwn1JyRcdrTXzj8vX8HwTTOdagsYl4LN5k/Salkm0cDka7PYNLP/kyN6PuQKBgFKZDCxvMRFmDlnRdF7dQljwckC+tUxCrEs+yg0r6JZf48jh/vwvJNhTfkx5SYxVcdJnBlbvI2Dw7LN8Qnwt00HUtazApU9EHrlt7z0HLW2D2/B6SqqZHukDfdRzqZi3AyHnKRKUFfklAzN1Qv0ySpYHZ4Jof4Cwjz2GWZq15Iy9AoGAX9mbT8QvC8/vLnxmvgz98tsDVarT6Tx7wcEMgyNC/2CUaPUf8P8rkP/Zfym/nTC8Ew/c5N5nLSivU/sPeLjpSQ/cFGZXXysTsVARSeQAvm4UvQy9+b6YoKWl/IOFWISp7TvKjpH3mPiJ4iX37xI7JeGcpUhFbj/q89NNTQMuVJo=",
+ ),
+ RegistrationTestDataGenerator.importAttestationCa(
+ "MIIDYzCCAkugAwIBAgICEMwwDQYJKoZIhvcNAQEMBQAwajEmMCQGA1UEAwwdWXViaWNvIFdlYkF1dGhuIHVuaXQgdGVzdHMgQ0ExDzANBgNVBAoMBll1YmljbzEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjELMAkGA1UEBhMCU0UwHhcNMTgwOTA2MTc0MjAwWhcNMTgwOTEzMTc0MjAwWjBqMSYwJAYDVQQDDB1ZdWJpY28gV2ViQXV0aG4gdW5pdCB0ZXN0cyBDQTEPMA0GA1UECgwGWXViaWNvMSIwIAYDVQQLDBlBdXRoZW50aWNhdG9yIEF0dGVzdGF0aW9uMQswCQYDVQQGEwJTRTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJC9fJz8zP+llv1FpFhtFJBqT/lIJnVrDBhW6j8a4VGulRVsNYTDzAi9vYZqgpFzWyf+/sEYSHZs+WRkXvxHRS6lmhO2rix3pqKzfgx0/5r1Im06EoSOe+hNgJz4HPG0bgr117HOh0XchXLGD3mjMUBdyYUw1iyL534UqdqORApKFBkrLq4dS99xRyjBG50+uCuNY3GrCgopXX1pH5Qy9KuFkTPRzzEpSf3L4hONK35DmbR3JD6vhAY9Fq/srduu0W2RWodA6s4KiMD6qz21FDMYfF//4wdySdvYAhwPPolQzsMdRsfWxKogDACscNe6WD7g36mHc3TJmB+l0cPXyQsCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQwFAAOCAQEAB/Dr6Bt5i1HoWm5OrgtyXEb817Zb8wMimA45vQWVuE44FBOuUbyacHLIlEAS7apBpfQKKOBpFruM+JRpYvJANS7TxX/APu+H8NgyGWZ3tGse5fQDWKmiRjemNweDOLMzp18IT4kS/T5NGjRpk0uFELno7Qtadi6CwbjXMGIIuKQ7iqStwVEE2lykXE3hWAlNWIpgDEhkLvagCK6x+MTzbRBn51/QbBtWjB0qspnwqM7gCS3OLNjyu/G9Bun8xCnaI7r8uujilG7zkFMroRErRufWTiowcGZV2BW2LaPz6Uuzhu89blep3z8VTeuHyliLOUzcbs8bsNXO2S+Dgmk9cw==",
+ "RSA",
+ "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCQvXyc/Mz/pZb9RaRYbRSQak/5SCZ1awwYVuo/GuFRrpUVbDWEw8wIvb2GaoKRc1sn/v7BGEh2bPlkZF78R0UupZoTtq4sd6ais34MdP+a9SJtOhKEjnvoTYCc+BzxtG4K9dexzodF3IVyxg95ozFAXcmFMNYsi+d+FKnajkQKShQZKy6uHUvfcUcowRudPrgrjWNxqwoKKV19aR+UMvSrhZEz0c8xKUn9y+ITjSt+Q5m0dyQ+r4QGPRav7K3brtFtkVqHQOrOCojA+qs9tRQzGHxf/+MHcknb2AIcDz6JUM7DHUbH1sSqIAwArHDXulg+4N+ph3N0yZgfpdHD18kLAgMBAAECggEARzbD+iWz41Cg4oqCWvOIe6mjIZ6rNXhu4gbZiCT8mYjRV1H/XwxK9j4M4vbCuTfNsPcYdv8wn/vsFMYBqhSS6GmYTnzCY6SXO1Qe/5gNLzvPLXi2JbxJILoJPrIg45eH0SK2doiMLAZdLmRettVhZS7/+OVXa2GGi5U1IRCAT6L3LoIxq1YycV4sFIl51B5TNIluqm8VHejlQ2mlc0TthpLLhvwWFQvLp3+qt5Il67MXTexEFTUMjCECCidqR5JM67rt5lzJKwMEi1GqtLd23A1p6raEyVXfS8wQZL4SRzu1JklE7nUM4nKukEFUNWpUUp/SYXqycjSnpWDYii+QQQKBgQDOiz4JcjGvDd0c0QdiAeobhgW5BcC5i85WKupTuQGPwLjpZuQJKnasMkXpf0sq6Gg3O8W89psj9qzNPnfRaY67li7Ymo34WLVGg7+UdDPl/7JN8SR6jPlTdH96iem2hLGnJQ8uLi5Ay3Fpj9/GVQY7xiYKWyTWRUGq+qbv/Y+FIQKBgQCzZcpm6z483F8IWWmBGUDRHjNx5Th2tIA9A0E0eNCsIMJ+EuBZCRqxpg487QsLCuOLz5NCGTSWKkZQ3XU9HynzKaDZY3zwuqw0YfConpmIjYnwmfaq2/4bKC22PTeVvhb+ghkG05U6YWmolgkGM9zlpMyb7VXrd5QbNSPdCuJcqwKBgGVu2nuXGjFHFLTHLuIB4K+9pOfVnG1C7IVCtCuDqvGnCuiNACZENV2hntXqDsc2tZ+Seiyvy0bhKMHvELbGKTOUsNLtLBWvsu67WwWT/7zPUwiWCX0p6HQEvWo3epCJIBneyFK8mTh28O6PmqzzKomGaT4ivrd/8Zz/VaZ8ltQhAoGATXmlWqM3grMtO37ZyI4uZuFjGEoFA4baZv8T1uRiQiP3utjOGMWMyLHNLlS00zUFpiikEQSvqDZjnaK2cgoWZNRSie+kUpZbrlepxjiQV9/Ada8YTxuo9vN4Il73tWydo5Zt1nvj042kQtFg3lPhjy+HycNKuuEujj152olLzvcCgYBNKJCD1w40qWy51usqrFgYLA0X8dBGc4yv1AEJVknGl/bfhlwovc82PgzEGLq9fXjKHQEeQtY+K56erFpCrLBU7JPVtfck9LX+HGN4D6LD7ycTIQVtA3AwW/b58OQqIqULO4++Vc3BvI/qoX5oBnnpXn7XnZUbtUSJF/4GxGJ6Vw==",
+ ),
+ ),
+ ) {
+ override def regenerate() =
+ TestAuthenticator.createBasicAttestedCredential(
+ keyAlgorithm = COSEAlgorithmIdentifier.RS384,
+ attestationMaker = AttestationMaker.tpm(
+ AttestationSigner.ca(
+ alg = COSEAlgorithmIdentifier.RS384,
+ certSubject = new X500Name(Array.empty[RDN]),
+ certExtensions = tpmCertExtensions,
+ )
+ ),
+ )
+ }
+
+ val ValidRs512: RegistrationTestData = new RegistrationTestData(
+ alg = COSEAlgorithmIdentifier.RS512,
+ attestationObject =
+ ByteArray.fromHex("bf68617574684461746159016849960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97634100000539000102030405060708090a0b0c0d0e0f0020720f0707397ade996822ef5474c9a796b1a89113fc7d79521e7008f414e6948ea4010303390102205901010099116dd8d7264698788f7916ceab8950ddef91a6627aef32f6fb682cb9d581ba4d979c22ee99b456e518399af900dc41b712086a85fa1f57b14e5a7e19c94c283024125f43ee03e62efd10470172cb890c3fddecef2f32937b74172175b66351691d49d441ebb6305d02fa844a2e0566c9e96aac07d28cf91a4d4c975c03f630a606a43537cf48e661aa16d8a035e302f12ca4d529ed8ac82c57b811dfa7fef0a84d1e8d3b27d3cd0cc140f45c82a737239f470bd737b0c52202173b37941d106ee456f6853a4adb12c5b42006f055808a256b96aaeb7a3c44ad2c03f11484735e603da10c1e7d1bbc29648c30ea38c4bbd4a9cb78b939b5a58b2d8ff8a8ba27214301000163666d746374706d6761747453746d74bf63616c67390102637369675901004a0200672a612871d63d6c5ac4483d044d8af21ea2e18864a1e49fddaeeddc505b74e332a76119e5eaecdeb1bf8fc9504d3f0e2c09d3c83a9357315d201b67332562030aa6a8a3ffc6573f05a12b595b69834f6412a4e1ccd8209d8742703e2e0637ad2ed0eaf8030bf29cb1b1344a5e2e9b316c84930bd65bae7ef8fea18913ee300db3e99965532f7f062024fc4424dee6e583b635b67d130e12947fba7dbcad8213d1c9a37e7abf1ee145ac84e54f02208cf5411fed5e8119f3302b8f4f9ef010c5c037f72704fe4653d2c7431df0e94f34854c5d8337e7f267b1a3e7ecce40b683fb1b2a859a92396cfc673e2bd1b72dc2fbb2f91ed5542e838e31a8801463783563825903913082038d30820275a00302010202021ba4300d06092a864886f70d01010d0500306a3126302406035504030c1d59756269636f20576562417574686e20756e6974207465737473204341310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b3009060355040613025345301e170d3138303930363137343230305a170d3138303931333137343230305a300030820122300d06092a864886f70d01010105000382010f003082010a0282010100935b5d593d9f8f6eec753e61735c7228915d78e5e4ce715437cedbd6c7a684acfbc01807e6f3762453ba48a29613a766db7aeac1682d2e23def0a9d7e2366caeee90f447188b55036a956bea104e9a4d7075da6312853f4ec3a57a04ddf31aaf505ec93249ff3324bc93053a88322c6a51901439c25c6b615e451567dee94d937afd2dbfd6406875eb174ee0e00d9716de98ee232c2d5fa6024391dd7e5eca424afb8daae8c26d65523296a891e0aaa7df792b9de7dc94d250ddc85a8c562bd331cd7616e07b208e281722110526e2eeb28a830b6ca051599368ee5ebd1306a4398f1f8f214c410b7e24bff8ca7193774454747e3cd6b4d0ba7c361981662b210203010001a381a63081a33021060b2b0601040182e51c01010404120410000102030405060708090a0b0c0d0e0f30690603551d110101ff045f305da45b305931573014060567810502010c0b69643a30303030303030303014060567810502030c0b69643a30303030303030303029060567810502020c20544553545f59756269636f5f6a6176612d776562617574686e2d73657276657230130603551d250101ff0409300706056781050803300d06092a864886f70d01010d0500038201010000a2dc0579f375ba042eeaf34837b8aefd0e43e228263f93da51ab3e991faefa282290b496004aa60b6454bfc1ffcfd004e84be4a71a5069b99d6eefad41629a7b8ba5b576ad954bd50a232b4f69f7d7bf3ad1882d6f2602e3508df6a670df11c9afe773493a94b004795e622f7448c56e4e45c11aa1a20a5244d76559c08d0c53e9886d470724cabd9a4f4d7a21fa37059d28a9e1604998312b5820888167406265ca6b6e39087216f9601d1eb3071947319e87451e9af1077dbf5e91a23623091051371c942698f2ad831c437f3ab9fcf1e94c1728fa201d86d353adfea9aec127255e0ca9b95e3d676d4feb6f6d327ee8bfa88d5f5df42aca2b23644ab010590367308203633082024ba00302010202021ed2300d06092a864886f70d01010d0500306a3126302406035504030c1d59756269636f20576562417574686e20756e6974207465737473204341310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b3009060355040613025345301e170d3138303930363137343230305a170d3138303931333137343230305a306a3126302406035504030c1d59756269636f20576562417574686e20756e6974207465737473204341310f300d060355040a0c0659756269636f31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e310b300906035504061302534530820122300d06092a864886f70d01010105000382010f003082010a028201010081f2dad381970f5bd571081f92db0a78a660f45e27b0bd11d321e906f1c03a17b72660eff210aa908e82edbb3e42a6c781e37fe1c42025b899d12683ce2512583018ebd54b129559465dbd3fd231d95271e23a60403a0104785d0e17b25256f4dfa4b1ab4b7d60e8985664642059335eca925fd985d1165c460d7ab4dc33b1e8883d9bfa45893df6d3b67cabe64122e935d6b6b084b5eae512b18f726c4db58295cb198b6e320197a5e3421af3705299bb3ae31c82c2016c123c0afec05e980b0df8840c225ce318373eac9cfbd1ac9b53d9ae67a0e59bd0f620e5af738fba98f3645df4f152e42ccce1b0f4d2b5295e136865f8a5f8ffc061666dfecac0ce390203010001a3133011300f0603551d130101ff040530030101ff300d06092a864886f70d01010d050003820101005824dab316433c723cd4ced60e46e3e630fca9048ba190c6d5ecb293c0a6f8e6a5186f5edb87350ba4126c374ccd03828292258e4770976b151e190b462198198f5048d9511973c3b1daedd5e0fa38c0bfe397cdf806681116a3f059b43ab008f35eeea352e62cfda1f76c8a37493ec598474783a2fd0bad36af0cef4a2ad838978295e02b4d6defd60c9c5f931b7e0e9e9b6513a4873f9ce15306c5c8944be2d01178ea62db5ae6c3c538c9c2792943e9209e622417d20aea8c876521ec25367e442823d1a8b545ef3251fbc1d53a21eb2b994231e70e1bddf290a356c30e7e049d9f7ee00503ec36a17ba6da0b01da6f8b545880551aa9da44f484ce38d4036863657274496e666f58a9ff5443478017000000409b34053f3da1fca291f1c83578b9fe3e950f89bfe9a6ac802dfc3170d9436d95a1c1229e0534b0ed34fda227f58c970bfabfd804ef554187baf643d44b150135000000000000000011111111222222223300000000000000000042000dfeb6906ef87911b6e6ebd3126d8f2d2c6bb2d72a96aa1a6db27e0547264077fffed8664eb459611c9f8094935453401316f519df3e52ecc8032a218029d33c6700006376657263322e3067707562417265615901170001000d0004000000000010001408000001000101010099116dd8d7264698788f7916ceab8950ddef91a6627aef32f6fb682cb9d581ba4d979c22ee99b456e518399af900dc41b712086a85fa1f57b14e5a7e19c94c283024125f43ee03e62efd10470172cb890c3fddecef2f32937b74172175b66351691d49d441ebb6305d02fa844a2e0566c9e96aac07d28cf91a4d4c975c03f630a606a43537cf48e661aa16d8a035e302f12ca4d529ed8ac82c57b811dfa7fef0a84d1e8d3b27d3cd0cc140f45c82a737239f470bd737b0c52202173b37941d106ee456f6853a4adb12c5b42006f055808a256b96aaeb7a3c44ad2c03f11484735e603da10c1e7d1bbc29648c30ea38c4bbd4a9cb78b939b5a58b2d8ff8a8ba27ffff"),
+ clientDataJson = """{"challenge":"AAEBAgMFCA0VIjdZEGl5Yls","origin":"https://localhost","type":"webauthn.create","tokenBinding":{"status":"supported"}}""",
+ privateKey = Some(
+ ByteArray.fromHex("308204bd020100300d06092a864886f70d0101010500048204a7308204a3020100028201010099116dd8d7264698788f7916ceab8950ddef91a6627aef32f6fb682cb9d581ba4d979c22ee99b456e518399af900dc41b712086a85fa1f57b14e5a7e19c94c283024125f43ee03e62efd10470172cb890c3fddecef2f32937b74172175b66351691d49d441ebb6305d02fa844a2e0566c9e96aac07d28cf91a4d4c975c03f630a606a43537cf48e661aa16d8a035e302f12ca4d529ed8ac82c57b811dfa7fef0a84d1e8d3b27d3cd0cc140f45c82a737239f470bd737b0c52202173b37941d106ee456f6853a4adb12c5b42006f055808a256b96aaeb7a3c44ad2c03f11484735e603da10c1e7d1bbc29648c30ea38c4bbd4a9cb78b939b5a58b2d8ff8a8ba270203010001028201000d41281cedcc7fb276461e3b2e5c4640bd67205aa30e782616a3008b56f0391293e37bfebe608af0375858aca5c140516473e84ca91b5699765e0d91fbd3a587995b9647af8f2dc141f261f57417a7ae4f643c6866f1d454570d5f6f634d0ede9ed68d6d16e43d5b84c25c45165353de69bf8fa023f14489d1903e00a1542a7e3aae6929ba4ec754ec4c01864fcd1e774a3b7f090078ad8fe618e8b79c07e644ae34bdd0534b6cdee6c08018548984f66641fb026afdd85d3a44bc715b2aa2e8583af5a99ccc81c7d55317425501e5b5aa82bd0958a3b0c0b068349fdea13ea6f043300d78c316d5f358297f1a023469b839c5c52a0f459a76524a0a8856c2b102818100fb570a0770ee0eabb7f51fed98bbdb14146a28518128af749f854fabb680ff6a597a3f17ba968ec7b8c4e4acf964044828741375b2cf513bef4f52d1dd39924d64b9037d109c85cdf0ffe1f7d44e0f8802fc48979a80587a84a8200162bf7d197c237ff6b91aac742e0e48d3022639f50c49405d6d69b8d7bc23f2a125421579028181009be7f3abcb18e8dd2a96f96e0f37589ddbafcc78dae698fd0b51c317971b9decddf06b4c219f3380db4c4d71fcdc9e27cf59847f002c2296bf6600b8391b9c2cdca3af4af8a5f421845780152637df0c9790021b73f9926cc160b5935918d6cde92db47a6cf918d434bcd5d5fc3a7b0c48f122a7a851ba9eabb42bf6e223849f02818100a652e5ff209b58b808273d76b4d0f3dc28da4b4e0c63c9202b044441c4a73edeb8d1adf8dcf00f1259d269e591afbf29a52393511b0018a8c9e7bb4dc7d0f66122db5054adee76995ef76628e3a4b8a07021554485e8932498aecd673d5aacc575a1e46777fd0fcc5e41f3ad3749e6a6a3f7c19151fb5967e24803a2e20e0639028180062b223ffcd42a7a7db1e5828e459153059b2a0aea164f9d4b725bb6b63ad87fc3b43c7a91a5fbe2b04a8f91e000569d9a9d9f196b4753c30525a307a6f2c9b618b0bd41c91ebfcf07ae7299e39e384c063f236634ab7e38a15a133516445e535d537a9d916c35a847c1e4f0077fc4d892963fd9c4561f7d21ac0a454563445f0281803aa51050b258667e701df840ce5975f5e6706c5e58b5a5b6c9100de05a7c3d420f41bfdac8c0c9fd98036f0d12ed2c4faf5fc07215a0a7e4a4517009117b1a2c24126dee8cab6d6d56c6fd3301983fdf2c77ffe58f83e0175e6793c38fea968bce6b640f67389769c4fbd4664c9130d2eef6c819f621f199027f4e824027c303")
+ ),
+ attestationCertChain = List(
+ RegistrationTestDataGenerator.importAttestationCa(
+ "MIIDjTCCAnWgAwIBAgICG6QwDQYJKoZIhvcNAQENBQAwajEmMCQGA1UEAwwdWXViaWNvIFdlYkF1dGhuIHVuaXQgdGVzdHMgQ0ExDzANBgNVBAoMBll1YmljbzEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjELMAkGA1UEBhMCU0UwHhcNMTgwOTA2MTc0MjAwWhcNMTgwOTEzMTc0MjAwWjAAMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk1tdWT2fj27sdT5hc1xyKJFdeOXkznFUN87b1semhKz7wBgH5vN2JFO6SKKWE6dm23rqwWgtLiPe8KnX4jZsru6Q9EcYi1UDapVr6hBOmk1wddpjEoU/TsOlegTd8xqvUF7JMkn/MyS8kwU6iDIsalGQFDnCXGthXkUVZ97pTZN6/S2/1kBodesXTuDgDZcW3pjuIywtX6YCQ5Hdfl7KQkr7jarowm1lUjKWqJHgqqffeSud59yU0lDdyFqMVivTMc12FuB7II4oFyIRBSbi7rKKgwtsoFFZk2juXr0TBqQ5jx+PIUxBC34kv/jKcZN3RFR0fjzWtNC6fDYZgWYrIQIDAQABo4GmMIGjMCEGCysGAQQBguUcAQEEBBIEEAABAgMEBQYHCAkKCwwNDg8waQYDVR0RAQH/BF8wXaRbMFkxVzAUBgVngQUCAQwLaWQ6MDAwMDAwMDAwFAYFZ4EFAgMMC2lkOjAwMDAwMDAwMCkGBWeBBQICDCBURVNUX1l1Ymljb19qYXZhLXdlYmF1dGhuLXNlcnZlcjATBgNVHSUBAf8ECTAHBgVngQUIAzANBgkqhkiG9w0BAQ0FAAOCAQEAAKLcBXnzdboELurzSDe4rv0OQ+IoJj+T2lGrPpkfrvooIpC0lgBKpgtkVL/B/8/QBOhL5KcaUGm5nW7vrUFimnuLpbV2rZVL1QojK09p99e/OtGILW8mAuNQjfamcN8Rya/nc0k6lLAEeV5iL3RIxW5ORcEaoaIKUkTXZVnAjQxT6YhtRwckyr2aT016Ifo3BZ0oqeFgSZgxK1ggiIFnQGJlymtuOQhyFvlgHR6zBxlHMZ6HRR6a8Qd9v16RojYjCRBRNxyUJpjyrYMcQ386ufzx6UwXKPogHYbTU63+qa7BJyVeDKm5Xj1nbU/rb20yfui/qI1fXfQqyisjZEqwEA==",
+ "RSA",
+ "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCTW11ZPZ+Pbux1PmFzXHIokV145eTOcVQ3ztvWx6aErPvAGAfm83YkU7pIopYTp2bbeurBaC0uI97wqdfiNmyu7pD0RxiLVQNqlWvqEE6aTXB12mMShT9Ow6V6BN3zGq9QXskySf8zJLyTBTqIMixqUZAUOcJca2FeRRVn3ulNk3r9Lb/WQGh16xdO4OANlxbemO4jLC1fpgJDkd1+XspCSvuNqujCbWVSMpaokeCqp995K53n3JTSUN3IWoxWK9MxzXYW4HsgjigXIhEFJuLusoqDC2ygUVmTaO5evRMGpDmPH48hTEELfiS/+Mpxk3dEVHR+PNa00Lp8NhmBZishAgMBAAECggEAUsSP009cr0kDwfsO66gyavzzfrPKZ/aZ8lrbenFb48vx//y/e4amhlMNID1KhLGTgZYyA/6K2g7F63HK08H0G/HeM4c3jxNqPtS875THQb5be6b13PJBE/GqobXYIPONI1yKMBgGIujwjrfyH4vnDLTRc7rZo+WgpD2zf0tiyfJBWPwpwhVbqMp2OIFKg2iLMdoDOfEEFswEQySxjzi+/gMR/kPb2U1JKFM5Lg77UKNb+t9YvZ1Qd5BL7pAwbFMjhpLz3F2V569+wzdwWj50WVpaVlBZwIGxn7pV/Tw26jl2DoV6Hc+16CpsqUd3baOuaWIv/sJZNSgYqM2ctYIzrQKBgQDJ7zBf4Eg3lSAJ2Ds3Gg0/jxDnwQaNQ9GHvYTXOFAXrkpntvwQxgntR4+X5RGiQDgCvE1Vvdi5ZR4erSFQ/dCaMSAarjnCm6LAuYYtIpycH6vOwaAjOm5p2hCvXykw/27cOsTHeSsJtxUd6n9pcnfajL/ZSFovgPhydEctQJbU1wKBgQC6z2AWhvKth0usyHSjUgAP+QRCMSFgRKpuf5AMXADmAgcvJlxuyPBLDlOtHdaJ9SN9wJuFnSU+rmQM0PaS1d3Aa+sWsJIs67lrSHO+w6nsEYbZrs3hH8MALqcMgU+25MY+mRMftHfXtsWHaOEu1Vh9sykGfz7SErnGBLRc5oAIxwKBgQChb0gEDgCN9vkDBcvpNDmNK2m/bRA41RPoabmOeWWGWP8AxUfkfP4opIIGU8nyJVbh0PoeZsShCla2/X/aCN/AtS9ORSTGELhfTLIY2UfMhIFMrHzCTQ9CLmQSX4hFtJ9DDvSL57Fhde062mJ7wVhR7x3crjvzKC73CUBxy+YJRwKBgQCnOAojIBkLDBjJSYZey4ASzCzrs17U9aI51yXyakjDmv0jT4td/7BY/zIXvKXWSADFCCwupkQ4n5Ifhs2xEo+1NuTxIo02eKs5RVmWYT8xeV7kbH0OD4hWGWye3QGmDZMHZa6gqsK77XdThqZLbd4QZtdKYYyyLuDsSDnLDul88QKBgG8kw4SrT5SjlpkzT2XZJg/ehPI//wnuL2jm1kqDr1Y+MZ78YOrTTGwRKFK1eBAuNh7mAhZ4LN4kYiwLQ5ucJ/ipqXknp00Hvkc88lW3DX7pBf4ZoXgooz8mctCvXaZgDLfR861rMJXYQw+XQjbyslbVShQLbdWTDbwOiqUZcDpN",
+ ),
+ RegistrationTestDataGenerator.importAttestationCa(
+ "MIIDYzCCAkugAwIBAgICHtIwDQYJKoZIhvcNAQENBQAwajEmMCQGA1UEAwwdWXViaWNvIFdlYkF1dGhuIHVuaXQgdGVzdHMgQ0ExDzANBgNVBAoMBll1YmljbzEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjELMAkGA1UEBhMCU0UwHhcNMTgwOTA2MTc0MjAwWhcNMTgwOTEzMTc0MjAwWjBqMSYwJAYDVQQDDB1ZdWJpY28gV2ViQXV0aG4gdW5pdCB0ZXN0cyBDQTEPMA0GA1UECgwGWXViaWNvMSIwIAYDVQQLDBlBdXRoZW50aWNhdG9yIEF0dGVzdGF0aW9uMQswCQYDVQQGEwJTRTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIHy2tOBlw9b1XEIH5LbCnimYPReJ7C9EdMh6QbxwDoXtyZg7/IQqpCOgu27PkKmx4Hjf+HEICW4mdEmg84lElgwGOvVSxKVWUZdvT/SMdlSceI6YEA6AQR4XQ4XslJW9N+ksatLfWDomFZkZCBZM17Kkl/ZhdEWXEYNerTcM7HoiD2b+kWJPfbTtnyr5kEi6TXWtrCEterlErGPcmxNtYKVyxmLbjIBl6XjQhrzcFKZuzrjHILCAWwSPAr+wF6YCw34hAwiXOMYNz6snPvRrJtT2a5noOWb0PYg5a9zj7qY82Rd9PFS5CzM4bD00rUpXhNoZfil+P/AYWZt/srAzjkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOCAQEAWCTasxZDPHI81M7WDkbj5jD8qQSLoZDG1eyyk8Cm+OalGG9e24c1C6QSbDdMzQOCgpIljkdwl2sVHhkLRiGYGY9QSNlRGXPDsdrt1eD6OMC/45fN+AZoERaj8Fm0OrAI817uo1LmLP2h92yKN0k+xZhHR4Oi/QutNq8M70oq2DiXgpXgK01t79YMnF+TG34OnptlE6SHP5zhUwbFyJRL4tAReOpi21rmw8U4ycJ5KUPpIJ5iJBfSCuqMh2Uh7CU2fkQoI9GotUXvMlH7wdU6IesrmUIx5w4b3fKQo1bDDn4EnZ9+4AUD7Dahe6baCwHab4tUWIBVGqnaRPSEzjjUAw==",
+ "RSA",
+ "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCB8trTgZcPW9VxCB+S2wp4pmD0XiewvRHTIekG8cA6F7cmYO/yEKqQjoLtuz5CpseB43/hxCAluJnRJoPOJRJYMBjr1UsSlVlGXb0/0jHZUnHiOmBAOgEEeF0OF7JSVvTfpLGrS31g6JhWZGQgWTNeypJf2YXRFlxGDXq03DOx6Ig9m/pFiT3207Z8q+ZBIuk11rawhLXq5RKxj3JsTbWClcsZi24yAZel40Ia83BSmbs64xyCwgFsEjwK/sBemAsN+IQMIlzjGDc+rJz70aybU9muZ6Dlm9D2IOWvc4+6mPNkXfTxUuQszOGw9NK1KV4TaGX4pfj/wGFmbf7KwM45AgMBAAECggEAS2SiLwpFoUSPjle/Mc3hwmQNZlnmPzVCzTMkZsIF2+58dUjSjae7vcjhD5qOIc9vet2KCWtnl1sF6wGkgQqjHQUywEjsmGiL9jZWoVuLTmH17uIdi8XbZ0OKAa4f6IPI6KQ97HsM0BfCooT2TopSMpHm4LNsXwXRHVeetKX5XCMHcUBAQYtd2DhHST3BCGeWWvxy+dbaX0TzscUfb7OkVoXYaCb+88c2GL74cNjz24D8MzEXUd2HBP2J7nBLHlUGWI1LIkbYLuNZ5oPSJOxZ3gZtD/xPzRvLY7aORKplu4AYfwgW24hHf0b3IQfDwVV+uhwDgJV7De0rh96kbIId8QKBgQD0jZ5ZQXxYK8GZGsyy48LZ07k/3/sMo483RY3y0We0JxKwBCkqZPfBjG5c2dSJuJsOkTKJfuhI7kdkp/+FJjLNpCqmcUbVYs4OSWBK2aoFltOJ210XFY09lzTLScfOcXf11jSiiFQ0HvxrOPpIWIoMn0K/VKppHnEoF2zlFRyepQKBgQCIB/oCkzLClWqghsDyz1Yr2HVQQsYUZWpKkzSLfw7VIPnppRQd7j5KysN6p9wuWX0+6SvJhhjM/Tn1n6vSco8Sn2f5XDq/ayblKrfAS/LV0/Z1M7G6U4Kcjr7Yd+mIPxCFqgES/XHZ+10Ikdp5R7Vr46L/Wq0pUS+0Z007imnRBQKBgHLAgS0grVgyMAXHrYXDmgrcbnCqiQLFPM6StKjb2e2O6BXv3eEmv5ryalbnX/O/zAJp32zlP9n49UcmDaBM7EnSXrD7NmGqm0XY6HY27LDytRBa/rN2SXA9I2jAliEo3UFd4hTiI6DRaWBmvAp2gVCq6ocdE1mAD1jgpRhZb7SBAoGAR4MR/sqNc9gC7xMIWl1/aptnyOLhqRVLlJrgk7ke/hJQ73B2K+n0W3NO4qteSAuJmUoRV+ckIJe7IZJoTMEmz953VZMT20+kafNUGEaVCa5dsW2UsGR4lH9CeyBG5/ZnZC1kVSxh7vuDBB9RIFL/YBGSvfVYdREWKBvqcTOpv1UCgYBjQNVSvXNOHb/0ChcFyAIZtN+p/7ykLyjVMbPK0TkWWGfVNSUXvz2/otUEiGQjnIMgQ+PHNOOLdmCwiSjgx1s1rbl6/xAFWS4/GlugPAug0u859sIdTkMjDmotAsBSATHHG4S4TYG7ArCBsxhVzkUdK4pH7vldSjSyOf4YPypyeQ==",
+ ),
+ ),
+ ) {
+ override def regenerate() =
+ TestAuthenticator.createBasicAttestedCredential(
+ keyAlgorithm = COSEAlgorithmIdentifier.RS512,
+ attestationMaker = AttestationMaker.tpm(
+ AttestationSigner.ca(
+ alg = COSEAlgorithmIdentifier.RS512,
+ certSubject = new X500Name(Array.empty[RDN]),
+ certExtensions = tpmCertExtensions,
+ )
+ ),
+ )
+ }
+
val ValidRs1: RegistrationTestData = new RegistrationTestData(
alg = COSEAlgorithmIdentifier.RS1,
attestationObject =
@@ -1048,6 +1174,13 @@ case class RegistrationTestData(
)
}
+ def attestationStatementFormat: String =
+ JacksonCodecs.cbor
+ .readTree(attestationObject.getBytes)
+ .asInstanceOf[ObjectNode]
+ .get("fmt")
+ .textValue
+
def setAttestationStatementFormat(value: String): RegistrationTestData =
editAttestationObject(
"fmt",
@@ -1080,6 +1213,8 @@ case class RegistrationTestData(
PublicKeyCredentialParameters.ES384,
PublicKeyCredentialParameters.ES512,
PublicKeyCredentialParameters.RS256,
+ PublicKeyCredentialParameters.RS384,
+ PublicKeyCredentialParameters.RS512,
).asJava
)
.extensions(requestedExtensions)
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 f817ad746..78daeb962 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
@@ -185,6 +185,7 @@ class RelyingPartyAssertionSpec
rpId: RelyingPartyIdentity = Defaults.rpId,
signature: ByteArray = Defaults.signature,
userHandleForResponse: Option[ByteArray] = Some(Defaults.userHandle),
+ userHandleForRequest: Option[ByteArray] = None,
userHandleForUser: ByteArray = Defaults.userHandle,
usernameForRequest: Option[String] = Some(Defaults.username),
usernameForUser: String = Defaults.username,
@@ -210,6 +211,7 @@ class RelyingPartyAssertionSpec
.build()
)
.username(usernameForRequest.toJava)
+ .userHandle(userHandleForRequest.toJava)
.build()
val response = PublicKeyCredential
@@ -418,9 +420,15 @@ class RelyingPartyAssertionSpec
)
result.isSuccess should be(true)
- result.getUserHandle should equal(registrationTestData.userId.getId)
- result.getCredentialId should equal(registrationTestData.response.getId)
- result.getCredentialId should equal(testData.response.getId)
+ result.getCredential.getUserHandle should equal(
+ registrationTestData.userId.getId
+ )
+ result.getCredential.getCredentialId should equal(
+ registrationTestData.response.getId
+ )
+ result.getCredential.getCredentialId should equal(
+ testData.response.getId
+ )
credRepo.lookupCount should equal(1)
}
@@ -642,9 +650,9 @@ class RelyingPartyAssertionSpec
) {
val steps = finishAssertion(
credentialRepository = credentialRepository,
- usernameForRequest = Some(owner.username),
- userHandleForUser = owner.userHandle,
userHandleForResponse = Some(nonOwner.userHandle),
+ userHandleForUser = owner.userHandle,
+ usernameForRequest = Some(owner.username),
)
val step: FinishAssertionSteps#Step6 = steps.begin.next
@@ -672,9 +680,9 @@ class RelyingPartyAssertionSpec
it("Succeeds if credential ID is owned by the given user handle.") {
val steps = finishAssertion(
credentialRepository = credentialRepository,
- usernameForRequest = Some(owner.username),
- userHandleForUser = owner.userHandle,
userHandleForResponse = Some(owner.userHandle),
+ userHandleForUser = owner.userHandle,
+ usernameForRequest = Some(owner.username),
)
val step: FinishAssertionSteps#Step6 = steps.begin.next
@@ -701,13 +709,30 @@ class RelyingPartyAssertionSpec
}
it(
- "Fails if credential ID is not owned by the given user handle."
+ "Fails if credential ID is not owned by the user handle in the response."
) {
val steps = finishAssertion(
credentialRepository = credentialRepository,
+ userHandleForResponse = Some(nonOwner.userHandle),
+ userHandleForUser = owner.userHandle,
usernameForRequest = None,
+ )
+ val step: FinishAssertionSteps#Step6 = steps.begin.next
+
+ step.validations shouldBe a[Failure[_]]
+ step.validations.failed.get shouldBe an[IllegalArgumentException]
+ step.tryNext shouldBe a[Failure[_]]
+ }
+
+ it(
+ "Fails if credential ID is not owned by the user handle in the request."
+ ) {
+ val steps = finishAssertion(
+ credentialRepository = credentialRepository,
+ userHandleForRequest = Some(nonOwner.userHandle),
+ userHandleForResponse = None,
userHandleForUser = owner.userHandle,
- userHandleForResponse = Some(nonOwner.userHandle),
+ usernameForRequest = None,
)
val step: FinishAssertionSteps#Step6 = steps.begin.next
@@ -719,9 +744,25 @@ class RelyingPartyAssertionSpec
it("Fails if neither username nor user handle is given.") {
val steps = finishAssertion(
credentialRepository = credentialRepository,
+ userHandleForRequest = None,
+ userHandleForResponse = None,
+ userHandleForUser = owner.userHandle,
usernameForRequest = None,
+ )
+ val step: FinishAssertionSteps#Step6 = steps.begin.next
+
+ step.validations shouldBe a[Failure[_]]
+ step.validations.failed.get shouldBe an[IllegalArgumentException]
+ step.tryNext shouldBe a[Failure[_]]
+ }
+
+ it("Fails if user handle in request does not agree with user handle in response.") {
+ val steps = finishAssertion(
+ credentialRepository = credentialRepository,
+ userHandleForRequest = Some(owner.userHandle),
+ userHandleForResponse = Some(nonOwner.userHandle),
userHandleForUser = owner.userHandle,
- userHandleForResponse = None,
+ usernameForRequest = None,
)
val step: FinishAssertionSteps#Step6 = steps.begin.next
@@ -730,12 +771,26 @@ class RelyingPartyAssertionSpec
step.tryNext shouldBe a[Failure[_]]
}
- it("Succeeds if credential ID is owned by the given user handle.") {
+ it("Succeeds if credential ID is owned by the user handle in the response.") {
val steps = finishAssertion(
credentialRepository = credentialRepository,
+ userHandleForResponse = Some(owner.userHandle),
+ userHandleForUser = owner.userHandle,
usernameForRequest = None,
+ )
+ val step: FinishAssertionSteps#Step6 = steps.begin.next
+
+ step.validations shouldBe a[Success[_]]
+ step.tryNext shouldBe a[Success[_]]
+ }
+
+ it("Succeeds if credential ID is owned by the user handle in the request.") {
+ val steps = finishAssertion(
+ credentialRepository = credentialRepository,
+ userHandleForRequest = Some(owner.userHandle),
+ userHandleForResponse = None,
userHandleForUser = owner.userHandle,
- userHandleForResponse = Some(owner.userHandle),
+ usernameForRequest = None,
)
val step: FinishAssertionSteps#Step6 = steps.begin.next
@@ -1906,8 +1961,12 @@ class RelyingPartyAssertionSpec
Try(steps.run) shouldBe a[Success[_]]
step.result.get.isSuccess should be(true)
- step.result.get.getCredentialId should equal(Defaults.credentialId)
- step.result.get.getUserHandle should equal(Defaults.userHandle)
+ step.result.get.getCredential.getCredentialId should equal(
+ Defaults.credentialId
+ )
+ step.result.get.getCredential.getUserHandle should equal(
+ Defaults.userHandle
+ )
step.result.get.getCredential.getCredentialId should equal(
step.result.get.getCredentialId
)
@@ -2001,8 +2060,8 @@ class RelyingPartyAssertionSpec
)
result.isSuccess should be(true)
- result.getUserHandle should equal(testData.userId.getId)
- result.getCredentialId should equal(credId)
+ result.getCredential.getUserHandle should equal(testData.userId.getId)
+ result.getCredential.getCredentialId should equal(credId)
}
it("an Ed25519 key.") {
@@ -2133,8 +2192,10 @@ class RelyingPartyAssertionSpec
)
result.isSuccess should be(true)
- result.getUserHandle should equal(registrationRequest.getUser.getId)
- result.getCredentialId should equal(credId)
+ result.getCredential.getUserHandle should equal(
+ registrationRequest.getUser.getId
+ )
+ result.getCredential.getCredentialId should equal(credId)
}
it("a generated Ed25519 key.") {
@@ -2172,9 +2233,15 @@ class RelyingPartyAssertionSpec
)
result.isSuccess should be(true)
- result.getUserHandle should equal(registrationTestData.userId.getId)
- result.getCredentialId should equal(registrationTestData.response.getId)
- result.getCredentialId should equal(testData.response.getId)
+ result.getCredential.getUserHandle should equal(
+ registrationTestData.userId.getId
+ )
+ result.getCredential.getCredentialId should equal(
+ registrationTestData.response.getId
+ )
+ result.getCredential.getCredentialId should equal(
+ testData.response.getId
+ )
}
describe("an RS1 key") {
@@ -2215,11 +2282,15 @@ class RelyingPartyAssertionSpec
)
result.isSuccess should be(true)
- result.getUserHandle should equal(registrationTestData.userId.getId)
- result.getCredentialId should equal(
+ result.getCredential.getUserHandle should equal(
+ registrationTestData.userId.getId
+ )
+ result.getCredential.getCredentialId should equal(
registrationTestData.response.getId
)
- result.getCredentialId should equal(testData.response.getId)
+ result.getCredential.getCredentialId should equal(
+ testData.response.getId
+ )
}
it("with basic attestation.") {
@@ -2275,8 +2346,10 @@ class RelyingPartyAssertionSpec
)
result.isSuccess should be(true)
- result.getUserHandle should equal(testData.userId.getId)
- result.getCredentialId should equal(testData.response.getId)
+ result.getCredential.getUserHandle should equal(testData.userId.getId)
+ result.getCredential.getCredentialId should equal(
+ testData.response.getId
+ )
}
}
}
diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyCeremoniesSpec.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyCeremoniesSpec.scala
index 94adc5a50..5a3f3244c 100644
--- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyCeremoniesSpec.scala
+++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyCeremoniesSpec.scala
@@ -110,8 +110,12 @@ class RelyingPartyCeremoniesSpec
)
assertionResult.isSuccess should be(true)
- assertionResult.getCredentialId should equal(testData.assertion.get.id)
- assertionResult.getUserHandle should equal(testData.user.getId)
+ assertionResult.getCredential.getCredentialId should equal(
+ testData.assertion.get.id
+ )
+ assertionResult.getCredential.getUserHandle should equal(
+ testData.user.getId
+ )
assertionResult.getUsername should equal(testData.user.getName)
assertionResult.getSignatureCount should be >= testData.attestation.authenticatorData.getSignatureCounter
assertionResult.isSignatureCounterValid should be(true)
@@ -163,9 +167,11 @@ class RelyingPartyCeremoniesSpec
it("a YubiKey 5Ci FIPS.") {
check(RealExamples.Yubikey5ciFips)
}
+
it("a YubiKey Bio.") {
check(RealExamples.YubikeyBio_5_5_4)
check(RealExamples.YubikeyBio_5_5_5)
+ check(RealExamples.YubikeyBio_5_5_6)
}
it("an Apple iOS device.") {
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 a737f23b4..2519fa756 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
@@ -235,19 +235,13 @@ class RelyingPartyRegistrationSpec
describe("3. Let response be credential.response.") {
it("If response is not an instance of AuthenticatorAttestationResponse, abort the ceremony with a user-visible error.") {
+ val testData = RegistrationTestData.Packed.BasicAttestationEdDsa
val frob = FinishRegistrationOptions
.builder()
- .request(
- RegistrationTestData.Packed.BasicAttestationEdDsa.request
- )
- val testData =
- RegistrationTestData.Packed.BasicAttestationEdDsa.assertion.get
- "frob.response(testData.response)" shouldNot compile
- frob
- .response(
- RegistrationTestData.Packed.BasicAttestationEdDsa.response
- )
- .build() should not be null
+ .request(testData.request)
+ "frob.response(testData.response)" should compile
+ "frob.response(testData.assertion.get.response)" shouldNot compile
+ frob.response(testData.response).build() should not be null
}
}
@@ -1714,7 +1708,7 @@ class RelyingPartyRegistrationSpec
).getAuthenticatorData.getAttestedCredentialData.get.getCredentialPublicKey.getBytes
)
.get(CBORObject.FromObject(3))
- .AsInt64 should equal(-7)
+ .AsInt64Value should equal(-7)
new AttestationObject(
testDataBase.attestationObject
).getAttestationStatement.get("alg").longValue should equal(
@@ -1791,7 +1785,7 @@ class RelyingPartyRegistrationSpec
attObj.getAuthenticatorData.getAttestedCredentialData.get.getCredentialPublicKey.getBytes
)
.get(CBORObject.FromObject(3))
- .AsInt64 should equal(-257)
+ .AsInt64Value should equal(-257)
attObj.getAttestationStatement
.get("alg")
.longValue should equal(-65535)
@@ -2131,6 +2125,8 @@ class RelyingPartyRegistrationSpec
def makeCred(
authDataAndKeypair: Option[(ByteArray, KeyPair)] = None,
+ credKeyAlgorithm: COSEAlgorithmIdentifier =
+ TestAuthenticator.Defaults.keyAlgorithm,
clientDataJson: Option[String] = None,
subject: X500Name = emptySubject,
rdn: Array[AttributeTypeAndValue] =
@@ -2158,17 +2154,20 @@ class RelyingPartyRegistrationSpec
) = {
val (authData, credentialKeypair) =
authDataAndKeypair.getOrElse(
- TestAuthenticator.createAuthenticatorData(keyAlgorithm =
- COSEAlgorithmIdentifier.ES256
+ TestAuthenticator.createAuthenticatorData(
+ credentialKeypair = Some(
+ TestAuthenticator.Defaults.defaultKeypair(
+ credKeyAlgorithm
+ )
+ ),
+ keyAlgorithm = credKeyAlgorithm,
)
)
TestAuthenticator.createCredential(
authDataBytes = authData,
credentialKeypair = credentialKeypair,
- clientDataJson = clientDataJson.getOrElse(
- TestAuthenticator.createClientData()
- ),
+ clientDataJson = clientDataJson,
attestationMaker = AttestationMaker.tpm(
cert = AttestationSigner.ca(
alg = COSEAlgorithmIdentifier.ES256,
@@ -2727,7 +2726,11 @@ class RelyingPartyRegistrationSpec
val (authData, keypair) =
TestAuthenticator.createAuthenticatorData(
aaguid = aaguid,
- keyAlgorithm = COSEAlgorithmIdentifier.ES256,
+ credentialKeypair = Some(
+ TestAuthenticator.Defaults.defaultKeypair(
+ COSEAlgorithmIdentifier.ES256
+ )
+ ),
)
val testData = (RegistrationTestData.from _).tupled(
makeCred(
@@ -2751,7 +2754,11 @@ class RelyingPartyRegistrationSpec
val (authData, keypair) =
TestAuthenticator.createAuthenticatorData(
aaguid = aaguidInCred,
- keyAlgorithm = COSEAlgorithmIdentifier.ES256,
+ credentialKeypair = Some(
+ TestAuthenticator.Defaults.defaultKeypair(
+ COSEAlgorithmIdentifier.ES256
+ )
+ ),
)
val testData = (RegistrationTestData.from _).tupled(
makeCred(
@@ -2770,18 +2777,34 @@ class RelyingPartyRegistrationSpec
describe("Other requirements:") {
it("RSA keys must have the SIGN_ENCRYPT attribute.") {
- forAll(Gen.chooseNum(0, Int.MaxValue.toLong * 2 + 1)) {
- attributes: Long =>
+ forAll(
+ Gen.chooseNum(0, Int.MaxValue.toLong * 2 + 1),
+ minSuccessful(5),
+ ) { attributes: Long =>
+ val testData = (RegistrationTestData.from _).tupled(
+ makeCred(
+ credKeyAlgorithm = COSEAlgorithmIdentifier.RS256,
+ attributes = Some(attributes & ~Attributes.SIGN_ENCRYPT),
+ )
+ )
+ val step = init(testData)
+ testData.alg should be(COSEAlgorithmIdentifier.RS256)
+
+ step.validations shouldBe a[Failure[_]]
+ step.tryNext shouldBe a[Failure[_]]
+ }
+ }
+
+ it("""RSA keys must have "symmetric" set to TPM_ALG_NULL""") {
+ forAll(
+ Gen.chooseNum(0, Short.MaxValue * 2 + 1),
+ minSuccessful(5),
+ ) { symmetric: Int =>
+ whenever(symmetric != TPM_ALG_NULL) {
val testData = (RegistrationTestData.from _).tupled(
makeCred(
- authDataAndKeypair = Some(
- TestAuthenticator
- .createAuthenticatorData(keyAlgorithm =
- COSEAlgorithmIdentifier.RS256
- )
- ),
- attributes =
- Some(attributes & ~Attributes.SIGN_ENCRYPT),
+ credKeyAlgorithm = COSEAlgorithmIdentifier.RS256,
+ symmetric = Some(symmetric),
)
)
val step = init(testData)
@@ -2789,72 +2812,62 @@ class RelyingPartyRegistrationSpec
step.validations shouldBe a[Failure[_]]
step.tryNext shouldBe a[Failure[_]]
+ }
}
}
- it("""RSA keys must have "symmetric" set to TPM_ALG_NULL""") {
- forAll(Gen.chooseNum(0, Short.MaxValue * 2 + 1)) {
- symmetric: Int =>
- whenever(symmetric != TPM_ALG_NULL) {
- val testData = (RegistrationTestData.from _).tupled(
- makeCred(
- authDataAndKeypair = Some(
- TestAuthenticator
- .createAuthenticatorData(keyAlgorithm =
- COSEAlgorithmIdentifier.RS256
- )
- ),
- symmetric = Some(symmetric),
- )
+ it("""RSA keys must have "scheme" set to TPM_ALG_RSASSA or TPM_ALG_NULL""") {
+ forAll(
+ Gen.chooseNum(0, Short.MaxValue * 2 + 1),
+ minSuccessful(5),
+ ) { scheme: Int =>
+ whenever(
+ scheme != TpmRsaScheme.RSASSA && scheme != TPM_ALG_NULL
+ ) {
+ val testData = (RegistrationTestData.from _).tupled(
+ makeCred(
+ credKeyAlgorithm = COSEAlgorithmIdentifier.RS256,
+ scheme = Some(scheme),
)
- val step = init(testData)
- testData.alg should be(COSEAlgorithmIdentifier.RS256)
+ )
+ val step = init(testData)
+ testData.alg should be(COSEAlgorithmIdentifier.RS256)
- step.validations shouldBe a[Failure[_]]
- step.tryNext shouldBe a[Failure[_]]
- }
+ step.validations shouldBe a[Failure[_]]
+ step.tryNext shouldBe a[Failure[_]]
+ }
}
}
- it("""RSA keys must have "scheme" set to TPM_ALG_RSASSA or TPM_ALG_NULL""") {
- forAll(Gen.chooseNum(0, Short.MaxValue * 2 + 1)) {
- scheme: Int =>
- whenever(
- scheme != TpmRsaScheme.RSASSA && scheme != TPM_ALG_NULL
- ) {
- val testData = (RegistrationTestData.from _).tupled(
- makeCred(
- authDataAndKeypair = Some(
- TestAuthenticator
- .createAuthenticatorData(keyAlgorithm =
- COSEAlgorithmIdentifier.RS256
- )
- ),
- scheme = Some(scheme),
- )
- )
- val step = init(testData)
- testData.alg should be(COSEAlgorithmIdentifier.RS256)
+ it("ECC keys must have the SIGN_ENCRYPT attribute.") {
+ forAll(
+ Gen.chooseNum(0, Int.MaxValue.toLong * 2 + 1),
+ minSuccessful(5),
+ ) { attributes: Long =>
+ val testData = (RegistrationTestData.from _).tupled(
+ makeCred(
+ credKeyAlgorithm = COSEAlgorithmIdentifier.ES256,
+ attributes = Some(attributes & ~Attributes.SIGN_ENCRYPT),
+ )
+ )
+ val step = init(testData)
+ testData.alg should be(COSEAlgorithmIdentifier.ES256)
- step.validations shouldBe a[Failure[_]]
- step.tryNext shouldBe a[Failure[_]]
- }
+ step.validations shouldBe a[Failure[_]]
+ step.tryNext shouldBe a[Failure[_]]
}
}
- it("ECC keys must have the SIGN_ENCRYPT attribute.") {
- forAll(Gen.chooseNum(0, Int.MaxValue.toLong * 2 + 1)) {
- attributes: Long =>
+ it("""ECC keys must have "symmetric" set to TPM_ALG_NULL""") {
+ forAll(
+ Gen.chooseNum(0, Short.MaxValue * 2 + 1),
+ minSuccessful(5),
+ ) { symmetric: Int =>
+ whenever(symmetric != TPM_ALG_NULL) {
val testData = (RegistrationTestData.from _).tupled(
makeCred(
- authDataAndKeypair = Some(
- TestAuthenticator
- .createAuthenticatorData(keyAlgorithm =
- COSEAlgorithmIdentifier.ES256
- )
- ),
- attributes =
- Some(attributes & ~Attributes.SIGN_ENCRYPT),
+ credKeyAlgorithm = COSEAlgorithmIdentifier.ES256,
+ symmetric = Some(symmetric),
)
)
val step = init(testData)
@@ -2862,54 +2875,28 @@ class RelyingPartyRegistrationSpec
step.validations shouldBe a[Failure[_]]
step.tryNext shouldBe a[Failure[_]]
- }
- }
-
- it("""ECC keys must have "symmetric" set to TPM_ALG_NULL""") {
- forAll(Gen.chooseNum(0, Short.MaxValue * 2 + 1)) {
- symmetric: Int =>
- whenever(symmetric != TPM_ALG_NULL) {
- val testData = (RegistrationTestData.from _).tupled(
- makeCred(
- authDataAndKeypair = Some(
- TestAuthenticator
- .createAuthenticatorData(keyAlgorithm =
- COSEAlgorithmIdentifier.ES256
- )
- ),
- symmetric = Some(symmetric),
- )
- )
- val step = init(testData)
- testData.alg should be(COSEAlgorithmIdentifier.ES256)
-
- step.validations shouldBe a[Failure[_]]
- step.tryNext shouldBe a[Failure[_]]
- }
+ }
}
}
it("""ECC keys must have "scheme" set to TPM_ALG_NULL""") {
- forAll(Gen.chooseNum(0, Short.MaxValue * 2 + 1)) {
- scheme: Int =>
- whenever(scheme != TPM_ALG_NULL) {
- val testData = (RegistrationTestData.from _).tupled(
- makeCred(
- authDataAndKeypair = Some(
- TestAuthenticator
- .createAuthenticatorData(keyAlgorithm =
- COSEAlgorithmIdentifier.ES256
- )
- ),
- scheme = Some(scheme),
- )
+ forAll(
+ Gen.chooseNum(0, Short.MaxValue * 2 + 1),
+ minSuccessful(5),
+ ) { scheme: Int =>
+ whenever(scheme != TPM_ALG_NULL) {
+ val testData = (RegistrationTestData.from _).tupled(
+ makeCred(
+ credKeyAlgorithm = COSEAlgorithmIdentifier.ES256,
+ scheme = Some(scheme),
)
- val step = init(testData)
- testData.alg should be(COSEAlgorithmIdentifier.ES256)
+ )
+ val step = init(testData)
+ testData.alg should be(COSEAlgorithmIdentifier.ES256)
- step.validations shouldBe a[Failure[_]]
- step.tryNext shouldBe a[Failure[_]]
- }
+ step.validations shouldBe a[Failure[_]]
+ step.tryNext shouldBe a[Failure[_]]
+ }
}
}
}
@@ -3204,6 +3191,53 @@ class RelyingPartyRegistrationSpec
step.tryNext shouldBe a[Success[_]]
}
+ it("When the AAGUID in authenticator data is zero, the AAGUID in the attestation certificate is used instead, if possible.") {
+ val example = RealExamples.SecurityKeyNfc
+ val testData = example.asRegistrationTestData
+ testData.aaguid should equal(
+ ByteArray.fromHex("00000000000000000000000000000000")
+ )
+ val certAaguid = new ByteArray(
+ CertificateParser
+ .parseFidoAaguidExtension(
+ CertificateParser.parseDer(example.attestationCert.getBytes)
+ )
+ .get
+ )
+
+ val attestationTrustSource = new AttestationTrustSource {
+ override def findTrustRoots(
+ attestationCertificateChain: util.List[X509Certificate],
+ aaguid: Optional[ByteArray],
+ ): TrustRootsResult = {
+ TrustRootsResult
+ .builder()
+ .trustRoots(
+ if (aaguid == Optional.of(certAaguid)) {
+ Set(attestationRootCert).asJava
+ } else {
+ Set.empty[X509Certificate].asJava
+ }
+ )
+ .build()
+ }
+ }
+ val steps = finishRegistration(
+ testData = testData,
+ attestationTrustSource = Some(attestationTrustSource),
+ )
+ val step: FinishRegistrationSteps#Step20 =
+ steps.begin.next.next.next.next.next.next.next.next.next.next.next.next.next
+
+ step.validations shouldBe a[Success[_]]
+ step.getTrustRoots.toScala.map(
+ _.getTrustRoots.asScala
+ ) should equal(
+ Some(Set(attestationRootCert))
+ )
+ step.tryNext shouldBe a[Success[_]]
+ }
+
it(
"If an attestation trust source is not set, no trust anchors are returned."
) {
@@ -4015,22 +4049,31 @@ class RelyingPartyRegistrationSpec
RegistrationTestData.defaultSettingsValidExamples.zipWithIndex
.foreach {
case (testData, i) =>
- it(s"Succeeds for example index ${i}.") {
- val rp = {
- val builder = RelyingParty
- .builder()
- .identity(testData.rpId)
- .credentialRepository(
- Helpers.CredentialRepository.empty
- )
- builder.origins(Set(testData.clientData.getOrigin).asJava)
- builder.build()
- }
+ it(s"Succeeds for example index ${i} (${testData.alg}, ${testData.attestationStatementFormat}).") {
+ val rp = RelyingParty
+ .builder()
+ .identity(testData.rpId)
+ .credentialRepository(
+ Helpers.CredentialRepository.empty
+ )
+ .origins(Set(testData.clientData.getOrigin).asJava)
+ .build()
+
+ val request = rp
+ .startRegistration(
+ StartRegistrationOptions
+ .builder()
+ .user(testData.userId)
+ .build()
+ )
+ .toBuilder
+ .challenge(testData.request.getChallenge)
+ .build()
val result = rp.finishRegistration(
FinishRegistrationOptions
.builder()
- .request(testData.request)
+ .request(request)
.response(testData.response)
.build()
)
@@ -4067,6 +4110,24 @@ class RelyingPartyRegistrationSpec
)
}
+ it("ES384.") {
+ pubKeyCredParams should contain(
+ PublicKeyCredentialParameters.ES384
+ )
+ pubKeyCredParams map (_.getAlg) should contain(
+ COSEAlgorithmIdentifier.ES384
+ )
+ }
+
+ it("ES512.") {
+ pubKeyCredParams should contain(
+ PublicKeyCredentialParameters.ES512
+ )
+ pubKeyCredParams map (_.getAlg) should contain(
+ COSEAlgorithmIdentifier.ES512
+ )
+ }
+
it("EdDSA, when available.") {
// The RelyingParty constructor call needs to be here inside the `it` call in order to have the right JCA provider environment
val rp = RelyingParty
@@ -4121,6 +4182,24 @@ class RelyingPartyRegistrationSpec
COSEAlgorithmIdentifier.RS256
)
}
+
+ it("RS384.") {
+ pubKeyCredParams should contain(
+ PublicKeyCredentialParameters.RS384
+ )
+ pubKeyCredParams map (_.getAlg) should contain(
+ COSEAlgorithmIdentifier.RS384
+ )
+ }
+
+ it("RS512.") {
+ pubKeyCredParams should contain(
+ PublicKeyCredentialParameters.RS512
+ )
+ pubKeyCredParams map (_.getAlg) should contain(
+ COSEAlgorithmIdentifier.RS512
+ )
+ }
}
describe("do not include") {
diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyStartOperationSpec.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyStartOperationSpec.scala
index b0893616f..7b491b189 100644
--- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyStartOperationSpec.scala
+++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyStartOperationSpec.scala
@@ -24,6 +24,7 @@
package com.yubico.webauthn
+import com.yubico.internal.util.JacksonCodecs
import com.yubico.webauthn.Generators._
import com.yubico.webauthn.data.AssertionExtensionInputs
import com.yubico.webauthn.data.AttestationConveyancePreference
@@ -33,6 +34,7 @@ import com.yubico.webauthn.data.AuthenticatorTransport
import com.yubico.webauthn.data.ByteArray
import com.yubico.webauthn.data.Generators.Extensions.registrationExtensionInputs
import com.yubico.webauthn.data.Generators._
+import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions
import com.yubico.webauthn.data.PublicKeyCredentialDescriptor
import com.yubico.webauthn.data.PublicKeyCredentialParameters
import com.yubico.webauthn.data.RegistrationExtensionInputs
@@ -454,18 +456,37 @@ class RelyingPartyStartOperationSpec
.build()
)
+ def jsonRequireResidentKey(
+ pkcco: PublicKeyCredentialCreationOptions
+ ): Option[Boolean] =
+ Option(
+ JacksonCodecs
+ .json()
+ .readTree(pkcco.toCredentialsCreateJson)
+ .get("publicKey")
+ .get("authenticatorSelection")
+ .get("requireResidentKey")
+ ).map(_.booleanValue)
+
pkccoDiscouraged.getAuthenticatorSelection.get.getResidentKey.toScala should be(
Some(ResidentKeyRequirement.DISCOURAGED)
)
+ jsonRequireResidentKey(pkccoDiscouraged) should be(Some(false))
+
pkccoPreferred.getAuthenticatorSelection.get.getResidentKey.toScala should be(
Some(ResidentKeyRequirement.PREFERRED)
)
+ jsonRequireResidentKey(pkccoPreferred) should be(Some(false))
+
pkccoRequired.getAuthenticatorSelection.get.getResidentKey.toScala should be(
Some(ResidentKeyRequirement.REQUIRED)
)
+ jsonRequireResidentKey(pkccoRequired) should be(Some(true))
+
pkccoUnspecified.getAuthenticatorSelection.get.getResidentKey.toScala should be(
None
)
+ jsonRequireResidentKey(pkccoUnspecified) should be(None)
}
it("respects the authenticatorAttachment parameter.") {
@@ -563,6 +584,34 @@ class RelyingPartyStartOperationSpec
}
}
+ it("passes username through to AssertionRequest.") {
+ forAll { username: String =>
+ val testCaseUserId = userId.toBuilder.name(username).build()
+ val rp = relyingParty(userId = testCaseUserId)
+ val result = rp.startAssertion(
+ StartAssertionOptions
+ .builder()
+ .username(testCaseUserId.getName)
+ .build()
+ )
+ result.getUsername.asScala should equal(Some(testCaseUserId.getName))
+ }
+ }
+
+ it("passes user handle through to AssertionRequest.") {
+ forAll { userHandle: ByteArray =>
+ val testCaseUserId = userId.toBuilder.id(userHandle).build()
+ val rp = relyingParty(userId = testCaseUserId)
+ val result = rp.startAssertion(
+ StartAssertionOptions
+ .builder()
+ .userHandle(testCaseUserId.getId)
+ .build()
+ )
+ result.getUserHandle.asScala should equal(Some(testCaseUserId.getId))
+ }
+ }
+
it("includes transports in allowCredentials when available.") {
forAll(
Gen.nonEmptyContainerOf[Set, AuthenticatorTransport](
@@ -875,4 +924,95 @@ class RelyingPartyStartOperationSpec
}
}
+ describe("AssertionRequest") {
+
+ it("resets username when userHandle is set.") {
+ forAll { (ar: AssertionRequest, userHandle: ByteArray) =>
+ val result = ar.toBuilder.userHandle(userHandle).build()
+ result.getUsername.asScala shouldBe empty
+ }
+
+ forAll { (ar: AssertionRequest, userHandle: ByteArray) =>
+ val result = ar.toBuilder.userHandle(Some(userHandle).asJava).build()
+ result.getUsername.asScala shouldBe empty
+ }
+ }
+
+ it("resets userHandle when username is set.") {
+ forAll { (ar: AssertionRequest, username: String) =>
+ val result = ar.toBuilder.username(username).build()
+ result.getUserHandle.asScala shouldBe empty
+ }
+
+ forAll { (ar: AssertionRequest, username: String) =>
+ val result = ar.toBuilder.username(Some(username).asJava).build()
+ result.getUserHandle.asScala shouldBe empty
+ }
+ }
+
+ it("does not reset username when userHandle is set to empty.") {
+ forAll { (ar: AssertionRequest, username: String) =>
+ val result = ar.toBuilder
+ .username(username)
+ .userHandle(Optional.empty[ByteArray])
+ .build()
+ result.getUsername.asScala should equal(Some(username))
+ }
+
+ forAll { (ar: AssertionRequest, username: String) =>
+ val result = ar.toBuilder
+ .username(username)
+ .userHandle(null: ByteArray)
+ .build()
+ result.getUsername.asScala should equal(Some(username))
+ }
+ }
+
+ it("does not reset userHandle when username is set to empty.") {
+ forAll { (ar: AssertionRequest, userHandle: ByteArray) =>
+ val result = ar.toBuilder
+ .userHandle(userHandle)
+ .username(Optional.empty[String])
+ .build()
+ result.getUserHandle.asScala should equal(Some(userHandle))
+ }
+
+ forAll { (ar: AssertionRequest, userHandle: ByteArray) =>
+ val result = ar.toBuilder
+ .userHandle(userHandle)
+ .username(null: String)
+ .build()
+ result.getUserHandle.asScala should equal(Some(userHandle))
+ }
+ }
+
+ it("allows unsetting username.") {
+ forAll { (ar: AssertionRequest, username: String) =>
+ val preresult = ar.toBuilder.username(username).build()
+ preresult.getUsername.asScala should equal(Some(username))
+
+ val result1 =
+ preresult.toBuilder.username(Optional.empty[String]).build()
+ result1.getUsername.asScala shouldBe empty
+
+ val result2 = preresult.toBuilder.username(null: String).build()
+ result2.getUsername.asScala shouldBe empty
+ }
+ }
+
+ it("allows unsetting userHandle.") {
+ forAll { (ar: AssertionRequest, userHandle: ByteArray) =>
+ val preresult = ar.toBuilder.userHandle(userHandle).build()
+ preresult.getUserHandle.asScala should equal(Some(userHandle))
+
+ val result1 =
+ preresult.toBuilder.userHandle(Optional.empty[ByteArray]).build()
+ result1.getUserHandle.asScala shouldBe empty
+
+ val result2 = preresult.toBuilder.userHandle(null: ByteArray).build()
+ result2.getUserHandle.asScala shouldBe empty
+ }
+ }
+ }
+
}
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 3d70dfbc5..83f3b5606 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
@@ -125,6 +125,20 @@ object TestAuthenticator {
)
val certValidFrom: Instant = Instant.parse("2018-09-06T17:42:00Z")
val certValidTo: Instant = certValidFrom.plusSeconds(7 * 24 * 3600)
+
+ private var defaultKeypairs: Map[COSEAlgorithmIdentifier, KeyPair] =
+ Map.empty
+ def defaultKeypair(
+ algorithm: COSEAlgorithmIdentifier = Defaults.keyAlgorithm
+ ): KeyPair = {
+ defaultKeypairs.get(algorithm) match {
+ case Some(keypair) => keypair
+ case None =>
+ val keypair = generateKeypair(algorithm)
+ defaultKeypairs = defaultKeypairs + (algorithm -> keypair)
+ keypair
+ }
+ }
}
val RsaKeySizeBits = 2048
val Es256PrimeModulus: BigInteger = new BigInteger(
@@ -137,6 +151,13 @@ object TestAuthenticator {
)
private def jsonFactory: JsonNodeFactory = JsonNodeFactory.instance
+ private def jsonMap[V <: JsonNode](
+ map: JsonNodeFactory => Map[String, V]
+ ): ObjectNode =
+ jsonFactory
+ .objectNode()
+ .setAll[ObjectNode](map.apply(jsonFactory).asJava)
+
private def toBytes(s: String): ByteArray = new ByteArray(s.getBytes("UTF-8"))
def sha256(s: String): ByteArray = sha256(toBytes(s))
def sha256(b: ByteArray): ByteArray =
@@ -146,24 +167,21 @@ object TestAuthenticator {
val format: String
def makeAttestationStatement(
authDataBytes: ByteArray,
- clientDataJson: String,
+ clientDataJson: ByteArray,
): JsonNode
def certChain: List[(X509Certificate, PrivateKey)] = Nil
def makeAttestationObjectBytes(
authDataBytes: ByteArray,
- clientDataJson: String,
+ clientDataJson: ByteArray,
): ByteArray = {
- val f = JsonNodeFactory.instance
- val attObj = f
- .objectNode()
- .setAll[ObjectNode](
- Map(
- "authData" -> f.binaryNode(authDataBytes.getBytes),
- "fmt" -> f.textNode(format),
- "attStmt" -> makeAttestationStatement(authDataBytes, clientDataJson),
- ).asJava
+ val attObj = jsonMap { f =>
+ Map(
+ "authData" -> f.binaryNode(authDataBytes.getBytes),
+ "fmt" -> f.textNode(format),
+ "attStmt" -> makeAttestationStatement(authDataBytes, clientDataJson),
)
+ }
new ByteArray(JacksonCodecs.cbor.writeValueAsBytes(attObj))
}
}
@@ -177,20 +195,22 @@ object TestAuthenticator {
override def certChain = signer.certChain
override def makeAttestationStatement(
authDataBytes: ByteArray,
- clientDataJson: String,
+ clientDataJson: ByteArray,
): JsonNode =
makePackedAttestationStatement(authDataBytes, clientDataJson, signer)
}
+
def fidoU2f(signer: AttestationSigner): AttestationMaker =
new AttestationMaker {
override val format = "fido-u2f"
override def certChain = signer.certChain
override def makeAttestationStatement(
authDataBytes: ByteArray,
- clientDataJson: String,
+ clientDataJson: ByteArray,
): JsonNode =
makeU2fAttestationStatement(authDataBytes, clientDataJson, signer)
}
+
def androidSafetynet(
cert: AttestationCert,
ctsProfileMatch: Boolean = true,
@@ -200,7 +220,7 @@ object TestAuthenticator {
override def certChain = cert.certChain
override def makeAttestationStatement(
authDataBytes: ByteArray,
- clientDataJson: String,
+ clientDataJson: ByteArray,
): JsonNode =
makeAndroidSafetynetAttestationStatement(
authDataBytes,
@@ -209,6 +229,7 @@ object TestAuthenticator {
ctsProfileMatch = ctsProfileMatch,
)
}
+
def apple(
addNonceExtension: Boolean = true,
nonceValue: Option[ByteArray] = None,
@@ -227,7 +248,7 @@ object TestAuthenticator {
override val format = "apple"
override def makeAttestationStatement(
authDataBytes: ByteArray,
- clientDataJson: String,
+ clientDataJson: ByteArray,
): JsonNode =
makeAppleAttestationStatement(
caCert,
@@ -243,6 +264,7 @@ object TestAuthenticator {
caKey,
)
}
+
def tpm(
cert: AttestationCert,
ver: Option[String] = Some("2.0"),
@@ -260,7 +282,7 @@ object TestAuthenticator {
override def certChain = cert.certChain
override def makeAttestationStatement(
authDataBytes: ByteArray,
- clientDataJson: String,
+ clientDataJson: ByteArray,
): JsonNode =
makeTpmAttestationStatement(
authDataBytes,
@@ -283,7 +305,7 @@ object TestAuthenticator {
override def certChain = Nil
override def makeAttestationStatement(
authDataBytes: ByteArray,
- clientDataJson: String,
+ clientDataJson: ByteArray,
): JsonNode =
makeNoneAttestationStatement()
}
@@ -294,6 +316,7 @@ object TestAuthenticator {
def cert: X509Certificate
def certChain: List[(X509Certificate, PrivateKey)]
}
+
case class SelfAttestation(keypair: KeyPair, alg: COSEAlgorithmIdentifier)
extends AttestationSigner {
override def key: PrivateKey = keypair.getPrivate
@@ -302,6 +325,7 @@ object TestAuthenticator {
}
override def certChain = Nil
}
+
case class AttestationCert(
override val cert: X509Certificate,
override val key: PrivateKey,
@@ -313,6 +337,7 @@ object TestAuthenticator {
keypair: (X509Certificate, PrivateKey),
) = this(keypair._1, keypair._2, alg, Nil)
}
+
object AttestationSigner {
def ca(
alg: COSEAlgorithmIdentifier,
@@ -419,41 +444,31 @@ object TestAuthenticator {
origin: String = Defaults.origin,
tokenBindingStatus: String = Defaults.TokenBinding.status,
tokenBindingId: Option[String] = Defaults.TokenBinding.id,
- ): String = {
- val clientDataJson: String =
- JacksonCodecs.json.writeValueAsString(clientData getOrElse {
- val json: ObjectNode = jsonFactory.objectNode()
-
- json.setAll(
+ ): String =
+ JacksonCodecs.json.writeValueAsString(clientData getOrElse {
+ jsonMap {
+ f =>
Map(
- "challenge" -> jsonFactory.textNode(challenge.getBase64Url),
- "origin" -> jsonFactory.textNode(origin),
- "type" -> jsonFactory.textNode("webauthn.create"),
- ).asJava
- )
-
- json.set(
- "tokenBinding", {
- val tokenBinding = jsonFactory.objectNode()
- tokenBinding.set("status", jsonFactory.textNode(tokenBindingStatus))
- tokenBindingId foreach { id =>
- tokenBinding.set("id", jsonFactory.textNode(id))
- }
- tokenBinding
- },
- )
-
- json
- })
-
- clientDataJson
- }
+ "challenge" -> f.textNode(challenge.getBase64Url),
+ "origin" -> f.textNode(origin),
+ "type" -> f.textNode("webauthn.create"),
+ "tokenBinding" -> {
+ val tokenBinding = f.objectNode()
+ tokenBinding.set("status", f.textNode(tokenBindingStatus))
+ tokenBindingId foreach { id =>
+ tokenBinding.set("id", f.textNode(id))
+ }
+ tokenBinding
+ },
+ )
+ }
+ })
def createCredential(
authDataBytes: ByteArray,
- clientDataJson: String,
credentialKeypair: KeyPair,
attestationMaker: AttestationMaker,
+ clientDataJson: Option[String] = None,
clientExtensions: ClientRegistrationExtensionOutputs =
ClientRegistrationExtensionOutputs.builder().build(),
): (
@@ -465,10 +480,15 @@ object TestAuthenticator {
List[(X509Certificate, PrivateKey)],
) = {
- val clientDataJsonBytes = toBytes(clientDataJson)
+ val clientDataJsonBytes = toBytes(
+ clientDataJson.getOrElse(createClientData())
+ )
val attestationObjectBytes =
- attestationMaker.makeAttestationObjectBytes(authDataBytes, clientDataJson)
+ attestationMaker.makeAttestationObjectBytes(
+ authDataBytes,
+ clientDataJsonBytes,
+ )
val response = AuthenticatorAttestationResponse
.builder()
@@ -510,7 +530,6 @@ object TestAuthenticator {
createCredential(
authDataBytes = authData,
credentialKeypair = credentialKeypair,
- clientDataJson = createClientData(),
attestationMaker = attestationMaker,
)
}
@@ -532,7 +551,6 @@ object TestAuthenticator {
val signer = SelfAttestation(keypair, keyAlgorithm)
createCredential(
authDataBytes = authData,
- clientDataJson = createClientData(),
credentialKeypair = keypair,
attestationMaker = attestationMaker(signer),
)
@@ -556,7 +574,7 @@ object TestAuthenticator {
)
createCredential(
authDataBytes = authData,
- clientDataJson = createClientData(challenge = challenge),
+ clientDataJson = Some(createClientData(challenge = challenge)),
credentialKeypair = keypair,
attestationMaker = AttestationMaker.none(),
)
@@ -611,28 +629,22 @@ object TestAuthenticator {
val clientDataJson: String =
JacksonCodecs.json.writeValueAsString(clientData getOrElse {
- val json: ObjectNode = jsonFactory.objectNode()
-
- json.setAll(
- Map(
- "challenge" -> jsonFactory.textNode(challenge.getBase64Url),
- "origin" -> jsonFactory.textNode(origin),
- "type" -> jsonFactory.textNode("webauthn.get"),
- ).asJava
- )
-
- json.set(
- "tokenBinding", {
- val tokenBinding = jsonFactory.objectNode()
- tokenBinding.set("status", jsonFactory.textNode(tokenBindingStatus))
- tokenBindingId foreach { id =>
- tokenBinding.set("id", jsonFactory.textNode(id))
- }
- tokenBinding
- },
- )
-
- json
+ jsonMap {
+ f =>
+ Map(
+ "challenge" -> f.textNode(challenge.getBase64Url),
+ "origin" -> f.textNode(origin),
+ "type" -> f.textNode("webauthn.get"),
+ "tokenBinding" -> {
+ val tokenBinding = f.objectNode()
+ tokenBinding.set("status", f.textNode(tokenBindingStatus))
+ tokenBindingId foreach { id =>
+ tokenBinding.set("id", f.textNode(id))
+ }
+ tokenBinding
+ },
+ )
+ }
})
val clientDataJsonBytes = toBytes(clientDataJson)
@@ -671,14 +683,14 @@ object TestAuthenticator {
def makeU2fAttestationStatement(
authDataBytes: ByteArray,
- clientDataJson: String,
+ clientDataJson: ByteArray,
signer: AttestationSigner,
): JsonNode = {
val authData = new AuthenticatorData(authDataBytes)
def makeSignedData(
rpIdHash: ByteArray,
- clientDataJson: String,
+ clientDataJson: ByteArray,
credentialId: ByteArray,
credentialPublicKeyRawBytes: ByteArray,
): ByteArray = {
@@ -704,28 +716,25 @@ object TestAuthenticator {
),
)
- val f = JsonNodeFactory.instance
- f.objectNode()
- .setAll(
- Map(
- "x5c" -> f.arrayNode().add(f.binaryNode(signer.cert.getEncoded)),
- "sig" -> f.binaryNode(
- sign(
- signedData,
- signer.key,
- signer.alg,
- ).getBytes
- ),
- ).asJava
+ jsonMap { f =>
+ Map(
+ "x5c" -> f.arrayNode().add(f.binaryNode(signer.cert.getEncoded)),
+ "sig" -> f.binaryNode(
+ sign(
+ signedData,
+ signer.key,
+ signer.alg,
+ ).getBytes
+ ),
)
+ }
}
- def makeNoneAttestationStatement(): JsonNode =
- JsonNodeFactory.instance.objectNode()
+ def makeNoneAttestationStatement(): JsonNode = jsonFactory.objectNode()
def makePackedAttestationStatement(
authDataBytes: ByteArray,
- clientDataJson: String,
+ clientDataJson: ByteArray,
signer: AttestationSigner,
): JsonNode = {
val signedData = new ByteArray(
@@ -737,66 +746,56 @@ object TestAuthenticator {
case AttestationCert(_, key, alg, _) => sign(signedData, key, alg)
}
- val f = JsonNodeFactory.instance
- f.objectNode()
- .setAll(
- (
+ jsonMap { f =>
+ Map(
+ "alg" -> f.numberNode(signer.alg.getId),
+ "sig" -> f.binaryNode(signature.getBytes),
+ ) ++ (signer match {
+ case _: SelfAttestation => Map.empty
+ case AttestationCert(cert, _, _, _) =>
Map(
- "alg" -> f.numberNode(signer.alg.getId),
- "sig" -> f.binaryNode(signature.getBytes),
- ) ++ (signer match {
- case _: SelfAttestation => Map.empty
- case AttestationCert(cert, _, _, _) =>
- Map(
- "x5c" -> f
- .arrayNode()
- .add(cert.getEncoded)
- )
- })
- ).asJava
- )
+ "x5c" -> f
+ .arrayNode()
+ .add(cert.getEncoded)
+ )
+ })
+ }
}
def makeAndroidSafetynetAttestationStatement(
authDataBytes: ByteArray,
- clientDataJson: String,
+ clientDataJson: ByteArray,
cert: AttestationCert,
ctsProfileMatch: Boolean = true,
): JsonNode = {
val nonce =
Crypto.sha256(authDataBytes concat Crypto.sha256(clientDataJson))
- val f = JsonNodeFactory.instance
-
- val jwsHeader = f
- .objectNode()
- .setAll[ObjectNode](
- Map(
- "alg" -> f.textNode("RS256"),
- "x5c" -> f
- .arrayNode()
- .add(new ByteArray(cert.cert.getEncoded).getBase64),
- ).asJava
+ val jwsHeader = jsonMap { f =>
+ Map(
+ "alg" -> f.textNode("RS256"),
+ "x5c" -> f
+ .arrayNode()
+ .add(new ByteArray(cert.cert.getEncoded).getBase64),
)
+ }
val jwsHeaderBase64 = new ByteArray(
JacksonCodecs.json().writeValueAsBytes(jwsHeader)
).getBase64Url
- val jwsPayload = f
- .objectNode()
- .setAll[ObjectNode](
- Map(
- "nonce" -> f.textNode(nonce.getBase64),
- "timestampMs" -> f.numberNode(Instant.now().toEpochMilli),
- "apkPackageName" -> f.textNode("com.yubico.webauthn.test"),
- "apkDigestSha256" -> f.textNode(Crypto.sha256("foo").getBase64),
- "ctsProfileMatch" -> f.booleanNode(ctsProfileMatch),
- "aplCertificateDigestSha256" -> f
- .arrayNode()
- .add(f.textNode(Crypto.sha256("foo").getBase64)),
- "basicIntegrity" -> f.booleanNode(true),
- ).asJava
+ val jwsPayload = jsonMap { f =>
+ Map(
+ "nonce" -> f.textNode(nonce.getBase64),
+ "timestampMs" -> f.numberNode(Instant.now().toEpochMilli),
+ "apkPackageName" -> f.textNode("com.yubico.webauthn.test"),
+ "apkDigestSha256" -> f.textNode(Crypto.sha256("foo").getBase64),
+ "ctsProfileMatch" -> f.booleanNode(ctsProfileMatch),
+ "aplCertificateDigestSha256" -> f
+ .arrayNode()
+ .add(f.textNode(Crypto.sha256("foo").getBase64)),
+ "basicIntegrity" -> f.booleanNode(true),
)
+ }
val jwsPayloadBase64 = new ByteArray(
JacksonCodecs.json().writeValueAsBytes(jwsPayload)
).getBase64Url
@@ -809,16 +808,14 @@ object TestAuthenticator {
val jwsCompact = jwsSignedCompact + "." + jwsSignature.getBase64Url
- val attStmt = f
- .objectNode()
- .setAll[ObjectNode](
- Map(
- "ver" -> f.textNode("14799021"),
- "response" -> f.binaryNode(
- jwsCompact.getBytes(StandardCharsets.UTF_8)
- ),
- ).asJava
+ val attStmt = jsonMap { f =>
+ Map(
+ "ver" -> f.textNode("14799021"),
+ "response" -> f.binaryNode(
+ jwsCompact.getBytes(StandardCharsets.UTF_8)
+ ),
)
+ }
attStmt
}
@@ -827,15 +824,12 @@ object TestAuthenticator {
caCert: X509Certificate,
caKey: PrivateKey,
authDataBytes: ByteArray,
- clientDataJson: String,
+ clientDataJson: ByteArray,
addNonceExtension: Boolean = true,
nonceValue: Option[ByteArray] = None,
certSubjectPublicKey: Option[PublicKey] = None,
): JsonNode = {
- val clientDataJSON = new ByteArray(
- clientDataJson.getBytes(StandardCharsets.UTF_8)
- )
- val clientDataJsonHash = Crypto.sha256(clientDataJSON)
+ val clientDataJsonHash = Crypto.sha256(clientDataJson)
val nonceToHash = authDataBytes.concat(clientDataJsonHash)
val nonce = Crypto.sha256(nonceToHash)
@@ -871,24 +865,22 @@ object TestAuthenticator {
} else Nil,
)
- val f = JsonNodeFactory.instance
- f.objectNode()
- .setAll(
- Map(
- "x5c" -> f
- .arrayNode()
- .addAll(
- List(subjectCert, caCert)
- .map(crt => f.binaryNode(crt.getEncoded))
- .asJava
- )
- ).asJava
+ jsonMap { f =>
+ Map(
+ "x5c" -> f
+ .arrayNode()
+ .addAll(
+ List(subjectCert, caCert)
+ .map(crt => f.binaryNode(crt.getEncoded))
+ .asJava
+ )
)
+ }
}
def makeTpmAttestationStatement(
authDataBytes: ByteArray,
- clientDataJson: String,
+ clientDataJson: ByteArray,
cert: AttestationCert,
ver: Option[String] = Some("2.0"),
magic: ByteArray = TpmAttestationStatementVerifier.TPM_GENERATED_VALUE,
@@ -917,7 +909,12 @@ object TestAuthenticator {
(TpmAlgHash.SHA512, TpmAlgAsym.ECC)
case COSEAlgorithmIdentifier.RS256 =>
(TpmAlgHash.SHA256, TpmAlgAsym.RSA)
- case COSEAlgorithmIdentifier.RS1 => (TpmAlgHash.SHA1, TpmAlgAsym.RSA)
+ case COSEAlgorithmIdentifier.RS384 =>
+ (TpmAlgHash.SHA384, TpmAlgAsym.RSA)
+ case COSEAlgorithmIdentifier.RS512 =>
+ (TpmAlgHash.SHA512, TpmAlgAsym.RSA)
+ case COSEAlgorithmIdentifier.RS1 => (TpmAlgHash.SHA1, TpmAlgAsym.RSA)
+ case COSEAlgorithmIdentifier.EdDSA => ???
}
val hashFunc = hashId match {
case TpmAlgHash.SHA256 => Crypto.sha256(_: ByteArray)
@@ -925,13 +922,10 @@ object TestAuthenticator {
case TpmAlgHash.SHA512 => Crypto.sha512 _
case TpmAlgHash.SHA1 => Crypto.sha1 _
}
- val extraData = {
- hashFunc(
- authDataBytes concat Crypto.sha256(
- new ByteArray(clientDataJson.getBytes(StandardCharsets.UTF_8))
- )
- )
- }
+ val extraData = hashFunc(
+ authDataBytes concat Crypto.sha256(clientDataJson)
+ )
+
val (parameters, unique) = WebAuthnTestCodecs.getCoseKty(cosePubkey) match {
case 3 => { // RSA
val cose = CBORObject.DecodeFromBytes(cosePubkey.getBytes)
@@ -972,6 +966,12 @@ object TestAuthenticator {
case COSEAlgorithmIdentifier.ES256 => 0x0003
case COSEAlgorithmIdentifier.ES384 => 0x0004
case COSEAlgorithmIdentifier.ES512 => 0x0005
+ case COSEAlgorithmIdentifier.RS1 |
+ COSEAlgorithmIdentifier.RS256 |
+ COSEAlgorithmIdentifier.RS384 |
+ COSEAlgorithmIdentifier.RS512 |
+ COSEAlgorithmIdentifier.EdDSA =>
+ ???
}))
)
.concat(
@@ -1032,23 +1032,20 @@ object TestAuthenticator {
val sig = sign(certInfo, cert.key, cert.alg)
- val f = JsonNodeFactory.instance
- f
- .objectNode()
- .setAll[ObjectNode](
- Map(
- "ver" -> ver.map(f.textNode).getOrElse(f.nullNode()),
- "alg" -> f.numberNode(cert.alg.getId),
- "x5c" -> f
- .arrayNode()
- .addAll(
- cert.certChain.map(_._1.getEncoded).map(f.binaryNode).asJava
- ),
- "sig" -> f.binaryNode(sig.getBytes),
- "certInfo" -> f.binaryNode(certInfo.getBytes),
- "pubArea" -> f.binaryNode(pubArea.getBytes),
- ).asJava
+ jsonMap { f =>
+ Map(
+ "ver" -> ver.map(f.textNode).getOrElse(f.nullNode()),
+ "alg" -> f.numberNode(cert.alg.getId),
+ "x5c" -> f
+ .arrayNode()
+ .addAll(
+ cert.certChain.map(_._1.getEncoded).map(f.binaryNode).asJava
+ ),
+ "sig" -> f.binaryNode(sig.getBytes),
+ "certInfo" -> f.binaryNode(certInfo.getBytes),
+ "pubArea" -> f.binaryNode(pubArea.getBytes),
)
+ }
}
def makeAuthDataBytes(
@@ -1124,8 +1121,9 @@ object TestAuthenticator {
case COSEAlgorithmIdentifier.ES256 => generateEcKeypair("secp256r1")
case COSEAlgorithmIdentifier.ES384 => generateEcKeypair("secp384r1")
case COSEAlgorithmIdentifier.ES512 => generateEcKeypair("secp521r1")
- case COSEAlgorithmIdentifier.RS256 => generateRsaKeypair()
- case COSEAlgorithmIdentifier.RS1 => generateRsaKeypair()
+ case COSEAlgorithmIdentifier.RS256 | COSEAlgorithmIdentifier.RS384 |
+ COSEAlgorithmIdentifier.RS512 | COSEAlgorithmIdentifier.RS1 =>
+ generateRsaKeypair()
}
def generateEcKeypair(curve: String = "secp256r1"): KeyPair = {
diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/WebAuthnCodecsSpec.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/WebAuthnCodecsSpec.scala
index f70ab9ca1..c1efa2775 100644
--- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/WebAuthnCodecsSpec.scala
+++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/WebAuthnCodecsSpec.scala
@@ -48,7 +48,7 @@ class WebAuthnCodecsSpec
Arbitrary(
for {
ySign: Byte <- Gen.oneOf(0x02: Byte, 0x03: Byte)
- rawBytes: Seq[Byte] <- Gen.listOfN[Byte](32, Arbitrary.arbitrary[Byte])
+ rawBytes <- Gen.listOfN[Byte](32, Arbitrary.arbitrary[Byte])
key = Try(
Util
.decodePublicKey(new ByteArray((ySign +: rawBytes).toArray))
diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/WebAuthnTestCodecs.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/WebAuthnTestCodecs.scala
index 7643a6d40..eb0202ebb 100644
--- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/WebAuthnTestCodecs.scala
+++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/WebAuthnTestCodecs.scala
@@ -40,7 +40,8 @@ object WebAuthnTestCodecs {
alg: COSEAlgorithmIdentifier,
): PrivateKey =
alg match {
- case COSEAlgorithmIdentifier.ES256 =>
+ case COSEAlgorithmIdentifier.ES256 | COSEAlgorithmIdentifier.ES384 |
+ COSEAlgorithmIdentifier.ES512 =>
val keyFactory: KeyFactory = KeyFactory.getInstance("EC")
val spec = new PKCS8EncodedKeySpec(encodedKey.getBytes)
keyFactory.generatePrivate(spec)
@@ -50,7 +51,8 @@ object WebAuthnTestCodecs {
val spec = new PKCS8EncodedKeySpec(encodedKey.getBytes)
keyFactory.generatePrivate(spec)
- case COSEAlgorithmIdentifier.RS256 | COSEAlgorithmIdentifier.RS1 =>
+ case COSEAlgorithmIdentifier.RS256 | COSEAlgorithmIdentifier.RS384 |
+ COSEAlgorithmIdentifier.RS512 | COSEAlgorithmIdentifier.RS1 =>
val keyFactory: KeyFactory = KeyFactory.getInstance("RSA")
val spec = new PKCS8EncodedKeySpec(encodedKey.getBytes)
keyFactory.generatePrivate(spec)
diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/Generators.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/Generators.scala
index 6a0ba9742..24d1fe2d0 100644
--- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/Generators.scala
+++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/Generators.scala
@@ -30,6 +30,7 @@ import com.upokecenter.cbor.CBOREncodeOptions
import com.upokecenter.cbor.CBORObject
import com.yubico.internal.util.BinaryUtil
import com.yubico.internal.util.JacksonCodecs
+import com.yubico.scalacheck.gen.GenUtil.halfsized
import com.yubico.scalacheck.gen.JacksonGenerators
import com.yubico.scalacheck.gen.JacksonGenerators._
import com.yubico.scalacheck.gen.JavaGenerators._
@@ -81,38 +82,42 @@ object Generators {
implicit val arbitraryAssertionRequest: Arbitrary[AssertionRequest] =
Arbitrary(
- for {
- publicKeyCredentialRequestOptions <-
- arbitrary[PublicKeyCredentialRequestOptions]
- username <- arbitrary[Optional[String]]
- } yield AssertionRequest
- .builder()
- .publicKeyCredentialRequestOptions(publicKeyCredentialRequestOptions)
- .username(username)
- .build()
+ halfsized(
+ for {
+ publicKeyCredentialRequestOptions <-
+ arbitrary[PublicKeyCredentialRequestOptions]
+ username <- arbitrary[Optional[String]]
+ } yield AssertionRequest
+ .builder()
+ .publicKeyCredentialRequestOptions(publicKeyCredentialRequestOptions)
+ .username(username)
+ .build()
+ )
)
implicit val arbitraryAttestedCredentialData
: Arbitrary[AttestedCredentialData] = Arbitrary(
- for {
- aaguid <- byteArray(16)
- credentialId <- arbitrary[ByteArray]
- credentialPublicKey <- Gen.delay(
- Gen.const(
- TestAuthenticator
- .generateEcKeypair()
- .getPublic
- .asInstanceOf[ECPublicKey]
+ halfsized(
+ for {
+ aaguid <- byteArray(16)
+ credentialId <- arbitrary[ByteArray]
+ credentialPublicKey <- Gen.delay(
+ Gen.const(
+ TestAuthenticator
+ .generateEcKeypair()
+ .getPublic
+ .asInstanceOf[ECPublicKey]
+ )
)
- )
- credentialPublicKeyCose =
- WebAuthnTestCodecs.ecPublicKeyToCose(credentialPublicKey)
- } yield AttestedCredentialData
- .builder()
- .aaguid(aaguid)
- .credentialId(credentialId)
- .credentialPublicKey(credentialPublicKeyCose)
- .build()
+ credentialPublicKeyCose =
+ WebAuthnTestCodecs.ecPublicKeyToCose(credentialPublicKey)
+ } yield AttestedCredentialData
+ .builder()
+ .aaguid(aaguid)
+ .credentialId(credentialId)
+ .credentialPublicKey(credentialPublicKeyCose)
+ .build()
+ )
)
def attestedCredentialDataBytes: Gen[ByteArray] =
for {
@@ -143,7 +148,7 @@ object Generators {
extensionOutputsGen: Gen[Option[CBORObject]] =
Gen.option(Extensions.authenticatorAssertionExtensionOutputs())
): Gen[ByteArray] =
- for {
+ halfsized(for {
authData <- authenticatorDataBytes(extensionOutputsGen)
alg <- arbitrary[COSEAlgorithmIdentifier]
sig <- arbitrary[ByteArray]
@@ -172,13 +177,13 @@ object Generators {
"attStmt" -> attStmt,
).asJava
)
- } yield new ByteArray(JacksonCodecs.cbor().writeValueAsBytes(attObj))
+ } yield new ByteArray(JacksonCodecs.cbor().writeValueAsBytes(attObj)))
def fidoU2fAttestationObject(
extensionOutputsGen: Gen[Option[CBORObject]] =
Gen.option(Extensions.authenticatorAssertionExtensionOutputs())
): Gen[ByteArray] =
- for {
+ halfsized(for {
authData <- authenticatorDataBytes(extensionOutputsGen)
sig <- arbitrary[ByteArray]
x5c <- arbitrary[List[ByteArray]]
@@ -205,7 +210,7 @@ object Generators {
"attStmt" -> attStmt,
).asJava
)
- } yield new ByteArray(JacksonCodecs.cbor().writeValueAsBytes(attObj))
+ } yield new ByteArray(JacksonCodecs.cbor().writeValueAsBytes(attObj)))
val authenticatorDataFlagsByte: Gen[Byte] = for {
value <- arbitrary[Byte]
@@ -225,41 +230,47 @@ object Generators {
extensionOutputsGen: Gen[Option[CBORObject]] =
Gen.option(Extensions.authenticatorAssertionExtensionOutputs())
): Gen[AuthenticatorAssertionResponse] =
- for {
- authenticatorData <- authenticatorDataBytes(extensionOutputsGen)
- clientDataJson <- clientDataJsonBytes
- signature <- arbitrary[ByteArray]
- userHandle <- arbitrary[Option[ByteArray]]
- } yield AuthenticatorAssertionResponse
- .builder()
- .authenticatorData(authenticatorData)
- .clientDataJSON(clientDataJson)
- .signature(signature)
- .userHandle(userHandle.toJava)
- .build()
+ halfsized(
+ for {
+ authenticatorData <- authenticatorDataBytes(extensionOutputsGen)
+ clientDataJson <- clientDataJsonBytes
+ signature <- arbitrary[ByteArray]
+ userHandle <- arbitrary[Option[ByteArray]]
+ } yield AuthenticatorAssertionResponse
+ .builder()
+ .authenticatorData(authenticatorData)
+ .clientDataJSON(clientDataJson)
+ .signature(signature)
+ .userHandle(userHandle.toJava)
+ .build()
+ )
implicit val arbitraryAuthenticatorAttestationResponse
: Arbitrary[AuthenticatorAttestationResponse] = Arbitrary(
- for {
- attestationObject <- attestationObjectBytes()
- clientDataJSON <- clientDataJsonBytes
- } yield AuthenticatorAttestationResponse
- .builder()
- .attestationObject(attestationObject)
- .clientDataJSON(clientDataJSON)
- .build()
+ halfsized(
+ for {
+ attestationObject <- attestationObjectBytes()
+ clientDataJSON <- clientDataJsonBytes
+ } yield AuthenticatorAttestationResponse
+ .builder()
+ .attestationObject(attestationObject)
+ .clientDataJSON(clientDataJSON)
+ .build()
+ )
)
implicit val arbitraryAuthenticatorData: Arbitrary[AuthenticatorData] =
Arbitrary(
- authenticatorDataBytes(extensionsGen =
- Gen.option(
- Gen.oneOf(
- Extensions.authenticatorRegistrationExtensionOutputs(),
- Extensions.authenticatorAssertionExtensionOutputs(),
+ halfsized(
+ authenticatorDataBytes(extensionsGen =
+ Gen.option(
+ Gen.oneOf(
+ Extensions.authenticatorRegistrationExtensionOutputs(),
+ Extensions.authenticatorAssertionExtensionOutputs(),
+ )
)
- )
- ) map (new AuthenticatorData(_))
+ ) map (new AuthenticatorData(_))
+ )
)
def authenticatorDataBytes(
@@ -271,39 +282,43 @@ object Generators {
arbitrary[(Boolean, Boolean)].map({ case (be, bs) => (be, be && bs) }),
signatureCountGen: Gen[ByteArray] = byteArray(4),
): Gen[ByteArray] =
- for {
- rpIdHash <- rpIdHashGen
- signatureCount <- signatureCountGen
- attestedCredentialDataBytes <- Gen.option(attestedCredentialDataBytes)
-
- extensions <- extensionsGen
- extensionsBytes = extensions map { exts =>
- new ByteArray(
- exts.EncodeToBytes(CBOREncodeOptions.DefaultCtap2Canonical)
- )
- }
+ halfsized(
+ for {
+ rpIdHash <- rpIdHashGen
+ signatureCount <- signatureCountGen
+ attestedCredentialDataBytes <- Gen.option(attestedCredentialDataBytes)
+
+ extensions <- extensionsGen
+ extensionsBytes = extensions map { exts =>
+ new ByteArray(
+ exts.EncodeToBytes(CBOREncodeOptions.DefaultCtap2Canonical)
+ )
+ }
- flagsBase <- arbitrary[Byte]
- upFlag <- upFlagGen
- uvFlag <- uvFlagGen
- (beFlag, bsFlag) <- backupFlagsGen
- atFlag = attestedCredentialDataBytes.isDefined
- edFlag = extensionsBytes.isDefined
- flagsByte: Byte = setFlag(0x01, upFlag)(
- setFlag(0x03, uvFlag)(
- setFlag(0x40, atFlag)(
- setFlag(BinaryUtil.singleFromHex("80"), edFlag)(
- setFlag(0x08, beFlag)(setFlag(0x10, bsFlag)(flagsBase))
+ flagsBase <- arbitrary[Byte]
+ upFlag <- upFlagGen
+ uvFlag <- uvFlagGen
+ (beFlag, bsFlag) <- backupFlagsGen
+ atFlag = attestedCredentialDataBytes.isDefined
+ edFlag = extensionsBytes.isDefined
+ flagsByte: Byte = setFlag(0x01, upFlag)(
+ setFlag(0x03, uvFlag)(
+ setFlag(0x40, atFlag)(
+ setFlag(BinaryUtil.singleFromHex("80"), edFlag)(
+ setFlag(0x08, beFlag)(setFlag(0x10, bsFlag)(flagsBase))
+ )
)
)
)
+ } yield new ByteArray(
+ rpIdHash.getBytes
+ :+ flagsByte
+ :++ signatureCount.getBytes
+ ++ attestedCredentialDataBytes
+ .map(_.getBytes)
+ .getOrElse(Array.empty)
+ ++ extensionsBytes.map(_.getBytes).getOrElse(Array.empty)
)
- } yield new ByteArray(
- rpIdHash.getBytes
- :+ flagsByte
- :++ signatureCount.getBytes
- ++ attestedCredentialDataBytes.map(_.getBytes).getOrElse(Array.empty)
- ++ extensionsBytes.map(_.getBytes).getOrElse(Array.empty)
)
implicit val arbitraryAuthenticatorSelectionCriteria
@@ -336,7 +351,7 @@ object Generators {
def byteArray(minSize: Int, maxSize: Int): Gen[ByteArray] =
for {
- nums <- Gen.infiniteLazyList(arbitrary[Byte]).map(_.take(minSize))
+ nums <- Gen.infiniteLazyList(arbitrary[Byte])
len <- Gen.chooseNum(minSize, maxSize)
} yield new ByteArray(nums.take(len).toArray)
@@ -360,8 +375,6 @@ object Generators {
Set("appidExclude", "credProps", "largeBlob", "uvm")
private val AuthenticationExtensionIds: Set[String] =
Set("appid", "largeBlob", "uvm")
- private val ExtensionIds: Set[String] =
- RegistrationExtensionIds ++ AuthenticationExtensionIds
private val ClientRegistrationExtensionOutputIds: Set[String] =
RegistrationExtensionIds - "uvm"
@@ -384,7 +397,7 @@ object Generators {
for {
appidExclude <- appidExcludeGen
credProps <- credPropsGen
- largeBlob <- largeBlobGen
+ largeBlob <- halfsized(largeBlobGen)
uvm <- uvmGen
} yield {
val b = RegistrationExtensionInputs.builder()
@@ -402,7 +415,7 @@ object Generators {
): Gen[ObjectNode] =
for {
base <- gen
- extra <- genExtra
+ extra <- halfsized(genExtra)
} yield {
val result = extra
result.setAll(JacksonCodecs.json().valueToTree[ObjectNode](base))
@@ -421,7 +434,7 @@ object Generators {
for {
appidExclude <- appidExcludeGen
credProps <- credPropsGen
- largeBlob <- largeBlobGen
+ largeBlob <- halfsized(largeBlobGen)
} yield {
val b = ClientRegistrationExtensionOutputs.builder()
appidExclude.foreach(appidExclude => b.appidExclude(appidExclude))
@@ -445,12 +458,17 @@ object Generators {
)
def authenticatorRegistrationExtensionOutputs(
- uvmGen: Gen[Option[CBORObject]] = Gen.option(Uvm.authenticatorOutput)
+ uvmGen: Gen[Option[CBORObject]] = Gen.option(Uvm.authenticatorOutput),
+ includeUnknown: Boolean = true,
): Gen[CBORObject] =
for {
- uvm: Option[CBORObject] <- uvmGen
+ base <-
+ if (includeUnknown)
+ halfsized(unknownAuthenticatorRegistrationExtensionOutput)
+ else Gen.const(CBORObject.NewMap())
+ uvm: Option[CBORObject] <- halfsized(uvmGen)
} yield {
- val result = CBORObject.NewMap()
+ val result = base
uvm.foreach(result.set("uvm", _))
result
}
@@ -473,7 +491,7 @@ object Generators {
): Gen[AssertionExtensionInputs] =
for {
appid <- appidGen
- largeBlob <- largeBlobGen
+ largeBlob <- halfsized(largeBlobGen)
uvm <- uvmGen
} yield {
val b = AssertionExtensionInputs.builder()
@@ -489,7 +507,7 @@ object Generators {
): Gen[ObjectNode] =
for {
base <- gen
- extra <- genExtra
+ extra <- halfsized(genExtra)
} yield {
val result = extra
result.setAll(JacksonCodecs.json().valueToTree[ObjectNode](base))
@@ -502,7 +520,7 @@ object Generators {
] = LargeBlob.largeBlobAuthenticationOutput
): Gen[ClientAssertionExtensionOutputs] =
for {
- largeBlob <- largeBlobGen
+ largeBlob <- halfsized(largeBlobGen)
} yield {
val b = ClientAssertionExtensionOutputs.builder()
b.appid(true)
@@ -511,12 +529,17 @@ object Generators {
}
def authenticatorAssertionExtensionOutputs(
- uvmGen: Gen[Option[CBORObject]] = Gen.option(Uvm.authenticatorOutput)
+ uvmGen: Gen[Option[CBORObject]] = Gen.option(Uvm.authenticatorOutput),
+ includeUnknown: Boolean = true,
): Gen[CBORObject] =
for {
- uvm: Option[CBORObject] <- uvmGen
+ base <-
+ if (includeUnknown)
+ halfsized(unknownAuthenticatorAssertionExtensionOutput)
+ else Gen.const(CBORObject.NewMap())
+ uvm: Option[CBORObject] <- halfsized(uvmGen)
} yield {
- val result = CBORObject.NewMap()
+ val result = base
uvm.foreach(result.set("uvm", _))
result
}
@@ -627,21 +650,23 @@ object Generators {
CBORObject,
)
] =
- for {
- inputs <- arbitrary[RegistrationExtensionInputs]
- clientOutputs <- allClientRegistrationExtensionOutputs()
- authenticatorOutputs <- allAuthenticatorRegistrationExtensionOutputs()
-
- requestedExtensionIds <-
- Gen.someOf(inputs.getExtensionIds.asScala).map(_.toSet)
- returnedExtensionIds <- Gen.oneOf(
- Gen.const(requestedExtensionIds),
- Gen.someOf(requestedExtensionIds).map(_.toSet),
+ halfsized(
+ for {
+ inputs <- arbitrary[RegistrationExtensionInputs]
+ clientOutputs <- allClientRegistrationExtensionOutputs()
+ authenticatorOutputs <- allAuthenticatorRegistrationExtensionOutputs()
+
+ requestedExtensionIds <-
+ Gen.someOf(inputs.getExtensionIds.asScala).map(_.toSet)
+ returnedExtensionIds <- Gen.oneOf(
+ Gen.const(requestedExtensionIds),
+ Gen.someOf(requestedExtensionIds).map(_.toSet),
+ )
+ } yield (
+ filter(inputs, requestedExtensionIds),
+ filter(clientOutputs, returnedExtensionIds),
+ filter(authenticatorOutputs, returnedExtensionIds),
)
- } yield (
- filter(inputs, requestedExtensionIds),
- filter(clientOutputs, returnedExtensionIds),
- filter(authenticatorOutputs, returnedExtensionIds),
)
def unrequestedClientRegistrationExtensions: Gen[
@@ -651,27 +676,29 @@ object Generators {
CBORObject,
)
] =
- for {
- inputs <- arbitrary[RegistrationExtensionInputs]
- clientOutputs <- allClientRegistrationExtensionOutputs()
- authenticatorOutputs <- allAuthenticatorRegistrationExtensionOutputs()
+ halfsized(
+ for {
+ inputs <- arbitrary[RegistrationExtensionInputs]
+ clientOutputs <- allClientRegistrationExtensionOutputs()
+ authenticatorOutputs <- allAuthenticatorRegistrationExtensionOutputs()
- unrequestedClientExtensionIds: Set[String] <-
- Gen.nonEmptyContainerOf[Set, String](
- Gen.oneOf(ClientRegistrationExtensionOutputIds)
- )
- requestedExtensionIds: Set[String] <-
- Gen
- .someOf(inputs.getExtensionIds.asScala)
- .map(_.toSet -- unrequestedClientExtensionIds)
- returnedExtensionIds: Set[String] <-
- Gen
- .someOf(requestedExtensionIds)
- .map(_.toSet ++ unrequestedClientExtensionIds)
- } yield (
- filter(inputs, requestedExtensionIds),
- filter(clientOutputs, returnedExtensionIds),
- filter(authenticatorOutputs, requestedExtensionIds),
+ unrequestedClientExtensionIds: Set[String] <-
+ Gen.nonEmptyContainerOf[Set, String](
+ Gen.oneOf(ClientRegistrationExtensionOutputIds)
+ )
+ requestedExtensionIds: Set[String] <-
+ Gen
+ .someOf(inputs.getExtensionIds.asScala)
+ .map(_.toSet -- unrequestedClientExtensionIds)
+ returnedExtensionIds: Set[String] <-
+ Gen
+ .someOf(requestedExtensionIds)
+ .map(_.toSet ++ unrequestedClientExtensionIds)
+ } yield (
+ filter(inputs, requestedExtensionIds),
+ filter(clientOutputs, returnedExtensionIds),
+ filter(authenticatorOutputs, requestedExtensionIds),
+ )
)
def unrequestedAuthenticatorRegistrationExtensions: Gen[
@@ -681,27 +708,29 @@ object Generators {
CBORObject,
)
] =
- for {
- inputs <- arbitrary[RegistrationExtensionInputs]
- clientOutputs <- allClientRegistrationExtensionOutputs()
- authenticatorOutputs <- allAuthenticatorRegistrationExtensionOutputs()
+ halfsized(
+ for {
+ inputs <- arbitrary[RegistrationExtensionInputs]
+ clientOutputs <- allClientRegistrationExtensionOutputs()
+ authenticatorOutputs <- allAuthenticatorRegistrationExtensionOutputs()
- unrequestedAuthenticatorExtensionIds: Set[String] <-
- Gen.nonEmptyContainerOf[Set, String](
- Gen.oneOf(AuthenticatorRegistrationExtensionOutputIds)
- )
- requestedExtensionIds: Set[String] <-
- Gen
- .someOf(inputs.getExtensionIds.asScala)
- .map(_.toSet -- unrequestedAuthenticatorExtensionIds)
- returnedExtensionIds: Set[String] <-
- Gen
- .someOf(requestedExtensionIds)
- .map(_.toSet ++ unrequestedAuthenticatorExtensionIds)
- } yield (
- filter(inputs, requestedExtensionIds),
- filter(clientOutputs, requestedExtensionIds),
- filter(authenticatorOutputs, returnedExtensionIds),
+ unrequestedAuthenticatorExtensionIds: Set[String] <-
+ Gen.nonEmptyContainerOf[Set, String](
+ Gen.oneOf(AuthenticatorRegistrationExtensionOutputIds)
+ )
+ requestedExtensionIds: Set[String] <-
+ Gen
+ .someOf(inputs.getExtensionIds.asScala)
+ .map(_.toSet -- unrequestedAuthenticatorExtensionIds)
+ returnedExtensionIds: Set[String] <-
+ Gen
+ .someOf(requestedExtensionIds)
+ .map(_.toSet ++ unrequestedAuthenticatorExtensionIds)
+ } yield (
+ filter(inputs, requestedExtensionIds),
+ filter(clientOutputs, requestedExtensionIds),
+ filter(authenticatorOutputs, returnedExtensionIds),
+ )
)
def anyRegistrationExtensions: Gen[
@@ -711,113 +740,125 @@ object Generators {
CBORObject,
)
] =
- for {
- inputs <- arbitrary[RegistrationExtensionInputs]
- clientOutputs <- allClientRegistrationExtensionOutputs()
- authenticatorOutputs <- allAuthenticatorRegistrationExtensionOutputs()
-
- requestedExtensionIds <-
- Gen.someOf(RegistrationExtensionIds).map(_.toSet)
- returnedClientExtensionIds <-
- Gen.someOf(ClientRegistrationExtensionOutputIds).map(_.toSet)
- returnedAuthenticatorExtensionIds <-
- Gen.someOf(AuthenticatorRegistrationExtensionOutputIds).map(_.toSet)
- } yield (
- filter(inputs, requestedExtensionIds),
- filter(clientOutputs, returnedClientExtensionIds),
- filter(authenticatorOutputs, returnedAuthenticatorExtensionIds),
+ halfsized(
+ for {
+ inputs <- arbitrary[RegistrationExtensionInputs]
+ clientOutputs <- allClientRegistrationExtensionOutputs()
+ authenticatorOutputs <- allAuthenticatorRegistrationExtensionOutputs()
+
+ requestedExtensionIds <-
+ Gen.someOf(RegistrationExtensionIds).map(_.toSet)
+ returnedClientExtensionIds <-
+ Gen.someOf(ClientRegistrationExtensionOutputIds).map(_.toSet)
+ returnedAuthenticatorExtensionIds <-
+ Gen.someOf(AuthenticatorRegistrationExtensionOutputIds).map(_.toSet)
+ } yield (
+ filter(inputs, requestedExtensionIds),
+ filter(clientOutputs, returnedClientExtensionIds),
+ filter(authenticatorOutputs, returnedAuthenticatorExtensionIds),
+ )
)
def subsetAssertionExtensions: Gen[
(AssertionExtensionInputs, ClientAssertionExtensionOutputs, CBORObject)
] =
- for {
- inputs <- arbitrary[AssertionExtensionInputs]
- clientOutputs <- allClientAssertionExtensionOutputs()
- authenticatorOutputs <- allAuthenticatorAssertionExtensionOutputs()
-
- requestedExtensionIds <-
- Gen.someOf(inputs.getExtensionIds.asScala).map(_.toSet)
- returnedExtensionIds <- Gen.oneOf(
- Gen.const(requestedExtensionIds),
- Gen.someOf(requestedExtensionIds).map(_.toSet),
+ halfsized(
+ for {
+ inputs <- arbitrary[AssertionExtensionInputs]
+ clientOutputs <- allClientAssertionExtensionOutputs()
+ authenticatorOutputs <- allAuthenticatorAssertionExtensionOutputs()
+
+ requestedExtensionIds <-
+ Gen.someOf(inputs.getExtensionIds.asScala).map(_.toSet)
+ returnedExtensionIds <- Gen.oneOf(
+ Gen.const(requestedExtensionIds),
+ Gen.someOf(requestedExtensionIds).map(_.toSet),
+ )
+ } yield (
+ filter(inputs, requestedExtensionIds),
+ filter(clientOutputs, returnedExtensionIds),
+ filter(authenticatorOutputs, returnedExtensionIds),
)
- } yield (
- filter(inputs, requestedExtensionIds),
- filter(clientOutputs, returnedExtensionIds),
- filter(authenticatorOutputs, returnedExtensionIds),
)
def unrequestedClientAssertionExtensions: Gen[
(AssertionExtensionInputs, ClientAssertionExtensionOutputs, CBORObject)
] =
- for {
- inputs <- arbitrary[AssertionExtensionInputs]
- clientOutputs <- allClientAssertionExtensionOutputs()
- authenticatorOutputs <- allAuthenticatorAssertionExtensionOutputs()
+ halfsized(
+ for {
+ inputs <- arbitrary[AssertionExtensionInputs]
+ clientOutputs <- allClientAssertionExtensionOutputs()
+ authenticatorOutputs <- allAuthenticatorAssertionExtensionOutputs()
- unrequestedClientExtensionIds: Set[String] <-
- Gen.nonEmptyContainerOf[Set, String](
- Gen.oneOf(ClientAuthenticationExtensionOutputIds)
- )
- requestedExtensionIds: Set[String] <-
- Gen
- .someOf(inputs.getExtensionIds.asScala)
- .map(_.toSet -- unrequestedClientExtensionIds)
- returnedExtensionIds: Set[String] <-
- Gen
- .someOf(requestedExtensionIds)
- .map(_.toSet ++ unrequestedClientExtensionIds)
- } yield (
- filter(inputs, requestedExtensionIds),
- filter(clientOutputs, returnedExtensionIds),
- filter(authenticatorOutputs, requestedExtensionIds),
+ unrequestedClientExtensionIds: Set[String] <-
+ Gen.nonEmptyContainerOf[Set, String](
+ Gen.oneOf(ClientAuthenticationExtensionOutputIds)
+ )
+ requestedExtensionIds: Set[String] <-
+ Gen
+ .someOf(inputs.getExtensionIds.asScala)
+ .map(_.toSet -- unrequestedClientExtensionIds)
+ returnedExtensionIds: Set[String] <-
+ Gen
+ .someOf(requestedExtensionIds)
+ .map(_.toSet ++ unrequestedClientExtensionIds)
+ } yield (
+ filter(inputs, requestedExtensionIds),
+ filter(clientOutputs, returnedExtensionIds),
+ filter(authenticatorOutputs, requestedExtensionIds),
+ )
)
def unrequestedAuthenticatorAssertionExtensions: Gen[
(AssertionExtensionInputs, ClientAssertionExtensionOutputs, CBORObject)
] =
- for {
- inputs <- arbitrary[AssertionExtensionInputs]
- clientOutputs <- allClientAssertionExtensionOutputs()
- authenticatorOutputs <- allAuthenticatorAssertionExtensionOutputs()
+ halfsized(
+ for {
+ inputs <- arbitrary[AssertionExtensionInputs]
+ clientOutputs <- allClientAssertionExtensionOutputs()
+ authenticatorOutputs <- allAuthenticatorAssertionExtensionOutputs()
- unrequestedAuthenticatorExtensionIds: Set[String] <-
- Gen.nonEmptyContainerOf[Set, String](
- Gen.oneOf(AuthenticatorAuthenticationExtensionOutputIds)
- )
- requestedExtensionIds: Set[String] <-
- Gen
- .someOf(inputs.getExtensionIds.asScala)
- .map(_.toSet -- unrequestedAuthenticatorExtensionIds)
- returnedExtensionIds: Set[String] <-
- Gen
- .someOf(requestedExtensionIds)
- .map(_.toSet ++ unrequestedAuthenticatorExtensionIds)
- } yield (
- filter(inputs, requestedExtensionIds),
- filter(clientOutputs, requestedExtensionIds),
- filter(authenticatorOutputs, returnedExtensionIds),
+ unrequestedAuthenticatorExtensionIds: Set[String] <-
+ Gen.nonEmptyContainerOf[Set, String](
+ Gen.oneOf(AuthenticatorAuthenticationExtensionOutputIds)
+ )
+ requestedExtensionIds: Set[String] <-
+ Gen
+ .someOf(inputs.getExtensionIds.asScala)
+ .map(_.toSet -- unrequestedAuthenticatorExtensionIds)
+ returnedExtensionIds: Set[String] <-
+ Gen
+ .someOf(requestedExtensionIds)
+ .map(_.toSet ++ unrequestedAuthenticatorExtensionIds)
+ } yield (
+ filter(inputs, requestedExtensionIds),
+ filter(clientOutputs, requestedExtensionIds),
+ filter(authenticatorOutputs, returnedExtensionIds),
+ )
)
def anyAssertionExtensions: Gen[
(AssertionExtensionInputs, ClientAssertionExtensionOutputs, CBORObject)
] =
- for {
- inputs <- arbitrary[AssertionExtensionInputs]
- clientOutputs <- allClientAssertionExtensionOutputs()
- authenticatorOutputs <- allAuthenticatorAssertionExtensionOutputs()
-
- requestedExtensionIds <-
- Gen.someOf(AuthenticationExtensionIds).map(_.toSet)
- returnedClientExtensionIds <-
- Gen.someOf(ClientAuthenticationExtensionOutputIds).map(_.toSet)
- returnedAuthenticatorExtensionIds <-
- Gen.someOf(AuthenticatorAuthenticationExtensionOutputIds).map(_.toSet)
- } yield (
- filter(inputs, requestedExtensionIds),
- filter(clientOutputs, returnedClientExtensionIds),
- filter(authenticatorOutputs, returnedAuthenticatorExtensionIds),
+ halfsized(
+ for {
+ inputs <- arbitrary[AssertionExtensionInputs]
+ clientOutputs <- allClientAssertionExtensionOutputs()
+ authenticatorOutputs <- allAuthenticatorAssertionExtensionOutputs()
+
+ requestedExtensionIds <-
+ Gen.someOf(AuthenticationExtensionIds).map(_.toSet)
+ returnedClientExtensionIds <-
+ Gen.someOf(ClientAuthenticationExtensionOutputIds).map(_.toSet)
+ returnedAuthenticatorExtensionIds <-
+ Gen
+ .someOf(AuthenticatorAuthenticationExtensionOutputIds)
+ .map(_.toSet)
+ } yield (
+ filter(inputs, requestedExtensionIds),
+ filter(clientOutputs, returnedClientExtensionIds),
+ filter(authenticatorOutputs, returnedAuthenticatorExtensionIds),
+ )
)
object CredProps {
@@ -842,22 +883,22 @@ object Generators {
} yield new LargeBlobRegistrationOutput(supported)
def largeBlobAuthenticationInput: Gen[LargeBlobAuthenticationInput] =
- arbitrary[ByteArray] flatMap { write =>
+ halfsized(
Gen.oneOf(
- LargeBlobAuthenticationInput.read(),
- LargeBlobAuthenticationInput.write(write),
+ Gen.const(LargeBlobAuthenticationInput.read()),
+ arbitrary[ByteArray].map(LargeBlobAuthenticationInput.write),
)
- }
+ )
def largeBlobAuthenticationOutput: Gen[LargeBlobAuthenticationOutput] =
- for {
+ halfsized(for {
blob <- arbitrary[ByteArray]
written <- arbitrary[Boolean]
result <- Gen.oneOf(
new LargeBlobAuthenticationOutput(blob, null),
new LargeBlobAuthenticationOutput(null, written),
)
- } yield result
+ } yield result)
}
object Uvm {
@@ -880,14 +921,9 @@ object Generators {
)
def authenticatorOutput: Gen[CBORObject] =
- for {
- entry1 <- uvmEntry
- entry23 <- Gen.listOfN(2, uvmEntry)
- } yield {
- CBORObject.FromObject(
- Array(encodeUvmEntry(entry1)) ++ (entry23.map(encodeUvmEntry))
- )
- }
+ halfsized(for {
+ entries <- Gen.resize(3, Gen.nonEmptyListOf(uvmEntry))
+ } yield CBORObject.FromObject(entries.map(encodeUvmEntry).toArray))
}
}
@@ -919,7 +955,7 @@ object Generators {
implicit val arbitraryCollectedClientData: Arbitrary[CollectedClientData] =
Arbitrary(clientDataJsonBytes map (new CollectedClientData(_)))
def clientDataJsonBytes: Gen[ByteArray] =
- for {
+ halfsized(for {
jsonBase <- arbitrary[ObjectNode]
challenge <- arbitrary[ByteArray]
origin <- arbitrary[URL]
@@ -965,7 +1001,7 @@ object Generators {
json
}
- } yield new ByteArray(JacksonCodecs.json().writeValueAsBytes(json))
+ } yield new ByteArray(JacksonCodecs.json().writeValueAsBytes(json)))
implicit val arbitraryCOSEAlgorithmIdentifier
: Arbitrary[COSEAlgorithmIdentifier] = Arbitrary(
@@ -977,17 +1013,19 @@ object Generators {
AuthenticatorAssertionResponse,
ClientAssertionExtensionOutputs,
]] = Arbitrary(
- for {
- id <- arbitrary[ByteArray]
- (_, clientExtensionResults, authenticatorExtensionOutputs) <-
- Extensions.anyAssertionExtensions
- response <- arbitrary[AuthenticatorAssertionResponse]
- } yield PublicKeyCredential
- .builder()
- .id(id)
- .response(response)
- .clientExtensionResults(clientExtensionResults)
- .build()
+ halfsized(
+ for {
+ id <- arbitrary[ByteArray]
+ (_, clientExtensionResults, authenticatorExtensionOutputs) <-
+ Extensions.anyAssertionExtensions
+ response <- arbitrary[AuthenticatorAssertionResponse]
+ } yield PublicKeyCredential
+ .builder()
+ .id(id)
+ .response(response)
+ .clientExtensionResults(clientExtensionResults)
+ .build()
+ )
)
implicit val arbitraryPublicKeyCredentialWithAttestation
@@ -995,92 +1033,102 @@ object Generators {
AuthenticatorAttestationResponse,
ClientRegistrationExtensionOutputs,
]] = Arbitrary(
- for {
- id <- arbitrary[ByteArray]
- response <- arbitrary[AuthenticatorAttestationResponse]
- clientExtensionResults <- arbitrary[ClientRegistrationExtensionOutputs]
- } yield PublicKeyCredential
- .builder()
- .id(id)
- .response(response)
- .clientExtensionResults(clientExtensionResults)
- .build()
+ halfsized(
+ for {
+ id <- arbitrary[ByteArray]
+ response <- arbitrary[AuthenticatorAttestationResponse]
+ clientExtensionResults <- arbitrary[ClientRegistrationExtensionOutputs]
+ } yield PublicKeyCredential
+ .builder()
+ .id(id)
+ .response(response)
+ .clientExtensionResults(clientExtensionResults)
+ .build()
+ )
)
implicit val arbitraryPublicKeyCredentialCreationOptions
: Arbitrary[PublicKeyCredentialCreationOptions] = Arbitrary(
- for {
- attestation <- arbitrary[AttestationConveyancePreference]
- authenticatorSelection <-
- arbitrary[Optional[AuthenticatorSelectionCriteria]]
- challenge <- arbitrary[ByteArray]
- excludeCredentials <-
- arbitrary[Optional[java.util.Set[PublicKeyCredentialDescriptor]]]
- extensions <- arbitrary[RegistrationExtensionInputs]
- pubKeyCredParams <-
- arbitrary[java.util.List[PublicKeyCredentialParameters]]
- rp <- arbitrary[RelyingPartyIdentity]
- timeout <- arbitrary[Optional[java.lang.Long]]
- user <- arbitrary[UserIdentity]
- } yield PublicKeyCredentialCreationOptions
- .builder()
- .rp(rp)
- .user(user)
- .challenge(challenge)
- .pubKeyCredParams(pubKeyCredParams)
- .attestation(attestation)
- .authenticatorSelection(authenticatorSelection)
- .excludeCredentials(excludeCredentials)
- .extensions(extensions)
- .timeout(timeout)
- .build()
+ halfsized(
+ for {
+ attestation <- arbitrary[AttestationConveyancePreference]
+ authenticatorSelection <-
+ arbitrary[Optional[AuthenticatorSelectionCriteria]]
+ challenge <- arbitrary[ByteArray]
+ excludeCredentials <-
+ arbitrary[Optional[java.util.Set[PublicKeyCredentialDescriptor]]]
+ extensions <- arbitrary[RegistrationExtensionInputs]
+ pubKeyCredParams <-
+ arbitrary[java.util.List[PublicKeyCredentialParameters]]
+ rp <- arbitrary[RelyingPartyIdentity]
+ timeout <- arbitrary[Optional[java.lang.Long]]
+ user <- arbitrary[UserIdentity]
+ } yield PublicKeyCredentialCreationOptions
+ .builder()
+ .rp(rp)
+ .user(user)
+ .challenge(challenge)
+ .pubKeyCredParams(pubKeyCredParams)
+ .attestation(attestation)
+ .authenticatorSelection(authenticatorSelection)
+ .excludeCredentials(excludeCredentials)
+ .extensions(extensions)
+ .timeout(timeout)
+ .build()
+ )
)
implicit val arbitraryPublicKeyCredentialDescriptor
: Arbitrary[PublicKeyCredentialDescriptor] = Arbitrary(
- for {
- id <- arbitrary[ByteArray]
- transports <- arbitrary[Optional[java.util.Set[AuthenticatorTransport]]]
- tpe <- arbitrary[PublicKeyCredentialType]
- } yield PublicKeyCredentialDescriptor
- .builder()
- .id(id)
- .transports(transports)
- .`type`(tpe)
- .build()
+ halfsized(
+ for {
+ id <- arbitrary[ByteArray]
+ transports <- arbitrary[Optional[java.util.Set[AuthenticatorTransport]]]
+ tpe <- arbitrary[PublicKeyCredentialType]
+ } yield PublicKeyCredentialDescriptor
+ .builder()
+ .id(id)
+ .transports(transports)
+ .`type`(tpe)
+ .build()
+ )
)
implicit val arbitraryPublicKeyCredentialParameters
: Arbitrary[PublicKeyCredentialParameters] = Arbitrary(
- for {
- alg <- arbitrary[COSEAlgorithmIdentifier]
- tpe <- arbitrary[PublicKeyCredentialType]
- } yield PublicKeyCredentialParameters
- .builder()
- .alg(alg)
- .`type`(tpe)
- .build()
+ halfsized(
+ for {
+ alg <- arbitrary[COSEAlgorithmIdentifier]
+ tpe <- arbitrary[PublicKeyCredentialType]
+ } yield PublicKeyCredentialParameters
+ .builder()
+ .alg(alg)
+ .`type`(tpe)
+ .build()
+ )
)
implicit val arbitraryPublicKeyCredentialRequestOptions
: Arbitrary[PublicKeyCredentialRequestOptions] = Arbitrary(
- for {
- allowCredentials <-
- arbitrary[Optional[java.util.List[PublicKeyCredentialDescriptor]]]
- challenge <- arbitrary[ByteArray]
- extensions <- arbitrary[AssertionExtensionInputs]
- rpId <- arbitrary[Optional[String]]
- timeout <- arbitrary[Optional[java.lang.Long]]
- userVerification <- arbitrary[UserVerificationRequirement]
- } yield PublicKeyCredentialRequestOptions
- .builder()
- .challenge(challenge)
- .allowCredentials(allowCredentials)
- .extensions(extensions)
- .rpId(rpId)
- .timeout(timeout)
- .userVerification(userVerification)
- .build()
+ halfsized(
+ for {
+ allowCredentials <-
+ arbitrary[Optional[java.util.List[PublicKeyCredentialDescriptor]]]
+ challenge <- arbitrary[ByteArray]
+ extensions <- arbitrary[AssertionExtensionInputs]
+ rpId <- arbitrary[Optional[String]]
+ timeout <- arbitrary[Optional[java.lang.Long]]
+ userVerification <- arbitrary[UserVerificationRequirement]
+ } yield PublicKeyCredentialRequestOptions
+ .builder()
+ .challenge(challenge)
+ .allowCredentials(allowCredentials)
+ .extensions(extensions)
+ .rpId(rpId)
+ .timeout(timeout)
+ .userVerification(userVerification)
+ .build()
+ )
)
implicit val arbitraryRegistrationExtensionInputs
@@ -1090,14 +1138,16 @@ object Generators {
implicit val arbitraryRelyingPartyIdentity: Arbitrary[RelyingPartyIdentity] =
Arbitrary(
- for {
- id <- arbitrary[String]
- name <- arbitrary[String]
- } yield RelyingPartyIdentity
- .builder()
- .id(id)
- .name(name)
- .build()
+ halfsized(
+ for {
+ id <- arbitrary[String]
+ name <- arbitrary[String]
+ } yield RelyingPartyIdentity
+ .builder()
+ .id(id)
+ .name(name)
+ .build()
+ )
)
implicit val arbitraryTokenBindingInfo: Arbitrary[TokenBindingInfo] =
@@ -1109,16 +1159,18 @@ object Generators {
)
implicit val arbitraryUserIdentity: Arbitrary[UserIdentity] = Arbitrary(
- for {
- displayName <- arbitrary[String]
- name <- arbitrary[String]
- id <- arbitrary[ByteArray]
- } yield UserIdentity
- .builder()
- .name(name)
- .displayName(displayName)
- .id(id)
- .build()
+ halfsized(
+ for {
+ displayName <- arbitrary[String]
+ name <- arbitrary[String]
+ id <- arbitrary[ByteArray]
+ } yield UserIdentity
+ .builder()
+ .name(name)
+ .displayName(displayName)
+ .id(id)
+ .build()
+ )
)
}
diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/JsonIoSpec.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/JsonIoSpec.scala
index 0fd026f9b..0b2602b1c 100644
--- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/JsonIoSpec.scala
+++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/JsonIoSpec.scala
@@ -61,32 +61,20 @@ class JsonIoSpec
def test[A](tpe: TypeReference[A])(implicit a: Arbitrary[A]): Unit = {
val cn = tpe.getType.getTypeName
describe(s"${cn}") {
- it("can be serialized to JSON.") {
- forAll { value: A =>
+ it("is identical after multiple serialization round-trips..") {
+ forAll(minSuccessful(10)) { value: A =>
val encoded: String = json.writeValueAsString(value)
- encoded should not be empty
- }
- }
-
- it("can be deserialized from JSON.") {
- forAll { value: A =>
- val encoded: String = json.writeValueAsString(value)
val decoded: A = json.readValue(encoded, tpe)
-
decoded should equal(value)
- }
- }
- it("is identical after multiple serialization round-trips..") {
- forAll { value: A =>
- val encoded: String = json.writeValueAsString(value)
- val decoded: A = json.readValue(encoded, tpe)
val recoded: String = json.writeValueAsString(decoded)
-
- decoded should equal(value)
recoded should equal(encoded)
+
+ val redecoded: A = json.readValue(recoded, tpe)
+ redecoded should equal(value)
}
+
}
}
}
@@ -372,7 +360,7 @@ class JsonIoSpec
val encoded = json.writeValueAsString(tree)
println(authenticatorAttachment)
val decoded = json.readValue(encoded, tpe)
- decoded.getAuthenticatorAttachment.asScala should be(None)
+ decoded.getAuthenticatorAttachment.toScala should be(None)
}
forAll(
@@ -388,7 +376,7 @@ class JsonIoSpec
println(authenticatorAttachment)
val decoded = json.readValue(encoded, tpe)
- decoded.getAuthenticatorAttachment.asScala should equal(
+ decoded.getAuthenticatorAttachment.toScala should equal(
Some(authenticatorAttachment)
)
}
@@ -402,7 +390,7 @@ class JsonIoSpec
val encoded = json.writeValueAsString(tree)
val decoded = json.readValue(encoded, tpe)
- decoded.getAuthenticatorAttachment.asScala should be(None)
+ decoded.getAuthenticatorAttachment.toScala should be(None)
}
}
diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/extension/uvm/Generators.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/extension/uvm/Generators.scala
index d3f20199c..92869f232 100644
--- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/extension/uvm/Generators.scala
+++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/extension/uvm/Generators.scala
@@ -2,15 +2,17 @@ package com.yubico.webauthn.extension.uvm
import org.scalacheck.Gen
+import scala.collection.immutable.ArraySeq
+
object Generators {
def userVerificationMethod: Gen[UserVerificationMethod] =
- Gen.oneOf(UserVerificationMethod.values)
+ Gen.oneOf(ArraySeq.unsafeWrapArray(UserVerificationMethod.values))
def keyProtectionType: Gen[KeyProtectionType] =
- Gen.oneOf(KeyProtectionType.values)
+ Gen.oneOf(ArraySeq.unsafeWrapArray(KeyProtectionType.values))
def matcherProtectionType: Gen[MatcherProtectionType] =
- Gen.oneOf(MatcherProtectionType.values)
+ Gen.oneOf(ArraySeq.unsafeWrapArray(MatcherProtectionType.values))
}
diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/test/RealExamples.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/test/RealExamples.scala
index ac5b154e8..24dd52197 100644
--- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/test/RealExamples.scala
+++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/test/RealExamples.scala
@@ -591,6 +591,36 @@ object RealExamples {
),
)
+ val YubikeyBio_5_5_6 = new Example(
+ RelyingPartyIdentity
+ .builder()
+ .id("demo.yubico.com")
+ .name("YubicoDemo")
+ .build(),
+ UserIdentity
+ .builder()
+ .name("Yubico demo user")
+ .displayName("Yubico demo user")
+ .id(ByteArray.fromBase64("KYljhyutCbO7mu5TI9Zt9ra11ScQvC+ArBpdYoAiEvg="))
+ .build(),
+ AttestationExample(
+ base64UrlToString("eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiQnhoWTY4ZGczeHNNVmFRaWRqaW1BdyIsIm9yaWdpbiI6Imh0dHBzOi8vZGVtby55dWJpY28uY29tIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ=="),
+ ByteArray.fromBase64("o2NmbXRmcGFja2VkZ2F0dFN0bXSjY2FsZyZjc2lnWEgwRgIhAMSgpu1ru29YJex9vN8Zmt7RJkvOj/DmD2Cnfz8nhVmLAiEA8qnz6llKsjWfZ1OYrR4AIS3JTIXsQgbmeK61pzuesYJjeDVjgVkC3DCCAtgwggHAoAMCAQICCQD/h2wtr3N5yDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowbjELMAkGA1UEBhMCU0UxEjAQBgNVBAoMCVl1YmljbyBBQjEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjEnMCUGA1UEAwweWXViaWNvIFUyRiBFRSBTZXJpYWwgNzYyMDg3NDIzMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJfEjoEgoP8V5bM+IfZlIn9k1wkGYxLXY1bLCv9fdXRWv5FtwcHdlZ9W1sLI+BFYLW+p3tIOx9kkeU6PyvuajmqOBgTB/MBMGCisGAQQBgsQKDQEEBQQDBQUGMCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0ODIuMS45MBMGCysGAQQBguUcAgEBBAQDAgUgMCEGCysGAQQBguUcAQEEBBIEENhSLZ9XW0hmiKm6mfoC81swDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAUrBpSduq0aZMG6nrwZizF+wx+aNzY7pRYbNC46ScrVBPNOdCi7iW6c/SjQOtEM4yWgaDjptsTssXrUDQkKFsnnw0SYMy/4U7YnR+j83wDa5idW5XvUCxbWd5B6g1wENaLrzpsLkGnKEiv52WSnMgavdP88ABROv/PefHdY0xR8jC+f6HwS8qlnWiBGsBB2NhqZchhx+nj7DeKUW1efkWbEitL9UMPOVsgiGnUIP2VhGTlDaP8X0skgxjoJ8B7SUBFGt98as5cKKjKTj6mlF69HEIXhYLPKeXZCMXRrpqu6aODRPOJZeWvNKgOtg8dOFTMTKOq0OOakGXyxLsb9HjiGhhdXRoRGF0YVifxGzvgq0bVGR3WR0Aiwh1nsPm0uy085R0v+ppaZJdA7fFAAAABNhSLZ9XW0hmiKm6mfoC81sAMIg/92bCZgLh2oUu6QF2XrSYZKh+qP1J3wf1SgOOkcMnF499E7JiLPi5YhY/308TfKQBAQMnIAYhWCCIP/dmwmYC4dqFLukBggD0oYvvkNUWXNzokKlsiK0/vaFrY3JlZFByb3RlY3QC"),
+ ),
+ AssertionExample(
+ id = ByteArray.fromBase64Url(
+ "iD_3ZsJmAuHahS7pAXZetJhkqH6o_UnfB_VKA46RwycXj30TsmIs-LliFj_fTxN8"
+ ),
+ clientData =
+ base64UrlToString("eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiX3RoYmVudXo3amZBcWJMZUxYVlFWQSIsIm9yaWdpbiI6Imh0dHBzOi8vZGVtby55dWJpY28uY29tIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ=="),
+ authDataBytes = ByteArray.fromBase64(
+ "xGzvgq0bVGR3WR0Aiwh1nsPm0uy085R0v+ppaZJdA7cFAAAACA=="
+ ),
+ sig =
+ ByteArray.fromBase64("ZeXxnNYjBwh5Irn+W6VzRna/3XQrsvYhKVa+T8tv2eEw/UuALFoLHlBRkFQr73wgmLZ4ma2gEXocOnuUjVBZAw=="),
+ ),
+ )
+
val CredPropsEmpty = AttestationExample(
base64UrlToString("eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiYlZjNWxvY3dnV0ZvdlJ6M2RzWGkzcFc1cHgxZ3pGOFFIaFJmLU90REhuVSIsIm9yaWdpbiI6Imh0dHBzOi8vbG9jYWxob3N0Ojg0NDMiLCJjcm9zc09yaWdpbiI6ZmFsc2UsIm90aGVyX2tleXNfY2FuX2JlX2FkZGVkX2hlcmUiOiJkbyBub3QgY29tcGFyZSBjbGllbnREYXRhSlNPTiBhZ2FpbnN0IGEgdGVtcGxhdGUuIFNlZSBodHRwczovL2dvby5nbC95YWJQZXgifQ"),
ByteArray.fromBase64Url("o2NmbXRmcGFja2VkZ2F0dFN0bXSjY2FsZyZjc2lnWEcwRQIgCTFl9y9YBafBiKkOnj59Cgypvz9hhPwpdsiFAmE8utcCIQC8bsfMEcI5-Di3Xj9CIWZ1PAGMjvxEiD1L2csJcgjoBmN4NWOBWQLwMIIC7DCCAdSgAwIBAgIJAN1TJeaFJ6cVMA0GCSqGSIb3DQEBCwUAMC4xLDAqBgNVBAMTI1l1YmljbyBVMkYgUm9vdCBDQSBTZXJpYWwgNDU3MjAwNjMxMCAXDTE0MDgwMTAwMDAwMFoYDzIwNTAwOTA0MDAwMDAwWjBvMQswCQYDVQQGEwJTRTESMBAGA1UECgwJWXViaWNvIEFCMSIwIAYDVQQLDBlBdXRoZW50aWNhdG9yIEF0dGVzdGF0aW9uMSgwJgYDVQQDDB9ZdWJpY28gVTJGIEVFIFNlcmlhbCAxNzEzNzIyMzMzMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDeoY3vFmcuLvf1SL2oqIV5WaVs9VGyB4GPmtxdHY84v_-R2wtLKvAfjIH9eTIq3-Ev3-UQLipTY0Bb9Xn9Sp3KOBlDCBkTATBgorBgEEAYLECg0BBAUEAwUEAjAQBgkrBgEEAYLECgwEAwIBBDAiBgkrBgEEAYLECgIEFTEuMy42LjEuNC4xLjQxNDgyLjEuNzATBgsrBgEEAYLlHAIBAQQEAwIEMDAhBgsrBgEEAYLlHAEBBAQSBBDB-aC8HdJASrJ_jikEekP9MAwGA1UdEwEB_wQCMAAwDQYJKoZIhvcNAQELBQADggEBAGl5dmZIe5GOHFOAvVUaWFWyet89UCHWKmLBTXXfuoPwYqatxGhVqIeiV4nAuFF127294SzJcMgzycToui5_g8OUonTvs9xWF9yH23fXjGcBWoGErlF7DqkycOz2NtjPhGwEfBnE--0_KRc_IN6bu7u_XPXNwNmCLcg0reERI23NO_ZftcWebjRBCwY3p6l0ahalKmrgqOi7bhU1AjbHmiEvJgeBcpZphS87eikierMO5PmwvdbV3okNseEoaeoHDDQ7Av6RwCtKCXwYupRs6sULgUwo0fz2znURA-zSuTzK4iZ_hmQvRVJtQBPtfpwBEmNEdwwZ1A-VxfspsYzA7AVoYXV0aERhdGFYxEmWDeWIDoxodDQXD2R2YFuP5K65ooYyx5lc87qDHZdjQQAAAATB-aC8HdJASrJ_jikEekP9AEAJSmR-h-HuKqKK2uvaDSjTQrjbfukR_-71-SoVyEFkfLEc09nidnTryBiqZGARKeDhwvtog3_c3f8C3REXcI4spQECAyYgASFYIDUR5e5GusKylrCRkKq1U3jnp-fJ_l_CeykL_-5tj4juIlgg72ksmbxNptIfwrG1hiwbViIoWIphEt2819hHdziqSsc"),
diff --git a/webauthn-server-demo/README b/webauthn-server-demo/README
index 06bc0e86c..12b1f2a45 100644
--- a/webauthn-server-demo/README
+++ b/webauthn-server-demo/README
@@ -40,7 +40,7 @@ layer.
This layer manages the general architecture of the system, and is where most
business logic and integration code would go. The demo server implements the
"persistent" storage of users and credential registrations - the
-link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`]
+link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.0/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`]
integration point - as the
link:src/main/java/demo/webauthn/InMemoryRegistrationStorage.java[`InMemoryRegistrationStorage`]
class, which simply keeps them stored in memory for a limited time. The
@@ -54,7 +54,7 @@ would be specific to a particular Relying Party (RP) would go in this layer.
- The server layer in turn calls the *library layer*, which is where the
link:../webauthn-server-core/[`webauthn-server-core`]
library gets involved. The entry point into the library is the
- link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.html[`RelyingParty`]
+ link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.0/com/yubico/webauthn/RelyingParty.html[`RelyingParty`]
class.
+
This layer implements the Web Authentication
@@ -65,11 +65,11 @@ and exposes integration points for storage of challenges and credentials. Some
notable integration points are:
+
** The library user must provide an implementation of the
-link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`]
+link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.0/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`]
interface to use for looking up stored public keys, user handles and signature
counters.
** The library user can optionally provide an instance of the
-link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/attestation/AttestationTrustSource.html[`AttestationTrustSource`]
+link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.0/com/yubico/webauthn/attestation/AttestationTrustSource.html[`AttestationTrustSource`]
interface to enable identification and validation of authenticator models. This
instance is then used to look up trusted attestation root certificates. The
link:../webauthn-server-attestation/[`webauthn-server-attestation`]
diff --git a/webauthn-server-demo/src/main/java/com/yubico/webauthn/attestation/YubicoJsonMetadataService.java b/webauthn-server-demo/src/main/java/com/yubico/webauthn/attestation/YubicoJsonMetadataService.java
index 62a7acd19..baf21282e 100644
--- a/webauthn-server-demo/src/main/java/com/yubico/webauthn/attestation/YubicoJsonMetadataService.java
+++ b/webauthn-server-demo/src/main/java/com/yubico/webauthn/attestation/YubicoJsonMetadataService.java
@@ -30,8 +30,8 @@
import com.google.common.collect.Maps;
import com.yubico.internal.util.CertificateParser;
import com.yubico.internal.util.CollectionUtil;
+import com.yubico.internal.util.OptionalUtil;
import com.yubico.webauthn.attestation.matcher.ExtensionMatcher;
-import com.yubico.webauthn.attestation.matcher.FingerprintMatcher;
import com.yubico.webauthn.data.ByteArray;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
@@ -55,9 +55,7 @@ public final class YubicoJsonMetadataService implements AttestationTrustSource {
private static final String SELECTOR_PARAMETERS = "parameters";
private static final Map DEFAULT_DEVICE_MATCHERS =
- ImmutableMap.of(
- ExtensionMatcher.SELECTOR_TYPE, new ExtensionMatcher(),
- FingerprintMatcher.SELECTOR_TYPE, new FingerprintMatcher());
+ ImmutableMap.of(ExtensionMatcher.SELECTOR_TYPE, new ExtensionMatcher());
private final Collection metadataObjects;
private final Map matchers;
@@ -127,8 +125,7 @@ public Optional findMetadata(X509Certificate attestationCertificate
.deviceProperties(deviceProps)
.build());
})
- .filter(Optional::isPresent)
- .map(Optional::get)
+ .flatMap(OptionalUtil::stream)
.findAny();
}
diff --git a/webauthn-server-demo/src/main/java/com/yubico/webauthn/attestation/matcher/ExtensionMatcher.java b/webauthn-server-demo/src/main/java/com/yubico/webauthn/attestation/matcher/ExtensionMatcher.java
index 56c6fa415..1dd0aa3f1 100644
--- a/webauthn-server-demo/src/main/java/com/yubico/webauthn/attestation/matcher/ExtensionMatcher.java
+++ b/webauthn-server-demo/src/main/java/com/yubico/webauthn/attestation/matcher/ExtensionMatcher.java
@@ -69,9 +69,7 @@ public boolean matches(X509Certificate attestationCertificate, JsonNode paramete
}
} catch (IOException e) {
log.error(
- "Failed to parse extension value as ASN1: {}",
- new ByteArray(extensionValue).getHex(),
- e);
+ "Failed to parse extension value as ASN1: {}", new ByteArray(extensionValue), e);
}
}
}
diff --git a/webauthn-server-demo/src/main/java/com/yubico/webauthn/attestation/matcher/FingerprintMatcher.java b/webauthn-server-demo/src/main/java/com/yubico/webauthn/attestation/matcher/FingerprintMatcher.java
deleted file mode 100644
index a057368c3..000000000
--- a/webauthn-server-demo/src/main/java/com/yubico/webauthn/attestation/matcher/FingerprintMatcher.java
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright (c) 2015-2018, Yubico AB
-// All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-// 1. Redistributions of source code must retain the above copyright notice, this
-// list of conditions and the following disclaimer.
-//
-// 2. Redistributions in binary form must reproduce the above copyright notice,
-// this list of conditions and the following disclaimer in the documentation
-// and/or other materials provided with the distribution.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-package com.yubico.webauthn.attestation.matcher;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.google.common.hash.Hashing;
-import com.yubico.webauthn.attestation.DeviceMatcher;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-
-public final class FingerprintMatcher implements DeviceMatcher {
- public static final String SELECTOR_TYPE = "fingerprint";
-
- private static final String FINGERPRINTS_KEY = "fingerprints";
-
- @Override
- public boolean matches(X509Certificate attestationCertificate, JsonNode parameters) {
- JsonNode fingerprints = parameters.get(FINGERPRINTS_KEY);
- if (fingerprints.isArray()) {
- try {
- String fingerprint =
- Hashing.sha1().hashBytes(attestationCertificate.getEncoded()).toString().toLowerCase();
- for (JsonNode candidate : fingerprints) {
- if (fingerprint.equals(candidate.asText().toLowerCase())) {
- return true;
- }
- }
- } catch (CertificateEncodingException e) {
- // Fall through to return false.
- }
- }
- return false;
- }
-}
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 72c99132f..0cba71a9c 100644
--- a/webauthn-server-demo/src/main/java/demo/webauthn/InMemoryRegistrationStorage.java
+++ b/webauthn-server-demo/src/main/java/demo/webauthn/InMemoryRegistrationStorage.java
@@ -154,13 +154,14 @@ public Collection getRegistrationsByUserHandle(ByteArray
public void updateSignatureCount(AssertionResult result) {
CredentialRegistration registration =
- getRegistrationByUsernameAndCredentialId(result.getUsername(), result.getCredentialId())
+ getRegistrationByUsernameAndCredentialId(
+ result.getUsername(), result.getCredential().getCredentialId())
.orElseThrow(
() ->
new NoSuchElementException(
String.format(
"Credential \"%s\" is not registered to user \"%s\"",
- result.getCredentialId(), result.getUsername())));
+ result.getCredential().getCredentialId(), result.getUsername())));
Set regs = storage.getIfPresent(result.getUsername());
regs.remove(registration);
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 6fed76f0c..217c64a4c 100644
--- a/webauthn-server-demo/src/main/java/demo/webauthn/WebAuthnServer.java
+++ b/webauthn-server-demo/src/main/java/demo/webauthn/WebAuthnServer.java
@@ -464,7 +464,7 @@ public Either, SuccessfulAuthenticationResult> finishAuthentication
response,
userStorage.getRegistrationsByUsername(result.getUsername()),
result.getUsername(),
- sessions.createSession(result.getUserHandle())));
+ sessions.createSession(result.getCredential().getUserHandle())));
} else {
return Either.left(Collections.singletonList("Assertion failed: Invalid assertion."));
}
diff --git a/webauthn-server-demo/src/main/java/demo/webauthn/data/CredentialRegistration.java b/webauthn-server-demo/src/main/java/demo/webauthn/data/CredentialRegistration.java
index 54e32e9ca..e88aba83d 100644
--- a/webauthn-server-demo/src/main/java/demo/webauthn/data/CredentialRegistration.java
+++ b/webauthn-server-demo/src/main/java/demo/webauthn/data/CredentialRegistration.java
@@ -35,11 +35,11 @@
import java.util.SortedSet;
import lombok.Builder;
import lombok.Value;
-import lombok.experimental.Wither;
+import lombok.With;
@Value
@Builder
-@Wither
+@With
public class CredentialRegistration {
UserIdentity userIdentity;
diff --git a/webauthn-server-demo/src/main/resources/metadata.json b/webauthn-server-demo/src/main/resources/metadata.json
index bed561955..a654e2622 100644
--- a/webauthn-server-demo/src/main/resources/metadata.json
+++ b/webauthn-server-demo/src/main/resources/metadata.json
@@ -1,6 +1,6 @@
{
"identifier": "2fb54029-7613-4f1d-94f1-fb876c14a6fe",
- "version": 17,
+ "version": 19,
"vendorInfo": {
"url": "https://yubico.com",
"imageUrl": "https://developers.yubico.com/U2F/Images/yubico.png",
@@ -40,6 +40,46 @@
]
},
+ {
+ "deviceId": "1.3.6.1.4.1.41482.1.1",
+ "displayName": "Security Key NFC by Yubico",
+ "transports": 12,
+ "deviceUrl": "https://support.yubico.com/support/solutions/articles/15000019469-security-key-nfc",
+ "imageUrl": "https://developers.yubico.com/U2F/Images/YK5NFC-CNFC.png",
+ "selectors": [
+ {
+ "type": "x509Extension",
+ "parameters": {
+ "key": "1.3.6.1.4.1.45724.1.1.4",
+ "value": {
+ "type": "hex",
+ "value": "a4e9fc6d4cbe4758b8ba37598bb5bbaa"
+ }
+ }
+ }
+ ]
+ },
+
+ {
+ "deviceId": "1.3.6.1.4.1.41482.1.1",
+ "displayName": "Security Key NFC by Yubico - Enterprise Edition",
+ "transports": 12,
+ "deviceUrl": "https://support.yubico.com/support/solutions/articles/15000019469-security-key-nfc",
+ "imageUrl": "https://developers.yubico.com/U2F/Images/YK5NFC-CNFC.png",
+ "selectors": [
+ {
+ "type": "x509Extension",
+ "parameters": {
+ "key": "1.3.6.1.4.1.45724.1.1.4",
+ "value": {
+ "type": "hex",
+ "value": "0bb43545fd2c418587ddfeb0b2916ace"
+ }
+ }
+ }
+ ]
+ },
+
{
"deviceId": "1.3.6.1.4.1.41482.1.1",
"displayName": "Security Key by Yubico",
@@ -178,7 +218,7 @@
"displayName": "YubiKey 5/5C NFC",
"transports": 12,
"deviceUrl": "https://support.yubico.com/support/solutions/articles/15000014174--yubikey-5-nfc",
- "imageUrl": "https://developers.yubico.com/U2F/Images/YK5.png",
+ "imageUrl": "https://developers.yubico.com/U2F/Images/YK5NFC-CNFC.png",
"selectors": [
{
"type": "x509Extension",
@@ -308,6 +348,8 @@
"deviceId": "1.3.6.1.4.1.41482.1.9",
"displayName": "YubiKey Bio - FIDO Edition",
"transports": 4,
+ "deviceUrl": "https://support.yubico.com/hc/en-us/articles/4407743521810-YubiKey-Bio-FIDO-Edition",
+ "imageUrl": "https://developers.yubico.com/U2F/Images/BIO.png",
"selectors": [
{
"type": "x509Extension",
diff --git a/webauthn-server-demo/src/test/scala/demo/webauthn/WebAuthnServerSpec.scala b/webauthn-server-demo/src/test/scala/demo/webauthn/WebAuthnServerSpec.scala
index a185d8c80..1542d51f4 100644
--- a/webauthn-server-demo/src/test/scala/demo/webauthn/WebAuthnServerSpec.scala
+++ b/webauthn-server-demo/src/test/scala/demo/webauthn/WebAuthnServerSpec.scala
@@ -251,7 +251,7 @@ class WebAuthnServerSpec
def newServerWithAuthenticationRequest(
testData: RegistrationTestData,
- signatureCount: Option[Long] = None,
+ signatureCount: Option[Long],
) = {
val assertionRequests: Cache[ByteArray, AssertionRequestWrapper] =
newCache()
diff --git a/yubico-util-scala/src/main/scala/com/yubico/scalacheck/gen/GenUtil.scala b/yubico-util-scala/src/main/scala/com/yubico/scalacheck/gen/GenUtil.scala
new file mode 100644
index 000000000..a93978680
--- /dev/null
+++ b/yubico-util-scala/src/main/scala/com/yubico/scalacheck/gen/GenUtil.scala
@@ -0,0 +1,23 @@
+package com.yubico.scalacheck.gen
+
+import org.scalacheck.Gen
+
+object GenUtil {
+
+ /** @return
+ * The generator `g` wrapped so that its size is at most `maxSize`.
+ */
+ def maxSized[T](maxSize: Int, g: Gen[T]): Gen[T] =
+ Gen.sized(size => Gen.resize(Math.min(maxSize, size), g))
+
+ /** @return
+ * The generator `g` wrapped to reduce its size by half, but no lower than
+ * to 1 if the original size was 1 or greater.
+ */
+ def halfsized[T](g: Gen[T]): Gen[T] =
+ Gen.sized(size => {
+ val s = if (size / 2 == 0 && size != 0) 1 else size / 2
+ Gen.resize(s, g)
+ })
+
+}
diff --git a/yubico-util-scala/src/main/scala/com/yubico/webauthn/TestWithEachProvider.scala b/yubico-util-scala/src/main/scala/com/yubico/webauthn/TestWithEachProvider.scala
index 7d9d53d0a..75d7438f2 100644
--- a/yubico-util-scala/src/main/scala/com/yubico/webauthn/TestWithEachProvider.scala
+++ b/yubico-util-scala/src/main/scala/com/yubico/webauthn/TestWithEachProvider.scala
@@ -6,6 +6,8 @@ import org.scalatest.matchers.should.Matchers
import java.security.Provider
import java.security.Security
+import java.security.Signature
+import scala.util.Try
trait TestWithEachProvider extends Matchers {
this: AnyFunSpec =>
@@ -78,21 +80,27 @@ trait TestWithEachProvider extends Matchers {
): Unit = {
val defaultProviders: List[Provider] = Security.getProviders.toList
- // TODO: Uncomment this in the next major version
- //it should behave like wrapItFunctionWithProviderContext("default", defaultProviders, registerTests)
+ if (Try(Signature.getInstance("EdDSA", "SunEC")).isSuccess) {
+ // Test with only stock providers for JDK >= 14
+ it should behave like wrapItFunctionWithProviderContext(
+ "default",
+ defaultProviders,
+ registerTests,
+ )
+ } else {
+ // JDK < 14 doesn't have EdDSA providers
+ it should behave like wrapItFunctionWithProviderContext(
+ "default and BouncyCastle",
+ defaultProviders.appended(new BouncyCastleProvider()),
+ registerTests,
+ )
+ }
it should behave like wrapItFunctionWithProviderContext(
"BouncyCastle",
List(new BouncyCastleProvider()),
registerTests,
)
-
- // TODO: Delete this in the next major version
- it should behave like wrapItFunctionWithProviderContext(
- "default and BouncyCastle",
- defaultProviders.appended(new BouncyCastleProvider()),
- registerTests,
- )
}
}
diff --git a/yubico-util/build.gradle.kts b/yubico-util/build.gradle.kts
index 83d58d030..ba6974f9e 100644
--- a/yubico-util/build.gradle.kts
+++ b/yubico-util/build.gradle.kts
@@ -5,6 +5,7 @@ plugins {
signing
id("info.solidsoft.pitest")
id("io.github.cosmicsilence.scalafix")
+ id("me.champeau.jmh") version "0.6.8"
}
description = "Yubico internal utilities"
@@ -24,7 +25,6 @@ dependencies {
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-cbor")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jdk8")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
- implementation("com.google.guava:guava")
implementation("com.upokecenter:cbor")
implementation("org.slf4j:slf4j-api")
@@ -36,6 +36,13 @@ dependencies {
testImplementation("org.scalatest:scalatest_2.13")
testImplementation("org.scalatestplus:junit-4-13_2.13")
testImplementation("org.scalatestplus:scalacheck-1-16_2.13")
+
+ jmhImplementation(platform(project(":test-platform")))
+ jmhRuntimeOnly("org.slf4j:slf4j-nop")
+}
+
+configurations.jmhRuntimeClasspath {
+ exclude(module = "slf4j-test")
}
diff --git a/yubico-util/src/jmh/java/com/yubico/internal/util/benchmark/BinaryUtilBenchmark.java b/yubico-util/src/jmh/java/com/yubico/internal/util/benchmark/BinaryUtilBenchmark.java
new file mode 100644
index 000000000..e50d1c7f2
--- /dev/null
+++ b/yubico-util/src/jmh/java/com/yubico/internal/util/benchmark/BinaryUtilBenchmark.java
@@ -0,0 +1,110 @@
+package com.yubico.internal.util.benchmark;
+
+import com.yubico.internal.util.BinaryUtil;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+
+@Warmup(iterations = 5, time = 100, timeUnit = TimeUnit.MILLISECONDS)
+@Measurement(iterations = 10, time = 100, timeUnit = TimeUnit.MILLISECONDS)
+public class BinaryUtilBenchmark {
+
+ private static final class FromHexTests {
+ public static final List shortTests =
+ Arrays.asList(
+ "", "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0a", "0b", "0c", "0d",
+ "0e", "0f", "10", "20", "30", "40", "50", "60", "70", "80", "90", "a0", "b0", "c0",
+ "d0", "e0", "f0", "ff");
+
+ public static final String long128 =
+ "05a32393cf6c4a162796e83360fe4cbc4e24bf0e89a0288d8594edda689e188379fb7d3d1e0dc7b6f5afaffcce40640c3a6bf197225f82039a568c3f232321cc3307edd7c10ff80f21fd5d1dd588054614db3a30d660e537143ee4604d5006a89226d9a0abd57e3108348d22f4dfd1c0ea3e2fb3d20f673f51b295809414c8bd";
+ public static final String long256 =
+ "9a21145c579178e21973096ba131bb0af24b122350ed15b585eee634231fe8c0b16c1920bf76b6e100572c462856a1dcdaaa6d2023e895cd74f8db0e1189d5a264840ec7a7f59011b13725d2ede8fe4f813cdfa9e4cb67953f6f609cd694e82567585b88e72276170b69e7531cf2f7378e3ce0fc5ec7c28c00c179d5c7cc621007b93bb658dd9d07ee0d5a8307bc26c9fddc7daa50ec0d4e751dec29649b50051967aa3c360e78c6d1abb32d1143cd706a50b5e353a1fc1f690b2c5bcfd188813c7338ed231784ef1f67e08f1b08925718bb0bd3ef3f37365c4c344672915008d20d37e6a3d7a779f95d6be0fffd7cf0ba5fb7ea4da0ae0c997102621e3f4841";
+ public static final String long512 =
+ "d6f686693781aad51a2cf40d1bfe4985f7dc094fbfa480023529b72602e376e9f51242176a16096ace8742807e6b2d5aae534762131fbd00b7adc9f6f8c70a29356f792cbf8b869a8265752c67658cd7566afa3c701889ba9e9b089200002ee69c7cf533d03d7077f31dc270de83e3a64a4102efdd5defb1e244ad2515b7f6043a7b34240dfd359ecc564aba8df30a9e41f36b9a7439a4b11304219dbfecd77cb30f69352276b816ac6ec5663b6fd72a7dbff03e9501b0dae97424abd6de101a150cdc446f7ad12ee578b62f1a468c8adcf717969b8c0580b453a920141a58ba2eaea32bf5a24fda5b4f7dabea985c0dec754d75683252f6a23623ce9248d7974075bd4eb6dc585ab516fa189cdcc987bde0548e4a06aac2682c3e41f23c53046933bcf62def3504d0f472066aa0c7ea9d2652aaa9e62d126a787292480617f09e8b75eef77bfe4fb0620366f406c83503f81f5778ca241137296ab9378a0201ddec60250330640fb9958cbb9c4e3cf0f0a78e0157882f393fce0bf9536cdae09390db232fc5d6902eb824c6ae5a8fd944898b9a0ead90e22e6b37d2c3e054ee50225f154fea740a38db7e857aeed6ca06fc7060d6a1e352969d26c6ee0966b855cb44ad22f5974c57992fca17d3d67c8132886c199bdff6eed11b3078e4805723742767af4d9f4ab04dcb176a2657adc7d28cc8b881fda344592d0a0f1ef34c";
+ public static final String long1024 =
+ "8a454aaa25b4e1f93e28145579aecef9bcb7abcb593957db4993b018d871903ab9c33eeb3004addbe33abb411ff3d659d0806274001f2a5d15f371280fece5c531c232d751c9eb82b46d140aced518dcba0c10226130887036bba2b9cb8bd2523e9a26f92bd438ade5367cbb2e7982243dc81f3af5705507c664f1bad84bbfe719bea97e1c5c8973b05176fe0a9bc497a0497ce4c6e8decfda7be928c6114a3302bc64e90ca903f6c8b4d42042508d2af828a893492c8ef5d04a1f166426227fcce07412c9a66259c349a4139728b3c288f1b2d08660d7482e3151a028ecbf41a6a4eba848e67076fbbf3a9109ce2e7386a83c76dbcac34948bcfd6094f53cb8181cbb32981afd8018e88c40524002a5b8415ee81582daa50c4e85b4ea3501878ae5bed8ce4fe8af9f1ed0e45aefee1cd743521025db3d2f907a6552c9e79800e67af100b22f3e4ea051060bc2e65d9ceeb113d6af8c41847c54421f533a6ee1908ecd013a169a566349e8ea05a2e5d93d6903da28088e09416d119b47363a8c7b33657257ff99f1ea225a087ee774d0efa7d002dfeff0707972b8c4dd4e5628719f3ae75c145b038f44b0032542bb0c00564c0d6d75ffb3c1a6976a6f6c766be92396ba53d8ba46d296820cde9096f4b089aa9090062d9b9af9823b9fc2078c6d15fba1b024aa90bcc174c6269718dc56002c32bf4ad62a13583db2aac4a28bd131f94ab78478f8f8d7a5819b936f1eeb1a519f16368a2741bb04005e61e592b2e2f0ee35a81eae58c5417801dda5d44a7d3b2887907e4c708b9f11e5d449484e6532de6068c01af9d5f2e4ebd1873bd457e4afcedbec1d9459c8772c9db7cfb83beecdf68977fd5319eeb00666b49dacd5b947549bc757a07a8ee8404a41bf44c87dd553ff88f536471175abd92f7448f2ee9c7860ec6059a8d53e1a8490c734557a141167d3f37e871de27706417ac786f0edee32651631931c9d3156a9667d09dbf5a77a3f894500599ba5eeb7be531aa63b29d87e7439fda3e85d05ca1b4412036b7fbb44b684920c219d3edcf56ce40d30f877167a014de32c71a82e767d8478394edf172288772e8bdf8968c22c9dc27c89ba69e5c68f165be133f25567cf91e74cdf472b31d7f68b4b189522f47498c4089ce356b123e1a5d3a87e7faba6ca7ff8699bc137de6161c12c21916f6017903e762fb34a383f9e1a3705e1bcd6fb0307cf1434d1a86da69ba237488e8c64bcadf419ad6722d695e835b33a450ee71d4db1237a7b26d414ccd963ba4cd0b31c63e68fc953bb51b824c2776de68dd95d41be3bac154d1c3776f88f371cb8b1b8f489fb84e6bf0d0a6cb74e1a9280f1d04a3c845033cae8f75c612659a3733c0d094487b039a483bf6ab66f27e39b950b8bda0cd4d0aac83d149c59c804b1f1abff5ae4aa54e88df6e8106d3f";
+ public static final String long2048 =
+ "77d3a29ca97179f409b407de4442f429a6f336ca638c5e702f192ddbdd92ebc127b5a9fcb0e71f211adcb5a55c06cb5b7be38e69bc68e23e6b9ab968e7bedd77516fb5fb4c59cbe8ad59ca163c2e7a829ec025b23e87724911f093a61dd23b86c8acb8c164c3567a89f9e32280c8f689997d790a6a22c1a2ea85a488587a889452a6c0764b205c8063db6c350521a8921034d058c33412a532b35f6cc42240150d5f1634a2918211a0de42097acde1eb3d562fc7c1294f2eb2ddd741832db6e7e77488389147164ff3c46f76ddfb181d080e5e8e0ffd9592bd45f6865470204aa4550c559f456e6c974bfe5dc435d8357971c63267f2a7bfe1649988b8273c2e9c8eb22a277197ce1477d21af24e6663de01aa11f439234968734312d355af88de4abffea68737af127cdc4b7f7a2d8b9789fb18469aff29f746fb981fca589b1992c03f477df26191cdc09c1251b94159ef19f0468a128899e1d6b8caefa0d6314627a8b594b062cfad0c5f557ded49e3b0a1b34f9f0eba37e513a6c7cf8fd45e22dcde01ef029f622bd1773c6cf882b82cab32fc8f37d485488a7acdda49781c0a4b53ee4ee10b2e8ccc6c452a5337586f168730b251e0edd2f9999f38dd0cbf5425ece00bb89b649068219ce4ebcca83b8d6630d2dcdd02f9d3aa6900f64e4ac999d9638977855d55ca3da12fdda22d988a0be6e9b450b927e92a4398fda8a1d9216616dc49c52f41044a01b4ce9113df65a50a3c4c0175b8a17e98b4a88e0b064e9b242fa6fa2e05e5894ee22ed82a66682348ca079e8fac16487eb2822ac67c77a1bb8d644dbf6b542c376dc19ad9f304ffc3f091eae942225340a89382b3d0f3d8edc9a8e8d4af813262c4fffb22ea84262e530645f91a0f2bb394f620dc9367ddd5649b524125456a84c1c8c64e6dbfa34f06bade952a92fe40bfa13e35a05e2aa7b2895576a5052af99a6d46b96dfe1e35a5c1723a1ed459828364af1ef5c6be4fb5f97e9b047030ae3908b27854564230878ee57e00c8288ee64249a1e1fc4a8536e32432163d355284db58ce0ef0a3e4e499c703081b86061790e2267c27fedf09a8446a1c8d8d68d5099502838547e9ec984e4713f0449a79d80cd15cd079478616e164915e780899666bf82dd508799ba5de00c3497c536b550888f355c4978f8f2ea2202c4ca7397d26a56e24d338e90af1b0458656462c4efebd47b3287c15e3ce99b10eac7680b0745659080dd7dac83f8ed1dace494c0018b1c671e8594d585574ed3bda263f7e616cbb970abd9ca54279d849ea9afe444ae0a658394d23f08dc8ad95956e40cef7741751a1ab775dd08e1d4c3834ab91a210392addd4647ecdfede03029d814967299b286add415a5c7078518f51a691a6302764f93c98471f9903a694e7b41e8c87a7985b0f23d9d4aa57ac6971777ff1f957058c41b51f4deb7225e3ef4cef334e06f90d43f2339ffc319fcb891cb64dd43d1cf1ccb10f25a9c717b4090168c4efb8e4539831fe48bd85df13849c48d9d26e448c369c51cd55de2e490c3aa2378725738b472251edf3e3e13e021783455d91ce2a66074b0f17e66a8c7fee8fa1df80f79b5ebcc0dc970fa7ed6f782d65182b349ff3c04a5e81472905d28efb6e1a003b9671a08416dcba67c18e02d9e8e9ea018d31dd981ffaa4e23c6656fa8ca05ec5c428e58ea9a530d863173e906bfec30be25dd61ad4f227d157bca31782ad26cd956c79016332a059e36fdb319591718778c047f129bf35136dab0012c2e5564a6cbd3d848d14de00316270160cae34d2255a7597573dc829ab2d11dff26c4c2442c6b37fa9b19a813af79e508ec32129355e47b062b7e392805faa47969b4ef520cc5c6b70a5eb57b11a0fec8b582b901bf1f70534037486e895205c05c3b9ce2eb6b0109bf74ae34d70820baf12eea1ef7da2dd2b0bd28c3beddfc08f56e4c16caf9aba86fa92d58fb85c17ddba36b867e63cdce69201ec9730a4058cbdac1a0a6e363f2e1601d5fb59474513902585af12334466d21ee5873aa8b352e1cf315f5c8fa3bd94210ac4866be795fbf789eefb38a799a796e5b466a32802dc51a6e80356af68c465e16bd86f26e12aaa41406707a6915f9f4449386f54b294d99e5b795fc3a651bc3b1ba365e835798ee7b99cfe98e2265a92abb2fd3067b3d90938d3a27f651eb82c6324181a0fecd3d7b847da04647130376b76603919dc4bc5bff3c51a7e8fff6ee9a4584f47f6352886bc7740ef536312848f0d40f444a3b4b4a611b058f174827007fede7df9da2f8759d8ef9d20d3444ef8d47b7477ee6316339d0b14f8545a1abd14902048173bae1cde1a455180a986a4428fd0e29c980266eba4b7c6fc80910dee6d43bd4163c3091121af38e173008add1d93ec00d9ea31fcb756000f6f59c78f6fb2d2c1a3e0644e81563e4ae678b5cbb9cc585f6f88c53ab866f423570764ab497ace4b89c7f5d9fefd49fbdcc2d3c9e35a369ae814821095b19ded47739107f219000dced7eff7a84a14e91e50d011b39ee1f2bd9c2a6b70686595e3e1d18bae9544d6157589352b0903ad22377f7155f8b0f0c67f12c3338732660fc113c81e65d93133523936727b3905c46eaec4dd9e3b9515871a7a551dfa0aee8d7391269e10153bf2dab86483aaf7aca5e86dbe501efd2aa8b5208fd55b4f0cdd53addb5db1f55593d3f698e63cab1c87868fe99f1fe91d149a3e7de7f2726bf7a1893f49291091a024d8c72d93da4fa051187d83e2ba755bb909157633ce5e4ceeeaaabd10c70be8a3401a15643e060dec472c605774ee6b4bdeb7ea264bedf33db21b18b5d876a818148da98a89a27447fbbf6aaa3cbd484f9c45b759d6d7c2eaf74d01";
+ }
+
+ private static final class ToHexTests {
+ public static final List shortTests =
+ FromHexTests.shortTests.stream().map(BinaryUtil::fromHex).collect(Collectors.toList());
+
+ public static final byte[] long128 = BinaryUtil.fromHex(FromHexTests.long128);
+ public static final byte[] long256 = BinaryUtil.fromHex(FromHexTests.long256);
+ public static final byte[] long512 = BinaryUtil.fromHex(FromHexTests.long512);
+ public static final byte[] long1024 = BinaryUtil.fromHex(FromHexTests.long1024);
+ public static final byte[] long2048 = BinaryUtil.fromHex(FromHexTests.long2048);
+ }
+
+ @Benchmark
+ public void fromHexShort(Blackhole bh) {
+ for (String s : FromHexTests.shortTests) {
+ bh.consume(BinaryUtil.fromHex(s));
+ }
+ }
+
+ @Benchmark
+ public void fromHexLong128(Blackhole bh) {
+ bh.consume(BinaryUtil.fromHex(FromHexTests.long128));
+ }
+
+ @Benchmark
+ public void fromHexLong256(Blackhole bh) {
+ bh.consume(BinaryUtil.fromHex(FromHexTests.long256));
+ }
+
+ @Benchmark
+ public void fromHexLong512(Blackhole bh) {
+ bh.consume(BinaryUtil.fromHex(FromHexTests.long512));
+ }
+
+ @Benchmark
+ public void fromHexLong1024(Blackhole bh) {
+ bh.consume(BinaryUtil.fromHex(FromHexTests.long1024));
+ }
+
+ @Benchmark
+ public void fromHexLong2048(Blackhole bh) {
+ bh.consume(BinaryUtil.fromHex(FromHexTests.long2048));
+ }
+
+ @Benchmark
+ public void toHexShort(Blackhole bh) {
+ for (byte[] b : ToHexTests.shortTests) {
+ bh.consume(BinaryUtil.toHex(b));
+ }
+ }
+
+ @Benchmark
+ public void toHexLong128(Blackhole bh) {
+ bh.consume(BinaryUtil.toHex(ToHexTests.long128));
+ }
+
+ @Benchmark
+ public void toHexLong256(Blackhole bh) {
+ bh.consume(BinaryUtil.toHex(ToHexTests.long256));
+ }
+
+ @Benchmark
+ public void toHexLong512(Blackhole bh) {
+ bh.consume(BinaryUtil.toHex(ToHexTests.long512));
+ }
+
+ @Benchmark
+ public void toHexLong1024(Blackhole bh) {
+ bh.consume(BinaryUtil.toHex(ToHexTests.long1024));
+ }
+
+ @Benchmark
+ public void toHexLong2048(Blackhole bh) {
+ bh.consume(BinaryUtil.toHex(ToHexTests.long2048));
+ }
+}
diff --git a/yubico-util/src/main/java/com/yubico/internal/util/BinaryUtil.java b/yubico-util/src/main/java/com/yubico/internal/util/BinaryUtil.java
index ed7eb5b78..2f47aee3f 100644
--- a/yubico-util/src/main/java/com/yubico/internal/util/BinaryUtil.java
+++ b/yubico-util/src/main/java/com/yubico/internal/util/BinaryUtil.java
@@ -24,7 +24,6 @@
package com.yubico.internal.util;
-import com.google.common.io.BaseEncoding;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
@@ -40,15 +39,33 @@ public static byte[] copy(byte[] bytes) {
/**
* @param bytes Bytes to encode
*/
- public static String toHex(byte[] bytes) {
- return BaseEncoding.base16().encode(bytes).toLowerCase();
+ public static String toHex(final byte[] bytes) {
+ final char[] digits = new char[bytes.length * 2];
+ for (int i = 0; i < bytes.length; ++i) {
+ final int i2 = i * 2;
+ digits[i2] = Character.forDigit((bytes[i] >> 4) & 0x0f, 16);
+ digits[i2 + 1] = Character.forDigit(bytes[i] & 0x0f, 16);
+ }
+ return new String(digits);
}
/**
* @param hex String of hexadecimal digits to decode as bytes.
*/
- public static byte[] fromHex(String hex) {
- return BaseEncoding.base16().decode(hex.toUpperCase());
+ public static byte[] fromHex(final String hex) {
+ if (hex.length() % 2 != 0) {
+ throw new IllegalArgumentException("Length of hex string is not even: " + hex);
+ }
+
+ final byte[] result = new byte[hex.length() / 2];
+ for (int i = 0; i < hex.length(); ++i) {
+ final int d = Character.digit(hex.charAt(i), 16);
+ if (d < 0) {
+ throw new IllegalArgumentException("Invalid hex digit at index " + i + " in: " + hex);
+ }
+ result[i / 2] |= d << (((i + 1) % 2) * 4);
+ }
+ return result;
}
/**
@@ -57,7 +74,7 @@ public static byte[] fromHex(String hex) {
* @param hex String of hexadecimal digits to decode as bytes.
*/
public static byte singleFromHex(String hex) {
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(
hex.length() == 2, "Argument must be exactly 2 hexadecimal characters, was: %s", hex);
return fromHex(hex)[0];
}
@@ -111,8 +128,9 @@ public static long getUint32(byte[] bytes) {
}
public static byte[] encodeUint16(int value) {
- ExceptionUtil.assure(value >= 0, "Argument must be non-negative, was: %d", value);
- ExceptionUtil.assure(value < 65536, "Argument must be smaller than 2^16=65536, was: %d", value);
+ ExceptionUtil.assertTrue(value >= 0, "Argument must be non-negative, was: %d", value);
+ ExceptionUtil.assertTrue(
+ value < 65536, "Argument must be smaller than 2^16=65536, was: %d", value);
ByteBuffer b = ByteBuffer.allocate(4);
b.order(ByteOrder.BIG_ENDIAN);
@@ -122,8 +140,8 @@ public static byte[] encodeUint16(int value) {
}
public static byte[] encodeUint32(long value) {
- ExceptionUtil.assure(value >= 0, "Argument must be non-negative, was: %d", value);
- ExceptionUtil.assure(
+ ExceptionUtil.assertTrue(value >= 0, "Argument must be non-negative, was: %d", value);
+ ExceptionUtil.assertTrue(
value < 4294967296L, "Argument must be smaller than 2^32=4294967296, was: %d", value);
ByteBuffer b = ByteBuffer.allocate(8);
diff --git a/yubico-util/src/main/java/com/yubico/internal/util/CertificateParser.java b/yubico-util/src/main/java/com/yubico/internal/util/CertificateParser.java
index bb03a8b32..1e1c72bfe 100755
--- a/yubico-util/src/main/java/com/yubico/internal/util/CertificateParser.java
+++ b/yubico-util/src/main/java/com/yubico/internal/util/CertificateParser.java
@@ -40,8 +40,6 @@
public class CertificateParser {
public static final String ID_FIDO_GEN_CE_AAGUID = "1.3.6.1.4.1.45724.1.1.4";
-
- // private static final Provider BC_PROVIDER = new BouncyCastleProvider();
private static final Base64.Decoder BASE64_DECODER = Base64.getDecoder();
private static final List FIXSIG =
@@ -76,7 +74,7 @@ public static X509Certificate parseDer(InputStream is) throws CertificateExcepti
(X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(is);
// Some known certs have an incorrect "unused bits" value, which causes problems on newer
// versions of BouncyCastle.
- if (FIXSIG.contains(cert.getSubjectDN().getName())) {
+ if (FIXSIG.contains(cert.getSubjectX500Principal().getName())) {
byte[] encoded = cert.getEncoded();
if (encoded.length >= UNUSED_BITS_BYTE_INDEX_FROM_END) {
diff --git a/yubico-util/src/main/java/com/yubico/internal/util/ExceptionUtil.java b/yubico-util/src/main/java/com/yubico/internal/util/ExceptionUtil.java
index dc61c11c2..9f5126384 100644
--- a/yubico-util/src/main/java/com/yubico/internal/util/ExceptionUtil.java
+++ b/yubico-util/src/main/java/com/yubico/internal/util/ExceptionUtil.java
@@ -36,7 +36,7 @@ public static RuntimeException wrapAndLog(Logger log, String message, Throwable
return err;
}
- public static void assure(
+ public static void assertTrue(
boolean condition, String failureMessageTemplate, Object... failureMessageArgs) {
if (!condition) {
throw new IllegalArgumentException(String.format(failureMessageTemplate, failureMessageArgs));
diff --git a/yubico-util/src/main/java/com/yubico/internal/util/OptionalUtil.java b/yubico-util/src/main/java/com/yubico/internal/util/OptionalUtil.java
index 9a60ced41..6afb76ea5 100644
--- a/yubico-util/src/main/java/com/yubico/internal/util/OptionalUtil.java
+++ b/yubico-util/src/main/java/com/yubico/internal/util/OptionalUtil.java
@@ -3,6 +3,8 @@
import java.util.Optional;
import java.util.function.BinaryOperator;
import java.util.function.Supplier;
+import java.util.stream.Stream;
+import lombok.NonNull;
import lombok.experimental.UtilityClass;
/** Utilities for working with {@link Optional} values. */
@@ -21,6 +23,17 @@ public static Optional orElseOptional(Optional primary, Supplier Stream stream(@NonNull Optional o) {
+ return o.map(Stream::of).orElseGet(Stream::empty);
+ }
+
/**
* If both a
and b
are present, return f(a, b)
.
*