diff --git a/web/spring-security-web.gradle b/web/spring-security-web.gradle index a709c95a991..4edead601bd 100644 --- a/web/spring-security-web.gradle +++ b/web/spring-security-web.gradle @@ -36,6 +36,7 @@ dependencies { api 'org.springframework:spring-web' optional 'com.fasterxml.jackson.core:jackson-databind' + optional 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' optional 'io.projectreactor:reactor-core' optional 'org.springframework:spring-jdbc' optional 'org.springframework:spring-tx' diff --git a/web/src/main/java/org/springframework/security/web/webauthn/api/AuthenticatorTransport.java b/web/src/main/java/org/springframework/security/web/webauthn/api/AuthenticatorTransport.java index 33e9d2123cb..7c8706089dc 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/api/AuthenticatorTransport.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/api/AuthenticatorTransport.java @@ -23,6 +23,7 @@ * order to obtain an assertion for a specific credential. * * @author Rob Winch + * @author Justin Cranford * @since 6.4 */ public final class AuthenticatorTransport { @@ -112,7 +113,7 @@ public static AuthenticatorTransport valueOf(String value) { } public static AuthenticatorTransport[] values() { - return new AuthenticatorTransport[] { USB, NFC, BLE, HYBRID, INTERNAL }; + return new AuthenticatorTransport[] { USB, NFC, BLE, SMART_CARD, HYBRID, INTERNAL }; } } diff --git a/web/src/main/java/org/springframework/security/web/webauthn/api/COSEAlgorithmIdentifier.java b/web/src/main/java/org/springframework/security/web/webauthn/api/COSEAlgorithmIdentifier.java index 0cafd9309bd..4510f9c010e 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/api/COSEAlgorithmIdentifier.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/api/COSEAlgorithmIdentifier.java @@ -22,6 +22,7 @@ * used to identify a cryptographic algorithm. * * @author Rob Winch + * @author Justin Cranford * @since 6.4 * @see PublicKeyCredentialParameters#getAlg() */ @@ -62,4 +63,24 @@ public static COSEAlgorithmIdentifier[] values() { return new COSEAlgorithmIdentifier[] { EdDSA, ES256, ES384, ES512, RS256, RS384, RS512, RS1 }; } + public static COSEAlgorithmIdentifier valueOf(long value) { + if (COSEAlgorithmIdentifier.EdDSA.getValue() == value) { + return EdDSA; + } else if (COSEAlgorithmIdentifier.ES256.getValue() == value) { + return ES256; + } else if (COSEAlgorithmIdentifier.ES384.getValue() == value) { + return ES384; + } else if (COSEAlgorithmIdentifier.ES512.getValue() == value) { + return ES512; + } else if (COSEAlgorithmIdentifier.RS256.getValue() == value) { + return RS256; + } else if (COSEAlgorithmIdentifier.RS384.getValue() == value) { + return RS384; + } else if (COSEAlgorithmIdentifier.RS512.getValue() == value) { + return RS512; + } else if (COSEAlgorithmIdentifier.RS1.getValue() == value) { + return RS1; + } + return new COSEAlgorithmIdentifier(value); + } } diff --git a/web/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialParameters.java b/web/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialParameters.java index abb8c028330..db91ae70e39 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialParameters.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialParameters.java @@ -22,6 +22,7 @@ * is used to supply additional parameters when creating a new credential. * * @author Rob Winch + * @author Justin Cranford * @since 6.4 * @see PublicKeyCredentialCreationOptions#getPubKeyCredParams() */ @@ -96,4 +97,25 @@ public COSEAlgorithmIdentifier getAlg() { return this.alg; } + public static PublicKeyCredentialParameters valueOf(PublicKeyCredentialType type, COSEAlgorithmIdentifier alg) { + if (PublicKeyCredentialParameters.EdDSA.getType().equals(type) && PublicKeyCredentialParameters.EdDSA.getAlg().equals(alg)) { + return EdDSA; + } else if (PublicKeyCredentialParameters.ES256.getType().equals(type) && PublicKeyCredentialParameters.ES256.getAlg().equals(alg)) { + return ES256; + } else if (PublicKeyCredentialParameters.ES384.getType().equals(type) && PublicKeyCredentialParameters.ES256.getAlg().equals(alg)) { + return ES384; + } else if (PublicKeyCredentialParameters.ES512.getType().equals(type) && PublicKeyCredentialParameters.ES256.getAlg().equals(alg)) { + return ES512; + } else if (PublicKeyCredentialParameters.RS256.getType().equals(type) && PublicKeyCredentialParameters.ES256.getAlg().equals(alg)) { + return RS256; + } else if (PublicKeyCredentialParameters.RS384.getType().equals(type) && PublicKeyCredentialParameters.ES256.getAlg().equals(alg)) { + return RS384; + } else if (PublicKeyCredentialParameters.RS512.getType().equals(type) && PublicKeyCredentialParameters.ES256.getAlg().equals(alg)) { + return RS512; + } else if (PublicKeyCredentialParameters.RS1.getType().equals(type) && PublicKeyCredentialParameters.ES256.getAlg().equals(alg)) { + return RS1; + } + return new PublicKeyCredentialParameters(type, alg); + } + } diff --git a/web/src/main/java/org/springframework/security/web/webauthn/api/UserVerificationRequirement.java b/web/src/main/java/org/springframework/security/web/webauthn/api/UserVerificationRequirement.java index adc915015b5..5d23f1a37e9 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/api/UserVerificationRequirement.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/api/UserVerificationRequirement.java @@ -22,6 +22,7 @@ * is used by the Relying Party to indicate if user verification is needed. * * @author Rob Winch + * @author Justin Cranford * @since 6.4 */ public final class UserVerificationRequirement { @@ -66,4 +67,24 @@ public String getValue() { return this.value; } + /** + * Gets the value + * @param value the string + * @return the value + * @author Justin Cranford + * @since 6.5 + */ + public static UserVerificationRequirement valueOf(String value) { + if (DISCOURAGED.getValue().equals(value)) { + return DISCOURAGED; + } + if (PREFERRED.getValue().equals(value)) { + return PREFERRED; + } + if (REQUIRED.getValue().equals(value)) { + return REQUIRED; + } + return new UserVerificationRequirement(value); + } + } diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputDeserializer.java new file mode 100644 index 00000000000..c16c60614a4 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputDeserializer.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.webauthn.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInput; +import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInput; + +import java.io.IOException; + +/** + * Jackson deserializer for {@link AuthenticationExtensionsClientInput} + * + * @author Justin Cranford + * @since 6.5 + */ +@SuppressWarnings("serial") +class AuthenticationExtensionsClientInputDeserializer extends StdDeserializer { + + AuthenticationExtensionsClientInputDeserializer() { + super(AuthenticationExtensionsClientInput.class); + } + + @Override + public AuthenticationExtensionsClientInput deserialize(JsonParser parser, DeserializationContext ctxt) + throws IOException { + if (parser.nextToken() != JsonToken.END_OBJECT) { + String extensionId = parser.currentName(); + Object value = parser.readValueAs(Object.class); + return new ImmutableAuthenticationExtensionsClientInput(extensionId, value); + } + return null; + } +} diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputMixin.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputMixin.java index 7c8e4c664b3..3969d017261 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputMixin.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputMixin.java @@ -16,17 +16,19 @@ package org.springframework.security.web.webauthn.jackson; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; - import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInputs; /** * Jackson mixin for {@link AuthenticationExtensionsClientInputs} * * @author Rob Winch + * @author Justin Cranford * @since 6.4 */ @JsonSerialize(using = AuthenticationExtensionsClientInputSerializer.class) +@JsonDeserialize(using = AuthenticationExtensionsClientInputDeserializer.class) class AuthenticationExtensionsClientInputMixin { } diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputsDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputsDeserializer.java new file mode 100644 index 00000000000..5d84bcf6fb3 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputsDeserializer.java @@ -0,0 +1,55 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.webauthn.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInput; +import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInputs; +import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInputs; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Jackson deserializer for {@link AuthenticationExtensionsClientInputs} + * + * @author Justin Cranford + * @since 6.5 + */ +@SuppressWarnings("serial") +class AuthenticationExtensionsClientInputsDeserializer extends StdDeserializer { + + AuthenticationExtensionsClientInputsDeserializer() { + super(AuthenticationExtensionsClientInputs.class); + } + + @Override + public AuthenticationExtensionsClientInputs deserialize(JsonParser parser, DeserializationContext ctxt) + throws IOException { + final AuthenticationExtensionsClientInputDeserializer authenticationExtensionsClientInputDeserializer = new AuthenticationExtensionsClientInputDeserializer(); + + final List extensions = new ArrayList<>(); + while (parser.nextToken() != JsonToken.END_OBJECT) { + extensions.add(authenticationExtensionsClientInputDeserializer.deserialize(parser, ctxt)); + } + return new ImmutableAuthenticationExtensionsClientInputs(extensions); + } +} diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputsMixin.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputsMixin.java index 9cbe52556ff..9d7fa04a1b0 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputsMixin.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputsMixin.java @@ -16,6 +16,7 @@ package org.springframework.security.web.webauthn.jackson; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInputs; @@ -27,6 +28,7 @@ * @since 6.4 */ @JsonSerialize(using = AuthenticationExtensionsClientInputsSerializer.class) +@JsonDeserialize(using = AuthenticationExtensionsClientInputsDeserializer.class) class AuthenticationExtensionsClientInputsMixin { } diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorSelectionCriteriaDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorSelectionCriteriaDeserializer.java new file mode 100644 index 00000000000..989fb5488fc --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorSelectionCriteriaDeserializer.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.webauthn.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import org.springframework.security.web.webauthn.api.AuthenticatorAttachment; +import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria; +import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria.AuthenticatorSelectionCriteriaBuilder; +import org.springframework.security.web.webauthn.api.ResidentKeyRequirement; +import org.springframework.security.web.webauthn.api.UserVerificationRequirement; + +import java.io.IOException; + +/** + * Jackson deserializer for {@link AuthenticatorSelectionCriteria} + * + * @author Justin Cranford + * @since 6.5 + */ +@SuppressWarnings("serial") +class AuthenticatorSelectionCriteriaDeserializer extends StdDeserializer { + + AuthenticatorSelectionCriteriaDeserializer() { + super(AuthenticatorSelectionCriteria.class); + } + + @Override + public AuthenticatorSelectionCriteria deserialize(JsonParser parser, DeserializationContext ctxt) + throws IOException { + final AuthenticatorSelectionCriteriaBuilder builder = AuthenticatorSelectionCriteria.builder(); + while (parser.nextToken() != JsonToken.END_OBJECT) { + final String fieldName = parser.currentName(); + parser.nextToken(); + if ("authenticatorAttachment".equals(fieldName)) { + builder.authenticatorAttachment(AuthenticatorAttachment.valueOf(parser.getText())); + } else if ("residentKey".equals(fieldName)) { + builder.residentKey(ResidentKeyRequirement.valueOf(parser.getText())); + } else if ("userVerification".equals(fieldName)) { + builder.userVerification(UserVerificationRequirement.valueOf(parser.getText())); + } else { + throw new IOException("Unsupported field name: " + fieldName); + } + } + return builder.build(); + } + +} diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorSelectionCriteriaMixin.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorSelectionCriteriaMixin.java index 5fb51f53ba2..2680e9fc867 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorSelectionCriteriaMixin.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorSelectionCriteriaMixin.java @@ -18,14 +18,19 @@ import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria; /** * Jackson mixin for {@link AuthenticatorSelectionCriteria} * * @author Rob Winch + * @author Justin Cranford * @since 6.4 */ +@JsonSerialize(using = AuthenticatorSelectionCriteriaSerializer.class) +@JsonDeserialize(using = AuthenticatorSelectionCriteriaDeserializer.class) @JsonInclude(JsonInclude.Include.NON_NULL) abstract class AuthenticatorSelectionCriteriaMixin { diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorSelectionCriteriaSerializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorSelectionCriteriaSerializer.java new file mode 100644 index 00000000000..7bc1d266b9c --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorSelectionCriteriaSerializer.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.webauthn.jackson; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria; + +import java.io.IOException; + +/** + * Jackson serializer for {@link AuthenticatorSelectionCriteria} + * + * @author Justin Cranford + * @since 6.5 + */ +@SuppressWarnings("serial") +class AuthenticatorSelectionCriteriaSerializer extends StdSerializer { + + AuthenticatorSelectionCriteriaSerializer() { + super(AuthenticatorSelectionCriteria.class); + } + + @Override + public void serialize(AuthenticatorSelectionCriteria value, JsonGenerator gen, SerializerProvider provider) + throws IOException { + gen.writeStartObject(); + if (value.getAuthenticatorAttachment() != null) { + gen.writeFieldName("authenticatorAttachment"); + gen.writeString(value.getAuthenticatorAttachment().getValue()); + } + if (value.getResidentKey() != null) { + gen.writeFieldName("residentKey"); + gen.writeString(value.getResidentKey().getValue()); + } + if (value.getUserVerification() != null) { + gen.writeFieldName("userVerification"); + gen.writeString(value.getUserVerification().getValue()); + } + gen.writeEndObject(); + } + +} diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorTransportDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorTransportDeserializer.java index 8cafd92aa96..4b7d83dc00c 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorTransportDeserializer.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorTransportDeserializer.java @@ -16,19 +16,18 @@ package org.springframework.security.web.webauthn.jackson; -import java.io.IOException; - -import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; - import org.springframework.security.web.webauthn.api.AuthenticatorTransport; +import java.io.IOException; + /** * Jackson deserializer for {@link AuthenticatorTransport} * * @author Rob Winch + * @author Justin Cranford * @since 6.4 */ @SuppressWarnings("serial") @@ -40,14 +39,9 @@ class AuthenticatorTransportDeserializer extends StdDeserializer { @Override diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/COSEAlgorithmIdentifierDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/COSEAlgorithmIdentifierDeserializer.java index ed1e6e48370..20bd538f9a4 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/COSEAlgorithmIdentifierDeserializer.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/COSEAlgorithmIdentifierDeserializer.java @@ -16,19 +16,18 @@ package org.springframework.security.web.webauthn.jackson; -import java.io.IOException; - -import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; - import org.springframework.security.web.webauthn.api.COSEAlgorithmIdentifier; +import java.io.IOException; + /** * Jackson serializer for {@link COSEAlgorithmIdentifier} * * @author Rob Winch + * @author Justin Cranford * @since 6.4 */ @SuppressWarnings("serial") @@ -40,14 +39,9 @@ class COSEAlgorithmIdentifierDeserializer extends StdDeserializer { + + /** + * Creates an instance. + */ + DurationDeserializer() { + super(Duration.class); + } + + @Override + public Duration deserialize(JsonParser parser, DeserializationContext ctxt) + throws IOException { + long millis = parser.getLongValue(); + return Duration.ofMillis(millis); + } +} diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialCreationOptionsMixin.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialCreationOptionsMixin.java index 95eaea0fd40..6d5b7949774 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialCreationOptionsMixin.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialCreationOptionsMixin.java @@ -16,23 +16,42 @@ package org.springframework.security.web.webauthn.jackson; -import java.time.Duration; - -import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; - +import org.springframework.security.web.webauthn.api.AttestationConveyancePreference; +import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInputs; +import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria; +import org.springframework.security.web.webauthn.api.Bytes; import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialParameters; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity; + +import java.time.Duration; +import java.util.List; /** * Jackson mixin for {@link PublicKeyCredentialCreationOptions} * * @author Rob Winch + * @author Justin Cranford * @since 6.4 */ -@JsonInclude(JsonInclude.Include.NON_NULL) abstract class PublicKeyCredentialCreationOptionsMixin { - - @JsonSerialize(using = DurationSerializer.class) - private Duration timeout; - + @JsonCreator + public PublicKeyCredentialCreationOptionsMixin( + @JsonProperty("rp") PublicKeyCredentialRpEntity rp, + @JsonProperty("user") PublicKeyCredentialUserEntity user, + @JsonProperty("challenge") Bytes challenge, + @JsonProperty("pubKeyCredParams") List pubKeyCredParams, + @JsonProperty("timeout") @JsonSerialize(using=DurationSerializer.class) @JsonDeserialize(using=DurationDeserializer.class) Duration timeout, + @JsonProperty("excludeCredentials") List excludeCredentials, + @JsonProperty("authenticatorSelection") AuthenticatorSelectionCriteria authenticatorSelection, + @JsonProperty("attestation") AttestationConveyancePreference attestation, + @JsonProperty("extensions") AuthenticationExtensionsClientInputs extensions + ) { + } } diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialDescriptorMixin.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialDescriptorMixin.java new file mode 100644 index 00000000000..a7024fc2235 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialDescriptorMixin.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.webauthn.jackson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.security.web.webauthn.api.AuthenticatorTransport; +import org.springframework.security.web.webauthn.api.Bytes; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialType; + +import java.util.Set; + +/** + * Jackson mixin for {@link PublicKeyCredentialDescriptor} + * + * @author Justin Cranford + * @since 6.5 + */ +abstract class PublicKeyCredentialDescriptorMixin { + @JsonCreator + public PublicKeyCredentialDescriptorMixin( + @JsonProperty("type") PublicKeyCredentialType type, + @JsonProperty("id") Bytes id, + @JsonProperty("transports") Set transports + ) { + } +} diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialParametersDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialParametersDeserializer.java new file mode 100644 index 00000000000..3e1cb00e318 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialParametersDeserializer.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.webauthn.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import org.springframework.security.web.webauthn.api.COSEAlgorithmIdentifier; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialParameters; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialType; + +import java.io.IOException; + +/** + * Jackson deserializer for {@link PublicKeyCredentialParameters} + * + * @author Justin Cranford + * @since 6.5 + */ +@SuppressWarnings("serial") +public class PublicKeyCredentialParametersDeserializer extends StdDeserializer { + + public PublicKeyCredentialParametersDeserializer() { + super(PublicKeyCredentialParameters.class); + } + + @Override + public PublicKeyCredentialParameters deserialize(JsonParser parser, DeserializationContext ctxt) + throws IOException { + PublicKeyCredentialType type = null; + COSEAlgorithmIdentifier alg = null; + while (parser.nextToken() != JsonToken.END_OBJECT) { + final String fieldName = parser.currentName(); + parser.nextToken(); + if ("type".equals(fieldName)) { + type = PublicKeyCredentialType.valueOf(parser.getText()); + } else if ("alg".equals(fieldName)) { + alg = COSEAlgorithmIdentifier.valueOf(parser.getLongValue()); + } else { + throw new IOException("Unsupported field name: " + fieldName); + } + } + return PublicKeyCredentialParameters.valueOf(type, alg); + } +} diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialParametersMixin.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialParametersMixin.java new file mode 100644 index 00000000000..efc79498071 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialParametersMixin.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.webauthn.jackson; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialParameters; + +/** + * Jackson mixin for {@link PublicKeyCredentialParameters} + * + * @author Justin Cranford + * @since 6.5 + */ +@JsonSerialize(using = PublicKeyCredentialParametersSerializer.class) +@JsonDeserialize(using = PublicKeyCredentialParametersDeserializer.class) +abstract class PublicKeyCredentialParametersMixin { +} diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialTypeSerializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialParametersSerializer.java similarity index 55% rename from web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialTypeSerializer.java rename to web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialParametersSerializer.java index 23319e366a3..64276ce6645 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialTypeSerializer.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialParametersSerializer.java @@ -16,34 +16,37 @@ package org.springframework.security.web.webauthn.jackson; -import java.io.IOException; - import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialParameters; -import org.springframework.security.web.webauthn.api.PublicKeyCredentialType; +import java.io.IOException; /** - * Jackson serializer for {@link PublicKeyCredentialType} + * Jackson serializer for {@link PublicKeyCredentialParameters} * - * @author Rob Winch - * @since 6.4 + * @author Justin Cranford + * @since 6.5 */ @SuppressWarnings("serial") -class PublicKeyCredentialTypeSerializer extends StdSerializer { - - /** - * Creates a new instance. - */ - PublicKeyCredentialTypeSerializer() { - super(PublicKeyCredentialType.class); - } - - @Override - public void serialize(PublicKeyCredentialType type, JsonGenerator jgen, SerializerProvider provider) - throws IOException { - jgen.writeString(type.getValue()); - } - +public class PublicKeyCredentialParametersSerializer extends StdSerializer { + + public PublicKeyCredentialParametersSerializer() { + super(PublicKeyCredentialParameters.class); + } + + @Override + public void serialize(PublicKeyCredentialParameters value, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeStartObject(); + if (value.getType() != null) { + gen.writeFieldName("type"); + gen.writeString(value.getType().getValue()); + } + if (value.getAlg() != null) { + gen.writeFieldName("alg"); + gen.writeNumber(value.getAlg().getValue()); + } + gen.writeEndObject(); + } } diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRequestOptionsMixin.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRequestOptionsMixin.java index 9062c5faaac..4749332851f 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRequestOptionsMixin.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRequestOptionsMixin.java @@ -16,23 +16,35 @@ package org.springframework.security.web.webauthn.jackson; -import java.time.Duration; - -import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; - +import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInputs; +import org.springframework.security.web.webauthn.api.Bytes; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor; import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions; +import org.springframework.security.web.webauthn.api.UserVerificationRequirement; + +import java.time.Duration; +import java.util.List; /** * Jackson mixin for {@link PublicKeyCredentialRequestOptions} * * @author Rob Winch + * @author Justin Cranford * @since 6.4 */ -@JsonInclude(content = JsonInclude.Include.NON_NULL) class PublicKeyCredentialRequestOptionsMixin { - - @JsonSerialize(using = DurationSerializer.class) - private final Duration timeout = null; - + @JsonCreator + public PublicKeyCredentialRequestOptionsMixin( + @JsonProperty("challenge") Bytes challenge, + @JsonProperty("timeout") @JsonSerialize(using=DurationSerializer.class) @JsonDeserialize(using=DurationDeserializer.class) Duration timeout, + @JsonProperty("rpId") String rpId, + @JsonProperty("allowCredentials") List allowCredentials, + @JsonProperty("userVerification") UserVerificationRequirement userVerification, + @JsonProperty("extensions") AuthenticationExtensionsClientInputs extensions + ) { + } } diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRpEntityMixin.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRpEntityMixin.java new file mode 100644 index 00000000000..4683f3f87c9 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRpEntityMixin.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.webauthn.jackson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity; + +/** + * Jackson mixin for {@link PublicKeyCredentialRpEntity} + * + * @author Justin Cranford + * @since 6.5 + */ +abstract class PublicKeyCredentialRpEntityMixin { + @JsonCreator + public PublicKeyCredentialRpEntityMixin( + @JsonProperty("name") String name, + @JsonProperty("id") String id + ) { + } + } diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialTypeMixin.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialTypeMixin.java index 58ac3add983..c10603c1f9c 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialTypeMixin.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialTypeMixin.java @@ -16,8 +16,8 @@ package org.springframework.security.web.webauthn.jackson; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; import org.springframework.security.web.webauthn.api.PublicKeyCredentialType; @@ -25,10 +25,13 @@ * Jackson mixin for {@link PublicKeyCredentialType} * * @author Rob Winch + * @author Justin Cranford * @since 6.4 */ -@JsonSerialize(using = PublicKeyCredentialTypeSerializer.class) -@JsonDeserialize(using = PublicKeyCredentialTypeDeserializer.class) abstract class PublicKeyCredentialTypeMixin { - + @JsonCreator + public PublicKeyCredentialTypeMixin( + @JsonProperty("value") String value + ) { + } } diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialUserEntityDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialUserEntityDeserializer.java new file mode 100644 index 00000000000..721ceb46b30 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialUserEntityDeserializer.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.webauthn.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import org.springframework.security.web.webauthn.api.Bytes; +import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity; +import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity.PublicKeyCredentialUserEntityBuilder; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity; + +import java.io.IOException; + +/** + * Jackson deserializer for {@link PublicKeyCredentialUserEntity} + * + * @author Justin Cranford + * @since 6.5 + */ +@SuppressWarnings("serial") +public class PublicKeyCredentialUserEntityDeserializer extends StdDeserializer { + + public PublicKeyCredentialUserEntityDeserializer() { + super(PublicKeyCredentialUserEntity.class); + } + + @Override + public PublicKeyCredentialUserEntity deserialize(JsonParser parser, DeserializationContext ctxt) + throws IOException { + final PublicKeyCredentialUserEntityBuilder builder = ImmutablePublicKeyCredentialUserEntity.builder(); + + while (parser.nextToken() != JsonToken.END_OBJECT) { + final String fieldName = parser.currentName(); + parser.nextToken(); + if ("id".equals(fieldName)) { + builder.id(Bytes.fromBase64(parser.getText())); + } else if ("name".equals(fieldName)) { + builder.name(parser.getText()); + } else if ("displayName".equals(fieldName)) { + builder.displayName(parser.getText()); + } else { + throw new IOException("Unsupported field name: " + fieldName); + } + } + + return builder.build(); + } +} diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialUserEntityMixin.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialUserEntityMixin.java new file mode 100644 index 00000000000..4810d5f7466 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialUserEntityMixin.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.webauthn.jackson; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity; + +/** + * Jackson mixin for {@link PublicKeyCredentialUserEntity} + * + * @author Justin Cranford + * @since 6.5 + */ +@JsonSerialize(using = PublicKeyCredentialUserEntitySerializer.class) +@JsonDeserialize(using = PublicKeyCredentialUserEntityDeserializer.class) +abstract class PublicKeyCredentialUserEntityMixin { +} diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialUserEntitySerializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialUserEntitySerializer.java new file mode 100644 index 00000000000..cd42e9fafcb --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialUserEntitySerializer.java @@ -0,0 +1,56 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.webauthn.jackson; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity; + +import java.io.IOException; + +/** + * Jackson serializer for {@link PublicKeyCredentialUserEntity} + * + * @author Justin Cranford + * @since 6.5 + */ +@SuppressWarnings("serial") +public class PublicKeyCredentialUserEntitySerializer extends StdSerializer { + + public PublicKeyCredentialUserEntitySerializer() { + super(PublicKeyCredentialUserEntity.class); + } + + @Override + public void serialize(PublicKeyCredentialUserEntity value, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeStartObject(); + if (value.getId() != null) { + gen.writeFieldName("id"); + gen.writeString(value.getId().toBase64UrlString()); + } + if (value.getName() != null) { + gen.writeFieldName("name"); + gen.writeString(value.getName()); + } + if (value.getDisplayName() != null) { + gen.writeFieldName("displayName"); + gen.writeString(value.getDisplayName()); + } + gen.writeEndObject(); + } +} diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialTypeDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/ResidentKeyRequirementDeserializer.java similarity index 60% rename from web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialTypeDeserializer.java rename to web/src/main/java/org/springframework/security/web/webauthn/jackson/ResidentKeyRequirementDeserializer.java index 7640d7a366c..ad92b23b7de 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialTypeDeserializer.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/ResidentKeyRequirementDeserializer.java @@ -16,36 +16,30 @@ package org.springframework.security.web.webauthn.jackson; -import java.io.IOException; - -import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import org.springframework.security.web.webauthn.api.ResidentKeyRequirement; -import org.springframework.security.web.webauthn.api.PublicKeyCredentialType; +import java.io.IOException; /** - * Jackson deserializer for {@link PublicKeyCredentialType} + * Jackson deserializer for {@link ResidentKeyRequirement} * - * @author Rob Winch - * @since 6.4 + * @author Justin Cranford + * @since 6.5 */ @SuppressWarnings("serial") -class PublicKeyCredentialTypeDeserializer extends StdDeserializer { +public class ResidentKeyRequirementDeserializer extends StdDeserializer { - /** - * Creates a new instance. - */ - PublicKeyCredentialTypeDeserializer() { - super(PublicKeyCredentialType.class); - } + public ResidentKeyRequirementDeserializer() { + super(ResidentKeyRequirement.class); + } - @Override - public PublicKeyCredentialType deserialize(JsonParser parser, DeserializationContext ctxt) - throws IOException, JacksonException { + @Override + public ResidentKeyRequirement deserialize(JsonParser parser, DeserializationContext ctxt) + throws IOException { String type = parser.readValueAs(String.class); - return PublicKeyCredentialType.valueOf(type); - } - + return ResidentKeyRequirement.valueOf(type); + } } diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/ResidentKeyRequirementMixin.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/ResidentKeyRequirementMixin.java index 1b910dc4e1a..f5298c7c414 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/ResidentKeyRequirementMixin.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/ResidentKeyRequirementMixin.java @@ -16,17 +16,19 @@ package org.springframework.security.web.webauthn.jackson; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; - import org.springframework.security.web.webauthn.api.ResidentKeyRequirement; /** * Jackson mixin for {@link ResidentKeyRequirement} * * @author Rob Winch + * @author Justin Cranford * @since 6.4 */ @JsonSerialize(using = ResidentKeyRequirementSerializer.class) +@JsonDeserialize(using = ResidentKeyRequirementDeserializer.class) abstract class ResidentKeyRequirementMixin { } diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/UserVerificationRequirementDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/UserVerificationRequirementDeserializer.java new file mode 100644 index 00000000000..97c050b97d7 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/UserVerificationRequirementDeserializer.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.webauthn.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import org.springframework.security.web.webauthn.api.UserVerificationRequirement; + +import java.io.IOException; + +/** + * Jackson deserializer for {@link UserVerificationRequirement} + * + * @author Justin Cranford + * @since 6.5 + */ +@SuppressWarnings("serial") +public class UserVerificationRequirementDeserializer extends StdDeserializer { + + public UserVerificationRequirementDeserializer() { + super(UserVerificationRequirement.class); + } + + @Override + public UserVerificationRequirement deserialize(JsonParser parser, DeserializationContext ctxt) + throws IOException { + String type = parser.readValueAs(String.class); + return UserVerificationRequirement.valueOf(type); + } +} diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/UserVerificationRequirementMixin.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/UserVerificationRequirementMixin.java index ad6c1496656..cf8f5c22533 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/UserVerificationRequirementMixin.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/UserVerificationRequirementMixin.java @@ -16,17 +16,19 @@ package org.springframework.security.web.webauthn.jackson; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; - import org.springframework.security.web.webauthn.api.UserVerificationRequirement; /** * Jackson mixin for {@link UserVerificationRequirement} * * @author Rob Winch + * @author Justin Cranford * @since 6.4 */ @JsonSerialize(using = UserVerificationRequirementSerializer.class) +@JsonDeserialize(using = UserVerificationRequirementDeserializer.class) abstract class UserVerificationRequirementMixin { } diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/WebauthnJackson2Module.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/WebauthnJackson2Module.java index 97a1c8e1f46..6149dc2cbbe 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/WebauthnJackson2Module.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/WebauthnJackson2Module.java @@ -34,8 +34,12 @@ import org.springframework.security.web.webauthn.api.CredentialPropertiesOutput; import org.springframework.security.web.webauthn.api.PublicKeyCredential; import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialParameters; import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity; import org.springframework.security.web.webauthn.api.PublicKeyCredentialType; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity; import org.springframework.security.web.webauthn.api.ResidentKeyRequirement; import org.springframework.security.web.webauthn.api.UserVerificationRequirement; import org.springframework.security.web.webauthn.management.RelyingPartyPublicKey; @@ -92,6 +96,12 @@ public void setupModule(SetupContext context) { context.setMixInAnnotations(RelyingPartyPublicKey.class, RelyingPartyPublicKeyMixin.class); context.setMixInAnnotations(ResidentKeyRequirement.class, ResidentKeyRequirementMixin.class); context.setMixInAnnotations(UserVerificationRequirement.class, UserVerificationRequirementMixin.class); + + context.setMixInAnnotations(PublicKeyCredentialUserEntity.class, PublicKeyCredentialUserEntityMixin.class); + context.setMixInAnnotations(PublicKeyCredentialRpEntity.class, PublicKeyCredentialRpEntityMixin.class); + context.setMixInAnnotations(PublicKeyCredentialParameters.class, PublicKeyCredentialParametersMixin.class); + context.setMixInAnnotations(PublicKeyCredentialDescriptor.class, PublicKeyCredentialDescriptorMixin.class); + } } diff --git a/web/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java b/web/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java index bff4498ccf5..45de0658fe9 100644 --- a/web/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java +++ b/web/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.skyscreamer.jsonassert.JSONAssert; @@ -52,6 +53,7 @@ class JacksonTests { void setup() { this.mapper = new ObjectMapper(); this.mapper.registerModule(new WebauthnJackson2Module()); + this.mapper.registerModule(new JavaTimeModule()); } @Test diff --git a/web/src/test/java/org/springframework/security/web/webauthn/jackson/MinPinLengthAuthenticationExtensionsClientInput.java b/web/src/test/java/org/springframework/security/web/webauthn/jackson/MinPinLengthAuthenticationExtensionsClientInput.java new file mode 100644 index 00000000000..fd4d5a77399 --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/webauthn/jackson/MinPinLengthAuthenticationExtensionsClientInput.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.webauthn.jackson; + +import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInput; + +/** + * Implements + * Minimum PIN Length Extension (minPinLength). + * + * @author Justin Cranford + * @since 6.5 + */ +record MinPinLengthAuthenticationExtensionsClientInput(Boolean getInput) implements AuthenticationExtensionsClientInput { + @Override + public String getExtensionId() { + return "minPinLength"; + } +} diff --git a/web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialCreationOptionsGivens.java b/web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialCreationOptionsGivens.java new file mode 100644 index 00000000000..5c5223af3f7 --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialCreationOptionsGivens.java @@ -0,0 +1,113 @@ +package org.springframework.security.web.webauthn.jackson; + +import org.springframework.security.web.webauthn.api.AttestationConveyancePreference; +import org.springframework.security.web.webauthn.api.AuthenticatorAttachment; +import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria; +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.CredProtectAuthenticationExtensionsClientInput.CredProtect; +import org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput.CredProtect.ProtectionPolicy; +import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInputs; +import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialParameters; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialType; +import org.springframework.security.web.webauthn.api.ResidentKeyRequirement; +import org.springframework.security.web.webauthn.api.UserVerificationRequirement; + +import java.time.Duration; +import java.util.List; +import java.util.Set; + +/** + * Object for {@code PublicKeyCredentialCreationOptions} + * + * @author Justin Cranford + * @since 6.5 + */ +public final class PublicKeyCredentialCreationOptionsGivens { + private PublicKeyCredentialCreationOptionsGivens() {} + + public static PublicKeyCredentialCreationOptions create() { + return PublicKeyCredentialCreationOptions.builder() + .rp( + PublicKeyCredentialRpEntity.builder() + .id("example.com") + .name("Example RP") + .build() + ) + .user( + ImmutablePublicKeyCredentialUserEntity.builder() + .name("name") + .id(Bytes.random()) + .displayName("displayName") + .build() + ) + .challenge(Bytes.random()) + .pubKeyCredParams( + List.of( + PublicKeyCredentialParameters.EdDSA, + PublicKeyCredentialParameters.ES256, + PublicKeyCredentialParameters.ES384, + PublicKeyCredentialParameters.ES512, + PublicKeyCredentialParameters.RS256, + PublicKeyCredentialParameters.RS384, + PublicKeyCredentialParameters.RS512, + PublicKeyCredentialParameters.RS1 + ) + ) + .timeout(Duration.ofSeconds(60)) + .excludeCredentials( + List.of( + PublicKeyCredentialDescriptor.builder() + .id(Bytes.random()) + .type(PublicKeyCredentialType.PUBLIC_KEY) + .transports(Set.of(AuthenticatorTransport.USB)) + .build(), + PublicKeyCredentialDescriptor.builder() + .id(Bytes.random()) + .type(PublicKeyCredentialType.PUBLIC_KEY) + .transports(Set.of(AuthenticatorTransport.NFC)) + .build(), + PublicKeyCredentialDescriptor.builder() + .id(Bytes.random()) + .type(PublicKeyCredentialType.PUBLIC_KEY) + .transports(Set.of(AuthenticatorTransport.BLE)) + .build(), + PublicKeyCredentialDescriptor.builder() + .id(Bytes.random()) + .type(PublicKeyCredentialType.PUBLIC_KEY) + .transports(Set.of(AuthenticatorTransport.SMART_CARD)) + .build(), + PublicKeyCredentialDescriptor.builder() + .id(Bytes.random()) + .type(PublicKeyCredentialType.PUBLIC_KEY) + .transports(Set.of(AuthenticatorTransport.HYBRID)) + .build(), + PublicKeyCredentialDescriptor.builder() + .id(Bytes.random()) + .type(PublicKeyCredentialType.PUBLIC_KEY) + .transports(Set.of(AuthenticatorTransport.INTERNAL)) + .build() + ) + ) + .authenticatorSelection( + AuthenticatorSelectionCriteria.builder() + .userVerification(UserVerificationRequirement.PREFERRED) + .residentKey(ResidentKeyRequirement.REQUIRED) + .authenticatorAttachment(AuthenticatorAttachment.PLATFORM) + .build() + ) + .attestation(AttestationConveyancePreference.DIRECT) + .extensions( + new ImmutableAuthenticationExtensionsClientInputs( + new CredProtectAuthenticationExtensionsClientInput(new CredProtect(ProtectionPolicy.USER_VERIFICATION_REQUIRED, true)), + new MinPinLengthAuthenticationExtensionsClientInput(true) + ) + ) + .build(); + } +} diff --git a/web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialCreationOptionsTests.java b/web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialCreationOptionsTests.java new file mode 100644 index 00000000000..5750f77425f --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialCreationOptionsTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.webauthn.jackson; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Test Jackson serialization and deserialization of PublicKeyCredentialCreationOptions + * + * @author Justin Cranford + * @since 6.5 + */ +class PublicKeyCredentialCreationOptionsTests { + + private ObjectMapper mapper; + + @BeforeEach + void setup() { + this.mapper = new ObjectMapper(); + this.mapper.registerModule(new WebauthnJackson2Module()); + this.mapper.registerModule(new JavaTimeModule()); + } + + @Test + public void testSerializeDeserialize() { + final PublicKeyCredentialCreationOptions given = PublicKeyCredentialCreationOptionsGivens.create(); + + final String serialized = assertDoesNotThrow(() -> this.mapper.writeValueAsString(given)); + //System.out.println("serialized:\n" + serialized + "\n\n"); + + final PublicKeyCredentialCreationOptions deserialized = assertDoesNotThrow(() -> this.mapper.readValue(serialized, PublicKeyCredentialCreationOptions.class)); + //System.out.println("deserialized:\n" + deserialized + "\n\n"); + + final String serializedAgain = assertDoesNotThrow(() -> this.mapper.writeValueAsString(deserialized)); + //System.out.println("serializedAgain:\n" + serializedAgain + "\n\n"); + + assertEquals(serialized, serializedAgain); + } +} diff --git a/web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRequestOptionsGivens.java b/web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRequestOptionsGivens.java new file mode 100644 index 00000000000..922694b3094 --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRequestOptionsGivens.java @@ -0,0 +1,75 @@ +package org.springframework.security.web.webauthn.jackson; + +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.CredProtectAuthenticationExtensionsClientInput.CredProtect; +import org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput.CredProtect.ProtectionPolicy; +import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInputs; +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.UserVerificationRequirement; + +import java.time.Duration; +import java.util.List; +import java.util.Set; + +/** + * Object for {@code PublicKeyCredentialRequestOptions} + * + * @author Justin Cranford + * @since 6.5 + */ +public final class PublicKeyCredentialRequestOptionsGivens { + private PublicKeyCredentialRequestOptionsGivens() {} + + public static PublicKeyCredentialRequestOptions create() { + return PublicKeyCredentialRequestOptions.builder() + .challenge(Bytes.random()) + .timeout(Duration.ofSeconds(60)) + .rpId("example.com") + .allowCredentials( + List.of( + PublicKeyCredentialDescriptor.builder() + .id(Bytes.random()) + .type(PublicKeyCredentialType.PUBLIC_KEY) + .transports(Set.of(AuthenticatorTransport.USB)) + .build(), + PublicKeyCredentialDescriptor.builder() + .id(Bytes.random()) + .type(PublicKeyCredentialType.PUBLIC_KEY) + .transports(Set.of(AuthenticatorTransport.NFC)) + .build(), + PublicKeyCredentialDescriptor.builder() + .id(Bytes.random()) + .type(PublicKeyCredentialType.PUBLIC_KEY) + .transports(Set.of(AuthenticatorTransport.BLE)) + .build(), + PublicKeyCredentialDescriptor.builder() + .id(Bytes.random()) + .type(PublicKeyCredentialType.PUBLIC_KEY) + .transports(Set.of(AuthenticatorTransport.SMART_CARD)) + .build(), + PublicKeyCredentialDescriptor.builder() + .id(Bytes.random()) + .type(PublicKeyCredentialType.PUBLIC_KEY) + .transports(Set.of(AuthenticatorTransport.HYBRID)) + .build(), + PublicKeyCredentialDescriptor.builder() + .id(Bytes.random()) + .type(PublicKeyCredentialType.PUBLIC_KEY) + .transports(Set.of(AuthenticatorTransport.INTERNAL)) + .build() + ) + ) + .userVerification(UserVerificationRequirement.PREFERRED) + .extensions( + new ImmutableAuthenticationExtensionsClientInputs( + new CredProtectAuthenticationExtensionsClientInput(new CredProtect(ProtectionPolicy.USER_VERIFICATION_REQUIRED, true)), + new MinPinLengthAuthenticationExtensionsClientInput(true) + ) + ) + .build(); + } +} diff --git a/web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRequestOptionsTests.java b/web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRequestOptionsTests.java new file mode 100644 index 00000000000..e44634c000a --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRequestOptionsTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.webauthn.jackson; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Test Jackson serialization and deserialization of PublicKeyCredentialRequestOptions + * + * @author Justin Cranford + * @since 6.5 + */ +class PublicKeyCredentialRequestOptionsTests { + + private ObjectMapper mapper; + + @BeforeEach + void setup() { + this.mapper = new ObjectMapper(); + this.mapper.registerModule(new WebauthnJackson2Module()); + this.mapper.registerModule(new JavaTimeModule()); + } + + @Test + public void testSerializeDeserialize() { + final PublicKeyCredentialRequestOptions given = PublicKeyCredentialRequestOptionsGivens.create(); + + final String serialized = assertDoesNotThrow(() -> this.mapper.writeValueAsString(given)); + //System.out.println("serialized:\n" + serialized + "\n\n"); + + final PublicKeyCredentialRequestOptions deserialized = assertDoesNotThrow(() -> this.mapper.readValue(serialized, PublicKeyCredentialRequestOptions.class)); + //System.out.println("deserialized:\n" + deserialized + "\n\n"); + + final String serializedAgain = assertDoesNotThrow(() -> this.mapper.writeValueAsString(deserialized)); + //System.out.println("serializedAgain:\n" + serializedAgain + "\n\n"); + + assertEquals(serialized, serializedAgain); + } +}