If parsed from JSON, this will be present if and only if the input was a valid value of + * {@link AuthenticatorAttachment}. + * + *
The same value will also be available via {@link
+ * RegistrationResult#getAuthenticatorAttachment()} or {@link
+ * AssertionResult#getAuthenticatorAttachment()} on the result from {@link
+ * RelyingParty#finishRegistration(FinishRegistrationOptions)} or {@link
+ * RelyingParty#finishAssertion(FinishAssertionOptions)}.
+ *
+ * @see RegistrationResult#getAuthenticatorAttachment()
+ * @see AssertionResult#getAuthenticatorAttachment()
+ * @deprecated EXPERIMENTAL: This feature is from a not yet mature standard; it could change as
+ * the standard matures.
+ */
+ @Deprecated
+ public Optional The main entry point is the {@link com.yubico.webauthn.RelyingParty} class. It provides
* methods for generating inputs to the After receiving the response from the client, construct a {@link
- * com.yubico.webauthn.data.PublicKeyCredential}<{@link
- * com.yubico.webauthn.data.AuthenticatorAttestationResponse}, {@link
- * com.yubico.webauthn.data.ClientRegistrationExtensionOutputs}> from the response and wrap that
- * in a {@link com.yubico.webauthn.FinishRegistrationOptions} along with the {@link
+ * toBuilder()} method to make any modifications you need, then the {@link
+ * com.yubico.webauthn.data.PublicKeyCredentialCreationOptions#toCredentialsCreateJson()} method is
+ * suitable for converting the value to JSON to send to the client.
+ *
+ * You should also store the {@link com.yubico.webauthn.data.PublicKeyCredentialCreationOptions}
+ * object in temporary storage so that it can later be passed as an argument to {@link
+ * com.yubico.webauthn.RelyingParty#finishRegistration(FinishRegistrationOptions)}. If you need to
+ * serialize the object for storage, the {@link
+ * com.yubico.webauthn.data.PublicKeyCredentialCreationOptions#toJson()} and {@link
+ * com.yubico.webauthn.data.PublicKeyCredentialCreationOptions#fromJson(java.lang.String)} methods
+ * are suitable for serializing to and from a string value.
+ *
+ * After receiving the response from the client, use the {@link
+ * com.yubico.webauthn.data.PublicKeyCredential#parseRegistrationResponseJson(java.lang.String)}
+ * function to parse the response and wrap it in a {@link
+ * com.yubico.webauthn.FinishRegistrationOptions} along with the {@link
* com.yubico.webauthn.data.PublicKeyCredentialCreationOptions} used to initiate the request. Pass
* that as the argument to {@link
* com.yubico.webauthn.RelyingParty#finishRegistration(FinishRegistrationOptions)}, which will
@@ -107,14 +114,19 @@
* com.yubico.webauthn.RegistrationResult#getPublicKeyCose() publicKeyCose} as a new
* credential for the user. The {@link com.yubico.webauthn.CredentialRepository} will need to
* look these up for authentication.
+ * After receiving the response from the client, construct a {@link
- * com.yubico.webauthn.data.PublicKeyCredential}<{@link
- * com.yubico.webauthn.data.AuthenticatorAssertionResponse}, {@link
- * com.yubico.webauthn.data.ClientAssertionExtensionOutputs}> from the response and wrap that in
- * a {@link com.yubico.webauthn.FinishAssertionOptions} along with the {@link
- * com.yubico.webauthn.AssertionRequest} used to initiate the request. Pass that as the argument to
- * {@link
+ * Again, {@link com.yubico.webauthn.AssertionRequest#toJson()} and {@link
+ * com.yubico.webauthn.AssertionRequest#fromJson(java.lang.String)} can be used to convert to and
+ * from JSON for storage.
+ *
+ * After receiving the response from the client, use {@link
+ * com.yubico.webauthn.data.PublicKeyCredential#parseAssertionResponseJson(java.lang.String)} to
+ * parse the response, then wrap that in a {@link com.yubico.webauthn.FinishAssertionOptions} along
+ * with the {@link com.yubico.webauthn.AssertionRequest} used to initiate the request. Pass that as
+ * the argument to {@link
* com.yubico.webauthn.RelyingParty#finishAssertion(com.yubico.webauthn.FinishAssertionOptions)},
* which will return an {@link com.yubico.webauthn.AssertionResult} if successful and throw an
* exception if not. Regardless of whether it succeeds, you should remove the {@link
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 8f9e309c7..f817ad746 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
@@ -31,6 +31,7 @@ import com.upokecenter.cbor.CBORObject
import com.yubico.internal.util.JacksonCodecs
import com.yubico.webauthn.data.AssertionExtensionInputs
import com.yubico.webauthn.data.AuthenticatorAssertionResponse
+import com.yubico.webauthn.data.AuthenticatorAttachment
import com.yubico.webauthn.data.AuthenticatorDataFlags
import com.yubico.webauthn.data.AuthenticatorTransport
import com.yubico.webauthn.data.ByteArray
@@ -2592,6 +2593,36 @@ class RelyingPartyAssertionSpec
resultWithBeOnly.isBackedUp should be(false)
resultWithBackup.isBackedUp should be(true)
}
+
+ it(
+ "exposes getAuthenticatorAttachment() with the authenticatorAttachment value from the PublicKeyCredential."
+ ) {
+ val pkcTemplate =
+ TestAuthenticator.createAssertion(
+ challenge =
+ request.getPublicKeyCredentialRequestOptions.getChallenge,
+ credentialKey = credentialKeypair,
+ credentialId = credential.getId,
+ )
+
+ forAll { authenticatorAttachment: Option[AuthenticatorAttachment] =>
+ val pkc = pkcTemplate.toBuilder
+ .authenticatorAttachment(authenticatorAttachment.orNull)
+ .build()
+
+ val result = rp.finishAssertion(
+ FinishAssertionOptions
+ .builder()
+ .request(request)
+ .response(pkc)
+ .build()
+ )
+
+ result.getAuthenticatorAttachment should equal(
+ pkc.getAuthenticatorAttachment
+ )
+ }
+ }
}
}
}
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 983cd281d..a737f23b4 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
@@ -42,6 +42,7 @@ import com.yubico.webauthn.attestation.AttestationTrustSource
import com.yubico.webauthn.attestation.AttestationTrustSource.TrustRootsResult
import com.yubico.webauthn.data.AttestationObject
import com.yubico.webauthn.data.AttestationType
+import com.yubico.webauthn.data.AuthenticatorAttachment
import com.yubico.webauthn.data.AuthenticatorAttestationResponse
import com.yubico.webauthn.data.AuthenticatorData
import com.yubico.webauthn.data.AuthenticatorDataFlags
@@ -4619,6 +4620,33 @@ class RelyingPartyRegistrationSpec
resultWithBeOnly.isBackedUp should be(false)
resultWithBackup.isBackedUp should be(true)
}
+
+ it(
+ "exposes getAuthenticatorAttachment() with the authenticatorAttachment value from the PublicKeyCredential."
+ ) {
+ val (pkcTemplate, _, _) =
+ TestAuthenticator.createUnattestedCredential(challenge =
+ request.getChallenge
+ )
+
+ forAll { authenticatorAttachment: Option[AuthenticatorAttachment] =>
+ val pkc = pkcTemplate.toBuilder
+ .authenticatorAttachment(authenticatorAttachment.orNull)
+ .build()
+
+ val result = rp.finishRegistration(
+ FinishRegistrationOptions
+ .builder()
+ .request(request)
+ .response(pkc)
+ .build()
+ )
+
+ result.getAuthenticatorAttachment should equal(
+ pkc.getAuthenticatorAttachment
+ )
+ }
+ }
}
}
diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/EnumsSpec.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/EnumsSpec.scala
index 8e35e8558..dbe5ca609 100644
--- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/EnumsSpec.scala
+++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/EnumsSpec.scala
@@ -61,11 +61,10 @@ class EnumsSpec
describe("AuthenticatorAttachment") {
describe("can be parsed from JSON") {
- it("but throws IllegalArgumentException for unknown values.") {
- val result = Try(
+ it("and ignores for unknown values.") {
+ val result =
json.readValue("\"foo\"", classOf[AuthenticatorAttachment])
- )
- result.failed.get.getCause shouldBe an[IllegalArgumentException]
+ result should be(null)
}
}
}
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 29cfa491f..0fd026f9b 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
@@ -41,12 +41,13 @@ 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.AnyFunSpec
import org.scalatest.matchers.should.Matchers
import org.scalatestplus.junit.JUnitRunner
import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks
+import scala.jdk.OptionConverters.RichOptional
+
@RunWith(classOf[JUnitRunner])
class JsonIoSpec
extends AnyFunSpec
@@ -351,15 +352,16 @@ class JsonIoSpec
)
}
- it("allows and ignores an authenticatorAttachment attribute.") {
+ it(
+ "allows an authenticatorAttachment attribute, but ignores unknown values."
+ ) {
def test[P <: PublicKeyCredential[_, _]](tpe: TypeReference[P])(implicit
a: Arbitrary[P]
): Unit = {
forAll(
a.arbitrary,
- Gen.oneOf(
- arbitrary[AuthenticatorAttachment].map(_.getValue),
- arbitrary[String],
+ arbitrary[String].suchThat(s =>
+ !AuthenticatorAttachment.values.map(_.getValue).contains(s)
),
) { (value: P, authenticatorAttachment: String) =>
val tree: ObjectNode = json.valueToTree(value)
@@ -370,8 +372,37 @@ class JsonIoSpec
val encoded = json.writeValueAsString(tree)
println(authenticatorAttachment)
val decoded = json.readValue(encoded, tpe)
+ decoded.getAuthenticatorAttachment.asScala should be(None)
+ }
+
+ forAll(
+ a.arbitrary,
+ arbitrary[AuthenticatorAttachment],
+ ) { (value: P, authenticatorAttachment: AuthenticatorAttachment) =>
+ val tree: ObjectNode = json.valueToTree(value)
+ tree.set(
+ "authenticatorAttachment",
+ new TextNode(authenticatorAttachment.getValue),
+ )
+ val encoded = json.writeValueAsString(tree)
+ println(authenticatorAttachment)
+ val decoded = json.readValue(encoded, tpe)
+
+ decoded.getAuthenticatorAttachment.asScala should equal(
+ Some(authenticatorAttachment)
+ )
+ }
+
+ forAll(
+ a.arbitrary
+ ) { (value: P) =>
+ val tree: ObjectNode = json.valueToTree(
+ value.toBuilder.authenticatorAttachment(null).build()
+ )
+ val encoded = json.writeValueAsString(tree)
+ val decoded = json.readValue(encoded, tpe)
- decoded should equal(value)
+ decoded.getAuthenticatorAttachment.asScala should be(None)
}
}
diff --git a/webauthn-server-demo/README b/webauthn-server-demo/README
index c38cc074a..06bc0e86c 100644
--- a/webauthn-server-demo/README
+++ b/webauthn-server-demo/README
@@ -40,7 +40,7 @@ layer.
This layer manages the general architecture of the system, and is where most
business logic and integration code would go. The demo server implements the
"persistent" storage of users and credential registrations - the
-link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`]
+link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`]
integration point - as the
link:src/main/java/demo/webauthn/InMemoryRegistrationStorage.java[`InMemoryRegistrationStorage`]
class, which simply keeps them stored in memory for a limited time. The
@@ -54,7 +54,7 @@ would be specific to a particular Relying Party (RP) would go in this layer.
- The server layer in turn calls the *library layer*, which is where the
link:../webauthn-server-core/[`webauthn-server-core`]
library gets involved. The entry point into the library is the
- link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.html[`RelyingParty`]
+ link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.html[`RelyingParty`]
class.
+
This layer implements the Web Authentication
@@ -65,11 +65,11 @@ and exposes integration points for storage of challenges and credentials. Some
notable integration points are:
+
** The library user must provide an implementation of the
-link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`]
+link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`]
interface to use for looking up stored public keys, user handles and signature
counters.
** The library user can optionally provide an instance of the
-link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/attestation/AttestationTrustSource.html[`AttestationTrustSource`]
+link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/attestation/AttestationTrustSource.html[`AttestationTrustSource`]
interface to enable identification and validation of authenticator models. This
instance is then used to look up trusted attestation root certificates. The
link:../webauthn-server-attestation/[`webauthn-server-attestation`]
navigator.credentials.create()
and
@@ -82,15 +82,22 @@
* com.yubico.webauthn.data.PublicKeyCredentialCreationOptions} which can be serialized to JSON and
* passed as the
publicKey
argument to navigator.credentials.create()
. You
* can use the {@link com.yubico.webauthn.data.PublicKeyCredentialCreationOptions#toBuilder()
- * toBuilder()} method to make any modifications you need. You should store this in temporary
- * storage so that it can later be passed as an argument to {@link
- * com.yubico.webauthn.RelyingParty#finishRegistration(FinishRegistrationOptions)}.
- *
- * webauthn-server-attestation
for an implementation of such an
+ * attestation trust and metadata source.
* publicKey
argument to
- * navigator.credentials.get()
. Again, store the {@link
- * com.yubico.webauthn.AssertionRequest} in temporary storage so it can be passed as an argument to
- * {@link
+ * navigator.credentials.get()
. Again, use {@link
+ * com.yubico.webauthn.AssertionRequest#toBuilder()} to make any necessary changes, {@link
+ * com.yubico.webauthn.AssertionRequest#toCredentialsGetJson()} to convert it to JSON for sending to
+ * the client, and store the {@link com.yubico.webauthn.AssertionRequest} in temporary storage so it
+ * can be passed as an argument to {@link
* com.yubico.webauthn.RelyingParty#finishAssertion(com.yubico.webauthn.FinishAssertionOptions)}.
- *
- *