Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 41 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ import eu.webeid.security.challenge.ChallengeNonceStore;

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.

First, copy the trusted certificates, for example `ESTEID2018.cer`, to `resources/cacerts/`, then load the certificates as follows:
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:

```java
import java.io.IOException;
Expand All @@ -111,7 +111,10 @@ import eu.webeid.security.certificate.CertificateLoader;

...
private X509Certificate[] trustedIntermediateCACertificates() throws CertificateException, IOException {
return CertificateLoader.loadCertificatesFromResources("cacerts/ESTEID2018.cer");
return CertificateLoader.loadCertificatesFromResources(
"cacerts/ESTEID2018.cer",
"cacerts/ESTEID2025.crt"
);
}
...
```
Expand Down Expand Up @@ -320,7 +323,42 @@ toTitleCase(CertificateData.getSubjectSurname(userCertificate).orElseThrow()); /

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

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.
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.

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.

```java
AuthTokenValidator tokenValidator = new AuthTokenValidatorBuilder()
.withSiteOrigin(URI.create("https://example.org"))
.withTrustedCertificateAuthorities(trustedIntermediateCACertificates())
.withAllowedCertificatePolicies(
new X500Principal("CN=ESTEID2018,2.5.4.97=#0c0e4e545245452d3130373437303133,O=SK ID Solutions AS,C=EE"),
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.1.1"),
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.1.2")
)
.withAllowedCertificatePolicies(
new X500Principal("C=EE,O=Zetes Estonia OÜ,2.5.4.97=#0c0e4e545245452d3137303636303439,CN=ESTEID2025"),
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.2.1.1"),
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.2.1.2")
)
.build();
```

For another country, add each supported issuer and its policy OIDs to the same builder chain and provide the corresponding trusted intermediate CAs.

To restore the old deny-list behaviour, use:
```java
AuthTokenValidator tokenValidator = new AuthTokenValidatorBuilder()
.withSiteOrigin(URI.create("https://example.org"))
.withTrustedCertificateAuthorities(trustedIntermediateCACertificates())
.withDisallowedCertificatePolicies(
SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY_V1,
SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY_V2,
SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY_V3,
SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY
)
.build();
```

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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

package eu.webeid.example.config;

import java.util.Map;
import java.util.Set;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectFactory;
Expand Down Expand Up @@ -42,6 +46,48 @@ public class ValidationConfiguration {
private static final String CERTS_RESOURCE_PATH = "/certs/";
public static final String TRUSTED_CERTIFICATES_JKS = "trusted_certificates.jks";

/**
* Test document policies for Estonian ID cards, digital ID cards, residence permit cards and diplomatic
* identity cards issued under the ESTEID2018 and eID certificate-policy generations, grouped by issuer distinguished name.
*
* @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/">
* ID.ee certificate policy index for Estonian identity documents</a>
* @see <a href="https://www.id.ee/wp-content/uploads/2026/08/cp_esteid_v4.1_17.08.2026_allkirjastatud.pdf">
* IDEMIA-generation certificate policy</a>
* @see <a href="https://www.id.ee/wp-content/uploads/2026/06/eid-cp-v-2.0_04.06.2026_allkirjastatud.pdf">
* Thales-generation certificate policy</a>
* @see <a href="https://repository.eidpki.ee/static/documents/CertificateProfiles-20260520.pdf">
* ESTEID2025 certificate profile</a>
*/
public static final Map<X500Principal, Set<ASN1ObjectIdentifier>> ESTEID_TEST_DOCUMENT_POLICIES = Map.of(
new X500Principal(
"CN=TEST of ESTEID2018,2.5.4.97=#0c0e4e545245452d3130373437303133,O=SK ID Solutions AS,C=EE"
), Set.of(
// 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
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.2.1"), // Identity card of Estonian citizens
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.2.2"), // Identity card of European Union citizens
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.2.3"), // Digital identity card for Estonian residents
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.2.4"), // Digital identity card for e-residents
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.2.5"), // Residence permit card for long-term residents
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.2.6"), // Residence permit card for temporary residents and NATO SOFA
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.2.7"), // Residence permit card for family members of European Union citizens
new ASN1ObjectIdentifier("1.3.6.1.4.1.51455.1.2.1") // Diplomatic identity card
),
new X500Principal(
"C=EE,O=Zetes Estonia OÜ,2.5.4.97=#0c0e4e545245452d3137303636303439,CN=Test ESTEID2025"
), Set.of(
// 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
// https://repository.eidpki.ee/static/documents/CertificateProfiles-20260520.pdf According to the general practice, the propriotary OIDs for UAT and test certificates will be prefixed with “2.999.“ to differentiate them from the production OID’s.
new ASN1ObjectIdentifier("2.999.1.3.6.1.4.1.51361.2.1.1"), // Identity card of Estonian citizens
new ASN1ObjectIdentifier("2.999.1.3.6.1.4.1.51361.2.1.2"), // Identity card of European Union citizens
new ASN1ObjectIdentifier("2.999.1.3.6.1.4.1.51361.2.1.3"), // Residence permit card for long-term residents
new ASN1ObjectIdentifier("2.999.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.
new ASN1ObjectIdentifier("2.999.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
new ASN1ObjectIdentifier("2.999.1.3.6.1.4.1.51361.2.1.6"), // Digital identity card of e-residents
new ASN1ObjectIdentifier("2.999.1.3.6.1.4.1.51455.2.1.1") // Diplomatic identity card
)
);

@Value("${spring.profiles.active}")
private String activeProfile;

Expand All @@ -61,11 +107,18 @@ public ChallengeNonceGenerator generator(ChallengeNonceStore challengeNonceStore
@Bean
public AuthTokenValidator validator(YAMLConfig yamlConfig) {
try {
return new AuthTokenValidatorBuilder()
.withSiteOrigin(URI.create(yamlConfig.getLocalOrigin()))
AuthTokenValidatorBuilder authTokenValidatorBuilder = new AuthTokenValidatorBuilder().withSiteOrigin(URI.create(yamlConfig.getLocalOrigin()))
.withTrustedCertificateAuthorities(loadTrustedCACertificatesFromCerFiles())
.withTrustedCertificateAuthorities(loadTrustedCACertificatesFromTrustStore(yamlConfig))
.build();
.withTrustedCertificateAuthorities(loadTrustedCACertificatesFromTrustStore(yamlConfig));

if (!activeProfile.equals("prod")) {
ESTEID_TEST_DOCUMENT_POLICIES
.forEach((x500Principal, asn1ObjectIdentifiers) ->
authTokenValidatorBuilder.withAllowedCertificatePolicies(x500Principal, asn1ObjectIdentifiers.toArray(new ASN1ObjectIdentifier[0])));

}

return authTokenValidatorBuilder.build();
} catch (JceException e) {
throw new RuntimeException("Error building the Web eID auth token validator.", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@

package eu.webeid.example.config;

import static eu.webeid.example.config.ValidationConfiguration.ESTEID_TEST_DOCUMENT_POLICIES;

import eu.webeid.security.exceptions.JceException;
import eu.webeid.security.validator.AuthTokenValidator;
import eu.webeid.security.validator.AuthTokenValidatorBuilder;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;

import javax.security.auth.x500.X500Principal;
import java.net.URI;
import java.util.List;

Expand All @@ -26,13 +30,19 @@ public class TestValidatorConfiguration {
@Primary
AuthTokenValidator validatorWithoutOcspCall(ValidationConfiguration validationConfiguration,
YAMLConfig yamlConfig) throws JceException {
return new AuthTokenValidatorBuilder()
AuthTokenValidatorBuilder authTokenValidatorBuilder = new AuthTokenValidatorBuilder()
.withSiteOrigin(URI.create(yamlConfig.getLocalOrigin()))
.withTrustedCertificateAuthorities(validationConfiguration.loadTrustedCACertificatesFromCerFiles())
.withTrustedCertificateAuthorities(validationConfiguration.loadTrustedCACertificatesFromTrustStore(yamlConfig))

// Do not call the real OCSP service in tests.
.withCertificateRevocationChecker((subjectCertificate, issuerCertificate) -> List.of())
.build();
.withCertificateRevocationChecker((subjectCertificate, issuerCertificate) -> List.of());

ESTEID_TEST_DOCUMENT_POLICIES
.forEach((x500Principal, asn1ObjectIdentifiers) ->
authTokenValidatorBuilder.withAllowedCertificatePolicies(x500Principal, asn1ObjectIdentifiers.toArray(new ASN1ObjectIdentifier[0])));

return authTokenValidatorBuilder.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@

import org.bouncycastle.asn1.ASN1ObjectIdentifier;

import javax.security.auth.x500.X500Principal;
import java.util.Map;
import java.util.Set;

public final class SubjectCertificatePolicies {

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

/**
* Production document policies for Estonian ID cards, digital ID cards, residence permit cards and diplomatic
* identity cards issued under the ESTEID2018 and eID certificate-policy generations, grouped by issuer distinguished name.
*
* @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/">
* ID.ee certificate policy index for Estonian identity documents</a>
* @see <a href="https://www.id.ee/wp-content/uploads/2026/08/cp_esteid_v4.1_17.08.2026_allkirjastatud.pdf">
* IDEMIA-generation certificate policy</a>
* @see <a href="https://www.id.ee/wp-content/uploads/2026/06/eid-cp-v-2.0_04.06.2026_allkirjastatud.pdf">
* Thales-generation certificate policy</a>
* @see <a href="https://repository.eidpki.ee/static/documents/CertificateProfiles-20260520.pdf">
* ESTEID2025 certificate profile</a>
*/
public static final Map<X500Principal, Set<ASN1ObjectIdentifier>> ESTEID_PRODUCTION_DOCUMENT_POLICIES = Map.of(
new X500Principal(
"CN=ESTEID2018,2.5.4.97=#0c0e4e545245452d3130373437303133,O=SK ID Solutions AS,C=EE"
), Set.of(
// 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
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.1.1"), // Identity card of Estonian citizens
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.1.2"), // Identity card of European Union citizens
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.1.3"), // Digital identity card for Estonian residents
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.1.4"), // Digital identity card for e-residents
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.1.5"), // Residence permit card for long-term residents
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.1.6"), // Residence permit card for temporary residents and NATO SOFA
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.1.1.7"), // Residence permit card for family members of European Union citizens
new ASN1ObjectIdentifier("1.3.6.1.4.1.51455.1.1.1") // Diplomatic identity card
),
new X500Principal(
"C=EE,O=Zetes Estonia OÜ,2.5.4.97=#0c0e4e545245452d3137303636303439,CN=ESTEID2025"
), Set.of(
// 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
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.2.1.1"), // Identity card of Estonian citizens
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.2.1.2"), // Identity card of European Union citizens
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.2.1.3"), // Residence permit card for long-term residents
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.
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
new ASN1ObjectIdentifier("1.3.6.1.4.1.51361.2.1.6"), // Digital identity card of e-residents
new ASN1ObjectIdentifier("1.3.6.1.4.1.51455.2.1.1") // Diplomatic identity card
)
);

private SubjectCertificatePolicies() {
throw new IllegalStateException("Constants class");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
package eu.webeid.security.exceptions;

/**
* Thrown when any of the configured disallowed policies is present in the user certificate.
* Thrown when the user certificate does not satisfy the configured allowed or disallowed policy restrictions.
*/
public class UserCertificateDisallowedPolicyException extends AuthTokenException {
public UserCertificateDisallowedPolicyException() {
Expand Down
Loading
Loading