diff --git a/NEWS b/NEWS index f33bcf1b3..fa28acfb0 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,10 @@ +== Version 1.10.0 == + +webauthn-server-attestation: + +* Added attestation metadata for YubiKey Bio. + + == Version 1.9.1 == * Added missing `` declaration to diff --git a/README b/README index ecf096d2c..1f4145c3d 100644 --- a/README +++ b/README @@ -25,7 +25,7 @@ Maven: com.yubico webauthn-server-core - 1.9.1 + 1.10.0 compile ---------- @@ -33,7 +33,7 @@ Maven: Gradle: ---------- -compile 'com.yubico:webauthn-server-core:1.9.1' +compile 'com.yubico:webauthn-server-core:1.10.0' ---------- === Semantic versioning @@ -74,6 +74,8 @@ In addition to the main `webauthn-server-core` module, there are also: - Optionally integrates with a "metadata service" to verify https://www.w3.org/TR/webauthn/#sctn-attestation[authenticator attestations] and annotate responses with additional authenticator metadata +- Reproducible builds: release signatures match fresh builds from source. See + link:#Building[Building] below. === Non-features @@ -340,18 +342,25 @@ will have a plain `x.y.z` version number, while a build on any other commit will result in a version number containing the abbreviated commit hash. Starting in version `1.4.0-RC2`, artifacts are built reproducibly. Fresh builds from -tagged commits should therefore be verifiable by signatures from Maven Central: +tagged commits should therefore be verifiable by signatures from Maven Central +and GitHub releases: ``` $ git checkout 1.4.0-RC2 $ ./gradlew :webauthn-server-core:jar + $ wget https://repo1.maven.org/maven2/com/yubico/webauthn-server-core/1.4.0-RC2/webauthn-server-core-1.4.0-RC2.jar.asc $ gpg --verify webauthn-server-core-1.4.0-RC2.jar.asc webauthn-server-core/build/libs/webauthn-server-core-1.4.0-RC2.jar + +$ wget https://github.com/Yubico/java-webauthn-server/releases/download/1.4.0-RC2/webauthn-server-core-1.4.0-RC2.jar.asc +$ gpg --verify webauthn-server-core-1.4.0-RC2.jar.asc webauthn-server-core/build/libs/webauthn-server-core-1.4.0-RC2.jar ``` Note that building with a different JDK may produce a different artifact. To ensure binary reproducibility, please build with the same JDK as specified in -the release notes. +the release notes. Reproducible builds also require building from a Git +repository, since the build embeds version number and Git commit ID into the +built artifacts. Official Yubico software signing keys are listed on the https://developers.yubico.com/Software_Projects/Software_Signing.html[Yubico diff --git a/build.gradle b/build.gradle index 644988ef5..1694b064b 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:5.12.5' + classpath 'com.diffplug.spotless:spotless-plugin-gradle:5.14.1' classpath 'io.github.cosmicsilence:gradle-scalafix:0.1.8' } } @@ -100,8 +100,7 @@ subprojects { repositories { mavenLocal() - - maven { url "https://repo.maven.apache.org/maven2" } + mavenCentral() } spotless { @@ -220,7 +219,7 @@ subprojects { project -> } } - if (publishEnabled && project.hasProperty('publishMe') && project.publishMe) { + if (project.hasProperty('publishMe') && project.publishMe) { apply plugin: 'maven-publish' apply plugin: 'signing' @@ -267,56 +266,59 @@ subprojects { project -> } } - signing { - useGpgCmd() - sign publishing.publications.jars + if (publishEnabled) { + signing { + useGpgCmd() + sign publishing.publications.jars + } } } + } // The root project has no sources, but the dependency platform also needs to be published as an artifact // See https://docs.gradle.org/current/userguide/java_platform_plugin.html // See https://github.com/Yubico/java-webauthn-server/issues/93#issuecomment-822806951 -if (publishEnabled) { - apply plugin: 'maven-publish' - apply plugin: 'signing' - - publishing { - publications { - jars(MavenPublication) { - from components.javaPlatform - - pom { - name = project.name - description = project.description - url = 'https://developers.yubico.com/java-webauthn-server/' - - developers { - developer { - id = 'emil' - name = 'Emil Lundberg' - email = 'emil@yubico.com' - } +apply plugin: 'maven-publish' +apply plugin: 'signing' + +publishing { + publications { + jars(MavenPublication) { + from components.javaPlatform + + pom { + name = project.name + description = project.description + url = 'https://developers.yubico.com/java-webauthn-server/' + + developers { + developer { + id = 'emil' + name = 'Emil Lundberg' + email = 'emil@yubico.com' } + } - licenses { - license { - name = 'BSD-license' - comments = 'Revised 2-clause BSD license' - } + licenses { + license { + name = 'BSD-license' + comments = 'Revised 2-clause BSD license' } + } - scm { - url = 'scm:git:git://github.com/Yubico/java-webauthn-server.git' - connection = 'scm:git:git://github.com/Yubico/java-webauthn-server.git' - developerConnection = 'scm:git:ssh://git@github.com/Yubico/java-webauthn-server.git' - tag = 'HEAD' - } + scm { + url = 'scm:git:git://github.com/Yubico/java-webauthn-server.git' + connection = 'scm:git:git://github.com/Yubico/java-webauthn-server.git' + developerConnection = 'scm:git:ssh://git@github.com/Yubico/java-webauthn-server.git' + tag = 'HEAD' } } } } +} +if (publishEnabled) { signing { useGpgCmd() sign publishing.publications.jars diff --git a/webauthn-server-attestation/src/main/resources/metadata.json b/webauthn-server-attestation/src/main/resources/metadata.json index 58254075d..e3f709576 100755 --- a/webauthn-server-attestation/src/main/resources/metadata.json +++ b/webauthn-server-attestation/src/main/resources/metadata.json @@ -1,6 +1,6 @@ { "identifier": "2fb54029-7613-4f1d-94f1-fb876c14a6fe", - "version": 15, + "version": 16, "vendorInfo": { "url": "https://yubico.com", "imageUrl": "https://developers.yubico.com/U2F/Images/yubico.png", @@ -302,6 +302,24 @@ } } ] + }, + + { + "deviceId": "1.3.6.1.4.1.41482.1.9", + "displayName": "YubiKey Bio", + "transports": 4, + "selectors": [ + { + "type": "x509Extension", + "parameters": { + "key": "1.3.6.1.4.1.45724.1.1.4", + "value": { + "type": "hex", + "value": "d8522d9f575b486688a9ba99fa02f35b" + } + } + } + ] } ] } diff --git a/webauthn-server-attestation/src/test/scala/com/yubico/webauthn/attestation/DeviceIdentificationSpec.scala b/webauthn-server-attestation/src/test/scala/com/yubico/webauthn/attestation/DeviceIdentificationSpec.scala index 65ae59ca9..e3d517346 100644 --- a/webauthn-server-attestation/src/test/scala/com/yubico/webauthn/attestation/DeviceIdentificationSpec.scala +++ b/webauthn-server-attestation/src/test/scala/com/yubico/webauthn/attestation/DeviceIdentificationSpec.scala @@ -169,6 +169,14 @@ class DeviceIdentificationSpec extends FunSpec with Matchers { Set(USB, LIGHTNING), ) } + + it("a YubiKey Bio.") { + check( + "YubiKey Bio", + RealExamples.YubikeyBio_5_5_4, + Set(USB), + ) + } } describe("fails to identify") { @@ -293,6 +301,14 @@ class DeviceIdentificationSpec extends FunSpec with Matchers { Set(USB, LIGHTNING), ) } + + it("a YubiKey Bio.") { + check( + "YubiKey Bio", + RealExamples.YubikeyBio_5_5_4, + Set(USB), + ) + } } } diff --git a/webauthn-server-attestation/src/test/scala/com/yubico/webauthn/attestation/StandardMetadataServiceSpec.scala b/webauthn-server-attestation/src/test/scala/com/yubico/webauthn/attestation/StandardMetadataServiceSpec.scala index c47ae6a75..595b71fe2 100644 --- a/webauthn-server-attestation/src/test/scala/com/yubico/webauthn/attestation/StandardMetadataServiceSpec.scala +++ b/webauthn-server-attestation/src/test/scala/com/yubico/webauthn/attestation/StandardMetadataServiceSpec.scala @@ -24,7 +24,6 @@ package com.yubico.webauthn.attestation -import com.fasterxml.jackson.databind.node.JsonNodeFactory import com.yubico.internal.util.JacksonCodecs import com.yubico.internal.util.scala.JavaConverters._ import com.yubico.webauthn.TestAuthenticator @@ -46,8 +45,6 @@ import scala.jdk.CollectionConverters._ @RunWith(classOf[JUnitRunner]) class StandardMetadataServiceSpec extends FunSpec with Matchers { - private def jsonFactory: JsonNodeFactory = JsonNodeFactory.instance - private val TRANSPORTS_EXT_OID = "1.3.6.1.4.1.45724.2.1.1" private val ooidA = "1.3.6.1.4.1.41482.1.1" @@ -109,10 +106,6 @@ class StandardMetadataServiceSpec extends FunSpec with Matchers { caCertAndKey = Some((caCert, caKey)), extensions = List((ooidB, false, new DEROctetString(Array[Byte]()))), ) - val (unknownCert, _) = TestAuthenticator.generateAttestationCertificate( - name = new X500Name("CN=Unknown Cert"), - extensions = List((ooidA, false, new DEROctetString(Array[Byte]()))), - ) val metadataJson = s"""{ 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 521375b87..47c98e771 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 @@ -121,18 +121,18 @@ class RelyingPartyAssertionSpec } - private def getUserHandleIfDefault( + private def getUserHandleIfDefaultUsername( username: String, - userHandle: ByteArray = Defaults.userHandle, + userHandle: ByteArray, ): Optional[ByteArray] = if (username == Defaults.username) Some(userHandle).asJava else ??? - private def getUsernameIfDefault( + private def getUsernameIfDefaultUserHandle( userHandle: ByteArray, - username: String = Defaults.username, + username: String, ): Optional[String] = if (userHandle == Defaults.userHandle) Some(username).asJava @@ -242,10 +242,16 @@ class RelyingPartyAssertionSpec override def getCredentialIdsForUsername(username: String) = ??? override def getUserHandleForUsername(username: String) : Optional[ByteArray] = - getUserHandleIfDefault(username, userHandle = userHandleForUser) + getUserHandleIfDefaultUsername( + username, + userHandle = userHandleForUser, + ) override def getUsernameForUserHandle(userHandle: ByteArray) : Optional[String] = - getUsernameIfDefault(userHandle, username = usernameForUser) + getUsernameIfDefaultUserHandle( + userHandle, + username = usernameForUser, + ) } ) .preferredPubkeyParams(Nil.asJava) diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyCeremoniesSpec.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyCeremoniesSpec.scala index 3415dc688..b9143290d 100644 --- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyCeremoniesSpec.scala +++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyCeremoniesSpec.scala @@ -24,9 +24,7 @@ package com.yubico.webauthn -import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions import com.yubico.webauthn.data.PublicKeyCredentialDescriptor -import com.yubico.webauthn.data.PublicKeyCredentialParameters import com.yubico.webauthn.data.PublicKeyCredentialRequestOptions import com.yubico.webauthn.test.Helpers import com.yubico.webauthn.test.RealExamples @@ -53,81 +51,83 @@ class RelyingPartyCeremoniesSpec .credentialRepository(credentialRepo) .build() - testWithEachProvider { it => - describe("The default RelyingParty settings") { + private def createCheck( + modRp: RelyingParty => RelyingParty = identity + )(testData: RealExamples.Example): Unit = { + val registrationRp = + modRp(newRp(testData, Helpers.CredentialRepository.empty)) - describe("can register and then authenticate") { - def check(testData: RealExamples.Example): Unit = { - val registrationRp = - newRp(testData, Helpers.CredentialRepository.empty) - - val registrationResult = registrationRp.finishRegistration( - FinishRegistrationOptions - .builder() - .request( - PublicKeyCredentialCreationOptions - .builder() - .rp(testData.rp) - .user(testData.user) - .challenge(testData.attestation.challenge) - .pubKeyCredParams( - List(PublicKeyCredentialParameters.ES256).asJava - ) - .build() - ) - .response(testData.attestation.credential) - .build() - ); - - registrationResult.getKeyId.getId should equal( - testData.attestation.credential.getId - ) - registrationResult.isAttestationTrusted should be(false) - registrationResult.getAttestationMetadata.isPresent should be(false) - - val assertionRp = newRp( - testData, - Helpers.CredentialRepository.withUser( - testData.user, - Helpers.toRegisteredCredential(testData.user, registrationResult), - ), - ) - - val assertionResult = assertionRp.finishAssertion( - FinishAssertionOptions - .builder() - .request( - AssertionRequest - .builder() - .publicKeyCredentialRequestOptions( - PublicKeyCredentialRequestOptions + val registrationRequest = registrationRp + .startRegistration( + StartRegistrationOptions.builder().user(testData.user).build() + ) + .toBuilder + .challenge(testData.attestation.challenge) + .build() + val registrationResult = registrationRp.finishRegistration( + FinishRegistrationOptions + .builder() + .request(registrationRequest) + .response(testData.attestation.credential) + .build() + ); + + registrationResult.getKeyId.getId should equal( + testData.attestation.credential.getId + ) + registrationResult.isAttestationTrusted should be(false) + registrationResult.getAttestationMetadata.isPresent should be(false) + + val assertionRp = newRp( + testData, + Helpers.CredentialRepository.withUser( + testData.user, + Helpers.toRegisteredCredential(testData.user, registrationResult), + ), + ).toBuilder + .allowUnrequestedExtensions(true) + .build() + + val assertionResult = assertionRp.finishAssertion( + FinishAssertionOptions + .builder() + .request( + AssertionRequest + .builder() + .publicKeyCredentialRequestOptions( + PublicKeyCredentialRequestOptions + .builder() + .challenge(testData.assertion.challenge) + .allowCredentials( + List( + PublicKeyCredentialDescriptor .builder() - .challenge(testData.assertion.challenge) - .allowCredentials( - List( - PublicKeyCredentialDescriptor - .builder() - .id(testData.assertion.id) - .build() - ).asJava - ) + .id(testData.assertion.id) .build() - ) - .username(testData.user.getName) - .build() - ) - .response(testData.assertion.credential) - .build() - ) - - assertionResult.isSuccess should be(true) - assertionResult.getCredentialId should equal(testData.assertion.id) - assertionResult.getUserHandle should equal(testData.user.getId) - assertionResult.getUsername should equal(testData.user.getName) - assertionResult.getSignatureCount should be >= testData.attestation.authenticatorData.getSignatureCounter - assertionResult.isSignatureCounterValid should be(true) - } + ).asJava + ) + .build() + ) + .username(testData.user.getName) + .build() + ) + .response(testData.assertion.credential) + .build() + ) + + assertionResult.isSuccess should be(true) + assertionResult.getCredentialId should equal(testData.assertion.id) + assertionResult.getUserHandle should equal(testData.user.getId) + assertionResult.getUsername should equal(testData.user.getName) + assertionResult.getSignatureCount should be >= testData.attestation.authenticatorData.getSignatureCounter + assertionResult.isSignatureCounterValid should be(true) + } + testWithEachProvider { it => + describe("The default RelyingParty settings") { + val check = createCheck()(_) + + describe("can register and then authenticate") { it("a YubiKey NEO.") { check(RealExamples.YubiKeyNeo) } @@ -161,6 +161,37 @@ class RelyingPartyCeremoniesSpec it("a Security Key NFC by Yubico.") { check(RealExamples.SecurityKeyNfc) } + + ignore("a YubiKey 5 NFC FIPS.") { // TODO Un-ignore when allowUnrequestedExtensions default changes to true + check(RealExamples.YubikeyFips5Nfc) + } + + it("a YubiKey 5Ci FIPS.") { + check(RealExamples.Yubikey5ciFips) + } + it("a YubiKey Bio.") { + check(RealExamples.YubikeyBio_5_5_4) + } + + it("an Apple iOS device.") { + check(RealExamples.AppleAttestationIos) + } + it("an Apple MacOS device.") { + check(RealExamples.AppleAttestationMacos) + } + } + } + + describe("The default RelyingParty settings, but with allowUnrequestedExtensions(true)") { + + describe("can register and then authenticate") { + val check = createCheck(rp => + rp.toBuilder.allowUnrequestedExtensions(true).build() + )(_) + + it("a YubiKey 5 NFC FIPS.") { // TODO Delete when allowUnrequestedExtensions default changes to true + check(RealExamples.YubikeyFips5Nfc) + } } } } 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 64cf95e8f..16ecfb791 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 @@ -102,7 +102,6 @@ class RelyingPartyRegistrationSpec allowUnrequestedExtensions: Boolean = false, allowUntrustedAttestation: Boolean = false, callerTokenBindingId: Option[ByteArray] = None, - credentialId: Option[ByteArray] = None, credentialRepository: CredentialRepository = Helpers.CredentialRepository.unimplemented, metadataService: Option[MetadataService] = None, @@ -1165,10 +1164,7 @@ class RelyingPartyRegistrationSpec val testData = RegistrationTestData.FidoU2f.BasicAttestation .editAuthenticatorData { flipByte(index, _) } - val steps = finishRegistration( - testData = testData, - credentialId = Some(new ByteArray(Array.fill(16)(0))), - ) + val steps = finishRegistration(testData = testData) val step: FinishRegistrationSteps#Step14 = new steps.Step14( Crypto.sha256(testData.clientDataJsonBytes), new AttestationObject(testData.attestationObject), @@ -1214,10 +1210,7 @@ class RelyingPartyRegistrationSpec ) ++ evilPublicKey.getBytes ) } - val steps = finishRegistration( - testData = testData, - credentialId = Some(new ByteArray(Array.fill(16)(0))), - ) + val steps = finishRegistration(testData = testData) val step: FinishRegistrationSteps#Step14 = new steps.Step14( Crypto.sha256(testData.clientDataJsonBytes), new AttestationObject(testData.attestationObject), @@ -1259,8 +1252,7 @@ class RelyingPartyRegistrationSpec credential.getResponse.getClientDataJSON.getBytes, "UTF-8", ), - ), - credentialId = Some(credential.getId), + ) ) val step: FinishRegistrationSteps#Step14 = steps.begin.next.next.next.next.next.next.next.next.next.next.next.next.next @@ -1311,8 +1303,7 @@ class RelyingPartyRegistrationSpec credential.getResponse.getClientDataJSON.getBytes, "UTF-8", ), - ), - credentialId = Some(credential.getId), + ) ) val step: FinishRegistrationSteps#Step14 = steps.begin.next.next.next.next.next.next.next.next.next.next.next.next.next @@ -1998,14 +1989,6 @@ class RelyingPartyRegistrationSpec IllegalArgumentException ] - val goodCert: X509Certificate = TestAuthenticator - .generateAttestationCertificate( - name = new X500Name( - "O=Yubico, C=SE, OU=Authenticator Attestation" - ), - extensions = Nil, - ) - ._1 val goodResult = Try( verifier.verifyX5cRequirements(badCert, testDataBase.aaguid) ) diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyUserIdentificationSpec.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyUserIdentificationSpec.scala index 91668eef8..be0863d8a 100644 --- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyUserIdentificationSpec.scala +++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyUserIdentificationSpec.scala @@ -24,7 +24,6 @@ package com.yubico.webauthn -import com.fasterxml.jackson.databind.node.JsonNodeFactory import com.fasterxml.jackson.databind.node.ObjectNode import com.yubico.internal.util.scala.JavaConverters._ import com.yubico.webauthn.data.AuthenticatorAssertionResponse @@ -50,8 +49,6 @@ import scala.util.Try @RunWith(classOf[JUnitRunner]) class RelyingPartyUserIdentificationSpec extends FunSpec with Matchers { - private def jsonFactory: JsonNodeFactory = JsonNodeFactory.instance - private object Defaults { val rpId = 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 0c6ec2d39..8b6572c83 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 @@ -25,7 +25,6 @@ package com.yubico.webauthn import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.JsonNodeFactory import com.fasterxml.jackson.databind.node.ObjectNode import com.yubico.internal.util.BinaryUtil @@ -41,7 +40,6 @@ import com.yubico.webauthn.data.ClientAssertionExtensionOutputs import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs import com.yubico.webauthn.data.PublicKeyCredential import com.yubico.webauthn.data.PublicKeyCredentialRequestOptions -import com.yubico.webauthn.data.UserIdentity import com.yubico.webauthn.test.Util import org.bouncycastle.asn1.ASN1ObjectIdentifier import org.bouncycastle.asn1.ASN1Primitive @@ -171,8 +169,6 @@ object TestAuthenticator { private def jsonFactory: JsonNodeFactory = JsonNodeFactory.instance private def toBytes(s: String): ByteArray = new ByteArray(s.getBytes("UTF-8")) - private def toJson(node: JsonNode): String = - new ObjectMapper().writeValueAsString(node) private def sha256(s: String): ByteArray = sha256(toBytes(s)) private def sha256(b: ByteArray): ByteArray = new ByteArray(MessageDigest.getInstance("SHA-256").digest(b.getBytes)) @@ -348,7 +344,7 @@ object TestAuthenticator { """.stripMargin def makeAssertionExample(alg: COSEAlgorithmIdentifier): String = { - val (credential, keypair) = + val (_, keypair) = createCredential(attestationMaker = AttestationMaker.default()) val assertion = createAssertion(alg, credentialKey = keypair) @@ -383,15 +379,8 @@ object TestAuthenticator { credentialKeypair: Option[KeyPair] = None, keyAlgorithm: COSEAlgorithmIdentifier = Defaults.keyAlgorithm, origin: String = Defaults.origin, - rpId: String = Defaults.rpId, tokenBindingStatus: String = Defaults.TokenBinding.status, tokenBindingId: Option[String] = Defaults.TokenBinding.id, - userId: UserIdentity = UserIdentity - .builder() - .name("Test") - .displayName("Test") - .id(new ByteArray(Array(42, 13, 37))) - .build(), ): ( data.PublicKeyCredential[ data.AuthenticatorAttestationResponse, @@ -452,7 +441,6 @@ object TestAuthenticator { makeAttestedCredentialDataBytes( aaguid = aaguid, publicKeyCose = publicKeyCose, - rpId = Defaults.rpId, ) ), ) @@ -544,7 +532,6 @@ object TestAuthenticator { credentialId = testData.response.getId, credentialKey = testData.keypair.get, origin = origin, - rpId = testData.rpId.getId, tokenBindingStatus = tokenBindingStatus, tokenBindingId = tokenBindingId, userHandle = if (withUserHandle) Some(testData.userId.getId) else None, @@ -561,7 +548,6 @@ object TestAuthenticator { credentialId: ByteArray = Defaults.credentialId, credentialKey: KeyPair = Defaults.credentialKey, origin: String = Defaults.origin, - rpId: String = Defaults.rpId, tokenBindingStatus: String = Defaults.TokenBinding.status, tokenBindingId: Option[String] = Defaults.TokenBinding.id, userHandle: Option[ByteArray] = None, @@ -882,8 +868,6 @@ object TestAuthenticator { def makeAttestedCredentialDataBytes( publicKeyCose: ByteArray, - rpId: String = Defaults.rpId, - counterBytes: ByteArray = ByteArray.fromHex("0539"), aaguid: ByteArray = Defaults.aaguid, ): ByteArray = { val credentialId = sha256(publicKeyCose) @@ -1083,7 +1067,7 @@ object TestAuthenticator { signingKey: PrivateKey, signingAlg: COSEAlgorithmIdentifier, isCa: Boolean = false, - extensions: Iterable[(String, Boolean, ASN1Primitive)] = Nil, + extensions: Iterable[(String, Boolean, ASN1Primitive)], ): X509Certificate = { CertificateParser.parseDer({ val builder = new X509v3CertificateBuilder( diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/Generators.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/Generators.scala index 618eff093..eafa70f4d 100644 --- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/Generators.scala +++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/Generators.scala @@ -157,7 +157,6 @@ object Generators { def fidoU2fAttestationObject: Gen[ByteArray] = for { authData <- authenticatorDataBytes - alg <- arbitrary[COSEAlgorithmIdentifier] sig <- arbitrary[ByteArray] x5c <- arbitrary[List[ByteArray]] attStmt = @@ -506,7 +505,6 @@ object Generators { name <- arbitrary[String] icon <- arbitrary[Optional[URL]] id <- arbitrary[ByteArray] - name <- arbitrary[String] } yield UserIdentity .builder() .name(name) diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/test/RealExamples.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/test/RealExamples.scala index 766ecf416..2fbd1ce07 100644 --- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/test/RealExamples.scala +++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/test/RealExamples.scala @@ -31,6 +31,9 @@ sealed trait HasClientData { object RealExamples { + private def base64ToString(b64: String): String = + new String(ByteArray.fromBase64(b64).getBytes, StandardCharsets.UTF_8) + case class AttestationExample( clientData: String, attestationObjectBytes: ByteArray, @@ -391,26 +394,13 @@ object RealExamples { ) .build(), AttestationExample( - new String( - ByteArray - .fromBase64( - "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiUUs2c25Jak40MGNNZG9oNlUtR3NEZnlFYzlQY3pKdEgtSTczM3daSDRIZyIsIm9yaWdpbiI6Imh0dHBzOi8vZGVtby55dWJpY28uY29tIn0=" - ) - .getBytes, - StandardCharsets.UTF_8, - ), + base64ToString("eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiUUs2c25Jak40MGNNZG9oNlUtR3NEZnlFYzlQY3pKdEgtSTczM3daSDRIZyIsIm9yaWdpbiI6Imh0dHBzOi8vZGVtby55dWJpY28uY29tIn0="), ByteArray.fromBase64("o2NmbXRlYXBwbGVnYXR0U3RtdKFjeDVjglkCRjCCAkIwggHJoAMCAQICBgF4xhYQszAKBggqhkjOPQQDAjBIMRwwGgYDVQQDDBNBcHBsZSBXZWJBdXRobiBDQSAxMRMwEQYDVQQKDApBcHBsZSBJbmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMB4XDTIxMDQxMTEyMzcxOFoXDTIxMDQxNDEyMzcxOFowgZExSTBHBgNVBAMMQDMxYzRlOTM2YzgwZjY1Y2VjNzcxZWZkOGNhNWMxNDdlZTgxZjY4ZjVhODE5YTUzNDFiMDU5NmJkYmU4YWI0OTExGjAYBgNVBAsMEUFBQSBDZXJ0aWZpY2F0aW9uMRMwEQYDVQQKDApBcHBsZSBJbmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYc87v7q19IYjqS3vizLAet/NcW0NVpYRvzvZFfCT00nBR0rzITI4iuuBupVtSRFhZfHa3GhYUu/w3Mo2h3s/+qNVMFMwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBPAwMwYJKoZIhvdjZAgCBCYwJKEiBCC+B6u5EUpszNBikhFRpOuBolX7jPReSqGkIvBr0orEZDAKBggqhkjOPQQDAgNnADBkAjAZpK9Vw3hR3uCca+kUAorfR4Sj/HkCcmydzm/KuewaYC5lmIwRTw9SKEVmAAITRlUCMEC9P/ksVc5DUHtKt+rQ9mXHeobdGymHSM7xZtYMNOfze8hPo5HLnwtWCB5qF8MQRVkCODCCAjQwggG6oAMCAQICEFYlU5XHp/tA6+Io2CYIU7YwCgYIKoZIzj0EAwMwSzEfMB0GA1UEAwwWQXBwbGUgV2ViQXV0aG4gUm9vdCBDQTETMBEGA1UECgwKQXBwbGUgSW5jLjETMBEGA1UECAwKQ2FsaWZvcm5pYTAeFw0yMDAzMTgxODM4MDFaFw0zMDAzMTMwMDAwMDBaMEgxHDAaBgNVBAMME0FwcGxlIFdlYkF1dGhuIENBIDExEzARBgNVBAoMCkFwcGxlIEluYy4xEzARBgNVBAgMCkNhbGlmb3JuaWEwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASDLocvJhSRgQIlufX81rtjeLX1Xz/LBFvHNZk0df1UkETfm/4ZIRdlxpod2gULONRQg0AaQ0+yTREtVsPhz7/LmJH+wGlggb75bLx3yI3dr0alruHdUVta+quTvpwLJpGjZjBkMBIGA1UdEwEB/wQIMAYBAf8CAQAwHwYDVR0jBBgwFoAUJtdk2cV4wlpn0afeaxLQG2PxxtcwHQYDVR0OBBYEFOuugsT/oaxbUdTPJGEFAL5jvXeIMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjEA3YsaNIGl+tnbtOdle4QeFEwnt1uHakGGwrFHV1Azcifv5VRFfvZIlQxjLlxIPnDBAjAsimBE3CAfz+Wbw00pMMFIeFHZYO1qdfHrSsq+OM0luJfQyAW+8Mf3iwelccboDgdoYXV0aERhdGFYmMRs74KtG1Rkd1kdAIsIdZ7D5tLstPOUdL/qaWmSXQO3RQAAAAAAAAAAAAAAAAAAAAAAAAAAABRK0rg7vzmd/BAatDNkXX6aBhPZSaUBAgMmIAEhWCBhzzu/urX0hiOpLe+LMsB6381xbQ1WlhG/O9kV8JPTSSJYIMFHSvMhMjiK64G6lW1JEWFl8drcaFhS7/DcyjaHez/6"), ), AssertionExample( id = ByteArray.fromBase64Url("StK4O785nfwQGrQzZF1-mgYT2Uk"), - clientData = new String( - ByteArray - .fromBase64( - "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoid2V5TG9keXVzUl96SWtPWUg3bTVUYjBreGViQnEtV2QzYVJreUhMeHl0SSIsIm9yaWdpbiI6Imh0dHBzOi8vZGVtby55dWJpY28uY29tIn0=" - ) - .getBytes, - StandardCharsets.UTF_8, - ), + clientData = + base64ToString("eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoid2V5TG9keXVzUl96SWtPWUg3bTVUYjBreGViQnEtV2QzYVJreUhMeHl0SSIsIm9yaWdpbiI6Imh0dHBzOi8vZGVtby55dWJpY28uY29tIn0="), authDataBytes = ByteArray.fromBase64( "xGzvgq0bVGR3WR0Aiwh1nsPm0uy085R0v+ppaZJdA7cFAAAAAA==" ), @@ -432,26 +422,13 @@ object RealExamples { .id(ByteArray.fromBase64("+8eKyPo9MGrhWx8Y7ZeoczjaS5mbRr2kqF7/zllIgZ8=")) .build(), AttestationExample( - new String( - ByteArray - .fromBase64( - "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoicWszNE1GRVA4dWxXaHVpOEpncmt0ZVE5RXhIV2NKYndJcjNDUm1lVGtqZyIsIm9yaWdpbiI6Imh0dHBzOi8vZGVtby55dWJpY28uY29tIn0=" - ) - .getBytes, - StandardCharsets.UTF_8, - ), + base64ToString("eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoicWszNE1GRVA4dWxXaHVpOEpncmt0ZVE5RXhIV2NKYndJcjNDUm1lVGtqZyIsIm9yaWdpbiI6Imh0dHBzOi8vZGVtby55dWJpY28uY29tIn0="), ByteArray.fromBase64("o2NmbXRlYXBwbGVnYXR0U3RtdKFjeDVjglkCRjCCAkIwggHJoAMCAQICBgF4xjGSqDAKBggqhkjOPQQDAjBIMRwwGgYDVQQDDBNBcHBsZSBXZWJBdXRobiBDQSAxMRMwEQYDVQQKDApBcHBsZSBJbmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMB4XDTIxMDQxMTEzMDcyMFoXDTIxMDQxNDEzMDcyMFowgZExSTBHBgNVBAMMQDYxYmQ5NzY4M2JlMTk0NTVjOGJjOWVhNDZhMjY4NzU0MzVjMmIwNmVlMTI4YzY4ZDFiMGE4NDczODkwNTgzMjYxGjAYBgNVBAsMEUFBQSBDZXJ0aWZpY2F0aW9uMRMwEQYDVQQKDApBcHBsZSBJbmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEdrvYDb+UbAjcbbommtRqw+2Lm1fvHG6ll1dOgeEM25H8ThQ0yj4R3hVbc/ean1I5eqc/RXDFm/jJI/Lmp1uEFqNVMFMwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBPAwMwYJKoZIhvdjZAgCBCYwJKEiBCAQ6ifyo7KWlR86ueS0JMAuIi66gYkJsX+VxAcvbtEEcTAKBggqhkjOPQQDAgNnADBkAjAIu8Vx1tdGHSarO63RF7QaUo3/Iuk1CXA2Z0YIbDG4mLS15JQ/AUwctOpePcZoDngCMFMfnXi6jlhNBmppj5/8VQz2Kbz5eNxg+dqALz59ctCqXkdCVLMhUOpHWgMhhOadj1kCODCCAjQwggG6oAMCAQICEFYlU5XHp/tA6+Io2CYIU7YwCgYIKoZIzj0EAwMwSzEfMB0GA1UEAwwWQXBwbGUgV2ViQXV0aG4gUm9vdCBDQTETMBEGA1UECgwKQXBwbGUgSW5jLjETMBEGA1UECAwKQ2FsaWZvcm5pYTAeFw0yMDAzMTgxODM4MDFaFw0zMDAzMTMwMDAwMDBaMEgxHDAaBgNVBAMME0FwcGxlIFdlYkF1dGhuIENBIDExEzARBgNVBAoMCkFwcGxlIEluYy4xEzARBgNVBAgMCkNhbGlmb3JuaWEwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASDLocvJhSRgQIlufX81rtjeLX1Xz/LBFvHNZk0df1UkETfm/4ZIRdlxpod2gULONRQg0AaQ0+yTREtVsPhz7/LmJH+wGlggb75bLx3yI3dr0alruHdUVta+quTvpwLJpGjZjBkMBIGA1UdEwEB/wQIMAYBAf8CAQAwHwYDVR0jBBgwFoAUJtdk2cV4wlpn0afeaxLQG2PxxtcwHQYDVR0OBBYEFOuugsT/oaxbUdTPJGEFAL5jvXeIMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjEA3YsaNIGl+tnbtOdle4QeFEwnt1uHakGGwrFHV1Azcifv5VRFfvZIlQxjLlxIPnDBAjAsimBE3CAfz+Wbw00pMMFIeFHZYO1qdfHrSsq+OM0luJfQyAW+8Mf3iwelccboDgdoYXV0aERhdGFYmMRs74KtG1Rkd1kdAIsIdZ7D5tLstPOUdL/qaWmSXQO3RQAAAAAAAAAAAAAAAAAAAAAAAAAAABRhYCgh40b6Uj1WdjckwPAdCwd4fKUBAgMmIAEhWCB2u9gNv5RsCNxtuiaa1GrD7YubV+8cbqWXV06B4QzbkSJYIPxOFDTKPhHeFVtz95qfUjl6pz9FcMWb+Mkj8uanW4QW"), ), AssertionExample( id = ByteArray.fromBase64Url("YWAoIeNG-lI9VnY3JMDwHQsHeHw"), - clientData = new String( - ByteArray - .fromBase64( - "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiVVdobmx5VTdlVzZBTEw1M1VPcENnU1N3ckEzNm92R3VpQUV6ZE91OFdTYyIsIm9yaWdpbiI6Imh0dHBzOi8vZGVtby55dWJpY28uY29tIn0=" - ) - .getBytes, - StandardCharsets.UTF_8, - ), + clientData = + base64ToString("eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiVVdobmx5VTdlVzZBTEw1M1VPcENnU1N3ckEzNm92R3VpQUV6ZE91OFdTYyIsIm9yaWdpbiI6Imh0dHBzOi8vZGVtby55dWJpY28uY29tIn0="), authDataBytes = ByteArray.fromBase64( "xGzvgq0bVGR3WR0Aiwh1nsPm0uy085R0v+ppaZJdA7cFAAAAAA==" ), @@ -500,7 +477,7 @@ object RealExamples { AssertionExample( id = ByteArray.fromBase64Url("qeNy9WGd6KRAq4aXf_xCgOrgjJoRH7Ve8KC7UJ3cpjDaFrv5egr5kJ7mBlrGiHlZ0OkD_Xtsd-lQTu_Ymr1crg"), - clientData = """{"type":"webauthn.get","challenge":"gJQG3mUBQv5rR7mwUuHbxQ","origin":"https://demo.yubico.com","crossOrigin":false"}""", + clientData = """{"type":"webauthn.get","challenge":"gJQG3mUBQv5rR7mwUuHbxQ","origin":"https://demo.yubico.com","crossOrigin":false}""", authDataBytes = ByteArray.fromBase64( "xGzvgq0bVGR3WR0Aiwh1nsPm0uy085R0v+ppaZJdA7cBAAAABQ==" ), @@ -509,4 +486,33 @@ object RealExamples { ), ) + val YubikeyBio_5_5_4 = Example( + RelyingPartyIdentity + .builder() + .id("demo.yubico.com") + .name("YubicoDemo") + .build(), + UserIdentity + .builder() + .name("Yubico demo user") + .displayName("Yubico demo user") + .id(ByteArray.fromBase64("n5iF3+LH/w9yfgIgEWdFL99YAD8PMpG41PEPzzV1RSc=")) + .build(), + AttestationExample( + base64ToString("eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoicnJIQmg3Q01yZElYTE0zMFBkOFZ1Ulg3TV9xVXl5VEpCWDRUN2xONUVRRSIsIm9yaWdpbiI6Imh0dHBzOi8vZGVtby55dWJpY28uY29tIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ=="), + ByteArray.fromBase64("o2NmbXRmcGFja2VkZ2F0dFN0bXSjY2FsZyZjc2lnWEYwRAIgGSqwfT67zXQVsgBU/TvN1MGbZkR5KEyzzMMbS9cJQJsCIDZJ90wxLjNnpzNZ+Ns64cmgwixb0CJcXdfVM35EBgm0Y3g1Y4FZAt0wggLZMIIBwaADAgECAgkAtcaOPpfL6PYwDQYJKoZIhvcNAQELBQAwLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMG8xCzAJBgNVBAYTAlNFMRIwEAYDVQQKDAlZdWJpY28gQUIxIjAgBgNVBAsMGUF1dGhlbnRpY2F0b3IgQXR0ZXN0YXRpb24xKDAmBgNVBAMMH1l1YmljbyBVMkYgRUUgU2VyaWFsIDEwNDk1NDQzNzMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQXmY7aJqXBb5wsBbCAeJFdFa3Fzz8VU1qdJxUCgPf2MNcoMnikaKg0yp/bakKjCNIqmsb75RhUzS5UQHwVOAe0o4GBMH8wEwYKKwYBBAGCxAoNAQQFBAMFBQQwIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjkwEwYLKwYBBAGC5RwCAQEEBAMCBSAwIQYLKwYBBAGC5RwBAQQEEgQQ2FItn1dbSGaIqbqZ+gLzWzAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQB9yNliBCNlTGBrhYTTqHJm73KjMszL24buZcvifix+GRYk7D8I0/BQ20mQ/CITqqGTr5cvxjIKVw/2ed+326hITlhaWiwwEnwuI5afqwd72ObWczklHNvoV+uWtM9YVfk9H7VZqtQTMb3m8O+UWmkCGxLdqTprgTUSF/Tmk6KPyF1S6es6RJvk9vxyJ0T/EFkr9yAPlDzqtc9hEAUEPP5xpzEWRYon6T12AUW6wQwlkiA8q4gFIQfTGks1JX6ob/1nTvigO3EYB4wP3EIAJ+0HkpprKKDl4mRv/7b/BPuANq4jMr/9YkYs8XrmuUHdF0PwRzYPN4KtovuG0YdFA0ObaGF1dGhEYXRhWMTEbO+CrRtUZHdZHQCLCHWew+bS7LTzlHS/6mlpkl0Dt0UAAAAB2FItn1dbSGaIqbqZ+gLzWwBAvPVBBCgvthNO8DNbim45zueAndDzuMAQDBXL/bVsH9uXfKkrza7ya2DM/xka1hYW+K2d97qNRJmoAeetc5haaKUBAgMmIAEhWCDqr+a3QuoQk4VqspgOfHlkS2Rk+NpsHL5Rs4rbxE2DQiJYICjaw5BRuZKz5CPZRjiDJFOq51wbOrUggICGmC88+ZXq"), + ), + AssertionExample( + id = + ByteArray.fromBase64Url("vPVBBCgvthNO8DNbim45zueAndDzuMAQDBXL_bVsH9uXfKkrza7ya2DM_xka1hYW-K2d97qNRJmoAeetc5haaA"), + clientData = + base64ToString("eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiOVVHcG1JOXdkM004dF9yUGFZMVRyWXd1LVVranRUdHV1N2RGcHlyNGtDOCIsIm9yaWdpbiI6Imh0dHBzOi8vZGVtby55dWJpY28uY29tIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ=="), + authDataBytes = ByteArray.fromBase64( + "xGzvgq0bVGR3WR0Aiwh1nsPm0uy085R0v+ppaZJdA7cFAAAABA==" + ), + sig = + ByteArray.fromBase64("MEUCIGM9xK+AHlLTv3mJLagZuNlLijI86T2SzkyAy3NidembAiEA6Y3I5GPYnRoHKil4R8yCSHUFZdgc59GO1KfsoHYhA3o="), + ), + ) + } diff --git a/webauthn-server-demo/src/test/scala/demo/webauthn/WebAuthnServerSpec.scala b/webauthn-server-demo/src/test/scala/demo/webauthn/WebAuthnServerSpec.scala index 46f8b28e1..27fa071ef 100644 --- a/webauthn-server-demo/src/test/scala/demo/webauthn/WebAuthnServerSpec.scala +++ b/webauthn-server-demo/src/test/scala/demo/webauthn/WebAuthnServerSpec.scala @@ -42,7 +42,6 @@ import com.yubico.webauthn.extension.appid.AppId import demo.webauthn.data.AssertionRequestWrapper import demo.webauthn.data.CredentialRegistration import demo.webauthn.data.RegistrationRequest -import demo.webauthn.data.RegistrationResponse import org.junit.runner.RunWith import org.scalatest.FunSpec import org.scalatest.Matchers @@ -95,12 +94,6 @@ class WebAuthnServerSpec extends FunSpec with Matchers { RegistrationTestData.FidoU2f.BasicAttestation ) - val response = new RegistrationResponse( - requestId, - RegistrationTestData.FidoU2f.BasicAttestation.response, - Optional.empty(), - ) - val authenticationAttestationResponseJson = """{"attestationObject":"v2hhdXRoRGF0YVikSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NBAAAFOQABAgMEBQYHCAkKCwwNDg8AIIjjhj6nH3qL2QF3tkUogilFykuaXjJTw35O4m-0NSX0pSJYIA5Nt8eYkLco-NQfKPXaA6dD9UfX_SHaYo-L-YQb78HsAyYBAiFYIOuzRl1o1Hem2jVRYhjkbSeIydhqLln9iltAgsDYjXRTIAFjZm10aGZpZG8tdTJmZ2F0dFN0bXS_Y3g1Y59ZAekwggHlMIIBjKADAgECAgIFOTAKBggqhkjOPQQDAjBqMSYwJAYDVQQDDB1ZdWJpY28gV2ViQXV0aG4gdW5pdCB0ZXN0cyBDQTEPMA0GA1UECgwGWXViaWNvMSIwIAYDVQQLDBlBdXRoZW50aWNhdG9yIEF0dGVzdGF0aW9uMQswCQYDVQQGEwJTRTAeFw0xODA5MDYxNzQyMDBaFw0xODA5MDYxNzQyMDBaMGcxIzAhBgNVBAMMGll1YmljbyBXZWJBdXRobiB1bml0IHRlc3RzMQ8wDQYDVQQKDAZZdWJpY28xIjAgBgNVBAsMGUF1dGhlbnRpY2F0b3IgQXR0ZXN0YXRpb24xCzAJBgNVBAYTAlNFMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJ-8bFED9TnFhaArujgB0foNaV4gQIulP1mC5DO1wvSByw4eOyXujpPHkTw9y5e5J2J3N9coSReZJgBRpvFzYD6MlMCMwIQYLKwYBBAGC5RwBAQQEEgQQAAECAwQFBgcICQoLDA0ODzAKBggqhkjOPQQDAgNHADBEAiB4bL25EH06vPBOVnReObXrS910ARVOLJPPnKNoZbe64gIgX1Rg5oydH45zEMEVDjNPStwv6Z3nE_isMeY-szlQhv3_Y3NpZ1hHMEUCIQDBs1nbSuuKQ6yoHMQoRp8eCT_HZvR45F_aVP6qFX_wKgIgMCL58bv-crkLwTwiEL9ibCV4nDYM-DZuW5_BFCJbcxn__w","clientDataJSON":"eyJjaGFsbGVuZ2UiOiJBQUVCQWdNRkNBMFZJamRaRUdsNVlscyIsIm9yaWdpbiI6ImxvY2FsaG9zdCIsInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUiLCJ0b2tlbkJpbmRpbmciOnsic3RhdHVzIjoic3VwcG9ydGVkIn19"}""" val publicKeyCredentialJson =