From 9e5a425859444d1fdc664673e46807459aff123b Mon Sep 17 00:00:00 2001 From: Mohammad Amin Pahlevani Date: Fri, 19 Dec 2025 06:29:40 -0800 Subject: [PATCH] Make PublicKeyCredentialCreationOptions Serializable Closes gh-16431 Signed-off-by: Mohammad Amin Pahlevani --- .../security/SerializationSamples.java | 57 +++++++++++------- ...AttestationConveyancePreference.serialized | Bin 0 -> 136 bytes ....AuthenticatorSelectionCriteria.serialized | Bin 0 -> 724 bytes ...thn.api.COSEAlgorithmIdentifier.serialized | Bin 0 -> 106 bytes ...licKeyCredentialCreationOptions.serialized | Bin 0 -> 3530 bytes ...i.PublicKeyCredentialParameters.serialized | Bin 0 -> 487 bytes ...api.PublicKeyCredentialRpEntity.serialized | Bin 0 -> 144 bytes ...uthn.api.ResidentKeyRequirement.serialized | Bin 0 -> 129 bytes .../api/AttestationConveyancePreference.java | 8 ++- .../api/AuthenticatorSelectionCriteria.java | 8 ++- .../webauthn/api/COSEAlgorithmIdentifier.java | 8 ++- .../PublicKeyCredentialCreationOptions.java | 7 ++- .../api/PublicKeyCredentialParameters.java | 8 ++- .../api/PublicKeyCredentialRpEntity.java | 8 ++- .../webauthn/api/ResidentKeyRequirement.java | 8 ++- .../Webauthn4JRelyingPartyOperations.java | 3 +- ...Webauthn4jRelyingPartyOperationsTests.java | 33 ++++++++++ 17 files changed, 117 insertions(+), 31 deletions(-) create mode 100644 config/src/test/resources/serialized/7.1.x/org.springframework.security.web.webauthn.api.AttestationConveyancePreference.serialized create mode 100644 config/src/test/resources/serialized/7.1.x/org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria.serialized create mode 100644 config/src/test/resources/serialized/7.1.x/org.springframework.security.web.webauthn.api.COSEAlgorithmIdentifier.serialized create mode 100644 config/src/test/resources/serialized/7.1.x/org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions.serialized create mode 100644 config/src/test/resources/serialized/7.1.x/org.springframework.security.web.webauthn.api.PublicKeyCredentialParameters.serialized create mode 100644 config/src/test/resources/serialized/7.1.x/org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity.serialized create mode 100644 config/src/test/resources/serialized/7.1.x/org.springframework.security.web.webauthn.api.ResidentKeyRequirement.serialized diff --git a/config/src/test/java/org/springframework/security/SerializationSamples.java b/config/src/test/java/org/springframework/security/SerializationSamples.java index 4bf96f0ccd..4974eb3985 100644 --- a/config/src/test/java/org/springframework/security/SerializationSamples.java +++ b/config/src/test/java/org/springframework/security/SerializationSamples.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Field; import java.security.Principal; +import java.time.Duration; import java.time.Instant; import java.util.Collection; import java.util.Date; @@ -245,32 +246,11 @@ import org.springframework.security.web.savedrequest.SimpleSavedRequest; import org.springframework.security.web.server.firewall.ServerExchangeRejectedException; import org.springframework.security.web.session.HttpSessionCreatedEvent; import org.springframework.security.web.session.HttpSessionIdChangedEvent; -import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInputs; -import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientOutputs; -import org.springframework.security.web.webauthn.api.AuthenticatorAssertionResponse; -import org.springframework.security.web.webauthn.api.AuthenticatorAttachment; -import org.springframework.security.web.webauthn.api.AuthenticatorTransport; -import org.springframework.security.web.webauthn.api.Bytes; -import org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput; -import org.springframework.security.web.webauthn.api.CredentialPropertiesOutput; -import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInput; -import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInputs; -import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientOutputs; -import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity; -import org.springframework.security.web.webauthn.api.PublicKeyCredential; -import org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor; -import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions; -import org.springframework.security.web.webauthn.api.PublicKeyCredentialType; -import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity; -import org.springframework.security.web.webauthn.api.TestAuthenticationAssertionResponses; -import org.springframework.security.web.webauthn.api.TestBytes; -import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialRequestOptions; -import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialUserEntities; -import org.springframework.security.web.webauthn.api.TestPublicKeyCredentials; -import org.springframework.security.web.webauthn.api.UserVerificationRequirement; +import org.springframework.security.web.webauthn.api.*; import org.springframework.security.web.webauthn.authentication.WebAuthnAuthentication; import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationRequestToken; import org.springframework.security.web.webauthn.management.RelyingPartyAuthenticationRequest; +import org.springframework.security.web.webauthn.management.TestPublicKeyCredentialRpEntities; import org.springframework.util.ReflectionUtils; final class SerializationSamples { @@ -879,6 +859,37 @@ final class SerializationSamples { generatorByClassName.put(CredentialPropertiesOutput.ExtensionOutput.class, (r) -> new CredentialPropertiesOutput(true).getOutput()); + AttestationConveyancePreference attestationConveyancePreference = AttestationConveyancePreference.DIRECT; + ResidentKeyRequirement residentKeyRequirement = ResidentKeyRequirement.REQUIRED; + AuthenticatorSelectionCriteria authenticatorSelectionCriteria = AuthenticatorSelectionCriteria.builder() + .authenticatorAttachment(AuthenticatorAttachment.PLATFORM) + .residentKey(residentKeyRequirement) + .userVerification(UserVerificationRequirement.REQUIRED) + .build(); + PublicKeyCredentialParameters publicKeyCredentialParameters = PublicKeyCredentialParameters.RS256; + PublicKeyCredentialRpEntity publicKeyCredentialRpEntity = TestPublicKeyCredentialRpEntities.createRpEntity().build(); + + generatorByClassName.put(AttestationConveyancePreference.class, (r) -> attestationConveyancePreference); + generatorByClassName.put(ResidentKeyRequirement.class, (r) -> residentKeyRequirement); + generatorByClassName.put(AuthenticatorSelectionCriteria.class, (r) -> authenticatorSelectionCriteria); + generatorByClassName.put(COSEAlgorithmIdentifier.class, (r -> COSEAlgorithmIdentifier.RS256)); + generatorByClassName.put(PublicKeyCredentialParameters.class, (r) -> publicKeyCredentialParameters); + generatorByClassName.put(PublicKeyCredentialRpEntity.class, (r) -> publicKeyCredentialRpEntity); + generatorByClassName.put(PublicKeyCredentialCreationOptions.class, (o) -> TestPublicKeyCredentialCreationOptions.createPublicKeyCredentialCreationOptions() + .extensions(inputs) + .attestation(attestationConveyancePreference) + .authenticatorSelection(authenticatorSelectionCriteria) + .challenge(TestBytes.get()) + .excludeCredentials(List.of(descriptor)) + .rp(publicKeyCredentialRpEntity) + .pubKeyCredParams(publicKeyCredentialParameters) + .timeout(Duration.ofMinutes(5)) + .user(TestPublicKeyCredentialUserEntities.userEntity() + .id(TestBytes.get()) + .build()) + .build() + ); + // One-Time Token DefaultOneTimeToken oneTimeToken = new DefaultOneTimeToken(UUID.randomUUID().toString(), "user", Instant.now().plusSeconds(300)); diff --git a/config/src/test/resources/serialized/7.1.x/org.springframework.security.web.webauthn.api.AttestationConveyancePreference.serialized b/config/src/test/resources/serialized/7.1.x/org.springframework.security.web.webauthn.api.AttestationConveyancePreference.serialized new file mode 100644 index 0000000000000000000000000000000000000000..ef40b44cca314e84c57dc3764c1e537775e41b86 GIT binary patch literal 136 zcmWm7u?@m75J1rbDGUI6IP_GA3MoiPh>qqkmtaWF*7@v^ov4_Bb=ZIcQhe!k|M`I; z3hcZzCI)fb)Pf!LUhXE6P2zMgJ)Ore>6HxzXV$6|71ep(c+NB+TiOfNB=pz&d)zLT huhSGJ6%-lUL<;ljh8fEixhW4io>q^bFuOP*t3UPHGMNAX literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.1.x/org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria.serialized b/config/src/test/resources/serialized/7.1.x/org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria.serialized new file mode 100644 index 0000000000000000000000000000000000000000..64b008dc316cea33eb236a64ee76c87dcc9a7226 GIT binary patch literal 724 zcmbu+J5Iwu5C-5OkSHPzqJff@#@j)pR8e>c#Z@BcKw6x!z2H~I<2)Kpl9CDqA+A8n z1)$^r9Dt79AaDo>$)Z?kSK9qQ&+q9glv4sbCeKO}QjzD93~?CKrIn(Sk>as6#B;M` z#(reUL|IL}!-$n5Ii@ZK=&*{TmcERnjZj)I$eyGn3hXxf3 zsQ5&M2F;qGLZjjvng=y(s1c^Br$gH~W;iIA+sPkyu%9VH^L;vYElZJ3^hB;1R1e6| zE|`71kT4I75?%cMKZ?&g$ae1i7a6aXV9AE^Kn58ytlJlIAVnY}PjuP*+Qukhs3w7A zH>ObQd&TtMo7!Btt3JG)-OS;AxPr=8apb6{ZZZX)>A#&y{`Wh4P1K%l8X>a literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.1.x/org.springframework.security.web.webauthn.api.COSEAlgorithmIdentifier.serialized b/config/src/test/resources/serialized/7.1.x/org.springframework.security.web.webauthn.api.COSEAlgorithmIdentifier.serialized new file mode 100644 index 0000000000000000000000000000000000000000..0a61cdf3e7c0d0760ddc11de6c0e91e088881718 GIT binary patch literal 106 zcmZ4UmVvdnh`}|#C|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qh5JT(b~6H7}n^7Il5 zGWDGOgIyhS(({3`8M&S*sd*)tX_=`-yPt*b)njM+#l*nq#lTvYm{XcsQScuO{{064 DlvOO+ literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.1.x/org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions.serialized b/config/src/test/resources/serialized/7.1.x/org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions.serialized new file mode 100644 index 0000000000000000000000000000000000000000..044a97d4fdcb04defbd0abf5819b6b80ee8074da GIT binary patch literal 3530 zcmcInU1%It6uvvV`Q4T@t=Q0LO>4m)>~5vBjYiO96Vo-@BxEMtRK!LUqYv5_^HPMEYU4RK*`L^rdJ|mO zpX|<@@7(Wv-#I7Kzmf`vlU|Fb1CGsgGd;v1QdbO{0cz`k zUN>Rr$u5=)ah77H(5V5RsL(C5$HrI3-?%t=jvaYOBQ+7?r$R7C$lnF&i2zveZ(zYZ zSP+2lDZzpO7Hlov9J0(&mZN5p^>Q}EID=0u5ppkHjA*joX5BDPopUMW5XP zWf3`msuFsJS*{S|o|UuW-@$g5OIg4LKG$JfkYk%i?T0|CxLF(2eN0W=|01}nfAoiU z9GH1QBc2GU7^Q{_V%WZNCWD&kU{s)wTCzZ4WlHBDM#uqArQooV`jsd;ufz*qJTg5| z85-1xH$rxsiiBQ8*a`Jw!< z*aY!K4&1{RhkvA2ZAsD5Dt;_-wQW!_Wbq7?xm^vd zJTncx52`=={p9(wV0?wa6o7@*!2W@%xuzS<=8?0_cO&8{8vyH|8kTH!*I&8x^uwP| z1|e#kR3xzf38KZ@vbM|defrkU#mef~yhcdYCWJIS(RAVP&HAPBhR!z*z9R+}_r5p# zmUa8V+7CSwS8jfM^#{nK9{Xef`(z+w8Ag=x<8Quy>B+{adN1*WiC56HjO7{gK|~MS z(WRDonJ(7OoFvz6m@O|>(+5r*I`g(Po~Eae9k5`n;M8<%iwihlT}dmCGL*Sb zs&*`sNelu(W07sT(M9y#|A{xevI(x#3als!bxmuC3rEc>rr3kS6=e3viym~Pwx$;sYt==d?J<*OZm3OTJ5=@ zA)g8Vry!b~!SKCU`3kVNN+05W8G!ll?%MHMz7Xv!qh5JT(b~6H7}n^7Il5 zGW7yVlX5bXy;Ccli&9fk^GY%ka{>}U`btuZivRyTQLEUzv66{_$%lbCF(G?q0Gjct_Hl$^y7Fqi+u#{95q>^qR76*p_ z4YIB%0D9h)G|%I3-0o+gd-d3velamHdNHt;CFYc-RuudPgMa@?w*`lb6YKsv?2^5k p546PxYD)=&kWW@(S)zVUVqUs_a0w_9fgUSi;3@z|lWuluB>-9(y=VXc literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.1.x/org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity.serialized b/config/src/test/resources/serialized/7.1.x/org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity.serialized new file mode 100644 index 0000000000000000000000000000000000000000..582daf62937adad4667f04a5bfd171bad9f03923 GIT binary patch literal 144 zcmZ4UmVvdnh`}?zC|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qh5JT(b~6H7}n^7Il5 zGW7yVlX5bXy;Ccli&9fk^GY%kbAk$7fpkgb&99#oZ?StYF)=XtFfe7NlrRYSWF?j* i>gOcprRxWmfUL0gVPMGv+EU0+$G})oPy$g076JfX8#56A literal 0 HcmV?d00001 diff --git a/config/src/test/resources/serialized/7.1.x/org.springframework.security.web.webauthn.api.ResidentKeyRequirement.serialized b/config/src/test/resources/serialized/7.1.x/org.springframework.security.web.webauthn.api.ResidentKeyRequirement.serialized new file mode 100644 index 0000000000000000000000000000000000000000..cb8e68daa0566f082d4d06749d7eebb5ab048233 GIT binary patch literal 129 zcmZ4UmVvdnh`}YlC|$3(peQphJ*_A)H?=&!C|j>MHMz7Xv!qh5JT(b~6H7}n^7Il5 zGWCK|i!)PF^Gdu^D}z!COEZg7bAdbwmh~Kme#Wh0Vqo-PU@c3`DNQY55c0`NEKAhS XNz6;v4=w>&U|msA!oX1k(U$@MY_c!C literal 0 HcmV?d00001 diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/AttestationConveyancePreference.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/AttestationConveyancePreference.java index fc8c5984e2..1241faf132 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/AttestationConveyancePreference.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/AttestationConveyancePreference.java @@ -16,6 +16,9 @@ package org.springframework.security.web.webauthn.api; +import java.io.Serial; +import java.io.Serializable; + /** * WebAuthn Relying * Parties may use COSEAlgorithmIdentifier is @@ -25,7 +28,10 @@ package org.springframework.security.web.webauthn.api; * @since 6.4 * @see PublicKeyCredentialParameters#getAlg() */ -public final class COSEAlgorithmIdentifier { +public final class COSEAlgorithmIdentifier implements Serializable { + + @Serial + private static final long serialVersionUID = -4907140472964185350L; public static final COSEAlgorithmIdentifier EdDSA = new COSEAlgorithmIdentifier(-8); diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialCreationOptions.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialCreationOptions.java index 35c1e2f0e0..47161f950c 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialCreationOptions.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialCreationOptions.java @@ -16,6 +16,8 @@ package org.springframework.security.web.webauthn.api; +import java.io.Serial; +import java.io.Serializable; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; @@ -34,7 +36,10 @@ import org.jspecify.annotations.Nullable; * @author Rob Winch * @since 6.4 */ -public final class PublicKeyCredentialCreationOptions { +public final class PublicKeyCredentialCreationOptions implements Serializable { + + @Serial + private static final long serialVersionUID = 800506727675279143L; private final PublicKeyCredentialRpEntity rp; diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialParameters.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialParameters.java index 4bc075b051..5503ea8d9f 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialParameters.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialParameters.java @@ -16,6 +16,9 @@ package org.springframework.security.web.webauthn.api; +import java.io.Serial; +import java.io.Serializable; + /** * The PublicKeyCredentialParameters @@ -25,7 +28,10 @@ package org.springframework.security.web.webauthn.api; * @since 6.4 * @see PublicKeyCredentialCreationOptions#getPubKeyCredParams() */ -public final class PublicKeyCredentialParameters { +public final class PublicKeyCredentialParameters implements Serializable { + + @Serial + private static final long serialVersionUID = -623985171385991L; public static final PublicKeyCredentialParameters EdDSA = new PublicKeyCredentialParameters( COSEAlgorithmIdentifier.EdDSA); diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialRpEntity.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialRpEntity.java index 6a817c205c..20049f0fcc 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialRpEntity.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialRpEntity.java @@ -16,6 +16,9 @@ package org.springframework.security.web.webauthn.api; +import java.io.Serial; +import java.io.Serializable; + import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; @@ -29,7 +32,10 @@ import org.springframework.util.Assert; * @author Rob Winch * @since 6.4 */ -public final class PublicKeyCredentialRpEntity { +public final class PublicKeyCredentialRpEntity implements Serializable { + + @Serial + private static final long serialVersionUID = -2741017471467698174L; private final String name; diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/ResidentKeyRequirement.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/ResidentKeyRequirement.java index b3f9f216a0..080940fb35 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/ResidentKeyRequirement.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/ResidentKeyRequirement.java @@ -16,6 +16,9 @@ package org.springframework.security.web.webauthn.api; +import java.io.Serial; +import java.io.Serializable; + /** * The ResidentKeyRequirement @@ -24,7 +27,10 @@ package org.springframework.security.web.webauthn.api; * @author Rob Winch * @since 6.4 */ -public final class ResidentKeyRequirement { +public final class ResidentKeyRequirement implements Serializable { + + @Serial + private static final long serialVersionUID = 1730700608982834858L; /** * The publicKeyCredential = TestPublicKeyCredentials + .createPublicKeyCredential() + .build(); + RelyingPartyPublicKey rpPublicKey = new RelyingPartyPublicKey(publicKeyCredential, this.label); + + try (ByteArrayOutputStream out = new ByteArrayOutputStream(); + ObjectOutputStream objectOutputStream = new ObjectOutputStream(out)) { + objectOutputStream.writeObject(creationOptions); + objectOutputStream.flush(); + + try (ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); + ObjectInputStream objectInputStream = new ObjectInputStream(in)) { + PublicKeyCredentialCreationOptions deserialized = (PublicKeyCredentialCreationOptions) objectInputStream + .readObject(); + ImmutableRelyingPartyRegistrationRequest rpRegistrationRequest = new ImmutableRelyingPartyRegistrationRequest( + deserialized, rpPublicKey); + assertThatNoException().isThrownBy(() -> this.rpOperations.registerCredential(rpRegistrationRequest)); + } + } + } + @Test void registerCredentialWhenInternalTransportThenCredentialRecordHasTransport() { PublicKeyCredentialCreationOptions creationOptions = TestPublicKeyCredentialCreationOptions