Skip to content

Commit

Permalink
Add serialize/deserialize PublicKeyCredentialCreationOptions and Publ…
Browse files Browse the repository at this point in the history
…icKeyCredentialRequestOptions support with ObjectMapper

gh-16433 PublicKeyCredentialRequestOptions
gh-16434 PublicKeyCredentialCreationOptions
  • Loading branch information
justincranford committed Jan 20, 2025
1 parent d3332e1 commit bab5868
Show file tree
Hide file tree
Showing 38 changed files with 1,186 additions and 90 deletions.
1 change: 1 addition & 0 deletions web/spring-security-web.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 };
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
* used to identify a cryptographic algorithm.
*
* @author Rob Winch
* @author Justin Cranford
* @since 6.4
* @see PublicKeyCredentialParameters#getAlg()
*/
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
*/
Expand Down Expand Up @@ -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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
}

}
Original file line number Diff line number Diff line change
@@ -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<AuthenticationExtensionsClientInput> {

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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

}
Original file line number Diff line number Diff line change
@@ -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<AuthenticationExtensionsClientInputs> {

AuthenticationExtensionsClientInputsDeserializer() {
super(AuthenticationExtensionsClientInputs.class);
}

@Override
public AuthenticationExtensionsClientInputs deserialize(JsonParser parser, DeserializationContext ctxt)
throws IOException {
final AuthenticationExtensionsClientInputDeserializer authenticationExtensionsClientInputDeserializer = new AuthenticationExtensionsClientInputDeserializer();

final List<AuthenticationExtensionsClientInput> extensions = new ArrayList<>();
while (parser.nextToken() != JsonToken.END_OBJECT) {
extensions.add(authenticationExtensionsClientInputDeserializer.deserialize(parser, ctxt));
}
return new ImmutableAuthenticationExtensionsClientInputs(extensions);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -27,6 +28,7 @@
* @since 6.4
*/
@JsonSerialize(using = AuthenticationExtensionsClientInputsSerializer.class)
@JsonDeserialize(using = AuthenticationExtensionsClientInputsDeserializer.class)
class AuthenticationExtensionsClientInputsMixin {

}
Original file line number Diff line number Diff line change
@@ -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<AuthenticatorSelectionCriteria> {

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();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down
Loading

0 comments on commit bab5868

Please sign in to comment.