From 34a1ec4fb3022e76ed4b0f3c9caf79d28d85c6bd Mon Sep 17 00:00:00 2001 From: Sander Kondratjev Date: Fri, 3 Jul 2026 15:10:00 +0300 Subject: [PATCH 1/2] NFC-176 Restrict supported Web eID token formats Signed-off-by: Sander Kondratjev sander.kondratjev@nortal.com Signed-off-by: Sander Kondratjev --- README.md | 2 +- .../AuthTokenVersion11Validator.java | 6 +++--- .../AuthTokenVersion1Validator.java | 8 ++------ .../AuthTokenVersion1ValidatorTest.java | 18 +++++++++++++++--- 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 8fd9f162..6d9dde3d 100644 --- a/README.md +++ b/README.md @@ -317,7 +317,7 @@ It contains the following fields: - `signature`: the base64-encoded signature of the token (see the description below), -- `format`: the type identifier and version of the token format separated by a colon character '`:`', `web-eid:1.0` as of now; the version number consists of the major and minor number separated by a dot, major version changes are incompatible with previous versions, minor version changes are backwards-compatible within the given major version, +- `format`: the type identifier and version of the token format separated by a colon character '`:`'. While minor version changes are intended to be backwards-compatible within the same major version, this validation library accepts only explicitly supported token format versions. - `appVersion`: the URL identifying the name and version of the application that issued the token; informative purpose, can be used to identify the affected application in case of faulty tokens. diff --git a/src/main/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion11Validator.java b/src/main/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion11Validator.java index db39fade..03280fa9 100644 --- a/src/main/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion11Validator.java +++ b/src/main/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion11Validator.java @@ -60,7 +60,7 @@ class AuthTokenVersion11Validator extends AuthTokenVersion1Validator implements AuthTokenVersionValidator { - private static final Pattern V11_SUPPORTED_TOKEN_FORMAT_PATTERN = Pattern.compile("^web-eid:1\\.1$"); + private static final String V11_SUPPORTED_TOKEN_FORMAT_PREFIX = "web-eid:1.1"; private static final Set SUPPORTED_SIGNING_CRYPTO_ALGORITHMS = Set.of("ECC", "RSA"); private static final Set SUPPORTED_SIGNING_PADDING_SCHEMES = Set.of("NONE", "PKCS1.5", "PSS"); private static final Set SUPPORTED_SIGNING_HASH_FUNCTIONS = Set.of( @@ -95,8 +95,8 @@ public AuthTokenVersion11Validator( } @Override - protected Pattern getSupportedFormatPattern() { - return V11_SUPPORTED_TOKEN_FORMAT_PATTERN; + public boolean supports(String format) { + return V11_SUPPORTED_TOKEN_FORMAT_PREFIX.equals(format); } @Override diff --git a/src/main/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion1Validator.java b/src/main/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion1Validator.java index f2c2105a..5b818c62 100644 --- a/src/main/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion1Validator.java +++ b/src/main/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion1Validator.java @@ -41,7 +41,6 @@ class AuthTokenVersion1Validator implements AuthTokenVersionValidator { private static final String V1_SUPPORTED_TOKEN_FORMAT_PREFIX = "web-eid:1"; - private static final Pattern V1_SUPPORTED_TOKEN_FORMAT_PATTERN = Pattern.compile("^web-eid:1(?:\\.\\d+)?$"); private final SubjectCertificateValidatorBatch simpleSubjectCertificateValidators; private final Set trustedCACertificateAnchors; private final CertStore trustedCACertificateCertStore; @@ -70,11 +69,8 @@ public AuthTokenVersion1Validator( @Override public boolean supports(String format) { - return format != null && getSupportedFormatPattern().matcher(format).matches(); - } - - protected Pattern getSupportedFormatPattern() { - return V1_SUPPORTED_TOKEN_FORMAT_PATTERN; + return V1_SUPPORTED_TOKEN_FORMAT_PREFIX.equals(format) + || "web-eid:1.0".equals(format); } @Override diff --git a/src/test/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion1ValidatorTest.java b/src/test/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion1ValidatorTest.java index 271f5ed3..02cf4722 100644 --- a/src/test/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion1ValidatorTest.java +++ b/src/test/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion1ValidatorTest.java @@ -61,14 +61,26 @@ class AuthTokenVersion1ValidatorTest { ); @ParameterizedTest - @ValueSource(strings = {"web-eid:1", "web-eid:1.0", "web-eid:1.1", "web-eid:1.10", "web-eid:1.999"}) - void whenFormatIsValidMajorV1Format_thenSupportsReturnsTrue(String format) { + @ValueSource(strings = {"web-eid:1", "web-eid:1.0"}) + void whenFormatIsSupportedV1_thenSupportsReturnsTrue(String format) { assertThat(validator.supports(format)).isTrue(); } @ParameterizedTest @NullAndEmptySource - @ValueSource(strings = {"web-eid", "web-eid:1.", "web-eid:1.0TEST", "web-eid:1.1.0", "web-eid:0.9", "web-eid:2", "webauthn:1"}) + @ValueSource(strings = { + "web-eid", + "web-eid:1.", + "web-eid:1.0TEST", + "web-eid:1.1", + "web-eid:1.1.0", + "web-eid:1.2", + "web-eid:1.10", + "web-eid:1.999", + "web-eid:0.9", + "web-eid:2", + "webauthn:1" + }) void whenFormatIsNullEmptyOrMalformedOrNotV1_thenSupportsReturnsFalse(String format) { assertThat(validator.supports(format)).isFalse(); } From 0f37296ebbdeacb195bda921526f126ae4572d21 Mon Sep 17 00:00:00 2001 From: Mart Aarma Date: Tue, 14 Jul 2026 14:31:49 +0300 Subject: [PATCH 2/2] NFC-176 Fix Web eID minor version validator routing --- README.md | 2 +- .../versionvalidators/AuthTokenVersion.java | 84 ++++++++++++++++++ .../AuthTokenVersion11Validator.java | 9 +- .../AuthTokenVersion1Validator.java | 14 +-- .../AuthTokenVersion11ValidatorTest.java | 8 +- .../AuthTokenVersion1ValidatorTest.java | 18 +--- .../AuthTokenVersionTest.java | 87 +++++++++++++++++++ 7 files changed, 187 insertions(+), 35 deletions(-) create mode 100644 src/main/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion.java create mode 100644 src/test/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersionTest.java diff --git a/README.md b/README.md index 6d9dde3d..8fd9f162 100644 --- a/README.md +++ b/README.md @@ -317,7 +317,7 @@ It contains the following fields: - `signature`: the base64-encoded signature of the token (see the description below), -- `format`: the type identifier and version of the token format separated by a colon character '`:`'. While minor version changes are intended to be backwards-compatible within the same major version, this validation library accepts only explicitly supported token format versions. +- `format`: the type identifier and version of the token format separated by a colon character '`:`', `web-eid:1.0` as of now; the version number consists of the major and minor number separated by a dot, major version changes are incompatible with previous versions, minor version changes are backwards-compatible within the given major version, - `appVersion`: the URL identifying the name and version of the application that issued the token; informative purpose, can be used to identify the affected application in case of faulty tokens. diff --git a/src/main/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion.java b/src/main/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion.java new file mode 100644 index 00000000..bcf0cf35 --- /dev/null +++ b/src/main/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2020-2025 Estonian Information System Authority + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package eu.webeid.security.validator.versionvalidators; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Utility for matching Web eID authentication token format version strings of the form + * {@code web-eid:[.]}, e.g. {@code web-eid:1} or {@code web-eid:1.1}. + */ +final class AuthTokenVersion { + + // Matches 'web-eid:' with an optional canonical '.', where both numbers have no leading + // zeros ('0' or '[1-9]\d*') and are at most 9 digits so that they always fit in an int. Non-canonical + // spellings such as 'web-eid:1.00' or 'web-eid:01' are rejected so that ambiguous version numbers cannot + // bypass the more specific validators. + private static final Pattern TOKEN_FORMAT_PATTERN = + Pattern.compile("^web-eid:(0|[1-9]\\d{0,8})(?:\\.(0|[1-9]\\d{0,8}))?$"); + + private AuthTokenVersion() { + throw new IllegalStateException("Utility class"); + } + + /** + * Returns whether the given token format has exactly the required major version and a minor version that + * is greater than or equal to the required minor version. A missing minor version is treated as 0. + * Backwards-compatible minor version changes are supported within the same major version, while an + * incompatible major version change is not. + */ + static boolean supports(String format, int requiredExactMajorVersion, int requiredMinimalMinorVersion) { + final Version version = parse(format); + return version != null + && version.major() == requiredExactMajorVersion + && version.minor() >= requiredMinimalMinorVersion; + } + + /** + * Returns whether the given token format has exactly the required major version and exactly the required + * minor version. A missing minor version is treated as 0. + */ + static boolean supportsExactly(String format, int requiredExactMajorVersion, int requiredExactMinorVersion) { + final Version version = parse(format); + return version != null + && version.major() == requiredExactMajorVersion + && version.minor() == requiredExactMinorVersion; + } + + private static Version parse(String format) { + if (format == null) { + return null; + } + final Matcher matcher = TOKEN_FORMAT_PATTERN.matcher(format); + if (!matcher.matches()) { + return null; + } + final int major = Integer.parseInt(matcher.group(1)); + final int minor = matcher.group(2) == null ? 0 : Integer.parseInt(matcher.group(2)); + return new Version(major, minor); + } + + private record Version(int major, int minor) { + } +} diff --git a/src/main/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion11Validator.java b/src/main/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion11Validator.java index 03280fa9..5b4f91f8 100644 --- a/src/main/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion11Validator.java +++ b/src/main/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion11Validator.java @@ -54,13 +54,12 @@ import java.util.Arrays; import java.util.List; import java.util.Set; -import java.util.regex.Pattern; import static eu.webeid.security.util.Strings.isNullOrEmpty; class AuthTokenVersion11Validator extends AuthTokenVersion1Validator implements AuthTokenVersionValidator { - private static final String V11_SUPPORTED_TOKEN_FORMAT_PREFIX = "web-eid:1.1"; + private static final int SUPPORTED_MINIMAL_MINOR_VERSION = 1; private static final Set SUPPORTED_SIGNING_CRYPTO_ALGORITHMS = Set.of("ECC", "RSA"); private static final Set SUPPORTED_SIGNING_PADDING_SCHEMES = Set.of("NONE", "PKCS1.5", "PSS"); private static final Set SUPPORTED_SIGNING_HASH_FUNCTIONS = Set.of( @@ -96,7 +95,7 @@ public AuthTokenVersion11Validator( @Override public boolean supports(String format) { - return V11_SUPPORTED_TOKEN_FORMAT_PREFIX.equals(format); + return AuthTokenVersion.supports(format, SUPPORTED_EXACT_MAJOR_VERSION, SUPPORTED_MINIMAL_MINOR_VERSION); } @Override @@ -140,14 +139,14 @@ private static List validateSigningCertificates(WebEidAuthToken List signingCertificates = token.getUnverifiedSigningCertificates(); if (signingCertificates == null || signingCertificates.isEmpty()) { - throw new AuthTokenParseException("'unverifiedSigningCertificates' field is missing, null or empty for format 'web-eid:1.1'"); + throw new AuthTokenParseException("'unverifiedSigningCertificates' field is missing, null or empty for format '" + token.getFormat() + "'"); } List result = new ArrayList<>(); for (UnverifiedSigningCertificate certificate : signingCertificates) { if (certificate == null || isNullOrEmpty(certificate.getCertificate())) { - throw new AuthTokenParseException("'unverifiedSigningCertificates' contains a null or empty entry for format 'web-eid:1.1'"); + throw new AuthTokenParseException("'unverifiedSigningCertificates' contains a null or empty entry for format '" + token.getFormat() + "'"); } validateSupportedSignatureAlgorithms(certificate); result.add(CertificateLoader.decodeCertificateFromBase64(certificate.getCertificate())); diff --git a/src/main/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion1Validator.java b/src/main/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion1Validator.java index 5b818c62..bdffae8a 100644 --- a/src/main/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion1Validator.java +++ b/src/main/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion1Validator.java @@ -36,11 +36,10 @@ import java.security.cert.TrustAnchor; import java.security.cert.X509Certificate; import java.util.Set; -import java.util.regex.Pattern; class AuthTokenVersion1Validator implements AuthTokenVersionValidator { - - private static final String V1_SUPPORTED_TOKEN_FORMAT_PREFIX = "web-eid:1"; + static final int SUPPORTED_EXACT_MAJOR_VERSION = 1; + private static final int SUPPORTED_MINIMAL_MINOR_VERSION = 0; private final SubjectCertificateValidatorBatch simpleSubjectCertificateValidators; private final Set trustedCACertificateAnchors; private final CertStore trustedCACertificateCertStore; @@ -69,13 +68,12 @@ public AuthTokenVersion1Validator( @Override public boolean supports(String format) { - return V1_SUPPORTED_TOKEN_FORMAT_PREFIX.equals(format) - || "web-eid:1.0".equals(format); + return AuthTokenVersion.supports(format, SUPPORTED_EXACT_MAJOR_VERSION, SUPPORTED_MINIMAL_MINOR_VERSION); } @Override public X509Certificate validate(WebEidAuthToken token, String currentChallengeNonce) throws AuthTokenException { - if (isExactV10Format(token.getFormat()) && token.getUnverifiedSigningCertificates() != null) { + if (AuthTokenVersion.supportsExactly(token.getFormat(), SUPPORTED_EXACT_MAJOR_VERSION, SUPPORTED_MINIMAL_MINOR_VERSION) && token.getUnverifiedSigningCertificates() != null) { throw new AuthTokenParseException( "'unverifiedSigningCertificates' field is not allowed for format '" + token.getFormat() + "'" ); @@ -108,8 +106,4 @@ public X509Certificate validate(WebEidAuthToken token, String currentChallengeNo return subjectCertificate; } - - private static boolean isExactV10Format(String format) { - return V1_SUPPORTED_TOKEN_FORMAT_PREFIX.equals(format) || "web-eid:1.0".equals(format); - } } diff --git a/src/test/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion11ValidatorTest.java b/src/test/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion11ValidatorTest.java index 12005ede..3d884d51 100644 --- a/src/test/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion11ValidatorTest.java +++ b/src/test/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion11ValidatorTest.java @@ -82,15 +82,15 @@ void setUp() { } @ParameterizedTest - @ValueSource(strings = {"web-eid:1.1"}) - void whenFormatIsV11_thenSupportsReturnsTrue(String format) { + @ValueSource(strings = {"web-eid:1.1", "web-eid:1.2", "web-eid:1.10", "web-eid:1.999"}) + void whenFormatIsV11OrHigherMinorVersion_thenSupportsReturnsTrue(String format) { assertThat(validator.supports(format)).isTrue(); } @ParameterizedTest @NullAndEmptySource - @ValueSource(strings = {"web-eid:1", "web-eid:1.0", "web-eid:1.1.0", "web-eid:1.10", "web-eid:1.2", "web-eid:2", "webauthn:1.1"}) - void whenFormatIsNullEmptyOrNotV11_thenSupportsReturnsFalse(String format) { + @ValueSource(strings = {"web-eid:1", "web-eid:1.0", "web-eid:1.", "web-eid:1.0TEST", "web-eid:1.00", "web-eid:1.1.0", "web-eid:2", "web-eid:0.9", "webauthn:1.1"}) + void whenFormatIsNullEmptyMinorVersion0NonCanonicalOrMalformed_thenSupportsReturnsFalse(String format) { assertThat(validator.supports(format)).isFalse(); } diff --git a/src/test/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion1ValidatorTest.java b/src/test/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion1ValidatorTest.java index 02cf4722..bc5c8f9b 100644 --- a/src/test/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion1ValidatorTest.java +++ b/src/test/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersion1ValidatorTest.java @@ -61,26 +61,14 @@ class AuthTokenVersion1ValidatorTest { ); @ParameterizedTest - @ValueSource(strings = {"web-eid:1", "web-eid:1.0"}) - void whenFormatIsSupportedV1_thenSupportsReturnsTrue(String format) { + @ValueSource(strings = {"web-eid:1", "web-eid:1.0", "web-eid:1.1", "web-eid:1.2", "web-eid:1.10", "web-eid:1.999"}) + void whenFormatIsValidMajorV1Format_thenSupportsReturnsTrue(String format) { assertThat(validator.supports(format)).isTrue(); } @ParameterizedTest @NullAndEmptySource - @ValueSource(strings = { - "web-eid", - "web-eid:1.", - "web-eid:1.0TEST", - "web-eid:1.1", - "web-eid:1.1.0", - "web-eid:1.2", - "web-eid:1.10", - "web-eid:1.999", - "web-eid:0.9", - "web-eid:2", - "webauthn:1" - }) + @ValueSource(strings = {"web-eid", "web-eid:1.", "web-eid:1.0TEST", "web-eid:1.00", "web-eid:1.000", "web-eid:01", "web-eid:1.1.0", "web-eid:0.9", "web-eid:2", "webauthn:1"}) void whenFormatIsNullEmptyOrMalformedOrNotV1_thenSupportsReturnsFalse(String format) { assertThat(validator.supports(format)).isFalse(); } diff --git a/src/test/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersionTest.java b/src/test/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersionTest.java new file mode 100644 index 00000000..68531ece --- /dev/null +++ b/src/test/java/eu/webeid/security/validator/versionvalidators/AuthTokenVersionTest.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020-2025 Estonian Information System Authority + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package eu.webeid.security.validator.versionvalidators; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.assertj.core.api.Assertions.assertThat; + +class AuthTokenVersionTest { + + @ParameterizedTest + @CsvSource({ + "web-eid:1, 1, 0, true", + "web-eid:1.0, 1, 0, true", + "web-eid:1.1, 1, 0, true", + "web-eid:1.1, 1, 1, true", + "web-eid:1.999, 1, 1, true", + "web-eid:2.0, 2, 0, true", + "web-eid:2.3, 2, 1, true", + "web-eid:1.0, 1, 1, false", + "web-eid:1, 1, 1, false", + "web-eid:2, 1, 0, false", + "web-eid:1.5, 2, 0, false", + "web-eid:1.00, 1, 0, false", + "web-eid:1.000, 1, 0, false", + "web-eid:01, 1, 0, false", + "web-eid:1., 1, 0, false", + "web-eid:1.1.0, 1, 0, false", + "web-eid:0.9, 1, 0, false", + "webauthn:1, 1, 0, false" + }) + void whenFormatMatchesRequiredMajorAndAtLeastRequiredMinor_thenSupportsReturnsExpected( + String format, int requiredMajorVersion, int requiredMinorVersion, boolean expected) { + assertThat(AuthTokenVersion.supports(format, requiredMajorVersion, requiredMinorVersion)).isEqualTo(expected); + } + + @Test + void whenFormatIsNull_thenSupportsReturnsFalse() { + assertThat(AuthTokenVersion.supports(null, 1, 0)).isFalse(); + } + + @ParameterizedTest + @CsvSource({ + "web-eid:1, 1, 0, true", + "web-eid:1.0, 1, 0, true", + "web-eid:1.1, 1, 1, true", + "web-eid:2.0, 2, 0, true", + "web-eid:1.1, 1, 0, false", + "web-eid:1.2, 1, 1, false", + "web-eid:1.0, 1, 1, false", + "web-eid:1, 2, 0, false", + "web-eid:1.00, 1, 0, false", + "web-eid:01, 1, 0, false", + "webauthn:1, 1, 0, false" + }) + void whenFormatMatchesRequiredMajorAndExactMinor_thenSupportsExactlyReturnsExpected( + String format, int requiredMajorVersion, int requiredMinorVersion, boolean expected) { + assertThat(AuthTokenVersion.supportsExactly(format, requiredMajorVersion, requiredMinorVersion)).isEqualTo(expected); + } + + @Test + void whenFormatIsNull_thenSupportsExactlyReturnsFalse() { + assertThat(AuthTokenVersion.supportsExactly(null, 1, 0)).isFalse(); + } +}