From 0a991a91ceffa92b64bb336d22689bf242b36cdd Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Wed, 3 Sep 2025 12:04:09 -0500 Subject: [PATCH] Enable Null checking in spring-security-webauthn via JSpecify Closes gh-17839 --- ...icKeyCredentialUserEntityRuntimeHints.java | 4 +- .../aot/UserCredentialRuntimeHints.java | 4 +- .../web/webauthn/aot/package-info.java | 23 +++++++ .../api/AuthenticatorAssertionResponse.java | 27 +++++--- .../api/AuthenticatorAttestationResponse.java | 12 ++-- .../api/AuthenticatorSelectionCriteria.java | 24 ++++--- .../security/web/webauthn/api/Bytes.java | 4 +- .../web/webauthn/api/CredentialRecord.java | 8 ++- .../api/ImmutableCredentialRecord.java | 33 +++++---- .../webauthn/api/ImmutablePublicKeyCose.java | 7 +- ...mmutablePublicKeyCredentialUserEntity.java | 12 ++-- .../web/webauthn/api/PublicKeyCredential.java | 29 +++++--- .../PublicKeyCredentialCreationOptions.java | 31 +++++---- .../api/PublicKeyCredentialDescriptor.java | 18 ++--- .../PublicKeyCredentialRequestOptions.java | 22 +++--- .../api/PublicKeyCredentialRpEntity.java | 10 ++- .../api/PublicKeyCredentialUserEntity.java | 4 +- .../web/webauthn/api/package-info.java | 23 +++++++ ...KeyCredentialRequestOptionsRepository.java | 5 +- ...KeyCredentialRequestOptionsRepository.java | 5 +- .../WebAuthnAuthentication.java | 4 +- .../WebAuthnAuthenticationRequestToken.java | 4 +- .../webauthn/authentication/package-info.java | 23 +++++++ .../AuthenticatorAttachmentDeserializer.java | 3 +- .../AuthenticatorTransportDeserializer.java | 3 +- .../COSEAlgorithmIdentifierDeserializer.java | 3 +- ...blicKeyCredentialCreationOptionsMixin.java | 3 +- ...ublicKeyCredentialRequestOptionsMixin.java | 3 +- .../web/webauthn/jackson/package-info.java | 23 +++++++ ...licKeyCredentialRequestOptionsRequest.java | 8 ++- ...utableRelyingPartyRegistrationRequest.java | 4 +- ...blicKeyCredentialUserEntityRepository.java | 6 +- .../JdbcUserCredentialRepository.java | 17 +++-- ...blicKeyCredentialUserEntityRepository.java | 6 +- .../MapUserCredentialRepository.java | 4 +- ...licKeyCredentialRequestOptionsRequest.java | 4 +- ...blicKeyCredentialUserEntityRepository.java | 6 +- .../RelyingPartyRegistrationRequest.java | 2 + .../management/UserCredentialRepository.java | 4 +- .../WebAuthnRelyingPartyOperations.java | 2 + .../Webauthn4JRelyingPartyOperations.java | 69 ++++++++++++------- .../web/webauthn/management/package-info.java | 23 +++++++ ...eyCredentialCreationOptionsRepository.java | 7 +- ...eyCredentialCreationOptionsRepository.java | 5 +- .../WebAuthnRegistrationFilter.java | 9 +-- .../webauthn/registration/package-info.java | 23 +++++++ 46 files changed, 421 insertions(+), 152 deletions(-) create mode 100644 webauthn/src/main/java/org/springframework/security/web/webauthn/aot/package-info.java create mode 100644 webauthn/src/main/java/org/springframework/security/web/webauthn/api/package-info.java create mode 100644 webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/package-info.java create mode 100644 webauthn/src/main/java/org/springframework/security/web/webauthn/jackson/package-info.java create mode 100644 webauthn/src/main/java/org/springframework/security/web/webauthn/management/package-info.java create mode 100644 webauthn/src/main/java/org/springframework/security/web/webauthn/registration/package-info.java diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/aot/PublicKeyCredentialUserEntityRuntimeHints.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/aot/PublicKeyCredentialUserEntityRuntimeHints.java index c1e6e94209..b57c182ee7 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/aot/PublicKeyCredentialUserEntityRuntimeHints.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/aot/PublicKeyCredentialUserEntityRuntimeHints.java @@ -16,6 +16,8 @@ package org.springframework.security.web.webauthn.aot; +import org.jspecify.annotations.Nullable; + import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.jdbc.core.JdbcOperations; @@ -33,7 +35,7 @@ import org.springframework.security.web.webauthn.management.PublicKeyCredentialU class PublicKeyCredentialUserEntityRuntimeHints implements RuntimeHintsRegistrar { @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { hints.resources().registerPattern("org/springframework/security/user-entities-schema.sql"); } diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/aot/UserCredentialRuntimeHints.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/aot/UserCredentialRuntimeHints.java index 50153db64a..9d503a80f9 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/aot/UserCredentialRuntimeHints.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/aot/UserCredentialRuntimeHints.java @@ -16,6 +16,8 @@ package org.springframework.security.web.webauthn.aot; +import org.jspecify.annotations.Nullable; + import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.jdbc.core.JdbcOperations; @@ -33,7 +35,7 @@ import org.springframework.security.web.webauthn.management.UserCredentialReposi class UserCredentialRuntimeHints implements RuntimeHintsRegistrar { @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { hints.resources() .registerPattern("org/springframework/security/user-credentials-schema.sql") .registerPattern("org/springframework/security/user-credentials-schema-postgres.sql"); diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/aot/package-info.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/aot/package-info.java new file mode 100644 index 0000000000..66fe269f01 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/aot/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2004-present 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. + */ + +/** + * WebAuthn AOT support. + */ +@NullMarked +package org.springframework.security.web.webauthn.aot; + +import org.jspecify.annotations.NullMarked; diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/AuthenticatorAssertionResponse.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/AuthenticatorAssertionResponse.java index d9af216306..e4b46e0ff3 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/AuthenticatorAssertionResponse.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/AuthenticatorAssertionResponse.java @@ -18,6 +18,10 @@ package org.springframework.security.web.webauthn.api; import java.io.Serial; +import org.jspecify.annotations.Nullable; + +import org.springframework.util.Assert; + /** * The AuthenticatorAssertionResponse @@ -47,12 +51,12 @@ public final class AuthenticatorAssertionResponse extends AuthenticatorResponse private final Bytes signature; - private final Bytes userHandle; + private final @Nullable Bytes userHandle; - private final Bytes attestationObject; + private final @Nullable Bytes attestationObject; private AuthenticatorAssertionResponse(Bytes clientDataJSON, Bytes authenticatorData, Bytes signature, - Bytes userHandle, Bytes attestationObject) { + @Nullable Bytes userHandle, @Nullable Bytes attestationObject) { super(clientDataJSON); this.authenticatorData = authenticatorData; this.signature = signature; @@ -101,7 +105,7 @@ public final class AuthenticatorAssertionResponse extends AuthenticatorResponse * ceremony is empty, and MAY return one otherwise. * @return the user handle */ - public Bytes getUserHandle() { + public @Nullable Bytes getUserHandle() { return this.userHandle; } @@ -113,7 +117,7 @@ public final class AuthenticatorAssertionResponse extends AuthenticatorResponse * object, if the authenticator supports attestation in assertions. * @return the {@code attestationObject} */ - public Bytes getAttestationObject() { + public @Nullable Bytes getAttestationObject() { return this.attestationObject; } @@ -133,15 +137,15 @@ public final class AuthenticatorAssertionResponse extends AuthenticatorResponse */ public static final class AuthenticatorAssertionResponseBuilder { - private Bytes authenticatorData; + private @Nullable Bytes authenticatorData; - private Bytes signature; + private @Nullable Bytes signature; - private Bytes userHandle; + private @Nullable Bytes userHandle; - private Bytes attestationObject; + private @Nullable Bytes attestationObject; - private Bytes clientDataJSON; + private @Nullable Bytes clientDataJSON; private AuthenticatorAssertionResponseBuilder() { } @@ -201,6 +205,9 @@ public final class AuthenticatorAssertionResponse extends AuthenticatorResponse * @return the {@link AuthenticatorAssertionResponse} */ public AuthenticatorAssertionResponse build() { + Assert.notNull(this.clientDataJSON, "clientDataJSON cannot be null"); + Assert.notNull(this.authenticatorData, "authenticatorData cannot be null"); + Assert.notNull(this.signature, "signature cannot be null"); return new AuthenticatorAssertionResponse(this.clientDataJSON, this.authenticatorData, this.signature, this.userHandle, this.attestationObject); } diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/AuthenticatorAttestationResponse.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/AuthenticatorAttestationResponse.java index 75123cb88f..a711f411b8 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/AuthenticatorAttestationResponse.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/AuthenticatorAttestationResponse.java @@ -19,6 +19,8 @@ package org.springframework.security.web.webauthn.api; import java.util.Arrays; import java.util.List; +import org.jspecify.annotations.Nullable; + /** * AuthenticatorAttestationResponse @@ -36,10 +38,10 @@ public final class AuthenticatorAttestationResponse extends AuthenticatorRespons private final Bytes attestationObject; - private final List transports; + private final @Nullable List transports; private AuthenticatorAttestationResponse(Bytes clientDataJSON, Bytes attestationObject, - List transports) { + @Nullable List transports) { super(clientDataJSON); this.attestationObject = attestationObject; this.transports = transports; @@ -63,7 +65,7 @@ public final class AuthenticatorAttestationResponse extends AuthenticatorRespons * "https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponse-transports-slot">transports * @return the transports */ - public List getTransports() { + public @Nullable List getTransports() { return this.transports; } @@ -83,10 +85,12 @@ public final class AuthenticatorAttestationResponse extends AuthenticatorRespons */ public static final class AuthenticatorAttestationResponseBuilder { + @SuppressWarnings("NullAway.Init") private Bytes attestationObject; - private List transports; + private @Nullable List transports; + @SuppressWarnings("NullAway.Init") private Bytes clientDataJSON; private AuthenticatorAttestationResponseBuilder() { diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/AuthenticatorSelectionCriteria.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/AuthenticatorSelectionCriteria.java index a1ec71c51d..d36ee1f6a6 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/AuthenticatorSelectionCriteria.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/AuthenticatorSelectionCriteria.java @@ -16,6 +16,8 @@ package org.springframework.security.web.webauthn.api; +import org.jspecify.annotations.Nullable; + /** * AuthenticatorAttachment @@ -33,11 +35,11 @@ package org.springframework.security.web.webauthn.api; */ public final class AuthenticatorSelectionCriteria { - private final AuthenticatorAttachment authenticatorAttachment; + private final @Nullable AuthenticatorAttachment authenticatorAttachment; - private final ResidentKeyRequirement residentKey; + private final @Nullable ResidentKeyRequirement residentKey; - private final UserVerificationRequirement userVerification; + private final @Nullable UserVerificationRequirement userVerification; // NOTE: There is no requireResidentKey property because it is only for backward // compatibility with WebAuthn Level 1 @@ -48,8 +50,8 @@ public final class AuthenticatorSelectionCriteria { * @param residentKey the resident key requirement * @param userVerification the user verification */ - private AuthenticatorSelectionCriteria(AuthenticatorAttachment authenticatorAttachment, - ResidentKeyRequirement residentKey, UserVerificationRequirement userVerification) { + private AuthenticatorSelectionCriteria(@Nullable AuthenticatorAttachment authenticatorAttachment, + @Nullable ResidentKeyRequirement residentKey, @Nullable UserVerificationRequirement userVerification) { this.authenticatorAttachment = authenticatorAttachment; this.residentKey = residentKey; this.userVerification = userVerification; @@ -67,7 +69,7 @@ public final class AuthenticatorSelectionCriteria { * Authenticator Attachment Modality). * @return the authenticator attachment */ - public AuthenticatorAttachment getAuthenticatorAttachment() { + public @Nullable AuthenticatorAttachment getAuthenticatorAttachment() { return this.authenticatorAttachment; } @@ -81,7 +83,7 @@ public final class AuthenticatorSelectionCriteria { * discoverable credential. * @return the resident key requirement */ - public ResidentKeyRequirement getResidentKey() { + public @Nullable ResidentKeyRequirement getResidentKey() { return this.residentKey; } @@ -96,7 +98,7 @@ public final class AuthenticatorSelectionCriteria { * operation. * @return the user verification requirement */ - public UserVerificationRequirement getUserVerification() { + public @Nullable UserVerificationRequirement getUserVerification() { return this.userVerification; } @@ -116,11 +118,11 @@ public final class AuthenticatorSelectionCriteria { */ public static final class AuthenticatorSelectionCriteriaBuilder { - private AuthenticatorAttachment authenticatorAttachment; + private @Nullable AuthenticatorAttachment authenticatorAttachment; - private ResidentKeyRequirement residentKey; + private @Nullable ResidentKeyRequirement residentKey; - private UserVerificationRequirement userVerification; + private @Nullable UserVerificationRequirement userVerification; private AuthenticatorSelectionCriteriaBuilder() { } diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/Bytes.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/Bytes.java index 519609e490..f3aa1a98e1 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/Bytes.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/Bytes.java @@ -22,6 +22,8 @@ import java.security.SecureRandom; import java.util.Arrays; import java.util.Base64; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; /** @@ -100,7 +102,7 @@ public final class Bytes implements Serializable { * @param base64UrlString the base64 url string * @return the {@link Bytes} */ - public static Bytes fromBase64(String base64UrlString) { + public static Bytes fromBase64(@Nullable String base64UrlString) { byte[] bytes = DECODER.decode(base64UrlString); return new Bytes(bytes); } diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/CredentialRecord.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/CredentialRecord.java index cc8d3b8a88..21c769b17c 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/CredentialRecord.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/CredentialRecord.java @@ -19,6 +19,8 @@ package org.springframework.security.web.webauthn.api; import java.time.Instant; import java.util.Set; +import org.jspecify.annotations.Nullable; + /** * Represents a Credential * Record that is stored by the Relying Party @@ -35,7 +37,7 @@ public interface CredentialRecord { * "https://www.w3.org/TR/webauthn-3/#abstract-opdef-credential-record-type">credential.type * @return */ - PublicKeyCredentialType getCredentialType(); + @Nullable PublicKeyCredentialType getCredentialType(); /** * The transports, boolean backupEligible, boolean backupState, - Bytes attestationObject, Bytes attestationClientDataJSON, Instant created, Instant lastUsed, String label) { + @Nullable Bytes attestationObject, @Nullable Bytes attestationClientDataJSON, Instant created, Instant lastUsed, String label) { this.credentialType = credentialType; this.credentialId = credentialId; this.userEntityUserId = userEntityUserId; @@ -76,7 +78,7 @@ public final class ImmutableCredentialRecord implements CredentialRecord { } @Override - public PublicKeyCredentialType getCredentialType() { + public @Nullable PublicKeyCredentialType getCredentialType() { return this.credentialType; } @@ -121,12 +123,12 @@ public final class ImmutableCredentialRecord implements CredentialRecord { } @Override - public Bytes getAttestationObject() { + public @Nullable Bytes getAttestationObject() { return this.attestationObject; } @Override - public Bytes getAttestationClientDataJSON() { + public @Nullable Bytes getAttestationClientDataJSON() { return this.attestationClientDataJSON; } @@ -155,32 +157,37 @@ public final class ImmutableCredentialRecord implements CredentialRecord { public static final class ImmutableCredentialRecordBuilder { - private PublicKeyCredentialType credentialType; + private @Nullable PublicKeyCredentialType credentialType; + @SuppressWarnings("NullAway.Init") private Bytes credentialId; + @SuppressWarnings("NullAway.Init") private Bytes userEntityUserId; + @SuppressWarnings("NullAway.Init") private PublicKeyCose publicKey; private long signatureCount; private boolean uvInitialized; + @SuppressWarnings("NullAway.Init") private Set transports; private boolean backupEligible; private boolean backupState; - private Bytes attestationObject; + private @Nullable Bytes attestationObject; - private Bytes attestationClientDataJSON; + private @Nullable Bytes attestationClientDataJSON; private Instant created = Instant.now(); private Instant lastUsed = this.created; + @SuppressWarnings("NullAway.Init") private String label; private ImmutableCredentialRecordBuilder() { @@ -248,12 +255,12 @@ public final class ImmutableCredentialRecord implements CredentialRecord { return this; } - public ImmutableCredentialRecordBuilder attestationObject(Bytes attestationObject) { + public ImmutableCredentialRecordBuilder attestationObject(@Nullable Bytes attestationObject) { this.attestationObject = attestationObject; return this; } - public ImmutableCredentialRecordBuilder attestationClientDataJSON(Bytes attestationClientDataJSON) { + public ImmutableCredentialRecordBuilder attestationClientDataJSON(@Nullable Bytes attestationClientDataJSON) { this.attestationClientDataJSON = attestationClientDataJSON; return this; } diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/ImmutablePublicKeyCose.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/ImmutablePublicKeyCose.java index 364da3f61c..97f29acdc7 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/ImmutablePublicKeyCose.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/ImmutablePublicKeyCose.java @@ -19,6 +19,10 @@ package org.springframework.security.web.webauthn.api; import java.util.Arrays; import java.util.Base64; +import org.jspecify.annotations.Nullable; + +import org.springframework.util.Assert; + /** * An immutable {@link PublicKeyCose} * @@ -33,7 +37,8 @@ public class ImmutablePublicKeyCose implements PublicKeyCose { * Creates a new instance. * @param bytes the raw bytes of the public key. */ - public ImmutablePublicKeyCose(byte[] bytes) { + public ImmutablePublicKeyCose(byte @Nullable [] bytes) { + Assert.notNull(bytes, "bytes cannot be null"); this.bytes = Arrays.copyOf(bytes, bytes.length); } diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/ImmutablePublicKeyCredentialUserEntity.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/ImmutablePublicKeyCredentialUserEntity.java index d4722cddd8..2ca0fd3a7d 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/ImmutablePublicKeyCredentialUserEntity.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/ImmutablePublicKeyCredentialUserEntity.java @@ -18,6 +18,8 @@ package org.springframework.security.web.webauthn.api; import java.io.Serial; +import org.jspecify.annotations.Nullable; + /** * PublicKeyCredentialUserEntity @@ -102,9 +104,9 @@ public final class ImmutablePublicKeyCredentialUserEntity implements PublicKeyCr * fits within 64 bytes. See 6.4.1 String Truncation about truncation and other * considerations. */ - private final String displayName; + private final @Nullable String displayName; - private ImmutablePublicKeyCredentialUserEntity(String name, Bytes id, String displayName) { + private ImmutablePublicKeyCredentialUserEntity(String name, Bytes id, @Nullable String displayName) { this.name = name; this.id = id; this.displayName = displayName; @@ -121,7 +123,7 @@ public final class ImmutablePublicKeyCredentialUserEntity implements PublicKeyCr } @Override - public String getDisplayName() { + public @Nullable String getDisplayName() { return this.displayName; } @@ -141,11 +143,13 @@ public final class ImmutablePublicKeyCredentialUserEntity implements PublicKeyCr */ public static final class PublicKeyCredentialUserEntityBuilder { + @SuppressWarnings("NullAway.Init") private String name; + @SuppressWarnings("NullAway.Init") private Bytes id; - private String displayName; + private @Nullable String displayName; private PublicKeyCredentialUserEntityBuilder() { } diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredential.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredential.java index baa7249691..aa2c76230e 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredential.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredential.java @@ -19,6 +19,10 @@ 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; + /** * PublicKeyCredential * contains the attributes that are returned to the caller when a new credential is @@ -40,13 +44,13 @@ public final class PublicKeyCredential implemen private final R response; - private final AuthenticatorAttachment authenticatorAttachment; + private final @Nullable AuthenticatorAttachment authenticatorAttachment; - private final AuthenticationExtensionsClientOutputs clientExtensionResults; + private final @Nullable AuthenticationExtensionsClientOutputs clientExtensionResults; private PublicKeyCredential(String id, PublicKeyCredentialType type, Bytes rawId, R response, - AuthenticatorAttachment authenticatorAttachment, - AuthenticationExtensionsClientOutputs clientExtensionResults) { + @Nullable AuthenticatorAttachment authenticatorAttachment, + @Nullable AuthenticationExtensionsClientOutputs clientExtensionResults) { this.id = id; this.type = type; this.rawId = rawId; @@ -107,7 +111,7 @@ public final class PublicKeyCredential implemen * navigator.credentials.get() methods successfully complete. * @return the authenticator attachment */ - public AuthenticatorAttachment getAuthenticatorAttachment() { + public @Nullable AuthenticatorAttachment getAuthenticatorAttachment() { return this.authenticatorAttachment; } @@ -117,7 +121,7 @@ public final class PublicKeyCredential implemen * is a mapping of extension identifier to client extension output. * @return the extension results */ - public AuthenticationExtensionsClientOutputs getClientExtensionResults() { + public @Nullable AuthenticationExtensionsClientOutputs getClientExtensionResults() { return this.clientExtensionResults; } @@ -139,17 +143,20 @@ public final class PublicKeyCredential implemen */ public static final class PublicKeyCredentialBuilder { + @SuppressWarnings("NullAway.Init") private String id; - private PublicKeyCredentialType type; + private @Nullable PublicKeyCredentialType type; + @SuppressWarnings("NullAway.Init") private Bytes rawId; + @SuppressWarnings("NullAway.Init") private R response; - private AuthenticatorAttachment authenticatorAttachment; + private @Nullable AuthenticatorAttachment authenticatorAttachment; - private AuthenticationExtensionsClientOutputs clientExtensionResults; + private @Nullable AuthenticationExtensionsClientOutputs clientExtensionResults; private PublicKeyCredentialBuilder() { } @@ -220,6 +227,10 @@ public final class PublicKeyCredential implemen * @return a new {@link PublicKeyCredential} */ public PublicKeyCredential build() { + Assert.notNull(this.id, "id cannot be null"); + Assert.notNull(this.type, "type cannot be null"); + Assert.notNull(this.rawId, "rawId cannot be null"); + Assert.notNull(this.response, "response cannot be null"); return new PublicKeyCredential(this.id, this.type, this.rawId, this.response, this.authenticatorAttachment, this.clientExtensionResults); } 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 d2ff095a76..5e5c0ccacb 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 @@ -22,6 +22,8 @@ import java.util.Arrays; import java.util.List; import java.util.function.Consumer; +import org.jspecify.annotations.Nullable; + /** * Represents the PublicKeyCredentialCreationOptions @@ -42,21 +44,22 @@ public final class PublicKeyCredentialCreationOptions { private final List pubKeyCredParams; - private final Duration timeout; + private final @Nullable Duration timeout; private final List excludeCredentials; private final AuthenticatorSelectionCriteria authenticatorSelection; - private final AttestationConveyancePreference attestation; + private final @Nullable AttestationConveyancePreference attestation; - private final AuthenticationExtensionsClientInputs extensions; + private final @Nullable AuthenticationExtensionsClientInputs extensions; private PublicKeyCredentialCreationOptions(PublicKeyCredentialRpEntity rp, PublicKeyCredentialUserEntity user, - Bytes challenge, List pubKeyCredParams, Duration timeout, + Bytes challenge, List pubKeyCredParams, @Nullable Duration timeout, List excludeCredentials, - AuthenticatorSelectionCriteria authenticatorSelection, AttestationConveyancePreference attestation, - AuthenticationExtensionsClientInputs extensions) { + AuthenticatorSelectionCriteria authenticatorSelection, + @Nullable AttestationConveyancePreference attestation, + @Nullable AuthenticationExtensionsClientInputs extensions) { this.rp = rp; this.user = user; this.challenge = challenge; @@ -117,7 +120,7 @@ public final class PublicKeyCredentialCreationOptions { * wait for the call to complete. * @return the timeout */ - public Duration getTimeout() { + public @Nullable Duration getTimeout() { return this.timeout; } @@ -150,7 +153,7 @@ public final class PublicKeyCredentialCreationOptions { * regarding attestation conveyance. * @return the attestation preference */ - public AttestationConveyancePreference getAttestation() { + public @Nullable AttestationConveyancePreference getAttestation() { return this.attestation; } @@ -161,7 +164,7 @@ public final class PublicKeyCredentialCreationOptions { * extension inputs requesting additional processing by the client and authenticator. * @return the extensions */ - public AuthenticationExtensionsClientInputs getExtensions() { + public @Nullable AuthenticationExtensionsClientInputs getExtensions() { return this.extensions; } @@ -181,23 +184,27 @@ public final class PublicKeyCredentialCreationOptions { */ public static final class PublicKeyCredentialCreationOptionsBuilder { + @SuppressWarnings("NullAway.Init") private PublicKeyCredentialRpEntity rp; + @SuppressWarnings("NullAway.Init") private PublicKeyCredentialUserEntity user; + @SuppressWarnings("NullAway.Init") private Bytes challenge; private List pubKeyCredParams = new ArrayList<>(); - private Duration timeout; + private @Nullable Duration timeout; private List excludeCredentials = new ArrayList<>(); + @SuppressWarnings("NullAway.Init") private AuthenticatorSelectionCriteria authenticatorSelection; - private AttestationConveyancePreference attestation; + private @Nullable AttestationConveyancePreference attestation; - private AuthenticationExtensionsClientInputs extensions; + private @Nullable AuthenticationExtensionsClientInputs extensions; private PublicKeyCredentialCreationOptionsBuilder() { } diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialDescriptor.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialDescriptor.java index 18153a6f3c..afa3802c73 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialDescriptor.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialDescriptor.java @@ -20,6 +20,8 @@ import java.io.Serial; import java.io.Serializable; import java.util.Set; +import org.jspecify.annotations.Nullable; + /** * PublicKeyCredentialDescriptor @@ -38,12 +40,12 @@ public final class PublicKeyCredentialDescriptor implements Serializable { private final PublicKeyCredentialType type; - private final Bytes id; + private final @Nullable Bytes id; - private final Set transports; + private final @Nullable Set transports; - private PublicKeyCredentialDescriptor(PublicKeyCredentialType type, Bytes id, - Set transports) { + private PublicKeyCredentialDescriptor(PublicKeyCredentialType type, @Nullable Bytes id, + @Nullable Set transports) { this.type = type; this.id = id; this.transports = transports; @@ -66,7 +68,7 @@ public final class PublicKeyCredentialDescriptor implements Serializable { * referring to. * @return the id */ - public Bytes getId() { + public @Nullable Bytes getId() { return this.id; } @@ -78,7 +80,7 @@ public final class PublicKeyCredentialDescriptor implements Serializable { * is referring to. * @return the transports */ - public Set getTransports() { + public @Nullable Set getTransports() { return this.transports; } @@ -100,9 +102,9 @@ public final class PublicKeyCredentialDescriptor implements Serializable { private PublicKeyCredentialType type = PublicKeyCredentialType.PUBLIC_KEY; - private Bytes id; + private @Nullable Bytes id; - private Set transports; + private @Nullable Set transports; private PublicKeyCredentialDescriptorBuilder() { } diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialRequestOptions.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialRequestOptions.java index 4bb861dd18..efb32c1e24 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialRequestOptions.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialRequestOptions.java @@ -24,6 +24,8 @@ import java.util.Collections; import java.util.List; import java.util.function.Consumer; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; /** @@ -43,17 +45,17 @@ public final class PublicKeyCredentialRequestOptions implements Serializable { private final Duration timeout; - private final String rpId; + private final @Nullable String rpId; private final List allowCredentials; - private final UserVerificationRequirement userVerification; + private final @Nullable UserVerificationRequirement userVerification; private final AuthenticationExtensionsClientInputs extensions; - private PublicKeyCredentialRequestOptions(Bytes challenge, Duration timeout, String rpId, - List allowCredentials, UserVerificationRequirement userVerification, - AuthenticationExtensionsClientInputs extensions) { + private PublicKeyCredentialRequestOptions(Bytes challenge, Duration timeout, @Nullable String rpId, + List allowCredentials, + @Nullable UserVerificationRequirement userVerification, AuthenticationExtensionsClientInputs extensions) { Assert.notNull(challenge, "challenge cannot be null"); Assert.hasText(rpId, "rpId cannot be empty"); this.challenge = challenge; @@ -93,7 +95,7 @@ public final class PublicKeyCredentialRequestOptions implements Serializable { * MUST verify that the Relying Party's origin matches the scope of this RP ID. * @return the relying party id */ - public String getRpId() { + public @Nullable String getRpId() { return this.rpId; } @@ -115,7 +117,7 @@ public final class PublicKeyCredentialRequestOptions implements Serializable { * user verification for the get() operation. * @return the user verification */ - public UserVerificationRequirement getUserVerification() { + public @Nullable UserVerificationRequirement getUserVerification() { return this.userVerification; } @@ -146,15 +148,15 @@ public final class PublicKeyCredentialRequestOptions implements Serializable { */ public static final class PublicKeyCredentialRequestOptionsBuilder { - private Bytes challenge; + private @Nullable Bytes challenge; private Duration timeout = Duration.ofMinutes(5); - private String rpId; + private @Nullable String rpId; private List allowCredentials = Collections.emptyList(); - private UserVerificationRequirement userVerification; + private @Nullable UserVerificationRequirement userVerification; private AuthenticationExtensionsClientInputs extensions = new ImmutableAuthenticationExtensionsClientInputs( new ArrayList<>()); 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 6d22ec45ce..4caf4f6eee 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,10 @@ package org.springframework.security.web.webauthn.api; +import org.jspecify.annotations.Nullable; + +import org.springframework.util.Assert; + /** * The PublicKeyCredentialRpEntity @@ -75,9 +79,9 @@ public final class PublicKeyCredentialRpEntity { */ public static final class PublicKeyCredentialRpEntityBuilder { - private String name; + private @Nullable String name; - private String id; + private @Nullable String id; private PublicKeyCredentialRpEntityBuilder() { } @@ -107,6 +111,8 @@ public final class PublicKeyCredentialRpEntity { * @return a new {@link PublicKeyCredentialRpEntity}. */ public PublicKeyCredentialRpEntity build() { + Assert.notNull(this.name, "name cannot be null"); + Assert.notNull(this.id, "id cannot be null"); return new PublicKeyCredentialRpEntity(this.name, this.id); } diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialUserEntity.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialUserEntity.java index bce1e8d649..269cc750e5 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialUserEntity.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialUserEntity.java @@ -18,6 +18,8 @@ package org.springframework.security.web.webauthn.api; import java.io.Serializable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.web.webauthn.management.RelyingPartyAuthenticationRequest; import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations; @@ -57,6 +59,6 @@ public interface PublicKeyCredentialUserEntity extends Serializable { * is a human-palatable name for the user account, intended only for display. * @return the display name */ - String getDisplayName(); + @Nullable String getDisplayName(); } diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/api/package-info.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/package-info.java new file mode 100644 index 0000000000..9679301c55 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/api/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2004-present 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. + */ + +/** + * WebAuthn APIs. + */ +@NullMarked +package org.springframework.security.web.webauthn.api; + +import org.jspecify.annotations.NullMarked; diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/HttpSessionPublicKeyCredentialRequestOptionsRepository.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/HttpSessionPublicKeyCredentialRequestOptionsRepository.java index c67a6b9387..0d19f14811 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/HttpSessionPublicKeyCredentialRequestOptionsRepository.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/HttpSessionPublicKeyCredentialRequestOptionsRepository.java @@ -19,6 +19,7 @@ package org.springframework.security.web.webauthn.authentication; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; +import org.jspecify.annotations.Nullable; import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions; import org.springframework.util.Assert; @@ -41,13 +42,13 @@ public class HttpSessionPublicKeyCredentialRequestOptionsRepository @Override public void save(HttpServletRequest request, HttpServletResponse response, - PublicKeyCredentialRequestOptions options) { + @Nullable PublicKeyCredentialRequestOptions options) { HttpSession session = request.getSession(); session.setAttribute(this.attrName, options); } @Override - public PublicKeyCredentialRequestOptions load(HttpServletRequest request) { + public @Nullable PublicKeyCredentialRequestOptions load(HttpServletRequest request) { HttpSession session = request.getSession(false); if (session == null) { return null; diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/PublicKeyCredentialRequestOptionsRepository.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/PublicKeyCredentialRequestOptionsRepository.java index 9df9d507ca..cd91b69fd6 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/PublicKeyCredentialRequestOptionsRepository.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/PublicKeyCredentialRequestOptionsRepository.java @@ -18,6 +18,7 @@ package org.springframework.security.web.webauthn.authentication; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions; @@ -38,7 +39,7 @@ public interface PublicKeyCredentialRequestOptionsRepository { * @param options the {@link PublicKeyCredentialRequestOptions} to save or null if an * existing {@link PublicKeyCredentialRequestOptions} should be removed. */ - void save(HttpServletRequest request, HttpServletResponse response, PublicKeyCredentialRequestOptions options); + void save(HttpServletRequest request, HttpServletResponse response, @Nullable PublicKeyCredentialRequestOptions options); /** * Gets a saved {@link PublicKeyCredentialRequestOptions} if it exists, otherwise @@ -47,6 +48,6 @@ public interface PublicKeyCredentialRequestOptionsRepository { * @return the {@link PublicKeyCredentialRequestOptions} that was saved, otherwise * null. */ - PublicKeyCredentialRequestOptions load(HttpServletRequest request); + @Nullable PublicKeyCredentialRequestOptions load(HttpServletRequest request); } diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthentication.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthentication.java index c44e648402..42007f9ece 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthentication.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthentication.java @@ -19,6 +19,8 @@ package org.springframework.security.web.webauthn.authentication; import java.io.Serial; import java.util.Collection; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity; @@ -53,7 +55,7 @@ public class WebAuthnAuthentication extends AbstractAuthenticationToken { } @Override - public Object getCredentials() { + public @Nullable Object getCredentials() { return null; } diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthenticationRequestToken.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthenticationRequestToken.java index 281f44fc41..e10aba38f8 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthenticationRequestToken.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthenticationRequestToken.java @@ -18,6 +18,8 @@ package org.springframework.security.web.webauthn.authentication; import java.io.Serial; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.web.webauthn.management.RelyingPartyAuthenticationRequest; @@ -68,7 +70,7 @@ public class WebAuthnAuthenticationRequestToken extends AbstractAuthenticationTo } @Override - public Object getPrincipal() { + public @Nullable Object getPrincipal() { return this.webAuthnRequest.getPublicKey().getResponse().getUserHandle(); } diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/package-info.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/package-info.java new file mode 100644 index 0000000000..ce8ae90ac3 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2004-present 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. + */ + +/** + * WebAuthn Authentication support. + */ +@NullMarked +package org.springframework.security.web.webauthn.authentication; + +import org.jspecify.annotations.NullMarked; diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorAttachmentDeserializer.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorAttachmentDeserializer.java index a66c798e17..895b594af6 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorAttachmentDeserializer.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorAttachmentDeserializer.java @@ -22,6 +22,7 @@ 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.jspecify.annotations.Nullable; import org.springframework.security.web.webauthn.api.AuthenticatorAttachment; @@ -39,7 +40,7 @@ class AuthenticatorAttachmentDeserializer extends StdDeserializer result = this.jdbcOperations.query(FIND_USER_BY_ID_SQL, this.userEntityRowMapper, id.toBase64UrlString()); @@ -113,7 +115,7 @@ public final class JdbcPublicKeyCredentialUserEntityRepository implements Public } @Override - public PublicKeyCredentialUserEntity findByUsername(String username) { + public @Nullable PublicKeyCredentialUserEntity findByUsername(String username) { Assert.hasText(username, "name cannot be null or empty"); List result = this.jdbcOperations.query(FIND_USER_BY_NAME_SQL, this.userEntityRowMapper, username); diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/management/JdbcUserCredentialRepository.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/management/JdbcUserCredentialRepository.java index 12195138f6..ee850a429c 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/management/JdbcUserCredentialRepository.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/management/JdbcUserCredentialRepository.java @@ -28,6 +28,8 @@ import java.util.List; import java.util.Set; import java.util.function.Function; +import org.jspecify.annotations.Nullable; + import org.springframework.jdbc.core.ArgumentPreparedStatementSetter; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.PreparedStatementSetter; @@ -171,7 +173,7 @@ public final class JdbcUserCredentialRepository implements UserCredentialReposit } @Override - public CredentialRecord findByCredentialId(Bytes credentialId) { + public @Nullable CredentialRecord findByCredentialId(Bytes credentialId) { Assert.notNull(credentialId, "credentialId cannot be null"); List result = this.jdbcOperations.query(FIND_CREDENTIAL_RECORD_BY_ID_SQL, this.credentialRecordRowMapper, credentialId.toBase64UrlString()); @@ -238,7 +240,7 @@ public final class JdbcUserCredentialRepository implements UserCredentialReposit return parameters; } - private Timestamp fromInstant(Instant instant) { + private @Nullable Timestamp fromInstant(Instant instant) { if (instant == null) { return null; } @@ -249,13 +251,13 @@ public final class JdbcUserCredentialRepository implements UserCredentialReposit private interface SetBytes { - void setBytes(PreparedStatement ps, int index, byte[] bytes) throws SQLException; + void setBytes(PreparedStatement ps, int index, byte @Nullable [] bytes) throws SQLException; } private interface GetBytes { - byte[] getBytes(ResultSet rs, String columnName) throws SQLException; + byte @Nullable [] getBytes(ResultSet rs, String columnName) throws SQLException; } @@ -269,7 +271,8 @@ public final class JdbcUserCredentialRepository implements UserCredentialReposit } @Override - protected void doSetValue(PreparedStatement ps, int parameterPosition, Object argValue) throws SQLException { + protected void doSetValue(PreparedStatement ps, int parameterPosition, @Nullable Object argValue) + throws SQLException { if (argValue instanceof SqlParameterValue paramValue) { if (paramValue.getSqlType() == Types.BLOB) { if (paramValue.getValue() != null) { @@ -327,6 +330,8 @@ public final class JdbcUserCredentialRepository implements UserCredentialReposit for (String transport : transports) { authenticatorTransports.add(AuthenticatorTransport.valueOf(transport)); } + Assert.notNull(lastUsed, "last_used cannot be null"); + Assert.notNull(created, "created cannot be null"); return ImmutableCredentialRecord.builder() .credentialId(credentialId) .userEntityUserId(userEntityUserId) @@ -345,7 +350,7 @@ public final class JdbcUserCredentialRepository implements UserCredentialReposit .build(); } - private Instant fromTimestamp(Timestamp timestamp) { + private @Nullable Instant fromTimestamp(Timestamp timestamp) { if (timestamp == null) { return null; } diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/management/MapPublicKeyCredentialUserEntityRepository.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/management/MapPublicKeyCredentialUserEntityRepository.java index 865fd4ebd6..022eca16c7 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/management/MapPublicKeyCredentialUserEntityRepository.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/management/MapPublicKeyCredentialUserEntityRepository.java @@ -19,6 +19,8 @@ package org.springframework.security.web.webauthn.management; import java.util.HashMap; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.security.web.webauthn.api.Bytes; import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity; import org.springframework.util.Assert; @@ -36,13 +38,13 @@ public class MapPublicKeyCredentialUserEntityRepository implements PublicKeyCred private final Map idToUserEntity = new HashMap<>(); @Override - public PublicKeyCredentialUserEntity findById(Bytes id) { + public @Nullable PublicKeyCredentialUserEntity findById(Bytes id) { Assert.notNull(id, "id cannot be null"); return this.idToUserEntity.get(id); } @Override - public PublicKeyCredentialUserEntity findByUsername(String username) { + public @Nullable PublicKeyCredentialUserEntity findByUsername(String username) { Assert.notNull(username, "username cannot be null"); return this.usernameToUserEntity.get(username); } diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/management/MapUserCredentialRepository.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/management/MapUserCredentialRepository.java index 4ac32c93eb..59009dc4ab 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/management/MapUserCredentialRepository.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/management/MapUserCredentialRepository.java @@ -24,6 +24,8 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; + import org.springframework.security.web.webauthn.api.Bytes; import org.springframework.security.web.webauthn.api.CredentialRecord; import org.springframework.util.Assert; @@ -62,7 +64,7 @@ public class MapUserCredentialRepository implements UserCredentialRepository { } @Override - public CredentialRecord findByCredentialId(Bytes credentialId) { + public @Nullable CredentialRecord findByCredentialId(Bytes credentialId) { Assert.notNull(credentialId, "credentialId cannot be null"); return this.credentialIdToUserCredential.get(credentialId); } diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/management/PublicKeyCredentialRequestOptionsRequest.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/management/PublicKeyCredentialRequestOptionsRequest.java index 5f92903a35..8c6d70c074 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/management/PublicKeyCredentialRequestOptionsRequest.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/management/PublicKeyCredentialRequestOptionsRequest.java @@ -16,6 +16,8 @@ package org.springframework.security.web.webauthn.management; +import org.jspecify.annotations.Nullable; + import org.springframework.security.core.Authentication; public interface PublicKeyCredentialRequestOptionsRequest { @@ -24,6 +26,6 @@ public interface PublicKeyCredentialRequestOptionsRequest { * The current {@link Authentication}. Possibly null or an anonymous. * @return the current {@link Authentication} */ - Authentication getAuthentication(); + @Nullable Authentication getAuthentication(); } diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/management/PublicKeyCredentialUserEntityRepository.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/management/PublicKeyCredentialUserEntityRepository.java index b43c019398..28d9384e50 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/management/PublicKeyCredentialUserEntityRepository.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/management/PublicKeyCredentialUserEntityRepository.java @@ -16,6 +16,8 @@ package org.springframework.security.web.webauthn.management; +import org.jspecify.annotations.Nullable; + import org.springframework.security.web.webauthn.api.Bytes; import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity; @@ -33,14 +35,14 @@ public interface PublicKeyCredentialUserEntityRepository { * @param id the id to lookup the username by * @return the username or null if not found. */ - PublicKeyCredentialUserEntity findById(Bytes id); + @Nullable PublicKeyCredentialUserEntity findById(Bytes id); /** * Finds the {@link PublicKeyCredentialUserEntity} by the username. * @param username the username to lookup the {@link PublicKeyCredentialUserEntity} * @return the {@link PublicKeyCredentialUserEntity} or null if not found. */ - PublicKeyCredentialUserEntity findByUsername(String username); + @Nullable PublicKeyCredentialUserEntity findByUsername(String username); /** * Saves the {@link PublicKeyCredentialUserEntity} to the associated username. diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/management/RelyingPartyRegistrationRequest.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/management/RelyingPartyRegistrationRequest.java index 363ca0a0eb..2229ffa8b2 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/management/RelyingPartyRegistrationRequest.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/management/RelyingPartyRegistrationRequest.java @@ -16,6 +16,8 @@ package org.springframework.security.web.webauthn.management; +import org.jspecify.annotations.Nullable; + import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions; /** diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/management/UserCredentialRepository.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/management/UserCredentialRepository.java index f4acc8ee20..5749ed536a 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/management/UserCredentialRepository.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/management/UserCredentialRepository.java @@ -18,6 +18,8 @@ package org.springframework.security.web.webauthn.management; import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.security.web.webauthn.api.Bytes; import org.springframework.security.web.webauthn.api.CredentialRecord; import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity; @@ -47,7 +49,7 @@ public interface UserCredentialRepository { * @param credentialId {@link CredentialRecord#getCredentialId()} * @return the {@link CredentialRecord} or null if not found. */ - CredentialRecord findByCredentialId(Bytes credentialId); + @Nullable CredentialRecord findByCredentialId(Bytes credentialId); /** * Finds all {@link CredentialRecord} instances for a specific user. diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/management/WebAuthnRelyingPartyOperations.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/management/WebAuthnRelyingPartyOperations.java index 0e8222dbd9..fda3ddf560 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/management/WebAuthnRelyingPartyOperations.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/management/WebAuthnRelyingPartyOperations.java @@ -16,6 +16,8 @@ package org.springframework.security.web.webauthn.management; +import org.jspecify.annotations.Nullable; + import org.springframework.security.core.Authentication; import org.springframework.security.web.webauthn.api.CredentialRecord; import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions; diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/management/Webauthn4JRelyingPartyOperations.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/management/Webauthn4JRelyingPartyOperations.java index d645118a58..f476bee8e5 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/management/Webauthn4JRelyingPartyOperations.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/management/Webauthn4JRelyingPartyOperations.java @@ -43,8 +43,11 @@ import com.webauthn4j.data.attestation.authenticator.COSEKey; import com.webauthn4j.data.client.Origin; import com.webauthn4j.data.client.challenge.Challenge; import com.webauthn4j.data.client.challenge.DefaultChallenge; +import com.webauthn4j.data.extension.authenticator.AuthenticationExtensionAuthenticatorOutput; import com.webauthn4j.data.extension.authenticator.RegistrationExtensionAuthenticatorOutput; import com.webauthn4j.server.ServerProperty; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.authentication.AuthenticationTrustResolverImpl; @@ -260,24 +263,28 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe transports); RegistrationParameters registrationParameters = new RegistrationParameters(serverProperty, pubKeyCredParams, userVerificationRequired, userPresenceRequired); - RegistrationData registrationData = this.webAuthnManager.validate(webauthn4jRegistrationRequest, + RegistrationData wa4jRegistrationData = this.webAuthnManager.validate(webauthn4jRegistrationRequest, registrationParameters); - AuthenticatorData authData = registrationData.getAttestationObject() + AttestationObject wa4jAttestationObject = wa4jRegistrationData.getAttestationObject(); + Assert.notNull(wa4jAttestationObject, "attestationObject cannot be null"); + AuthenticatorData wa4jAuthData = wa4jAttestationObject .getAuthenticatorData(); CborConverter cborConverter = this.objectConverter.getCborConverter(); - COSEKey coseKey = authData.getAttestedCredentialData().getCOSEKey(); + AttestedCredentialData wa4jCredData = wa4jAuthData.getAttestedCredentialData(); + Assert.notNull(wa4jCredData, "attestedCredentialData cannot be null"); + COSEKey coseKey = wa4jCredData.getCOSEKey(); byte[] rawCoseKey = cborConverter.writeValueAsBytes(coseKey); ImmutableCredentialRecord userCredential = ImmutableCredentialRecord.builder() .userEntityUserId(creationOptions.getUser().getId()) .credentialType(credential.getType()) .credentialId(credential.getRawId()) .publicKey(new ImmutablePublicKeyCose(rawCoseKey)) - .signatureCount(authData.getSignCount()) - .uvInitialized(authData.isFlagUV()) - .transports(convertTransports(registrationData.getTransports())) - .backupEligible(authData.isFlagBE()) - .backupState(authData.isFlagBS()) + .signatureCount(wa4jAuthData.getSignCount()) + .uvInitialized(wa4jAuthData.isFlagUV()) + .transports(convertTransports(wa4jRegistrationData.getTransports())) + .backupEligible(wa4jAuthData.isFlagBE()) + .backupState(wa4jAuthData.isFlagBS()) .label(publicKey.getLabel()) .attestationClientDataJSON(credential.getResponse().getClientDataJSON()) .attestationObject(credential.getResponse().getAttestationObject()) @@ -286,7 +293,7 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe return userCredential; } - private static Set convertTransportsToString(AuthenticatorAttestationResponse response) { + private static @Nullable Set convertTransportsToString(AuthenticatorAttestationResponse response) { if (response.getTransports() == null) { return null; } @@ -320,7 +327,7 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe } private static Set convertTransports( - Set transports) { + @Nullable Set transports) { if (transports == null) { return Collections.emptySet(); } @@ -344,7 +351,8 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe .build(); } - private List findCredentialRecords(Authentication authentication) { + @NullUnmarked + private List findCredentialRecords(@Nullable Authentication authentication) { if (!this.trustResolver.isAuthenticated(authentication)) { return Collections.emptyList(); } @@ -361,22 +369,30 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe AuthenticatorAssertionResponse assertionResponse = request.getPublicKey().getResponse(); Bytes keyId = request.getPublicKey().getRawId(); CredentialRecord credentialRecord = this.userCredentials.findByCredentialId(keyId); - + if (credentialRecord == null) { + throw new IllegalArgumentException("Unable to find CredentialRecord with id " + keyId); + } CborConverter cborConverter = this.objectConverter.getCborConverter(); - AttestationObject attestationObject = cborConverter - .readValue(credentialRecord.getAttestationObject().getBytes(), AttestationObject.class); + Bytes attestationObject = credentialRecord.getAttestationObject(); + Assert.notNull(attestationObject, "attestationObject cannot be null"); + AttestationObject wa4jAttestationObject = cborConverter + .readValue(attestationObject.getBytes(), AttestationObject.class); + Assert.notNull(wa4jAttestationObject, "attestationObject cannot be null"); + AuthenticatorData wa4jAuthData = wa4jAttestationObject.getAuthenticatorData(); + AttestedCredentialData wa4jCredData = wa4jAuthData.getAttestedCredentialData(); + Assert.notNull(wa4jCredData, "attestedCredentialData cannot be null"); + AttestedCredentialData data = new AttestedCredentialData(wa4jCredData.getAaguid(), + keyId.getBytes(), wa4jCredData.getCOSEKey()); - AuthenticatorData authData = attestationObject.getAuthenticatorData(); - AttestedCredentialData data = new AttestedCredentialData(authData.getAttestedCredentialData().getAaguid(), - keyId.getBytes(), authData.getAttestedCredentialData().getCOSEKey()); - - Authenticator authenticator = new AuthenticatorImpl(data, attestationObject.getAttestationStatement(), + Authenticator authenticator = new AuthenticatorImpl(data, wa4jAttestationObject.getAttestationStatement(), credentialRecord.getSignatureCount()); Set origins = toOrigins(); Challenge challenge = new DefaultChallenge(requestOptions.getChallenge().getBytes()); // FIXME: should populate this byte[] tokenBindingId = null /* set tokenBindingId */; - ServerProperty serverProperty = new ServerProperty(origins, requestOptions.getRpId(), challenge, + String rpId = requestOptions.getRpId(); + Assert.notNull(rpId, "rpId cannot be null"); + ServerProperty serverProperty = new ServerProperty(origins, rpId, challenge, tokenBindingId); boolean userVerificationRequired = request.getRequestOptions() .getUserVerification() == UserVerificationRequirement.REQUIRED; @@ -387,17 +403,24 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe AuthenticationParameters authenticationParameters = new AuthenticationParameters(serverProperty, authenticator, userVerificationRequired); - AuthenticationData authenticationData = this.webAuthnManager.validate(authenticationRequest, + AuthenticationData wa4jAuthenticationData = this.webAuthnManager.validate(authenticationRequest, authenticationParameters); - long updatedSignCount = authenticationData.getAuthenticatorData().getSignCount(); + AuthenticatorData wa4jValidatedAuthData = wa4jAuthenticationData.getAuthenticatorData(); + Assert.notNull(wa4jValidatedAuthData, "authenticatorData cannot be null"); + long updatedSignCount = wa4jValidatedAuthData.getSignCount(); ImmutableCredentialRecord updatedRecord = ImmutableCredentialRecord.fromCredentialRecord(credentialRecord) .lastUsed(Instant.now()) .signatureCount(updatedSignCount) .build(); this.userCredentials.save(updatedRecord); - return this.userEntities.findById(credentialRecord.getUserEntityUserId()); + PublicKeyCredentialUserEntity userEntity = this.userEntities.findById( + credentialRecord.getUserEntityUserId()); + if (userEntity == null) { + throw new IllegalArgumentException("Unable to find UserEntity with id " + credentialRecord.getUserEntityUserId() + " for " + request); + } + return userEntity; } } diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/management/package-info.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/management/package-info.java new file mode 100644 index 0000000000..c24a1a0615 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/management/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2004-present 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. + */ + +/** + * Management of the WebAuthn APIs. + */ +@NullMarked +package org.springframework.security.web.webauthn.management; + +import org.jspecify.annotations.NullMarked; diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/registration/HttpSessionPublicKeyCredentialCreationOptionsRepository.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/registration/HttpSessionPublicKeyCredentialCreationOptionsRepository.java index 393d241177..6ee8d66d3d 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/registration/HttpSessionPublicKeyCredentialCreationOptionsRepository.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/registration/HttpSessionPublicKeyCredentialCreationOptionsRepository.java @@ -19,8 +19,10 @@ package org.springframework.security.web.webauthn.registration; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; +import org.jspecify.annotations.Nullable; import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions; import org.springframework.util.Assert; public class HttpSessionPublicKeyCredentialCreationOptionsRepository @@ -32,11 +34,12 @@ public class HttpSessionPublicKeyCredentialCreationOptionsRepository @Override public void save(HttpServletRequest request, HttpServletResponse response, - PublicKeyCredentialCreationOptions options) { + @Nullable PublicKeyCredentialCreationOptions options) { request.getSession().setAttribute(this.attrName, options); } - public PublicKeyCredentialCreationOptions load(HttpServletRequest request) { + + public @Nullable PublicKeyCredentialCreationOptions load(HttpServletRequest request) { HttpSession session = request.getSession(false); if (session == null) { return null; diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsRepository.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsRepository.java index 6aaeb88eb0..5165e55010 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsRepository.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsRepository.java @@ -18,6 +18,7 @@ package org.springframework.security.web.webauthn.registration; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions; @@ -38,7 +39,7 @@ public interface PublicKeyCredentialCreationOptionsRepository { * @param options the {@link PublicKeyCredentialCreationOptions} to save or null if an * existing {@link PublicKeyCredentialCreationOptions} should be removed. */ - void save(HttpServletRequest request, HttpServletResponse response, PublicKeyCredentialCreationOptions options); + void save(HttpServletRequest request, HttpServletResponse response, @Nullable PublicKeyCredentialCreationOptions options); /** * Gets a saved {@link PublicKeyCredentialCreationOptions} if it exists, otherwise @@ -47,6 +48,6 @@ public interface PublicKeyCredentialCreationOptionsRepository { * @return the {@link PublicKeyCredentialCreationOptions} that was saved, otherwise * null. */ - PublicKeyCredentialCreationOptions load(HttpServletRequest request); + @Nullable PublicKeyCredentialCreationOptions load(HttpServletRequest request); } diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/registration/WebAuthnRegistrationFilter.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/registration/WebAuthnRegistrationFilter.java index 46694fca03..c6359d0c4c 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/registration/WebAuthnRegistrationFilter.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/registration/WebAuthnRegistrationFilter.java @@ -25,6 +25,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpMethod; @@ -190,7 +191,7 @@ public class WebAuthnRegistrationFilter extends OncePerRequestFilter { this.converter.write(registrationResponse, MediaType.APPLICATION_JSON, outputMessage); } - private WebAuthnRegistrationRequest readRegistrationRequest(HttpServletRequest request) { + private @Nullable WebAuthnRegistrationRequest readRegistrationRequest(HttpServletRequest request) { HttpInputMessage inputMessage = new ServletServerHttpRequest(request); try { return (WebAuthnRegistrationRequest) this.converter.read(WebAuthnRegistrationRequest.class, inputMessage); @@ -201,7 +202,7 @@ public class WebAuthnRegistrationFilter extends OncePerRequestFilter { } } - private void removeCredential(HttpServletRequest request, HttpServletResponse response, String id) + private void removeCredential(HttpServletRequest request, HttpServletResponse response, @Nullable String id) throws IOException { this.userCredentials.delete(Bytes.fromBase64(id)); response.setStatus(HttpStatus.NO_CONTENT.value()); @@ -209,9 +210,9 @@ public class WebAuthnRegistrationFilter extends OncePerRequestFilter { static class WebAuthnRegistrationRequest { - private RelyingPartyPublicKey publicKey; + private @Nullable RelyingPartyPublicKey publicKey; - RelyingPartyPublicKey getPublicKey() { + @Nullable RelyingPartyPublicKey getPublicKey() { return this.publicKey; } diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/registration/package-info.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/registration/package-info.java new file mode 100644 index 0000000000..639a9aefa9 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/registration/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2004-present 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. + */ + +/** + * WebAuthn Registration support. + */ +@NullMarked +package org.springframework.security.web.webauthn.registration; + +import org.jspecify.annotations.NullMarked;