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
59 changes: 56 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,57 @@ 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 both the configured allow-list and deny-list are empty. Allow-lists and deny-lists cannot be configured together. Strict certificate policy validation is enabled by default: a matching issuer must contain one of its configured policy OIDs, and an issuer without an allow-list entry is rejected. Calling `withAllowedCertificatePolicies(issuer)` without policy OIDs removes that issuer's entry. If no allow-list or deny-list 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.
Comment thread
svenzik marked this conversation as resolved.
Resolved

```java
AuthTokenValidator tokenValidator = new AuthTokenValidatorBuilder()
.withSiteOrigin(URI.create("https://example.org"))
.withTrustedCertificateAuthorities(trustedIntermediateCACertificates())
.withAllowedCertificatePolicies(ESTEID2018_ISSUER, ESTEID_PRODUCTION_DOCUMENT_POLICIES.get(ESTEID2018_ISSUER))
.withAllowedCertificatePolicies(ESTEID2025_ISSUER, ESTEID_PRODUCTION_DOCUMENT_POLICIES.get(ESTEID2025_ISSUER))
.build();
```

To accept only identity cards issued to Estonian citizens or European Union citizens, configure the two corresponding policy OIDs for each supported certificate generation:

```java
AuthTokenValidator tokenValidator = new AuthTokenValidatorBuilder()
.withSiteOrigin(URI.create("https://example.org"))
.withTrustedCertificateAuthorities(trustedIntermediateCACertificates())
.withAllowedCertificatePolicies(
ESTEID2018_ISSUER,
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
)
.withAllowedCertificatePolicies(
ESTEID2025_ISSUER,
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
)
.build();
```

For this restriction, trust only the intermediate CAs that issue the supported Estonian ID cards. Other policies from the configured issuers, including digital identity cards, residence permit cards and diplomatic identity cards, are rejected. Certificates from an issuer without an allow-list entry are also rejected.

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

To retain the previous lenient behaviour, call `allowIssuersNotInWhitelist(true)`. The allow-list then applies only to issuers with a configured entry; other issuers are not restricted by certificate policy.

To restore the old deny-list behaviour, configure only disallowed policies. Configuring a deny-list prevents the default Estonian allow-list from being applied:
```java
AuthTokenValidator tokenValidator = new AuthTokenValidatorBuilder()
.withSiteOrigin(URI.create("https://example.org"))
.withTrustedCertificateAuthorities(trustedIntermediateCACertificates())
.withDisallowedCertificatePolicies(
new ASN1ObjectIdentifier("1.3.6.1.4.1.10015.1.3.1"), // ESTEID-SK 2015 Mobile-ID policy v1
new ASN1ObjectIdentifier("1.3.6.1.4.1.10015.1.3.2"), // ESTEID-SK 2015 Mobile-ID policy v2
new ASN1ObjectIdentifier("1.3.6.1.4.1.10015.1.3.3"), // ESTEID-SK 2015 Mobile-ID policy v3
new ASN1ObjectIdentifier("1.3.6.1.4.1.10015.1.3") // 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,60 @@ 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, 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.skidsolutions.eu/upload/files/SK-CPR-ESTEID2018-EN-v1_3_20220217.pdf 2.2.3. Certificate Policy
// 1.3.6.1.4.1.51361.1.{1 or 2} - System Sub-OID: production = 1; test = 2
// Example OID 1:Identity card of Estonian citizen (test): 51361.1.2.1
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

// Some test ID-cards that have production OID-s
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
).toArray(new ASN1ObjectIdentifier[0]),
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
).toArray(new ASN1ObjectIdentifier[0])
);

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

Expand All @@ -61,11 +119,16 @@ 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()))
.allowIssuersNotInWhitelist(true)
.withTrustedCertificateAuthorities(loadTrustedCACertificatesFromCerFiles())
.withTrustedCertificateAuthorities(loadTrustedCACertificatesFromTrustStore(yamlConfig))
.build();
.withTrustedCertificateAuthorities(loadTrustedCACertificatesFromTrustStore(yamlConfig));

if (!activeProfile.equals("prod")) {
ESTEID_TEST_DOCUMENT_POLICIES.forEach(authTokenValidatorBuilder::withAllowedCertificatePolicies);
}

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,16 @@

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.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 +29,18 @@ public class TestValidatorConfiguration {
@Primary
AuthTokenValidator validatorWithoutOcspCall(ValidationConfiguration validationConfiguration,
YAMLConfig yamlConfig) throws JceException {
return new AuthTokenValidatorBuilder()
AuthTokenValidatorBuilder authTokenValidatorBuilder = new AuthTokenValidatorBuilder()
.withSiteOrigin(URI.create(yamlConfig.getLocalOrigin()))
.allowIssuersNotInWhitelist(true)
.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(authTokenValidatorBuilder::withAllowedCertificatePolicies);

return authTokenValidatorBuilder.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,55 @@

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";
public static final X500Principal ESTEID2018_ISSUER = new X500Principal(
"CN=ESTEID2018,2.5.4.97=#0c0e4e545245452d3130373437303133,O=SK ID Solutions AS,C=EE"
);
public static final X500Principal ESTEID2025_ISSUER = new X500Principal(
"C=EE,O=Zetes Estonia OÜ,2.5.4.97=#0c0e4e545245452d3137303636303439,CN=ESTEID2025"
);

public static final ASN1ObjectIdentifier ESTEID_SK_2015_MOBILE_ID_POLICY =
new ASN1ObjectIdentifier(ESTEID_SK_2015_MOBILE_ID_POLICY_PREFIX);
public static final ASN1ObjectIdentifier ESTEID_SK_2015_MOBILE_ID_POLICY_V1 =
new ASN1ObjectIdentifier(ESTEID_SK_2015_MOBILE_ID_POLICY_PREFIX + ".1");
public static final ASN1ObjectIdentifier ESTEID_SK_2015_MOBILE_ID_POLICY_V2 =
new ASN1ObjectIdentifier(ESTEID_SK_2015_MOBILE_ID_POLICY_PREFIX + ".2");
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, ASN1ObjectIdentifier[]> ESTEID_PRODUCTION_DOCUMENT_POLICIES = Map.of(
ESTEID2018_ISSUER, 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
).toArray(new ASN1ObjectIdentifier[0]),
ESTEID2025_ISSUER, 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
).toArray(new ASN1ObjectIdentifier[0])
);

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