Skip to content

Commit 61616fe

Browse files
NFC-46 Rename nfc token validator to auth token v11 validator. Strategy + Factory for token format validation (V1 / V1.1); delegate from AuthTokenValidatorImpl. Create unit tests.
1 parent a419213 commit 61616fe

File tree

10 files changed

+329
-18
lines changed

10 files changed

+329
-18
lines changed

src/main/java/eu/webeid/security/validator/NfcAuthTokenValidator.java renamed to src/main/java/eu/webeid/security/validator/AuthTokenV11Validator.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,20 @@
1414

1515
import static eu.webeid.security.util.Strings.isNullOrEmpty;
1616

17-
public class NfcAuthTokenValidator {
17+
public class AuthTokenV11Validator {
1818

1919
private final SubjectCertificateValidatorBatch simpleSubjectCertificateValidators;
2020
private final Supplier<SubjectCertificateValidatorBatch> certTrustValidatorsSupplier;
2121

22-
NfcAuthTokenValidator(
22+
public AuthTokenV11Validator(
2323
SubjectCertificateValidatorBatch simpleSubjectCertificateValidators,
2424
Supplier<SubjectCertificateValidatorBatch> certTrustValidatorsSupplier
2525
) {
2626
this.simpleSubjectCertificateValidators = simpleSubjectCertificateValidators;
2727
this.certTrustValidatorsSupplier = certTrustValidatorsSupplier;
2828
}
2929

30-
void validate(WebEidAuthToken token, X509Certificate subjectCertificate) throws AuthTokenException {
30+
public void validate(WebEidAuthToken token, X509Certificate subjectCertificate) throws AuthTokenException {
3131
if (isNullOrEmpty(token.getUnverifiedSigningCertificate())) {
3232
throw new AuthTokenParseException("'unverifiedSigningCertificate' field is missing, null or empty for format 'web-eid:1.1'");
3333
}

src/main/java/eu/webeid/security/validator/AuthTokenValidator.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,6 @@
3434
*/
3535
public interface AuthTokenValidator {
3636

37-
String CURRENT_TOKEN_FORMAT_VERSION = "web-eid:1";
38-
String CURRENT_NFC_TOKEN_FORMAT_VERSION = "web-eid:1.1";
39-
4037
/**
4138
* Parses the Web eID authentication token signed by the subject.
4239
*

src/main/java/eu/webeid/security/validator/AuthTokenValidatorImpl.java

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
import eu.webeid.security.validator.certvalidators.SubjectCertificatePurposeValidator;
3636
import eu.webeid.security.validator.certvalidators.SubjectCertificateTrustedValidator;
3737
import eu.webeid.security.validator.certvalidators.SubjectCertificateValidatorBatch;
38+
import eu.webeid.security.validator.format.AuthTokenV11FormatValidator;
39+
import eu.webeid.security.validator.format.AuthTokenV1FormatValidator;
40+
import eu.webeid.security.validator.format.TokenFormatValidator;
41+
import eu.webeid.security.validator.format.TokenFormatValidatorFactory;
3842
import eu.webeid.security.validator.ocsp.OcspClient;
3943
import eu.webeid.security.validator.ocsp.OcspServiceProvider;
4044
import eu.webeid.security.validator.ocsp.service.AiaOcspServiceConfiguration;
@@ -45,6 +49,7 @@
4549
import java.security.cert.CertStore;
4650
import java.security.cert.TrustAnchor;
4751
import java.security.cert.X509Certificate;
52+
import java.util.List;
4853
import java.util.Objects;
4954
import java.util.Set;
5055

@@ -68,7 +73,8 @@ final class AuthTokenValidatorImpl implements AuthTokenValidator {
6873
private OcspClient ocspClient;
6974
private OcspServiceProvider ocspServiceProvider;
7075
private final AuthTokenSignatureValidator authTokenSignatureValidator;
71-
private final NfcAuthTokenValidator nfcAuthTokenValidator;
76+
private final TokenFormatValidatorFactory tokenFormatValidatorFactory;
77+
7278

7379
/**
7480
* @param configuration configuration parameters for the token validator
@@ -97,10 +103,10 @@ final class AuthTokenValidatorImpl implements AuthTokenValidator {
97103
trustedCACertificateCertStore));
98104
}
99105

100-
this.nfcAuthTokenValidator = new NfcAuthTokenValidator(
101-
simpleSubjectCertificateValidators,
102-
this::getCertTrustValidators
103-
);
106+
this.tokenFormatValidatorFactory = new TokenFormatValidatorFactory(List.of(
107+
new AuthTokenV11FormatValidator(simpleSubjectCertificateValidators, this::getCertTrustValidators),
108+
new AuthTokenV1FormatValidator()
109+
));
104110

105111
authTokenSignatureValidator = new AuthTokenSignatureValidator(configuration.getSiteOrigin());
106112
}
@@ -152,18 +158,14 @@ private WebEidAuthToken parseToken(String authToken) throws AuthTokenParseExcept
152158
}
153159

154160
private X509Certificate validateToken(WebEidAuthToken token, String currentChallengeNonce) throws AuthTokenException {
155-
if (token.getFormat() == null || !token.getFormat().startsWith(CURRENT_TOKEN_FORMAT_VERSION)) {
156-
throw new AuthTokenParseException("Only token format version '" + CURRENT_TOKEN_FORMAT_VERSION +
157-
"' is currently supported");
158-
}
161+
TokenFormatValidator formatValidator = tokenFormatValidatorFactory.requireFor(token.getFormat());
162+
159163
if (token.getUnverifiedCertificate() == null || token.getUnverifiedCertificate().isEmpty()) {
160164
throw new AuthTokenParseException("'unverifiedCertificate' field is missing, null or empty");
161165
}
162166
final X509Certificate subjectCertificate = CertificateLoader.decodeCertificateFromBase64(token.getUnverifiedCertificate());
163167

164-
if (token.getFormat().startsWith(CURRENT_NFC_TOKEN_FORMAT_VERSION)) {
165-
nfcAuthTokenValidator.validate(token, subjectCertificate);
166-
}
168+
formatValidator.validate(token, subjectCertificate);
167169

168170
simpleSubjectCertificateValidators.executeFor(subjectCertificate);
169171
getCertTrustValidators().executeFor(subjectCertificate);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package eu.webeid.security.validator.format;
2+
3+
import eu.webeid.security.authtoken.WebEidAuthToken;
4+
import eu.webeid.security.exceptions.AuthTokenException;
5+
import eu.webeid.security.validator.AuthTokenV11Validator;
6+
import eu.webeid.security.validator.certvalidators.SubjectCertificateValidatorBatch;
7+
8+
import java.security.cert.X509Certificate;
9+
import java.util.function.Supplier;
10+
11+
public final class AuthTokenV11FormatValidator implements TokenFormatValidator {
12+
13+
private static final String SUPPORTED_PREFIX = "web-eid:1.1";
14+
15+
private final AuthTokenV11Validator delegate;
16+
17+
public AuthTokenV11FormatValidator(SubjectCertificateValidatorBatch simpleSubjectCertificateValidators,
18+
Supplier<SubjectCertificateValidatorBatch> certTrustValidatorsSupplier) {
19+
this.delegate = new AuthTokenV11Validator(simpleSubjectCertificateValidators, certTrustValidatorsSupplier);
20+
}
21+
22+
@Override
23+
public boolean supports(String format) {
24+
return format != null && format.startsWith(SUPPORTED_PREFIX);
25+
}
26+
27+
@Override
28+
public void validate(WebEidAuthToken token, X509Certificate subjectCertificate) throws AuthTokenException {
29+
delegate.validate(token, subjectCertificate);
30+
}
31+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package eu.webeid.security.validator.format;
2+
3+
import eu.webeid.security.authtoken.WebEidAuthToken;
4+
5+
import java.security.cert.X509Certificate;
6+
7+
public final class AuthTokenV1FormatValidator implements TokenFormatValidator {
8+
private static final String SUPPORTED_TOKEN_FORMAT_VERSION = "web-eid:1";
9+
10+
@Override
11+
public boolean supports(String format) {
12+
return format != null && format.startsWith(SUPPORTED_TOKEN_FORMAT_VERSION);
13+
}
14+
15+
@Override
16+
@SuppressWarnings("java:S1186")
17+
public void validate(WebEidAuthToken token, X509Certificate subjectCertificate) {
18+
}
19+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package eu.webeid.security.validator.format;
2+
3+
import eu.webeid.security.authtoken.WebEidAuthToken;
4+
import eu.webeid.security.exceptions.AuthTokenException;
5+
6+
import java.security.cert.X509Certificate;
7+
8+
public interface TokenFormatValidator {
9+
boolean supports(String format);
10+
void validate(WebEidAuthToken token, X509Certificate subjectCertificate) throws AuthTokenException;
11+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package eu.webeid.security.validator.format;
2+
3+
import eu.webeid.security.exceptions.AuthTokenParseException;
4+
5+
import java.util.List;
6+
7+
public final class TokenFormatValidatorFactory {
8+
public static final String CURRENT_TOKEN_FORMAT_VERSION = "web-eid:1";
9+
private final List<TokenFormatValidator> validators;
10+
11+
public TokenFormatValidatorFactory(List<TokenFormatValidator> validators) {
12+
this.validators = List.copyOf(validators);
13+
}
14+
15+
public TokenFormatValidator requireFor(String format) throws AuthTokenParseException {
16+
if (format == null || format.isBlank()) {
17+
throw new AuthTokenParseException("'format' field is missing");
18+
}
19+
20+
if (!format.startsWith(CURRENT_TOKEN_FORMAT_VERSION)) {
21+
throw new AuthTokenParseException("Only token format version '" + CURRENT_TOKEN_FORMAT_VERSION + "' is currently supported");
22+
}
23+
return validators.stream()
24+
.filter(v -> v.supports(format))
25+
.findFirst()
26+
.orElseThrow(() -> new AuthTokenParseException("Unsupported token format: " + format));
27+
}
28+
}
29+
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package eu.webeid.security.validator.format;
2+
3+
import eu.webeid.security.authtoken.WebEidAuthToken;
4+
import eu.webeid.security.exceptions.AuthTokenException;
5+
import eu.webeid.security.validator.AuthTokenV11Validator;
6+
import eu.webeid.security.validator.certvalidators.SubjectCertificateValidatorBatch;
7+
import org.junit.jupiter.api.AfterEach;
8+
import org.junit.jupiter.api.BeforeEach;
9+
import org.junit.jupiter.api.Test;
10+
import org.junit.jupiter.params.ParameterizedTest;
11+
import org.junit.jupiter.params.provider.NullAndEmptySource;
12+
import org.junit.jupiter.params.provider.ValueSource;
13+
import org.mockito.ArgumentMatchers;
14+
import org.mockito.Mock;
15+
import org.mockito.MockitoAnnotations;
16+
17+
import java.lang.reflect.Field;
18+
import java.security.cert.X509Certificate;
19+
import java.util.function.Supplier;
20+
21+
import static org.assertj.core.api.Assertions.assertThat;
22+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
23+
import static org.mockito.Mockito.doThrow;
24+
import static org.mockito.Mockito.mock;
25+
import static org.mockito.Mockito.times;
26+
import static org.mockito.Mockito.verify;
27+
import static org.mockito.Mockito.verifyNoMoreInteractions;
28+
29+
class AuthTokenV11FormatValidatorTest {
30+
31+
@Mock
32+
private AuthTokenV11Validator delegate;
33+
34+
@Mock
35+
private WebEidAuthToken token;
36+
37+
@Mock
38+
private X509Certificate certificate;
39+
40+
private AutoCloseable mocks;
41+
42+
@BeforeEach
43+
void setUp() {
44+
this.mocks = MockitoAnnotations.openMocks(this);
45+
}
46+
47+
@AfterEach
48+
void tearDown() throws Exception {
49+
if (this.mocks != null) {
50+
this.mocks.close();
51+
}
52+
}
53+
54+
@ParameterizedTest
55+
@ValueSource(strings = { "web-eid:1.1", "web-eid:1.1.0", "web-eid:1.10" })
56+
void supports_acceptsV11AndPrefixedVariants(String format) {
57+
SubjectCertificateValidatorBatch scvb = mock(SubjectCertificateValidatorBatch.class);
58+
Supplier<SubjectCertificateValidatorBatch> supplier = () -> mock(SubjectCertificateValidatorBatch.class);
59+
60+
AuthTokenV11FormatValidator v = new AuthTokenV11FormatValidator(scvb, supplier);
61+
62+
assertThat(v.supports(format)).isTrue();
63+
}
64+
65+
@ParameterizedTest
66+
@NullAndEmptySource
67+
@ValueSource(strings = { "web-eid:1", "web-eid:1.0", "web-eid:2", "webauthn:1.1" })
68+
void supports_rejectsOthers(String format) {
69+
SubjectCertificateValidatorBatch scvb = mock(SubjectCertificateValidatorBatch.class);
70+
Supplier<SubjectCertificateValidatorBatch> supplier = () -> mock(SubjectCertificateValidatorBatch.class);
71+
72+
AuthTokenV11FormatValidator v = new AuthTokenV11FormatValidator(scvb, supplier);
73+
74+
assertThat(v.supports(format)).isFalse();
75+
}
76+
77+
@Test
78+
void validate_delegatesToUnderlyingValidator() throws Exception {
79+
SubjectCertificateValidatorBatch scvb = mock(SubjectCertificateValidatorBatch.class);
80+
Supplier<SubjectCertificateValidatorBatch> supplier = () -> mock(SubjectCertificateValidatorBatch.class);
81+
82+
AuthTokenV11FormatValidator v = new AuthTokenV11FormatValidator(scvb, supplier);
83+
setPrivateDelegate(v, this.delegate);
84+
85+
v.validate(this.token, this.certificate);
86+
87+
verify(this.delegate, times(1)).validate(this.token, this.certificate);
88+
verifyNoMoreInteractions(this.delegate);
89+
}
90+
91+
@Test
92+
void validate_propagatesExceptionsFromDelegate() throws Exception {
93+
SubjectCertificateValidatorBatch scvb = mock(SubjectCertificateValidatorBatch.class);
94+
Supplier<SubjectCertificateValidatorBatch> supplier = () -> mock(SubjectCertificateValidatorBatch.class);
95+
96+
AuthTokenV11FormatValidator v = new AuthTokenV11FormatValidator(scvb, supplier);
97+
setPrivateDelegate(v, this.delegate);
98+
99+
doThrow(new AuthTokenException("boom"){}).when(this.delegate)
100+
.validate(ArgumentMatchers.any(), ArgumentMatchers.any());
101+
102+
assertThatThrownBy(() -> v.validate(this.token, this.certificate))
103+
.isInstanceOf(AuthTokenException.class)
104+
.hasMessageContaining("boom");
105+
}
106+
107+
private static void setPrivateDelegate(AuthTokenV11FormatValidator v, AuthTokenV11Validator mockDelegate)
108+
throws NoSuchFieldException, IllegalAccessException {
109+
Field f = AuthTokenV11FormatValidator.class.getDeclaredField("delegate");
110+
f.setAccessible(true);
111+
f.set(v, mockDelegate);
112+
}
113+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package eu.webeid.security.validator.format;
2+
3+
import eu.webeid.security.authtoken.WebEidAuthToken;
4+
import org.junit.jupiter.params.ParameterizedTest;
5+
import org.junit.jupiter.params.provider.NullAndEmptySource;
6+
import org.junit.jupiter.params.provider.ValueSource;
7+
import org.junit.jupiter.api.Test;
8+
9+
import java.security.cert.X509Certificate;
10+
11+
import static org.assertj.core.api.Assertions.assertThat;
12+
import static org.assertj.core.api.Assertions.assertThatCode;
13+
import static org.mockito.Mockito.mock;
14+
15+
class AuthTokenV1FormatValidatorTest {
16+
17+
private final AuthTokenV1FormatValidator validator = new AuthTokenV1FormatValidator();
18+
19+
@ParameterizedTest
20+
@ValueSource(strings = { "web-eid:1", "web-eid:1.0", "web-eid:1.1", "web-eid:1.10" })
21+
void supports_acceptsAllMajor1Variants(String format) {
22+
assertThat(validator.supports(format)).isTrue();
23+
}
24+
25+
@ParameterizedTest
26+
@NullAndEmptySource
27+
@ValueSource(strings = { "web-eid", "web-eid:0.9", "web-eid:2", "webauthn:1" })
28+
void supports_rejectsNullEmptyAndOtherMajors(String format) {
29+
assertThat(validator.supports(format)).isFalse();
30+
}
31+
32+
@Test
33+
void validate_isNoopAndDoesNotThrow() {
34+
WebEidAuthToken token = mock(WebEidAuthToken.class);
35+
X509Certificate cert = mock(X509Certificate.class);
36+
assertThatCode(() -> validator.validate(token, cert)).doesNotThrowAnyException();
37+
}
38+
}

0 commit comments

Comments
 (0)