Skip to content
Merged
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
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ import org.webeid.security.nonce.NonceGeneratorBuilder;

## 4. Add trusted certificate authority certificates

You must explicitly specify which **intermediate** certificate authorities (CAs) are trusted to issue the eID authentication certificates. CA certificates can be loaded from either the truststore file, resources or any stream source. We use the [`CertificateLoader`](https://github.com/web-eid/web-eid-authtoken-validation-java/blob/main/src/test/java/org/webeid/security/testutil/CertificateLoader.java) helper class from [`testutil`](https://github.com/web-eid/web-eid-authtoken-validation-java/tree/main/src/test/java/org/webeid/security/testutil) to load CA certificates from resources here, but consider using [the truststore file](https://github.com/web-eid/web-eid-spring-boot-example/blob/main/src/main/java/org/webeid/example/config/ValidationConfiguration.java#L104-L122) instead.
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`](https://github.com/web-eid/web-eid-authtoken-validation-java/blob/main/src/test/java/org/webeid/security/testutil/CertificateLoader.java) helper class from [`testutil`](https://github.com/web-eid/web-eid-authtoken-validation-java/tree/main/src/test/java/org/webeid/security/testutil) to load CA certificates from resources here, but consider using [the truststore file](https://github.com/web-eid/web-eid-spring-boot-example/blob/main/src/main/java/org/webeid/example/config/ValidationConfiguration.java#L104-L122) instead.

First, copy the trusted certificates, for example `ESTEID-SK_2015.cer` and `ESTEID2018.cer`, to `resources/cacerts/`, then load the certificates as follows:

Expand Down Expand Up @@ -260,7 +260,7 @@ As described in section *[5. Configure the authentication token validator](#5-co

The **nonce cache** instance is used to look up nonce expiry time using its unique value as key. The values in the cache are populated by the nonce generator as described in section *[Nonce generation](#nonce-generation)* below. Consider using [Caffeine](https://github.com/ben-manes/caffeine) or [Ehcache](https://www.ehcache.org/) as the caching provider if your application does not run in a cluster, or [Hazelcast](https://hazelcast.com/), [Infinispan](https://infinispan.org/) or non-Java distributed cahces like [Memcached](https://memcached.org/) or [Redis](https://redis.io/) if it does. Cache configuration is described in more detail in section *[2. Add cache support](#2-add-cache-support)*.

The **trusted certificate authority certificates** are used to validate that the user certificate from the authentication token is signed by a trusted certificate authority. Intermediate CA certificates must be used instead of the root CA certificates so that revoked CA certificates can be detected. Trusted certificate authority certificates configuration is described in more detail in section *[4. Add trusted certificate authority certificates](#4-add-trusted-certificate-authority-certificates)*.
The **trusted certificate authority certificates** are used to validate that the user certificate from the authentication token and the OCSP responder certificate is signed by a trusted certificate authority. Intermediate CA certificates must be used instead of the root CA certificates so that revoked CA certificates can be removed. Trusted certificate authority certificates configuration is described in more detail in section *[4. Add trusted certificate authority certificates](#4-add-trusted-certificate-authority-certificates)*.

The authentication token validator configuration and construction is described in more detail in section *[5. Configure the authentication token validator](#5-configure-the-authentication-token-validator)*. Once the validator object has been constructed, it can be used for validating authentication tokens as follows:

Expand All @@ -287,12 +287,13 @@ toTitleCase(CertUtil.getSubjectSurname(userCertificate)); // "Jõeorg"

The following additional configuration options are available in `AuthTokenValidatorBuilder`:

- `withSiteCertificateSha256Fingerprint(String siteCertificateFingerprint)` – turns on origin website certificate fingerprint validation. The validator checks that the site certificate fingerprint from the authentication token matches with the provided site certificate SHA-256 fingerprint. This disables powerful man-in-the-middle attacks where attackers are able to issue falsified certificates for the origin, but also disables TLS proxy usage. Due to the technical limitations of web browsers, certificate fingerprint validation currently works only with Firefox. The provided certificate SHA-256 fingerprint should have the prefix `urn:cert:sha-256:` followed by the hexadecimal encoding of the hash value octets as specified in [URN Namespace for Certificates](https://tools.ietf.org/id/draft-seantek-certspec-01.html). Certificate fingerprint validation is disabled by default.
- `withoutUserCertificateRevocationCheckWithOcsp()` – turns off user certificate revocation check with OCSP. The OCSP URL is extracted from the user certificate AIA extension. OCSP check is enabled by default.
- `withoutUserCertificateRevocationCheckWithOcsp()` – turns off user certificate revocation check with OCSP. OCSP check is enabled by default and the OCSP responder access location URL is extracted from the user certificate AIA extension unless a designated OCSP service is activated.
- `withDesignatedOcspServiceConfiguration(DesignatedOcspServiceConfiguration serviceConfiguration)` – activates the provided designated OCSP responder service configuration for user certificate revocation check with OCSP. The designated service is only used for checking the status of the certificates whose issuers are supported by the service, for other certificates the default AIA extension service access location will be used. See configuration examples in `testutil.OcspServiceMaker.getDesignatedOcspServiceConfiguration()`.
- `withOcspRequestTimeout(Duration ocspRequestTimeout)` – sets both the connection and response timeout of user certificate revocation check OCSP requests. Default is 5 seconds.
- `withAllowedClientClockSkew(Duration allowedClockSkew)` – sets the tolerated clock skew of the client computer when verifying the token expiration. Default value is 3 minutes.
- `withDisallowedCertificatePolicies(ASN1ObjectIdentifier... policies)` – adds the given policies to the list of disallowed user certificate policies. In order for the user certificate to be considered valid, it must not contain any policies present in this list. Contains the Estonian Mobile-ID policies by default as it must not be possible to authenticate with a Mobile-ID certificate when an eID smart card is expected.
- `withNonceDisabledOcspUrls(URI... urls)` – adds the given URLs to the list of OCSP URLs for which the nonce protocol extension will be disabled. Some OCSP services don't support the nonce extension. Contains the ESTEID-2015 OCSP URL by default.
- `withNonceDisabledOcspUrls(URI... urls)` – adds the given URLs to the list of OCSP responder access location URLs for which the nonce protocol extension will be disabled. Some OCSP responders don't support the nonce extension. Contains the ESTEID-2015 OCSP responder URL by default.
- `withSiteCertificateSha256Fingerprint(String siteCertificateFingerprint)` – turns on origin website certificate fingerprint validation. The validator checks that the site certificate fingerprint from the authentication token matches with the provided site certificate SHA-256 fingerprint. This disables powerful man-in-the-middle attacks where attackers are able to issue falsified certificates for the origin, but also disables TLS proxy usage. Due to the technical limitations of web browsers, certificate fingerprint validation is an experimental feature that currently works only with Firefox. The provided certificate SHA-256 fingerprint should have the prefix `urn:cert:sha-256:` followed by the hexadecimal encoding of the hash value octets as specified in [URN Namespace for Certificates](https://tools.ietf.org/id/draft-seantek-certspec-01.html). Certificate fingerprint validation is disabled by default.

Extended configuration example:

Expand All @@ -311,9 +312,9 @@ AuthTokenValidator validator = new AuthTokenValidatorBuilder()

### Certificates' *Authority Information Access* (AIA) extension

It is assumed that the AIA extension that contains the certificates’ OCSP service location, is part of both the user and CA certificates. The AIA OCSP URL will be used to check the certificate revocation status with OCSP.
Unless a designated OCSP responder service is in use, it is required that the AIA extension that contains the certificate’s OCSP responder access location is present in the user certificate. The AIA OCSP URL will be used to check the certificate revocation status with OCSP.

**Note that there may be legal limitations to using AIA URLs during signing** as the services behind these URLs provide different security and SLA guarantees than dedicated OCSP services. For digital signing, OCSP responder certificate validation is additionally needed. Using AIA URLs during authentication is sufficient, however.
**Note that there may be legal limitations to using AIA URLs during signing** as the services behind these URLs provide different security and SLA guarantees than dedicated OCSP responder services. Using AIA URLs during authentication is sufficient, however.

## Possible validation errors

Expand Down Expand Up @@ -343,6 +344,7 @@ String nonce = nonceGenerator.generateAndStoreNonce();
The `generateAndStoreNonce()` method both generates the nonce and stores it in the cache.

## Extended configuration

The following additional configuration options are available in `NonceGeneratorBuilder`:

- `withNonceTtl(Duration duration)` – overrides the default nonce time-to-live duration. When the time-to-live passes, the nonce is considered to be expired. Default nonce time-to-live is 5 minutes.
Expand Down
17 changes: 15 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>authtoken-validation</artifactId>
<groupId>org.webeid.security</groupId>
<version>1.1.0</version>
<version>1.2.0</version>
<packaging>jar</packaging>
<name>authtoken-validation</name>
<description>Web eID authentication token validation library for Java</description>
Expand All @@ -16,9 +16,11 @@
<java.version>1.8</java.version>
<jjwt.version>0.11.2</jjwt.version>
<slf4j.version>1.7.30</slf4j.version>
<bouncycastle.version>1.69</bouncycastle.version>
<caffeine.version>2.8.5</caffeine.version>
<junit-jupiter.version>5.6.2</junit-jupiter.version>
<assertj.version>3.17.2</assertj.version>
<mockito.version>3.12.4</mockito.version>
<jacoco.version>0.8.5</jacoco.version>
<sonar.coverage.jacoco.xmlReportPaths>
${project.basedir}/../jacoco-coverage-report/target/site/jacoco-aggregate/jacoco.xml
Expand Down Expand Up @@ -67,10 +69,15 @@
<artifactId>guava</artifactId>
<version>30.1-jre</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.65</version>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
Expand All @@ -90,6 +97,12 @@
<version>${assertj.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
* SOFTWARE.
*/

package org.webeid.security.util;
package org.webeid.security.certificate;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x500.RDN;
Expand All @@ -34,38 +34,44 @@
import java.util.Arrays;
import java.util.stream.Collectors;

public final class CertUtil {
public final class CertificateData {

public static String getSubjectCN(X509Certificate certificate) throws CertificateEncodingException {
return getField(certificate, BCStyle.CN);
return getSubjectField(certificate, BCStyle.CN);
}

public static String getSubjectSurname(X509Certificate certificate) throws CertificateEncodingException {
return getField(certificate, BCStyle.SURNAME);
return getSubjectField(certificate, BCStyle.SURNAME);
}

public static String getSubjectGivenName(X509Certificate certificate) throws CertificateEncodingException {
return getField(certificate, BCStyle.GIVENNAME);
return getSubjectField(certificate, BCStyle.GIVENNAME);
}

public static String getSubjectIdCode(X509Certificate certificate) throws CertificateEncodingException {
return getField(certificate, BCStyle.SERIALNUMBER);
return getSubjectField(certificate, BCStyle.SERIALNUMBER);
}

public static String getSubjectCountryCode(X509Certificate certificate) throws CertificateEncodingException {
return getField(certificate, BCStyle.C);
return getSubjectField(certificate, BCStyle.C);
}

private static String getField(X509Certificate certificate, ASN1ObjectIdentifier fieldId) throws CertificateEncodingException {
final X500Name x500Name = new JcaX509CertificateHolder(certificate).getSubject();
private static String getSubjectField(X509Certificate certificate, ASN1ObjectIdentifier fieldId) throws CertificateEncodingException {
return getField(new JcaX509CertificateHolder(certificate).getSubject(), fieldId);
}

private static String getField(X500Name x500Name, ASN1ObjectIdentifier fieldId) throws CertificateEncodingException {
// Example value: [C=EE, CN=JÕEORG\,JAAK-KRISTJAN\,38001085718, 2.5.4.4=#0c074ac395454f5247, 2.5.4.42=#0c0d4a41414b2d4b524953544a414e, 2.5.4.5=#1311504e4f45452d3338303031303835373138]
final RDN[] rdns = x500Name.getRDNs(fieldId);
if (rdns.length == 0 || rdns[0].getFirst() == null) {
throw new CertificateEncodingException("X500 name RDNs empty or first element is null");
}
return Arrays.stream(rdns)
.map(rdn -> IETFUtils.valueToString(rdn.getFirst().getValue()))
.collect(Collectors.joining(", "));
}

private CertUtil() {
private CertificateData() {
throw new IllegalStateException("Utility class");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,43 @@
* SOFTWARE.
*/

package org.webeid.security.testutil;
package org.webeid.security.certificate;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;

public final class CertificateLoader {

public static X509Certificate[] loadCertificatesFromResources(String... resourceNames) throws CertificateException {
List<X509Certificate> caCertificates = new ArrayList<>();
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
public static X509Certificate[] loadCertificatesFromResources(String... resourceNames) throws CertificateException, IOException {
final List<X509Certificate> caCertificates = new ArrayList<>();
final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");

for (final String resourceName : resourceNames) {
try (final InputStream resourceAsStream = ClassLoader.getSystemResourceAsStream(resourceName)) {
X509Certificate caCertificate = (X509Certificate) certFactory.generateCertificate(resourceAsStream);
caCertificates.add(caCertificate);
} catch (IOException e) {
throw new RuntimeException("Error loading certificate resource.", e);
}
}

return caCertificates.toArray(new X509Certificate[0]);
}

public static X509Certificate loadCertificateFromBase64String(String certificate) throws CertificateException, IOException {
try (final InputStream targetStream = new ByteArrayInputStream(Base64.getDecoder().decode(certificate))) {
return (X509Certificate) CertificateFactory
.getInstance("X509")
.generateCertificate(targetStream);
}
}

private CertificateLoader() {
throw new IllegalStateException("Utility class");
}
}
Loading