Skip to content

Commit d88babe

Browse files
committed
feat: add whitelisting to subject certificate policies, add known Estonian production policies as default, add to example test card policies
WE2-1308 Signed-off-by: Sven Mitt <svenzik@users.noreply.github.com>
1 parent 3bd65ad commit d88babe

15 files changed

Lines changed: 633 additions & 34 deletions

‎README.md‎

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ import eu.webeid.security.challenge.ChallengeNonceStore;
101101

102102
You must explicitly specify which **intermediate** certificate authorities (CAs) are trusted to issue the eID authentication and OCSP responder certificates. CA certificates can be loaded from either the truststore file, resources or any stream source. We use the [`CertificateLoader`](src/main/java/eu/webeid/security/certificate/CertificateLoader.java) helper class to load CA certificates from resources here, but consider using [the truststore file](example/src/main/java/eu/webeid/example/config/ValidationConfiguration.java) instead.
103103

104-
First, copy the trusted certificates, for example `ESTEID2018.cer`, to `resources/cacerts/`, then load the certificates as follows:
104+
To accept both ID-card generations that are currently in use, copy the `ESTEID2018` intermediate certificate from [SK ID Solutions](https://www.skidsolutions.eu/resources/certificates/) and the `ESTEID2025` intermediate certificate from [Zetes](https://repository.eidpki.ee/crt/) to `resources/cacerts/`, then load both certificates as follows:
105105

106106
```java
107107
import java.io.IOException;
@@ -111,7 +111,10 @@ import eu.webeid.security.certificate.CertificateLoader;
111111

112112
...
113113
private X509Certificate[] trustedIntermediateCACertificates() throws CertificateException, IOException {
114-
return CertificateLoader.loadCertificatesFromResources("cacerts/ESTEID2018.cer");
114+
return CertificateLoader.loadCertificatesFromResources(
115+
"cacerts/ESTEID2018.cer",
116+
"cacerts/ESTEID2025.crt"
117+
);
115118
}
116119
...
117120
```
@@ -320,7 +323,42 @@ toTitleCase(CertificateData.getSubjectSurname(userCertificate).orElseThrow()); /
320323

321324
The default validator uses the platform OCSP implementation to check certificate revocation. A revoked certificate or an unsuccessful revocation check causes authentication to fail.
322325

323-
Use `withDisallowedCertificatePolicies(ASN1ObjectIdentifier... policies)` to add disallowed certificate policies. Estonian Mobile-ID policies are disallowed by default because smart-card authentication must not accept Mobile-ID certificates.
326+
Certificate policy allow-lists are selected by the subject certificate's exact issuer `X500Principal`. At build time, production Estonian identity-document policies from `ESTEID_PRODUCTION_DOCUMENT_POLICIES` are used when the configured allow-list is empty. When one or more issuer entries are configured, only those entries are used. A matching issuer must contain one of its configured policy OIDs; other issuers fall back to the deprecated deny-list, which is empty by default. Calling `withAllowedCertificatePolicies(issuer)` without policy OIDs removes that issuer's entry. If no entries remain, the Estonian defaults are used.
327+
328+
Use `withAllowedCertificatePolicies(X500Principal issuer, ASN1ObjectIdentifier... policies)` to define a custom allow-list. Repeated calls configure all supported issuers on the same validator. The following example explicitly configures both Estonian ID-card generations in one builder chain.
329+
330+
```java
331+
AuthTokenValidator tokenValidator = new AuthTokenValidatorBuilder()
332+
.withSiteOrigin(URI.create("https://example.org"))
333+
.withTrustedCertificateAuthorities(trustedIntermediateCACertificates())
334+
.withAllowedCertificatePolicies(
335+
new X500Principal("CN=ESTEID2018,2.5.4.97=#0c0e4e545245452d3130373437303133,O=SK ID Solutions AS,C=EE"),
336+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.1.1"),
337+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.1.2")
338+
)
339+
.withAllowedCertificatePolicies(
340+
new X500Principal("C=EE,O=Zetes Estonia OÜ,2.5.4.97=#0c0e4e545245452d3137303636303439,CN=ESTEID2025"),
341+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.2.1.1"),
342+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.2.1.2")
343+
)
344+
.build();
345+
```
346+
347+
For another country, add each supported issuer and its policy OIDs to the same builder chain and provide the corresponding trusted intermediate CAs.
348+
349+
To restore the old deny-list behaviour, use:
350+
```java
351+
AuthTokenValidator tokenValidator = new AuthTokenValidatorBuilder()
352+
.withSiteOrigin(URI.create("https://example.org"))
353+
.withTrustedCertificateAuthorities(trustedIntermediateCACertificates())
354+
.withDisallowedCertificatePolicies(
355+
SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY_V1,
356+
SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY_V2,
357+
SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY_V3,
358+
SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY
359+
)
360+
.build();
361+
```
324362

325363
For more advanced revocation requirements, supply a `CertificateRevocationChecker` with `withCertificateRevocationChecker(...)`. The [OCSP configuration guide](src/main/java/eu/webeid/ocsp/README.md) covers custom implementations, the bundled OCSP checker, custom PKIX checkers, responder selection, HTTP settings, and nonce policies.
326364

‎example/src/main/java/eu/webeid/example/config/ValidationConfiguration.java‎

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33

44
package eu.webeid.example.config;
55

6+
import java.util.Map;
7+
import java.util.Set;
8+
import javax.security.auth.x500.X500Principal;
9+
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
610
import org.slf4j.Logger;
711
import org.slf4j.LoggerFactory;
812
import org.springframework.beans.factory.ObjectFactory;
@@ -42,6 +46,47 @@ public class ValidationConfiguration {
4246
private static final String CERTS_RESOURCE_PATH = "/certs/";
4347
public static final String TRUSTED_CERTIFICATES_JKS = "trusted_certificates.jks";
4448

49+
/**
50+
* Test document policies for Estonian ID cards, digital ID cards, residence permit cards and diplomatic
51+
* identity cards issued under the ESTEID2018 and eID certificate-policy generations, grouped by issuer distinguished name.
52+
*
53+
* @see <a href="https://www.id.ee/en/article/certificate-policy-for-identity-card-digital-identity-card-residence-permit-card-and-diplomatic-identity-card-2/">
54+
* ID.ee certificate policy index for Estonian identity documents</a>
55+
* @see <a href="https://www.id.ee/wp-content/uploads/2026/08/cp_esteid_v4.1_17.08.2026_allkirjastatud.pdf">
56+
* IDEMIA-generation certificate policy</a>
57+
* @see <a href="https://www.id.ee/wp-content/uploads/2026/06/eid-cp-v-2.0_04.06.2026_allkirjastatud.pdf">
58+
* Thales-generation certificate policy</a>
59+
* @see <a href="https://repository.eidpki.ee/static/documents/CertificateProfiles-20260520.pdf">
60+
* ESTEID2025 certificate profile</a>
61+
*/
62+
public static final Map<X500Principal, Set<ASN1ObjectIdentifier>> ESTEID_TEST_DOCUMENT_POLICIES = Map.of(
63+
new X500Principal(
64+
"CN=TEST of ESTEID2018,2.5.4.97=#0c0e4e545245452d3130373437303133,O=SK ID Solutions AS,C=EE"
65+
), Set.of(
66+
// https://www.id.ee/wp-content/uploads/2026/08/cp_esteid_v4.1_17.08.2026_allkirjastatud.pdf Chapter 1.2. Document Name and Identification
67+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.2.1"), // Identity card of Estonian citizens
68+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.2.2"), // Identity card of European Union citizens
69+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.2.3"), // Digital identity card for Estonian residents
70+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.2.4"), // Digital identity card for e-residents
71+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.2.5"), // Residence permit card for long-term residents
72+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.2.6"), // Residence permit card for temporary residents and NATO SOFA
73+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.2.7"), // Residence permit card for family members of European Union citizens
74+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51455.1.2.1") // Diplomatic identity card
75+
),
76+
new X500Principal(
77+
"C=EE,O=Zetes Estonia OÜ,2.5.4.97=#0c0e4e545245452d3137303636303439,CN=Test ESTEID2025"
78+
), Set.of(
79+
// https://www.id.ee/wp-content/uploads/2026/06/eid-cp-v-2.0_04.06.2026_allkirjastatud.pdf Chapter 1.2. Document Name and Identification
80+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.2.1.1"), // Identity card of Estonian citizens
81+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.2.1.2"), // Identity card of European Union citizens
82+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.2.1.3"), // Residence permit card for long-term residents
83+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.2.1.4"), // Residence permit card for temporary residents and the right of stay in the Republic of Estonia.
84+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.2.1.5"), // Residence permit card for family members of EU and UK citizens and the right of residence of UK citizens
85+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.2.1.6"), // Digital identity card of e-residents
86+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51455.2.1.1") // Diplomatic identity card
87+
)
88+
);
89+
4590
@Value("${spring.profiles.active}")
4691
private String activeProfile;
4792

@@ -61,11 +106,18 @@ public ChallengeNonceGenerator generator(ChallengeNonceStore challengeNonceStore
61106
@Bean
62107
public AuthTokenValidator validator(YAMLConfig yamlConfig) {
63108
try {
64-
return new AuthTokenValidatorBuilder()
65-
.withSiteOrigin(URI.create(yamlConfig.getLocalOrigin()))
109+
AuthTokenValidatorBuilder authTokenValidatorBuilder = new AuthTokenValidatorBuilder().withSiteOrigin(URI.create(yamlConfig.getLocalOrigin()))
66110
.withTrustedCertificateAuthorities(loadTrustedCACertificatesFromCerFiles())
67-
.withTrustedCertificateAuthorities(loadTrustedCACertificatesFromTrustStore(yamlConfig))
68-
.build();
111+
.withTrustedCertificateAuthorities(loadTrustedCACertificatesFromTrustStore(yamlConfig));
112+
113+
if (!activeProfile.equals("prod")) {
114+
ESTEID_TEST_DOCUMENT_POLICIES
115+
.forEach((x500Principal, asn1ObjectIdentifiers) ->
116+
authTokenValidatorBuilder.withAllowedCertificatePolicies(x500Principal, asn1ObjectIdentifiers.toArray(new ASN1ObjectIdentifier[0])));
117+
118+
}
119+
120+
return authTokenValidatorBuilder.build();
69121
} catch (JceException e) {
70122
throw new RuntimeException("Error building the Web eID auth token validator.", e);
71123
}

‎example/src/test/java/eu/webeid/example/config/TestValidatorConfiguration.java‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33

44
package eu.webeid.example.config;
55

6+
import static eu.webeid.example.config.ValidationConfiguration.ESTEID_TEST_DOCUMENT_POLICIES;
7+
68
import eu.webeid.security.exceptions.JceException;
79
import eu.webeid.security.validator.AuthTokenValidator;
810
import eu.webeid.security.validator.AuthTokenValidatorBuilder;
11+
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
912
import org.springframework.boot.test.context.TestConfiguration;
1013
import org.springframework.context.annotation.Bean;
1114
import org.springframework.context.annotation.Primary;
1215

16+
import javax.security.auth.x500.X500Principal;
1317
import java.net.URI;
1418
import java.util.List;
1519

@@ -26,13 +30,19 @@ public class TestValidatorConfiguration {
2630
@Primary
2731
AuthTokenValidator validatorWithoutOcspCall(ValidationConfiguration validationConfiguration,
2832
YAMLConfig yamlConfig) throws JceException {
29-
return new AuthTokenValidatorBuilder()
33+
AuthTokenValidatorBuilder authTokenValidatorBuilder = new AuthTokenValidatorBuilder()
3034
.withSiteOrigin(URI.create(yamlConfig.getLocalOrigin()))
3135
.withTrustedCertificateAuthorities(validationConfiguration.loadTrustedCACertificatesFromCerFiles())
3236
.withTrustedCertificateAuthorities(validationConfiguration.loadTrustedCACertificatesFromTrustStore(yamlConfig))
37+
3338
// Do not call the real OCSP service in tests.
34-
.withCertificateRevocationChecker((subjectCertificate, issuerCertificate) -> List.of())
35-
.build();
39+
.withCertificateRevocationChecker((subjectCertificate, issuerCertificate) -> List.of());
40+
41+
ESTEID_TEST_DOCUMENT_POLICIES
42+
.forEach((x500Principal, asn1ObjectIdentifiers) ->
43+
authTokenValidatorBuilder.withAllowedCertificatePolicies(x500Principal, asn1ObjectIdentifiers.toArray(new ASN1ObjectIdentifier[0])));
44+
45+
return authTokenValidatorBuilder.build();
3646
}
3747

3848
}

‎src/main/java/eu/webeid/security/certificate/SubjectCertificatePolicies.java‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55

66
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
77

8+
import javax.security.auth.x500.X500Principal;
9+
import java.util.Map;
10+
import java.util.Set;
11+
812
public final class SubjectCertificatePolicies {
913

1014
private static final String ESTEID_SK_2015_MOBILE_ID_POLICY_PREFIX = "1.3.6.1.4.1.10015.1.3";
@@ -18,6 +22,47 @@ public final class SubjectCertificatePolicies {
1822
public static final ASN1ObjectIdentifier ESTEID_SK_2015_MOBILE_ID_POLICY_V3 =
1923
new ASN1ObjectIdentifier(ESTEID_SK_2015_MOBILE_ID_POLICY_PREFIX + ".3");
2024

25+
/**
26+
* Production document policies for Estonian ID cards, digital ID cards, residence permit cards and diplomatic
27+
* identity cards issued under the ESTEID2018 and eID certificate-policy generations, grouped by issuer distinguished name.
28+
*
29+
* @see <a href="https://www.id.ee/en/article/certificate-policy-for-identity-card-digital-identity-card-residence-permit-card-and-diplomatic-identity-card-2/">
30+
* ID.ee certificate policy index for Estonian identity documents</a>
31+
* @see <a href="https://www.id.ee/wp-content/uploads/2026/08/cp_esteid_v4.1_17.08.2026_allkirjastatud.pdf">
32+
* IDEMIA-generation certificate policy</a>
33+
* @see <a href="https://www.id.ee/wp-content/uploads/2026/06/eid-cp-v-2.0_04.06.2026_allkirjastatud.pdf">
34+
* Thales-generation certificate policy</a>
35+
* @see <a href="https://repository.eidpki.ee/static/documents/CertificateProfiles-20260520.pdf">
36+
* ESTEID2025 certificate profile</a>
37+
*/
38+
public static final Map<X500Principal, Set<ASN1ObjectIdentifier>> ESTEID_PRODUCTION_DOCUMENT_POLICIES = Map.of(
39+
new X500Principal(
40+
"CN=ESTEID2018,2.5.4.97=#0c0e4e545245452d3130373437303133,O=SK ID Solutions AS,C=EE"
41+
), Set.of(
42+
// https://www.id.ee/wp-content/uploads/2026/08/cp_esteid_v4.1_17.08.2026_allkirjastatud.pdf Chapter 1.2. Document Name and Identification
43+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.1.1"), // Identity card of Estonian citizens
44+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.1.2"), // Identity card of European Union citizens
45+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.1.3"), // Digital identity card for Estonian residents
46+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.1.4"), // Digital identity card for e-residents
47+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.1.5"), // Residence permit card for long-term residents
48+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.1.6"), // Residence permit card for temporary residents and NATO SOFA
49+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.1.7"), // Residence permit card for family members of European Union citizens
50+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51455.1.1.1") // Diplomatic identity card
51+
),
52+
new X500Principal(
53+
"C=EE,O=Zetes Estonia OÜ,2.5.4.97=#0c0e4e545245452d3137303636303439,CN=ESTEID2025"
54+
), Set.of(
55+
// https://www.id.ee/wp-content/uploads/2026/06/eid-cp-v-2.0_04.06.2026_allkirjastatud.pdf Chapter 1.2. Document Name and Identification
56+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.2.1.1"), // Identity card of Estonian citizens
57+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.2.1.2"), // Identity card of European Union citizens
58+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.2.1.3"), // Residence permit card for long-term residents
59+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.2.1.4"), // Residence permit card for temporary residents and the right of stay in the Republic of Estonia.
60+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.2.1.5"), // Residence permit card for family members of EU and UK citizens and the right of residence of UK citizens
61+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.2.1.6"), // Digital identity card of e-residents
62+
new ASN1ObjectIdentifier("1.3.6.1.4.1.51455.2.1.1") // Diplomatic identity card
63+
)
64+
);
65+
2166
private SubjectCertificatePolicies() {
2267
throw new IllegalStateException("Constants class");
2368
}

‎src/main/java/eu/webeid/security/exceptions/UserCertificateDisallowedPolicyException.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
package eu.webeid.security.exceptions;
55

66
/**
7-
* Thrown when any of the configured disallowed policies is present in the user certificate.
7+
* Thrown when the user certificate does not satisfy the configured allowed or disallowed policy restrictions.
88
*/
99
public class UserCertificateDisallowedPolicyException extends AuthTokenException {
1010
public UserCertificateDisallowedPolicyException() {

0 commit comments

Comments
 (0)