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', - ) }