diff --git a/NEWS b/NEWS
index ae4c811be..b9a4e3d6a 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,16 @@
+== Version 1.12.3 ==
+
+Fixes:
+
+* Fixed `PublicKeyCredential` failing to parse from JSON if an
+ `"authenticatorAttachment"` attribute was present.
+* Bumped Jackson dependency to version [2.13.2.1,3) in response to
+ CVE-2020-36518
+* Fixed bug in `RelyingParty.finishAssertion` that would throw a nondescript
+ `NoSuchElementException` if username and user handle are both absent, instead
+ of an `IllegalArgumentException` with a better error message.
+
+
== Version 1.12.2 ==
Fixes:
diff --git a/README b/README
index 109191c79..cd0e0afde 100644
--- a/README
+++ b/README
@@ -25,7 +25,7 @@ Maven:
com.yubico
webauthn-server-core
- 1.12.2
+ 1.12.3
compile
----------
@@ -33,7 +33,7 @@ Maven:
Gradle:
----------
-compile 'com.yubico:webauthn-server-core:1.12.2'
+compile 'com.yubico:webauthn-server-core:1.12.3'
----------
=== Semantic versioning
diff --git a/build.gradle b/build.gradle
index f3da8fb6e..13a282d97 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,7 +4,7 @@ buildscript {
}
dependencies {
classpath 'com.cinnober.gradle:semver-git:2.5.0'
- classpath 'com.diffplug.spotless:spotless-plugin-gradle:6.2.0'
+ classpath 'com.diffplug.spotless:spotless-plugin-gradle:6.3.0'
classpath 'io.github.cosmicsilence:gradle-scalafix:0.1.8'
}
}
@@ -45,11 +45,17 @@ wrapper {
dependencies {
constraints {
- api('ch.qos.logback:logback-classic:[1.2.3,2)')
api('com.augustcellars.cose:cose-java:[1.0.0,2)')
- api('com.fasterxml.jackson.core:jackson-databind:[2.11.0,3)')
- api('com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:[2.11.0,3)')
- api('com.fasterxml.jackson.datatype:jackson-datatype-jdk8:[2.11.0,3)')
+ api('com.fasterxml.jackson.core:jackson-databind:[2.13.2.1,3)')
+ api('com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:[2.13.2,3)')
+ api('com.fasterxml.jackson.datatype:jackson-datatype-jdk8:[2.13.2,3)')
+ api('com.fasterxml.jackson:jackson-bom') {
+ version {
+ strictly '[2.13.2.1,3)'
+ reject '2.13.2.1'
+ }
+ because 'jackson-databind 2.13.2.1 references nonexistent BOM'
+ }
api('com.google.guava:guava:[24.1.1,31)')
api('com.upokecenter:cbor:[4.5.1,5)')
api('javax.ws.rs:javax.ws.rs-api:[2.1,3)')
diff --git a/webauthn-server-attestation/build.gradle b/webauthn-server-attestation/build.gradle
index 36899ea93..df8dfff19 100644
--- a/webauthn-server-attestation/build.gradle
+++ b/webauthn-server-attestation/build.gradle
@@ -40,17 +40,10 @@ dependencies {
'org.scalatest:scalatest_2.13',
)
- testRuntimeOnly(
- 'ch.qos.logback:logback-classic',
- )
testRuntimeOnly(
// Transitive dependency from :webauthn-server-core:test
'org.bouncycastle:bcpkix-jdk15on',
)
-
- testRuntimeOnly(
- 'ch.qos.logback:logback-classic',
- )
}
diff --git a/webauthn-server-attestation/src/test/resources/logback.xml b/webauthn-server-attestation/src/test/resources/logback.xml
deleted file mode 100644
index 6f078dd41..000000000
--- a/webauthn-server-attestation/src/test/resources/logback.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
- %d{HH:mm:ss.SSSZ} [%thread] %-5level %logger{36} - %msg%n%rEx
-
-
-
-
-
-
-
-
-
-
diff --git a/webauthn-server-core/build.gradle b/webauthn-server-core/build.gradle
index 5c619ef08..bc9dec636 100644
--- a/webauthn-server-core/build.gradle
+++ b/webauthn-server-core/build.gradle
@@ -46,10 +46,6 @@ dependencies {
'org.scalacheck:scalacheck_2.13',
'org.scalatest:scalatest_2.13',
)
-
- testRuntimeOnly(
- 'ch.qos.logback:logback-classic',
- )
}
jar {
diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/FinishAssertionSteps.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/FinishAssertionSteps.java
index 4f5ba22a9..04024cad7 100644
--- a/webauthn-server-core/src/main/java/com/yubico/webauthn/FinishAssertionSteps.java
+++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/FinishAssertionSteps.java
@@ -124,7 +124,8 @@ class Step0 implements Step {
.getUserHandle()
.map(Optional::of)
.orElseGet(
- () -> credentialRepository.getUserHandleForUsername(request.getUsername().get()));
+ () ->
+ request.getUsername().flatMap(credentialRepository::getUserHandleForUsername));
private final Optional username =
request
@@ -132,8 +133,10 @@ class Step0 implements Step {
.map(Optional::of)
.orElseGet(
() ->
- credentialRepository.getUsernameForUserHandle(
- response.getResponse().getUserHandle().get()));
+ response
+ .getResponse()
+ .getUserHandle()
+ .flatMap(credentialRepository::getUsernameForUserHandle));
@Override
public Step1 nextStep() {
@@ -147,12 +150,12 @@ public void validate() {
"At least one of username and user handle must be given; none was.");
assure(
userHandle.isPresent(),
- "No user found for username: %s, userHandle: %s",
+ "User handle not found for username: %s",
request.getUsername(),
response.getResponse().getUserHandle());
assure(
username.isPresent(),
- "No user found for username: %s, userHandle: %s",
+ "Username not found for userHandle: %s",
request.getUsername(),
response.getResponse().getUserHandle());
}
diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredential.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredential.java
index 8570fe7e9..3141912ca 100644
--- a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredential.java
+++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredential.java
@@ -25,6 +25,7 @@
package com.yubico.webauthn.data;
import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import com.yubico.internal.util.JacksonCodecs;
@@ -45,6 +46,7 @@
*/
@Value
@Builder(toBuilder = true)
+@JsonIgnoreProperties({"authenticatorAttachment"})
public class PublicKeyCredential<
A extends AuthenticatorResponse, B extends ClientExtensionOutputs> {
diff --git a/webauthn-server-core/src/test/resources/logback.xml b/webauthn-server-core/src/test/resources/logback.xml
deleted file mode 100644
index 6f078dd41..000000000
--- a/webauthn-server-core/src/test/resources/logback.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
- %d{HH:mm:ss.SSSZ} [%thread] %-5level %logger{36} - %msg%n%rEx
-
-
-
-
-
-
-
-
-
-
diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/OriginMatcherSpec.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/OriginMatcherSpec.scala
index 975430f6f..956359b77 100644
--- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/OriginMatcherSpec.scala
+++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/OriginMatcherSpec.scala
@@ -101,7 +101,6 @@ class OriginMatcherSpec
it("accepts nothing if no allowed origins are given.") {
forAll(urlOrArbitraryString, arbitrary[Boolean], arbitrary[Boolean]) {
(origin, allowPort, allowSubdomain) =>
- println(origin)
OriginMatcher.isAllowed(
origin,
Set.empty[String].asJava,
@@ -114,7 +113,6 @@ class OriginMatcherSpec
it("always accepts string equality even for invalid URLs.") {
forAll(urlOrArbitraryString, arbitrary[Boolean], arbitrary[Boolean]) {
(origin, allowPort, allowSubdomain) =>
- println(origin)
OriginMatcher.isAllowed(
origin,
Set(origin).asJava,
@@ -127,7 +125,6 @@ class OriginMatcherSpec
it("does not accept superdomains.") {
forAll(superAndSubdomain) {
case (origin: URL, allowedOrigin: URL) =>
- println(allowedOrigin, origin)
OriginMatcher.isAllowed(
origin.toExternalForm,
Set(allowedOrigin.toExternalForm).asJava,
@@ -141,7 +138,6 @@ class OriginMatcherSpec
it("by default.") {
forAll(superAndSubdomain, arbitrary[Boolean]) { (origins, allowPort) =>
val (allowedOrigin: URL, origin: URL) = origins
- println(allowedOrigin, origin)
OriginMatcher.isAllowed(
origin.toExternalForm,
@@ -156,8 +152,6 @@ class OriginMatcherSpec
forAll(superAndSubdomain) {
case (allowedOrigin: URL, origin: URL) =>
val invalidAllowedOrigin = invalidize(allowedOrigin)
- println(allowedOrigin, origin, invalidAllowedOrigin)
-
OriginMatcher.isAllowed(
origin.toExternalForm,
Set(invalidAllowedOrigin).asJava,
@@ -171,8 +165,6 @@ class OriginMatcherSpec
forAll(superAndSubdomain) {
case (allowedOrigin: URL, origin: URL) =>
val invalidOrigin = invalidize(origin)
- println(allowedOrigin, origin, invalidOrigin)
-
OriginMatcher.isAllowed(
invalidOrigin,
Set(allowedOrigin.toExternalForm).asJava,
@@ -185,8 +177,6 @@ class OriginMatcherSpec
it("unless configured to.") {
forAll(superAndSubdomain, arbitrary[Boolean]) { (origins, allowPort) =>
val (allowedOrigin: URL, origin: URL) = origins
- println(allowedOrigin, origin)
-
OriginMatcher.isAllowed(
origin.toExternalForm,
Set(allowedOrigin.toExternalForm).asJava,
@@ -203,8 +193,6 @@ class OriginMatcherSpec
(allowedOrigin, port, allowSubdomain) =>
whenever(port > 0) {
val origin = replacePort(allowedOrigin, port)
- println(allowedOrigin, origin)
-
OriginMatcher.isAllowed(
origin.toExternalForm,
Set(allowedOrigin.toExternalForm).asJava,
@@ -218,8 +206,6 @@ class OriginMatcherSpec
it("unless the same port is specified in an allowed origin.") {
forAll(urlWithPort, arbitrary[Boolean]) {
(origin: URL, allowSubdomain: Boolean) =>
- println(origin)
-
OriginMatcher.isAllowed(
origin.toExternalForm,
Set(origin.toExternalForm).asJava,
@@ -242,8 +228,6 @@ class OriginMatcherSpec
port,
allowedOrigin.getFile,
)
- println(allowedOrigin, origin)
-
OriginMatcher.isAllowed(
origin.toExternalForm,
Set(allowedOrigin.toExternalForm).asJava,
@@ -258,8 +242,6 @@ class OriginMatcherSpec
it("accepts subdomains and arbitrary ports when configured to.") {
forAll(superAndSubdomainWithPorts) {
case (allowedOrigin, origin) =>
- println(allowedOrigin, origin)
-
OriginMatcher.isAllowed(
origin.toExternalForm,
Set(allowedOrigin.toExternalForm).asJava,
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 949830f49..e65eb6ffe 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
@@ -179,7 +179,7 @@ class RelyingPartyAssertionSpec
Defaults.requestedExtensions,
rpId: RelyingPartyIdentity = Defaults.rpId,
signature: ByteArray = Defaults.signature,
- userHandleForResponse: ByteArray = Defaults.userHandle,
+ userHandleForResponse: Option[ByteArray] = Some(Defaults.userHandle),
userHandleForUser: ByteArray = Defaults.userHandle,
usernameForRequest: Option[String] = Some(Defaults.username),
usernameForUser: String = Defaults.username,
@@ -220,7 +220,7 @@ class RelyingPartyAssertionSpec
if (clientDataJsonBytes == null) null else clientDataJsonBytes
)
.signature(if (signature == null) null else signature)
- .userHandle(userHandleForResponse)
+ .userHandle(userHandleForResponse.asJava)
.build()
)
.clientExtensionResults(clientExtensionResults)
@@ -523,7 +523,7 @@ class RelyingPartyAssertionSpec
credentialRepository = credentialRepository,
usernameForRequest = Some(owner.username),
userHandleForUser = owner.userHandle,
- userHandleForResponse = nonOwner.userHandle,
+ userHandleForResponse = Some(nonOwner.userHandle),
)
val step: FinishAssertionSteps#Step2 = steps.begin.next.next
@@ -537,7 +537,7 @@ class RelyingPartyAssertionSpec
credentialRepository = credentialRepository,
usernameForRequest = Some(owner.username),
userHandleForUser = owner.userHandle,
- userHandleForResponse = owner.userHandle,
+ userHandleForResponse = Some(owner.userHandle),
)
val step: FinishAssertionSteps#Step2 = steps.begin.next.next
@@ -554,7 +554,7 @@ class RelyingPartyAssertionSpec
credentialRepository = credentialRepository,
usernameForRequest = None,
userHandleForUser = owner.userHandle,
- userHandleForResponse = nonOwner.userHandle,
+ userHandleForResponse = Some(nonOwner.userHandle),
)
val step: FinishAssertionSteps#Step2 = steps.begin.next.next
@@ -563,12 +563,26 @@ class RelyingPartyAssertionSpec
step.tryNext shouldBe a[Failure[_]]
}
+ it("Fails if neither username nor user handle is given.") {
+ val steps = finishAssertion(
+ credentialRepository = credentialRepository,
+ usernameForRequest = None,
+ userHandleForUser = owner.userHandle,
+ userHandleForResponse = None,
+ )
+ val step: FinishAssertionSteps#Step0 = steps.begin
+
+ step.validations shouldBe a[Failure[_]]
+ step.validations.failed.get shouldBe an[IllegalArgumentException]
+ step.tryNext shouldBe a[Failure[_]]
+ }
+
it("Succeeds if credential ID is owned by the given user handle.") {
val steps = finishAssertion(
credentialRepository = credentialRepository,
usernameForRequest = None,
userHandleForUser = owner.userHandle,
- userHandleForResponse = owner.userHandle,
+ userHandleForResponse = Some(owner.userHandle),
)
val step: FinishAssertionSteps#Step2 = steps.begin.next.next
@@ -1320,12 +1334,6 @@ class RelyingPartyAssertionSpec
forAll(Extensions.unrequestedClientAssertionExtensions) {
case (extensionInputs, clientExtensionOutputs, _) =>
- println(extensionInputs.getExtensionIds, extensionInputs)
- println(
- clientExtensionOutputs.getExtensionIds,
- clientExtensionOutputs,
- )
-
val steps = finishAssertion(
requestedExtensions = extensionInputs,
clientExtensionResults = clientExtensionOutputs,
@@ -1344,12 +1352,6 @@ class RelyingPartyAssertionSpec
it("Succeeds if clientExtensionResults is not a subset of the extensions requested by the Relying Party, but the Relying Party has enabled allowing unrequested extensions.") {
forAll(Extensions.unrequestedClientAssertionExtensions) {
case (extensionInputs, clientExtensionOutputs, _) =>
- println(extensionInputs.getExtensionIds, extensionInputs)
- println(
- clientExtensionOutputs.getExtensionIds,
- clientExtensionOutputs,
- )
-
val steps = finishAssertion(
allowUnrequestedExtensions = true,
requestedExtensions = extensionInputs,
@@ -1366,12 +1368,6 @@ class RelyingPartyAssertionSpec
it("Succeeds if clientExtensionResults is a subset of the extensions requested by the Relying Party.") {
forAll(Extensions.subsetAssertionExtensions) {
case (extensionInputs, clientExtensionOutputs, _) =>
- println(extensionInputs.getExtensionIds, extensionInputs)
- println(
- clientExtensionOutputs.getExtensionIds,
- clientExtensionOutputs,
- )
-
val steps = finishAssertion(
requestedExtensions = extensionInputs,
clientExtensionResults = clientExtensionOutputs,
@@ -1393,9 +1389,6 @@ class RelyingPartyAssertionSpec
_,
authenticatorExtensionOutputs: CBORObject,
) =>
- println(extensionInputs)
- println(authenticatorExtensionOutputs)
-
val steps = finishAssertion(
requestedExtensions = extensionInputs,
authenticatorData = TestAuthenticator.makeAuthDataBytes(
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 26c557918..353d7d60d 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
@@ -40,7 +40,6 @@ import com.yubico.webauthn.attestation.MetadataService
import com.yubico.webauthn.data.AttestationObject
import com.yubico.webauthn.data.AttestationType
import com.yubico.webauthn.data.AuthenticatorData
-import com.yubico.webauthn.data.AuthenticatorRegistrationExtensionOutputs
import com.yubico.webauthn.data.AuthenticatorSelectionCriteria
import com.yubico.webauthn.data.AuthenticatorTransport
import com.yubico.webauthn.data.ByteArray
@@ -947,12 +946,6 @@ class RelyingPartyRegistrationSpec
it("Succeeds if clientExtensionResults is a subset of the extensions requested by the Relying Party.") {
forAll(Extensions.subsetRegistrationExtensions) {
case (extensionInputs, clientExtensionOutputs, _) =>
- println(extensionInputs.getExtensionIds, extensionInputs)
- println(
- clientExtensionOutputs.getExtensionIds,
- clientExtensionOutputs,
- )
-
val steps = finishRegistration(
testData =
RegistrationTestData.Packed.BasicAttestation.copy(
@@ -971,12 +964,6 @@ class RelyingPartyRegistrationSpec
it("Succeeds if clientExtensionResults is not a subset of the extensions requested by the Relying Party, but the Relying Party has enabled allowing unrequested extensions.") {
forAll(Extensions.unrequestedClientRegistrationExtensions) {
case (extensionInputs, clientExtensionOutputs, _) =>
- println(extensionInputs.getExtensionIds, extensionInputs)
- println(
- clientExtensionOutputs.getExtensionIds,
- clientExtensionOutputs,
- )
-
val steps = finishRegistration(
allowUnrequestedExtensions = true,
testData =
@@ -996,12 +983,6 @@ class RelyingPartyRegistrationSpec
it("Fails if clientExtensionResults is not a subset of the extensions requested by the Relying Party.") {
forAll(Extensions.unrequestedClientRegistrationExtensions) {
case (extensionInputs, clientExtensionOutputs, _) =>
- println(extensionInputs.getExtensionIds, extensionInputs)
- println(
- clientExtensionOutputs.getExtensionIds,
- clientExtensionOutputs,
- )
-
val steps = finishRegistration(
testData =
RegistrationTestData.Packed.BasicAttestation.copy(
@@ -1038,12 +1019,6 @@ class RelyingPartyRegistrationSpec
_,
authenticatorExtensionOutputs: CBORObject,
) =>
- println(extensionInputs.getExtensionIds, extensionInputs)
- println(
- authenticatorExtensionOutputs.getKeys,
- authenticatorExtensionOutputs,
- )
-
val steps = finishRegistration(
testData = RegistrationTestData.Packed.BasicAttestation
.copy(
@@ -1063,13 +1038,6 @@ class RelyingPartyRegistrationSpec
step.validations shouldBe a[Success[_]]
step.tryNext shouldBe a[Success[_]]
-
- println(
- AuthenticatorRegistrationExtensionOutputs
- .fromAuthenticatorData(
- step.getAttestation.getAuthenticatorData
- )
- )
}
}
@@ -1082,12 +1050,6 @@ class RelyingPartyRegistrationSpec
_,
authenticatorExtensionOutputs: CBORObject,
) =>
- println(extensionInputs.getExtensionIds, extensionInputs)
- println(
- authenticatorExtensionOutputs.getKeys,
- authenticatorExtensionOutputs,
- )
-
val steps = finishRegistration(
allowUnrequestedExtensions = true,
testData = RegistrationTestData.Packed.BasicAttestation
@@ -1108,13 +1070,6 @@ class RelyingPartyRegistrationSpec
step.validations shouldBe a[Success[_]]
step.tryNext shouldBe a[Success[_]]
-
- println(
- AuthenticatorRegistrationExtensionOutputs
- .fromAuthenticatorData(
- step.getAttestation.getAuthenticatorData
- )
- )
}
}
@@ -1127,12 +1082,6 @@ class RelyingPartyRegistrationSpec
_,
authenticatorExtensionOutputs: CBORObject,
) =>
- println(extensionInputs.getExtensionIds, extensionInputs)
- println(
- authenticatorExtensionOutputs.getKeys,
- authenticatorExtensionOutputs,
- )
-
val steps = finishRegistration(
testData = RegistrationTestData.Packed.BasicAttestation
.copy(
@@ -1155,13 +1104,6 @@ class RelyingPartyRegistrationSpec
IllegalArgumentException
]
step.tryNext shouldBe a[Failure[_]]
-
- println(
- AuthenticatorRegistrationExtensionOutputs
- .fromAuthenticatorData(
- step.getAttestation.getAuthenticatorData
- )
- )
}
}
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 10dd8f359..84602be6d 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
@@ -346,9 +346,6 @@ class RelyingPartyStartOperationSpec
it("by default sets the credProps extension.") {
forAll(registrationExtensionInputs(credPropsGen = None)) {
extensions: RegistrationExtensionInputs =>
- println(extensions.getExtensionIds)
- println(extensions)
-
val rp = relyingParty(userId = userId)
val result = rp.startRegistration(
StartRegistrationOptions
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 c87c7c6d4..0b9f28d52 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
@@ -91,61 +91,6 @@ import scala.util.Try
object TestAuthenticator {
- def main(args: Array[String]): Unit = {
- val attestationCertBytes: ByteArray =
- ByteArray.fromHex("308201313081d8a003020102020441c4567d300a06082a8648ce3d0403023021311f301d0603550403131646697265666f782055324620536f667420546f6b656e301e170d3137303930353134303030345a170d3137303930373134303030345a3021311f301d0603550403131646697265666f782055324620536f667420546f6b656e3059301306072a8648ce3d020106082a8648ce3d03010703420004f9b7dfc17c8a7dcaacdaaad402c7f1f8570e3e9165f6ce2b9b9a4f64333405e1b952c516560bbe7d304d2da3b6582734dadd980e379b0f86a3e42cc657cffe84300a06082a8648ce3d0403020348003045022067fd4da98db1ddbcef53041d3cfd15ed6b8315cb4116889c2eabe6b50b7f985f02210098842f6835ee18181acc765f642fa124556121f418e108c5ec1bb22e9c28b76b")
- val publicKeyHex: String =
- "04f9b7dfc17c8a7dcaacdaaad402c7f1f8570e3e9165f6ce2b9b9a4f64333405e1b952c516560bbe7d304d2da3b6582734dadd980e379b0f86a3e42cc657cffe84"
- val signedDataBytes: ByteArray =
- ByteArray.fromHex("0049960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976354543ac68315afe4cd7947adf5f7e8e7dc87ddf4582ef6e7fb467e5cad098af50008f926c96b3248cb3733c70a10e3e0995af0892220d6293780335390594e35a73a3743ed97c8e4fd9c0e183d60ccb764edac2fcbdb84b6b940089be98744673db427ce9d4f09261d4f6535bf52dcd216d9ba81a88f2ed5d7fa04bb25e641a3cd7ef9922fdb8d7d4b9f81a55f661b74f26d97a9382dda9a6b62c378cf6603b9f1218a87c158d88bf1ac51b0e4343657de0e9a6b6d60289fed2b46239abe00947e6a04c6733148283cb5786a678afc959262a71be0925da9992354ba6438022d68ae573285e5564196d62edfc46432cba9393c6138882856a0296b41f5b4b97e00e935")
- val signatureBytes: ByteArray =
- ByteArray.fromHex("3046022100a78ca2cb9feb402acc9f50d16d96487821122bbbdf70c8745a6d37161a16de09022100e10db1bf39b73b18acf9236f758558a7811e04a7901d12f7f34f503b171fe51e")
-
- verifyU2fExampleWithCert(
- attestationCertBytes,
- signedDataBytes,
- signatureBytes,
- )
- verifyU2fExampleWithExplicitParams(
- publicKeyHex,
- signedDataBytes,
- signatureBytes,
- )
-
- println(generateAttestationCertificate())
-
- val (credential, _) = createBasicAttestedCredential(attestationMaker =
- AttestationMaker.packed(
- AttestationSigner.selfsigned(COSEAlgorithmIdentifier.ES256)
- )
- )
-
- println(credential)
- println(
- s"Client data: ${new String(credential.getResponse.getClientDataJSON.getBytes, "UTF-8")}"
- )
- println(s"Client data: ${credential.getResponse.getClientDataJSON.getHex}")
- println(s"Client data: ${credential.getResponse.getClientData}")
- println(s"Attestation object: ${credential.getResponse.getAttestationObject.getHex}")
- println(s"Attestation object: ${credential.getResponse.getAttestation}")
-
- println("Javascript:")
- println(s"""parseCreateCredentialResponse({ response: { attestationObject: new Buffer("${credential.getResponse.getAttestationObject.getHex}", 'hex'), clientDataJSON: new Buffer("${credential.getResponse.getClientDataJSON.getHex}", 'hex') } })""")
-
- println(s"Public key: ${BinaryUtil.toHex(Defaults.credentialKey.getPublic.getEncoded)}")
- println(s"Private key: ${BinaryUtil.toHex(Defaults.credentialKey.getPrivate.getEncoded)}")
-
- val assertion = createAssertion()
- println(
- s"Assertion signature: ${assertion.getResponse.getSignature.getHex}"
- )
- println(s"Authenticator data: ${assertion.getResponse.getAuthenticatorData.getHex}")
- println(s"Client data: ${assertion.getResponse.getClientDataJSON.getHex}")
- println(
- s"Client data: ${new String(assertion.getResponse.getClientDataJSON.getBytes, "UTF-8")}"
- )
- }
-
object Defaults {
val aaguid: ByteArray = new ByteArray(
Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
@@ -333,41 +278,6 @@ object TestAuthenticator {
}
}
- def makeCreateCredentialExample(
- publicKeyCredential: PublicKeyCredential[
- AuthenticatorAttestationResponse,
- ClientRegistrationExtensionOutputs,
- ]
- ): String =
- s"""Attestation object: ${publicKeyCredential.getResponse.getAttestationObject.getHex}
- |Client data: ${publicKeyCredential.getResponse.getClientDataJSON.getHex}
- """.stripMargin
-
- def makeAssertionExample(alg: COSEAlgorithmIdentifier): String = {
- val (_, keypair) =
- createCredential(attestationMaker = AttestationMaker.default())
- val assertion = createAssertion(alg, credentialKey = keypair)
-
- s"""
- |val keyAlgorithm: COSEAlgorithmIdentifier = COSEAlgorithmIdentifier.${alg.name}
- |val authenticatorData: ByteArray = ByteArray.fromHex("${assertion.getResponse.getAuthenticatorData.getHex}")
- |val clientDataJson: String = "\""${new String(
- assertion.getResponse.getClientDataJSON.getBytes,
- StandardCharsets.UTF_8,
- )}""\"
- |val credentialId: ByteArray = ByteArray.fromBase64Url("${assertion.getId.getBase64Url}")
- |val credentialKey: KeyPair = TestAuthenticator.importEcKeypair(
- | privateBytes = ByteArray.fromHex("${new ByteArray(
- keypair.getPrivate.getEncoded
- ).getHex}"),
- | publicBytes = ByteArray.fromHex("${new ByteArray(
- keypair.getPublic.getEncoded
- ).getHex}")
- |)
- |val signature: ByteArray = ByteArray.fromHex("${assertion.getResponse.getSignature.getHex}")
- """.stripMargin
- }
-
private def createCredential(
aaguid: ByteArray = Defaults.aaguid,
attestationMaker: AttestationMaker,
diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/ExtensionsSpec.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/ExtensionsSpec.scala
index 7b677f0f5..29dbcdaa2 100644
--- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/ExtensionsSpec.scala
+++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/ExtensionsSpec.scala
@@ -50,7 +50,6 @@ class ExtensionsSpec
val json = JacksonCodecs.json()
forAll(Generators.Extensions.registrationExtensionInputsJson()) {
encoded: ObjectNode =>
- println(encoded)
val decoded =
json.treeToValue(encoded, classOf[RegistrationExtensionInputs])
@@ -249,9 +248,6 @@ class ExtensionsSpec
val jsonKeyNames = json.fieldNames.asScala.toList
val extensionIds = input.getExtensionIds
- println(input)
- println(json)
-
jsonKeyNames.length should equal(extensionIds.size)
jsonKeyNames.toSet should equal(extensionIds.asScala)
}
@@ -267,7 +263,6 @@ class ExtensionsSpec
val encoded = json.valueToTree[ObjectNode](clientExtensionOutputs)
encoded.setAll(unknownOutputs)
- println(encoded)
val decoded = json.treeToValue(
encoded,
classOf[ClientRegistrationExtensionOutputs],
@@ -292,7 +287,6 @@ class ExtensionsSpec
)
) { input: ClientRegistrationExtensionOutputs =>
val json = JacksonCodecs.json().valueToTree[ObjectNode](input)
- println(json)
json.has(Extensions.AppidExclude.EXTENSION_ID) should be(true)
json.get("appidExclude").booleanValue should equal(
input.getAppidExclude.get
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 d17ffa725..cfc77a6c5 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
@@ -47,6 +47,8 @@ import com.yubico.webauthn.extension.appid.AppId
import com.yubico.webauthn.extension.appid.Generators._
import org.junit.runner.RunWith
import org.scalacheck.Arbitrary
+import org.scalacheck.Arbitrary.arbitrary
+import org.scalacheck.Gen
import org.scalatest.FunSpec
import org.scalatest.Matchers
import org.scalatestplus.junit.JUnitRunner
@@ -89,16 +91,9 @@ class JsonIoSpec
it("is identical after multiple serialization round-trips..") {
forAll { value: A =>
- println(value)
-
val encoded: String = json.writeValueAsString(value)
- println(encoded)
-
val decoded: A = json.readValue(encoded, tpe)
- println(decoded)
-
val recoded: String = json.writeValueAsString(decoded)
- println(recoded)
decoded should equal(value)
recoded should equal(encoded)
@@ -369,12 +364,49 @@ class JsonIoSpec
]]() {}
)
}
+
+ it("allows and ignores an authenticatorAttachment attribute.") {
+ def test[P <: PublicKeyCredential[_, _]](tpe: TypeReference[P])(implicit
+ a: Arbitrary[P]
+ ): Unit = {
+ forAll(
+ a.arbitrary,
+ Gen.oneOf(
+ arbitrary[AuthenticatorAttachment].map(_.toJsonString),
+ arbitrary[String],
+ ),
+ ) { (value: P, authenticatorAttachment: String) =>
+ val tree: ObjectNode = json.valueToTree(value)
+ tree.set(
+ "authenticatorAttachment",
+ new TextNode(authenticatorAttachment),
+ )
+ val encoded = json.writeValueAsString(tree)
+ println(authenticatorAttachment)
+ val decoded = json.readValue(encoded, tpe)
+
+ decoded should equal(value)
+ }
+ }
+
+ test(
+ new TypeReference[PublicKeyCredential[
+ AuthenticatorAssertionResponse,
+ ClientAssertionExtensionOutputs,
+ ]]() {}
+ )
+ test(
+ new TypeReference[PublicKeyCredential[
+ AuthenticatorAttestationResponse,
+ ClientRegistrationExtensionOutputs,
+ ]]() {}
+ )
+ }
}
describe("The class PublicKeyCredentialCreationOptions") {
it("""has a toCredentialsCreateJson() method which returns a JSON object with the PublicKeyCredentialCreationOptions set as a top-level "publicKey" property.""") {
forAll { pkcco: PublicKeyCredentialCreationOptions =>
- println(pkcco)
val jsonValue =
JacksonCodecs.json.readTree(pkcco.toCredentialsCreateJson)
jsonValue.get("publicKey") should not be null
@@ -388,7 +420,6 @@ class JsonIoSpec
describe("has a toJson() method and a fromJson(String) factory method") {
it("which behave like a Jackson ObjectMapper.") {
forAll { req: PublicKeyCredentialCreationOptions =>
- println(req)
val json1 = req.toJson
val json2 = JacksonCodecs.json.writeValueAsString(req)
json1 should equal(json2)
@@ -404,7 +435,6 @@ class JsonIoSpec
it("which are stable over multiple serialization round-trips.") {
forAll { req: PublicKeyCredentialCreationOptions =>
- println(req)
val encoded = req.toJson
val decoded = PublicKeyCredentialCreationOptions.fromJson(encoded)
val reencoded = decoded.toJson
@@ -421,7 +451,6 @@ class JsonIoSpec
describe("The class PublicKeyCredentialRequestOptions") {
it("""has a toCredentialsGetJson() method which returns a JSON object with the PublicKeyCredentialGetOptions set as a top-level "publicKey" property.""") {
forAll { pkcro: PublicKeyCredentialRequestOptions =>
- println(pkcro)
val jsonValue = JacksonCodecs.json.readTree(pkcro.toCredentialsGetJson)
jsonValue.get("publicKey") should not be null
JacksonCodecs.json.treeToValue(
@@ -435,8 +464,6 @@ class JsonIoSpec
describe("The class AssertionRequest") {
it("""has a toCredentialsGetJson() method which returns a JSON object with the PublicKeyCredentialGetOptions set as a top-level "publicKey" property.""") {
forAll { req: AssertionRequest =>
- println(req)
-
val jsonValue = JacksonCodecs.json.readTree(req.toCredentialsGetJson)
jsonValue.get("publicKey") should not be null
JacksonCodecs.json.treeToValue(
@@ -449,7 +476,6 @@ class JsonIoSpec
describe("has a toJson() method and a fromJson(String) factory method") {
it("which behave like a Jackson ObjectMapper.") {
forAll { req: AssertionRequest =>
- println(req)
val json1 = req.toJson
val json2 = JacksonCodecs.json.writeValueAsString(req)
@@ -463,7 +489,6 @@ class JsonIoSpec
it("which are stable over multiple serialization round-trips.") {
forAll { req: AssertionRequest =>
- println(req)
val encoded = req.toJson
val decoded = AssertionRequest.fromJson(encoded)
val reencoded = decoded.toJson
diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/test/Test.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/test/Test.scala
deleted file mode 100644
index d47749ac0..000000000
--- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/test/Test.scala
+++ /dev/null
@@ -1,248 +0,0 @@
-// Copyright (c) 2018, Yubico AB
-// All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-// 1. Redistributions of source code must retain the above copyright notice, this
-// list of conditions and the following disclaimer.
-//
-// 2. Redistributions in binary form must reproduce the above copyright notice,
-// this list of conditions and the following disclaimer in the documentation
-// and/or other materials provided with the distribution.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-package com.yubico.webauthn.test
-
-import COSE.OneKey
-import com.fasterxml.jackson.databind.JsonNode
-import com.upokecenter.cbor.CBORObject
-import com.yubico.internal.util.BinaryUtil
-import com.yubico.internal.util.CertificateParser
-import com.yubico.internal.util.JacksonCodecs
-import com.yubico.webauthn.RegistrationTestData
-import com.yubico.webauthn.WebAuthnTestCodecs
-import com.yubico.webauthn.data.AttestationObject
-import com.yubico.webauthn.data.AuthenticatorDataFlags
-import com.yubico.webauthn.data.ByteArray
-import org.bouncycastle.asn1.ASN1InputStream
-import org.bouncycastle.asn1.ASN1Primitive
-import org.bouncycastle.asn1.util.ASN1Dump
-
-import java.security.interfaces.ECPublicKey
-import java.util.Base64
-import scala.jdk.CollectionConverters._
-
-object Test extends App {
-
- // val attestationObject: ByteArray = ByteArray.fromBase64Url("o2NmbXRmcGFja2VkaGF1dGhEYXRhWLhsce9f2O4QMCXrg1cu1lwknOxtXBryURzsRWDk8tD7pkEAAAAAAAAAAAAAAAAAAAAAAAAAAABAawIjwBjPgvsbJe-gqVwMFEQeZ0zTgj93jw3fFdOTLTshl3F2qwb6O35qI520Iw53fXcsNMoFWL767oiSpHB4ggQ0PVe2C-FLegMiA73oT8Tbd-R7wB7HOrYY5FOQdmCN2aGm5dT2RrmsRHq_EhEUF6L_X4aY2zIkXH7-UlI0MtQMZ2F0dFN0bXSjY2FsZ2VFUzI1NmNzaWdYRzBFAiBgPH9xOEVrf3XqFbYkn78oHbBu-c8-0z0g6sT00MzcJAIhAJdwAJuhzS_SqJm8q8R--yc_YXj4VvNLlCVWnFlycIIXY3g1Y4FZAlMwggJPMIIBN6ADAgECAgQSNtF_MA0GCSqGSIb3DQEBCwUAMC4xLDAqBgNVBAMTI1l1YmljbyBVMkYgUm9vdCBDQSBTZXJpYWwgNDU3MjAwNjMxMCAXDTE0MDgwMTAwMDAwMFoYDzIwNTAwOTA0MDAwMDAwWjAxMS8wLQYDVQQDDCZZdWJpY28gVTJGIEVFIFNlcmlhbCAyMzkyNTczNDEwMzI0MTA4NzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNNlqR5emeDVtDnA2a-7h_QFjkfdErFE7bFNKzP401wVE-QNefD5maviNnGVk4HJ3CsHhYuCrGNHYgTM9zTWriGjOzA5MCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0ODIuMS41MBMGCysGAQQBguUcAgEBBAQDAgUgMA0GCSqGSIb3DQEBCwUAA4IBAQAiG5uzsnIk8T6-oyLwNR6vRklmo29yaYV8jiP55QW1UnXdTkEiPn8mEQkUac-Sn6UmPmzHdoGySG2q9B-xz6voVQjxP2dQ9sgbKd5gG15yCLv6ZHblZKkdfWSrUkrQTrtaziGLFSbxcfh83vUjmOhDLFC5vxV4GXq2674yq9F2kzg4nCS4yXrO4_G8YWR2yvQvE2ffKSjQJlXGO5080Ktptplv5XN4i5lS-AKrT5QRVbEJ3B4g7G0lQhdYV-6r4ZtHil8mF4YNMZ0-RaYPxAaYNWkFYdzOZCaIdQbXRZefgGfbMUiAC2gwWN7fiPHV9eu82NYypGU32OijG9BjhGt_")
- // val attestationObject: ByteArray = ByteArray.fromBase64Url("o2hhdXRoRGF0YVjKbHHvX9juEDAl64NXLtZcJJzsbVwa8lEc7EVg5PLQ-6ZBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQOiUTsFEO0Ng5k3_k2diNEWJw8PDfXUMm9dPQ6piFAgN2ZqYXc2edf-nm9qYcznSHZ7My05HRWC8b15UdtpNYHWjY2FsZ2VFUzI1NmF4WCDS7Esl2DRo9RpWifrwLuAwCx-x5JN5Vl5RNla0xeBf0mF5WCDXSTYlDGlsKid4rRm6wi6NY5FDLiCOXJQpzkC8l2guKGNmbXRoZmlkby11MmZnYXR0U3RtdKJjeDVjgVkCUzCCAk8wggE3oAMCAQICBCrZavMwDQYJKoZIhvcNAQELBQAwLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMDExLzAtBgNVBAMMJll1YmljbyBVMkYgRUUgU2VyaWFsIDIzOTI1NzM0NTE2NTUwMzg3MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEL-GiPr-lWz5GHVmkNSLXl0iYHLptKJqY8b19_2VmgNu77bwrrmB-bvdy9XawTVTE5fMvWW8m5hEVxycs9sp1lKM7MDkwIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjIwEwYLKwYBBAGC5RwCAQEEBAMCBDAwDQYJKoZIhvcNAQELBQADggEBAIVq-ovPTz9iXykbwRWOPH69JVK891cHU_USHaalTSTMz64nztarMRKMKX5bW4kF3aAgF5MfH19ZJZNZUfwAS8viCt19jQUvlUOzSWwVuDEOEMvZuwU4J09YPq0fRRKIw-p20HCtROU6_qjyLR9zYl_y1Yn-MN8mYst8u3yZYYCtz6mKTQEs8xNGzRF0alhI6L7t8-MMy9nB3SIWcbKDiGH2WkU2I7UY1VZ_qPCjzhBd9PE5U-EU6lngp_L-ZohnQy5S_WovZPc8SM2bOPLfuix6SzsRKN8m1mok-JXdoLYRgPQUT2twdcMYpJrgi1jTatseMFNnKxfFoZ9_CiLxDpRjc2lnWEcwRQIhAOdPkMJhSa5IQ8nOA4BYUjYMA6d5WGEA3sDuEBnkXxm6AiAJ_vfKhJdC5WGcMxZfgnz4I9JJ_-D-VHSHljEvDyUS7w")
-
- // println(JacksonCodecs.cbor.readTree(attestationObject.getBytes))
-
- // val attObj = JacksonCodecs.cbor.readTree(attestationObject.getBytes)
-
- // println(attObj.get("authData").getNodeType)
- // println(attObj.get("fmt").getNodeType)
- // println(attObj.get("attStmt").getNodeType)
-
- // val attStmt = attObj.get("attStmt")
-
- // println(attStmt)
- // println(attStmt.fieldNames)
-
- // println(attStmt.get("x5c").getNodeType)
- // println(attStmt.get("x5c"))
-
- // println(attStmt.get("x5c").get(0).getNodeType)
- // println(attStmt.get("x5c").get(0).isBinary)
- // println(attStmt.get("x5c").get(0))
-
- runWith(RegistrationTestData.AndroidSafetynet.RealExample.attestationObject)
-
- val attestationObjectFirefox58 =
- ByteArray.fromBase64Url("o2hhdXRoRGF0YVjKlJKaCrOPLCdHaydNzfSylZWY-KV_PzorqW39Kb9p5mdBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAixHvZmRdDp0DhMTCfB9dHQF9frTlVXNqXGJ3tGfVV0hR6mIk9ioAbK4AK9VkoxdIE04kjemwBHc5Yaz8BrZ9ujY2FsZ2VFUzI1NmF4WCD7ESIYejaHqAg9C9hTMy1hQafvKmy1KIuXW6Artariq2F5WCCpWfXbnYPAUpTL18oD9A_BUFR7z9IhodehhSYlN_Y2mWNmbXRoZmlkby11MmZnYXR0U3RtdKJjeDVjgVkCTjCCAkowggEyoAMCAQICBFcW98AwDQYJKoZIhvcNAQELBQAwLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMCwxKjAoBgNVBAMMIVl1YmljbyBVMkYgRUUgU2VyaWFsIDI1MDU2OTIyNjE3NjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGTZHFTW2VeN07BgeExGNtVIo5g6rzUnzustxuMrZ54z_OnoYxStvPhXUhsvTEmflK9wVWW-IhlbKbZOTa0N0GKjOzA5MCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0ODIuMS41MBMGCysGAQQBguUcAgEBBAQDAgUgMA0GCSqGSIb3DQEBCwUAA4IBAQB4mxjKm6TbdiDi-IuM_elRJm0qInfV4_vptoyaoOkaakjqdUzvC95ZhANtrn-Lo_3mS1F2BMOhGy5tdU1KNxnvqHaTD6Dg8wY_WkygvKAxT38Fo-pdTt2R00pmtV1cj6huAOe_X92AI36z24I-NOMSZ7NJqsecJKcSpZ6ASqqNa6klJqJ3p3HeMWpzvzxxH8VNImYn-teV5PROG3ADTwn8_ji33il4k_tZrIscM8_Fxr5djkzCf0ofRvb4RPh4wHKL3B37pnHaEIf1jOOOWWuj8p_QWUFdxQqjL4kUfNPbCAE31OZbvOsLv-VBiBiOzBQxGHlRkqs6c4eppXJ_EEiGY3NpZ1hHMEUCIEMdeuTafyyaQFAjrZv0ANFt6mHzqjABJBwtUPFfbU0BAiEAvHJuqQCMUoNErDJFR928WnJnykwmoEi5XxdvsjtbDIw")
- // val attestationObjectFirefoxNightly = ByteArray.fromBase64Url("o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEcwRQIhAMN34Lky1H00Yio4AJcCVh-cIbw__8fOgVPacfZqQMtSAiAHLWI6GKHAi7pmRMEljNuWBq_BHrKObzzzui9Duqmo7GN4NWOBWQJOMIICSjCCATKgAwIBAgIEVxb3wDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowLDEqMCgGA1UEAwwhWXViaWNvIFUyRiBFRSBTZXJpYWwgMjUwNTY5MjI2MTc2MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZNkcVNbZV43TsGB4TEY21UijmDqvNSfO6y3G4ytnnjP86ehjFK28-FdSGy9MSZ-Ur3BVZb4iGVsptk5NrQ3QYqM7MDkwIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjUwEwYLKwYBBAGC5RwCAQEEBAMCBSAwDQYJKoZIhvcNAQELBQADggEBAHibGMqbpNt2IOL4i4z96VEmbSoid9Xj--m2jJqg6RpqSOp1TO8L3lmEA22uf4uj_eZLUXYEw6EbLm11TUo3Ge-odpMPoODzBj9aTKC8oDFPfwWj6l1O3ZHTSma1XVyPqG4A579f3YAjfrPbgj404xJns0mqx5wkpxKlnoBKqo1rqSUmonencd4xanO_PHEfxU0iZif615Xk9E4bcANPCfz-OLfeKXiT-1msixwzz8XGvl2OTMJ_Sh9G9vhE-HjAcovcHfumcdoQh_WM445Za6Pyn9BZQV3FCqMviRR809sIATfU5lu86wu_5UGIGI7MFDEYeVGSqzpzh6mlcn8QSIZoYXV0aERhdGFYxJSSmgqzjywnR2snTc30spWVmPilfz86K6lt_Sm_aeZnQQAAAAAAAAAAAAAAAAAAAAAAAAAAAEBsMp3vlrdYz8qUyb6o0J9M9l7FLS6XI70p9Txx0LIDuG87doFwc-9Tu6pW0njfyIISSif4kXZkF87vrgCcDp3UpQECAyYgASFYIKjQ7ovDDFsXm-I3q1vX8WUtU2CQ5IwX0cPfgR1KxBZLIlggQR9CYSfpMsRLoL9Y1ADVV_rKHMStoipUywjOct0g7cA")
- val attestationObjectFirefoxNightly = new ByteArray(
- Base64.getMimeDecoder.decode("o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEcwRQIhAMN34Lky1H00Yio4AJcCVh+cIbw//8fOgVPacfZqQMtSAiAHLWI6GKHAi7pmRMEljNuWBq/BHrKObzzzui9Duqmo7GN4NWOBWQJOMIICSjCCATKgAwIBAgIEVxb3wDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowLDEqMCgGA1UEAwwhWXViaWNvIFUyRiBFRSBTZXJpYWwgMjUwNTY5MjI2MTc2MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZNkcVNbZV43TsGB4TEY21UijmDqvNSfO6y3G4ytnnjP86ehjFK28+FdSGy9MSZ+Ur3BVZb4iGVsptk5NrQ3QYqM7MDkwIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjUwEwYLKwYBBAGC5RwCAQEEBAMCBSAwDQYJKoZIhvcNAQELBQADggEBAHibGMqbpNt2IOL4i4z96VEmbSoid9Xj++m2jJqg6RpqSOp1TO8L3lmEA22uf4uj/eZLUXYEw6EbLm11TUo3Ge+odpMPoODzBj9aTKC8oDFPfwWj6l1O3ZHTSma1XVyPqG4A579f3YAjfrPbgj404xJns0mqx5wkpxKlnoBKqo1rqSUmonencd4xanO/PHEfxU0iZif615Xk9E4bcANPCfz+OLfeKXiT+1msixwzz8XGvl2OTMJ/Sh9G9vhE+HjAcovcHfumcdoQh/WM445Za6Pyn9BZQV3FCqMviRR809sIATfU5lu86wu/5UGIGI7MFDEYeVGSqzpzh6mlcn8QSIZoYXV0aERhdGFYxJSSmgqzjywnR2snTc30spWVmPilfz86K6lt/Sm/aeZnQQAAAAAAAAAAAAAAAAAAAAAAAAAAAEBsMp3vlrdYz8qUyb6o0J9M9l7FLS6XI70p9Txx0LIDuG87doFwc+9Tu6pW0njfyIISSif4kXZkF87vrgCcDp3UpQECAyYgASFYIKjQ7ovDDFsXm+I3q1vX8WUtU2CQ5IwX0cPfgR1KxBZLIlggQR9CYSfpMsRLoL9Y1ADVV/rKHMStoipUywjOct0g7cA=")
- )
-
- runWith(attestationObjectFirefoxNightly)
-
- def runWith(attestationObject: ByteArray): Unit = {
- println(attestationObject)
- println(attestationObject.getHex)
-
- val parsedAttObj = new AttestationObject(attestationObject)
- println(parsedAttObj)
- println(parsedAttObj.getAuthenticatorData.getBytes.getHex)
- println(
- WebAuthnTestCodecs.importCosePublicKey(
- parsedAttObj.getAuthenticatorData.getAttestedCredentialData.get.getCredentialPublicKey
- )
- )
-
- val attestationObjectCbor =
- JacksonCodecs.cbor.readTree(attestationObject.getBytes)
- println(attestationObjectCbor)
- println(attestationObjectCbor.get("authData"))
-
- val authDataBytes: ByteArray = new ByteArray(
- attestationObjectCbor.get("authData").binaryValue
- )
- println(authDataBytes)
-
- doAuthData(authDataBytes)
-
- println("Manually extracted public key:")
- val manuallyExtractedPubKeyBytes = new ByteArray(
- attestationObject.getBytes.drop(32 + 1 + 4 + 16 + 2 + 64)
- )
- println(manuallyExtractedPubKeyBytes)
- println(JacksonCodecs.cbor.readTree(manuallyExtractedPubKeyBytes.getBytes))
-
- println("Attestation statement:")
- println(parsedAttObj.getAttestationStatement)
-
- if (parsedAttObj.getFormat == "android-safetynet") {
- println("Attestation statement \"response\" field:")
- println(
- parsedAttObj.getAttestationStatement
- .get("response")
- .binaryValue
- .toVector
- )
- val safetynetJwsCompact = new String(
- parsedAttObj.getAttestationStatement.get("response").binaryValue,
- "UTF-8",
- )
-
- println(safetynetJwsCompact)
-
- println(safetynetJwsCompact.split('.').toVector)
- val Array(jwsHeaderBase64, jwsPayloadBase64, jwsSigBase64) =
- safetynetJwsCompact.split('.')
-
- println(ByteArray.fromBase64Url(jwsHeaderBase64))
- println(
- prettifyJson(
- new String(ByteArray.fromBase64Url(jwsHeaderBase64).getBytes, "UTF-8")
- )
- )
- for {
- x5cNode: JsonNode <-
- JacksonCodecs
- .json()
- .readTree(ByteArray.fromBase64Url(jwsHeaderBase64).getBytes)
- .get("x5c")
- .elements()
- .asScala
- } {
- val x5cBytes = ByteArray.fromBase64(x5cNode.textValue())
- val cert = CertificateParser.parseDer(x5cBytes.getBytes)
- println(cert)
- }
-
- println(ByteArray.fromBase64Url(jwsPayloadBase64))
- println(
- prettifyJson(
- new String(
- ByteArray.fromBase64Url(jwsPayloadBase64).getBytes,
- "UTF-8",
- )
- )
- )
- println(ByteArray.fromBase64Url(jwsSigBase64))
- }
-
- println()
- println()
- }
-
- def prettifyJson(json: String): String =
- JacksonCodecs
- .json()
- .writerWithDefaultPrettyPrinter()
- .writeValueAsString(JacksonCodecs.json().readTree(json))
-
- def doAuthData(authDataBytes: ByteArray) = {
- val rpidBytes: Array[Byte] = authDataBytes.getBytes.slice(0, 32)
- val flagsByte: Byte = authDataBytes.getBytes()(32)
- val flags = new AuthenticatorDataFlags(flagsByte)
- val counterBytes: Array[Byte] =
- authDataBytes.getBytes.slice(32 + 1, 32 + 1 + 4)
- val attestedCredData: Array[Byte] = authDataBytes.getBytes.drop(32 + 1 + 4)
-
- println("Authenticator data:")
- println(s"RP ID: ${rpidBytes}")
- println(s"flags: ${flagsByte.toBinaryString}")
- println(s"flags: AT=${flags.AT}, ED=${flags.ED}")
- println(s"counter: ${BinaryUtil.getUint32(counterBytes)}")
-
- val aaguid = attestedCredData.slice(0, 16)
- val Lbytes = attestedCredData.slice(16, 16 + 2)
- val L = BinaryUtil.getUint16(Lbytes)
- val credentialIdBytes = attestedCredData.slice(16 + 2, 16 + 2 + L)
- val credentialPublicKeyBytes = attestedCredData.drop(16 + 2 + L)
- val credentialPublicKeyCbor =
- JacksonCodecs.cbor.readTree(credentialPublicKeyBytes)
- val credentialPublicKeyCbor2 =
- CBORObject.DecodeFromBytes(credentialPublicKeyBytes)
- val credentialPublicKeyDecoded: ECPublicKey = new OneKey(
- CBORObject.DecodeFromBytes(credentialPublicKeyBytes)
- ).AsPublicKey().asInstanceOf[ECPublicKey]
-
- println("Attested credential data:")
- println(s"AAGUID: ${aaguid}")
- println(s"Lbytes: ${Lbytes}")
- println(s"L: ${L}")
- println(s"credentialId: ${BinaryUtil.toHex(credentialIdBytes)}")
- println(
- s"credentialPublicKeyBytes: ${BinaryUtil.toHex(credentialPublicKeyBytes)}"
- )
- println(
- s"credentialPublicKeyBytes length: ${credentialPublicKeyBytes.length}"
- )
- println(s"credentialPublicKeyCbor: ${credentialPublicKeyCbor}")
- println(s"credentialPublicKeyCbor2: ${credentialPublicKeyCbor2}")
- println(s"credentialPublicKeyDecoded: ${credentialPublicKeyDecoded}")
- println(s"raw credential public key: ${WebAuthnTestCodecs.ecPublicKeyToRaw(credentialPublicKeyDecoded)}")
- println(s"raw credential public key: ${WebAuthnTestCodecs.ecPublicKeyToRaw(credentialPublicKeyDecoded).getHex}")
- println(s"raw credential public key length: ${WebAuthnTestCodecs.ecPublicKeyToRaw(credentialPublicKeyDecoded).getBytes.length}")
-
- // asn1dump(credentialPublicKeyBytes)
- }
-
- def asn1dump(bytes: ByteArray) = {
- val input = new ASN1InputStream(bytes.getBytes)
- var p: ASN1Primitive = null
- p = input.readObject()
- while (p != null) {
- println(ASN1Dump.dumpAsString(p))
- p = input.readObject()
- }
- }
-
- // doAuthData(authDataBytes)
- val cbormeAuthDataBytes =
- ByteArray.fromHex("94929A0AB38F2C27476B274DCDF4B2959598F8A57F3F3A2BA96DFD29BF69E66741000000000000000000000000000000000000000000406C329DEF96B758CFCA94C9BEA8D09F4CF65EC52D2E9723BD29F53C71D0B203B86F3B76817073EF53BBAA56D278DFC882124A27F891766417CEEFAE009C0E9DD4A5010203262001215820A8D0EE8BC30C5B179BE237AB5BD7F1652D536090E48C17D1C3DF811D4AC4164B225820411F426127E932C44BA0BF58D400D557FACA1CC4ADA22A54CB08CE72DD20EDC0")
- // val cbormeAuthDataBytes = ByteArray.fromHex("94929A0AB38F2C27476B274DCDF4B2959598F8A57F3F3A2BA96DFD29BF69E667410000000000000000000000000000000000000000004008B11EF66645D0E9D0384C4C27C1F5D1D017D7EB4E555736A5C6277B467D5574851EA6224F62A006CAE002BD564A31748134E248DE9B004773961ACFC06B67DBA363616C6765455332353661785820FB1122187A3687A8083D0BD853332D6141A7EF2A6CB5288B975BA02BB5AAE2AB61795820A959F5DB9D83C05294CBD7CA03F40FC150547BCFD221A1D7A185262537F63699")
- doAuthData(cbormeAuthDataBytes)
-
- // val parsedAuthData = AuthenticatorData(authDataBytes)
- // println(parsedAuthData)
- // println(parsedAuthData.attestedCredentialData)
- // println(parsedAuthData.attestedCredentialData.get)
- // println(parsedAuthData.attestedCredentialData.get.credentialPublicKey)
-
-}
diff --git a/webauthn-server-demo/build.gradle b/webauthn-server-demo/build.gradle
index 68979343f..d4a5d86fc 100644
--- a/webauthn-server-demo/build.gradle
+++ b/webauthn-server-demo/build.gradle
@@ -27,7 +27,7 @@ dependencies {
)
runtimeOnly(
- 'ch.qos.logback:logback-classic',
+ 'ch.qos.logback:logback-classic:[1.2.3,2)',
'org.glassfish.jersey.containers:jersey-container-servlet',
'org.glassfish.jersey.inject:jersey-hk2',
)
diff --git a/webauthn-server-demo/src/test/resources/logback-test.xml b/webauthn-server-demo/src/test/resources/logback-test.xml
deleted file mode 100644
index 6f078dd41..000000000
--- a/webauthn-server-demo/src/test/resources/logback-test.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
- %d{HH:mm:ss.SSSZ} [%thread] %-5level %logger{36} - %msg%n%rEx
-
-
-
-
-
-
-
-
-
-
diff --git a/yubico-util/build.gradle b/yubico-util/build.gradle
index a61c65bd9..807d3af16 100644
--- a/yubico-util/build.gradle
+++ b/yubico-util/build.gradle
@@ -35,14 +35,6 @@ dependencies {
'org.scalacheck:scalacheck_2.13',
'org.scalatest:scalatest_2.13',
)
-
- testRuntimeOnly(
- 'ch.qos.logback:logback-classic',
- )
-
- testRuntimeOnly(
- 'ch.qos.logback:logback-classic',
- )
}