Skip to content

Commit

Permalink
feat(OCSP): make collections immutable in AuthtokeValidationConfigura…
Browse files Browse the repository at this point in the history
…tion.copy() and CertificateValidator.buildTrustAnchorsFromCertificates(), reimplement AIA OCSP access location retriving from certificate with higher level AuthorityInformationAccess API
  • Loading branch information
mrts committed Sep 30, 2021
1 parent bb09f83 commit d7f291c
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 87 deletions.
6 changes: 3 additions & 3 deletions README.md
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,13 +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 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.
- `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 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 Down
Expand Up @@ -40,6 +40,7 @@
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Set;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -87,9 +88,9 @@ public static X509Certificate validateIsSignedByTrustedCA(X509Certificate certif
}

public static Set<TrustAnchor> buildTrustAnchorsFromCertificates(Collection<X509Certificate> certificates) {
return certificates.stream()
return Collections.unmodifiableSet(certificates.stream()
.map(cert -> new TrustAnchor(cert, null))
.collect(Collectors.toSet());
.collect(Collectors.toSet()));
}

public static CertStore buildCertStoreFromCertificates(Collection<X509Certificate> certificates) throws JceException {
Expand Down
Expand Up @@ -33,6 +33,7 @@
import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;

Expand Down Expand Up @@ -70,15 +71,15 @@ public final class AuthTokenValidationConfiguration {
private AuthTokenValidationConfiguration(AuthTokenValidationConfiguration other) {
this.siteOrigin = other.siteOrigin;
this.nonceCache = other.nonceCache;
this.trustedCACertificates = new HashSet<>(other.trustedCACertificates);
this.trustedCACertificates = Collections.unmodifiableSet(new HashSet<>(other.trustedCACertificates));
this.isUserCertificateRevocationCheckWithOcspEnabled = other.isUserCertificateRevocationCheckWithOcspEnabled;
this.ocspRequestTimeout = other.ocspRequestTimeout;
this.allowedClientClockSkew = other.allowedClientClockSkew;
this.designatedOcspServiceConfiguration = other.designatedOcspServiceConfiguration;
this.isSiteCertificateFingerprintValidationEnabled = other.isSiteCertificateFingerprintValidationEnabled;
this.siteCertificateSha256Fingerprint = other.siteCertificateSha256Fingerprint;
this.disallowedSubjectCertificatePolicies = new HashSet<>(other.disallowedSubjectCertificatePolicies);
this.nonceDisabledOcspUrls = new HashSet<>(other.nonceDisabledOcspUrls);
this.disallowedSubjectCertificatePolicies = Collections.unmodifiableSet(new HashSet<>(other.disallowedSubjectCertificatePolicies));
this.nonceDisabledOcspUrls = Collections.unmodifiableSet(new HashSet<>(other.nonceDisabledOcspUrls));
}

void setSiteOrigin(URI siteOrigin) {
Expand Down
114 changes: 35 additions & 79 deletions src/main/java/org/webeid/security/validator/ocsp/OcspUrl.java
@@ -1,109 +1,65 @@
/*
* Copyright (c) 2017 The Netty Project
* Copyright (c) 2020-2021 Estonian Information System Authority
*
* The Netty Project and The Web eID Project license this file to you under the
* Apache License, version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* http://www.apache.org/licenses/LICENSE-2.0
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package org.webeid.security.validator.ocsp;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.BERTags;
import org.bouncycastle.asn1.DLSequence;
import org.bouncycastle.asn1.DLTaggedObject;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.asn1.ASN1String;
import org.bouncycastle.asn1.x509.AccessDescription;
import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.cert.X509CertificateHolder;

import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Objects;

public final class OcspUrl {

public static final URI AIA_ESTEID_2015 = URI.create("http://aia.sk.ee/esteid2015");

/**
* The OID for OCSP responder URLs.
* <p>
* https://oidref.com/1.3.6.1.5.5.7.48.1
*/
private static final ASN1ObjectIdentifier OCSP_RESPONDER_OID
= new ASN1ObjectIdentifier("1.3.6.1.5.5.7.48.1").intern();

/**
* Returns the OCSP responder {@link URI} or {@code null} if it doesn't have one.
*/
public static URI getOcspUri(X509Certificate certificate) {
Objects.requireNonNull(certificate, "certificate");
final byte[] value = certificate.getExtensionValue(Extension.authorityInfoAccess.getId());
if (value == null) {
return null;
}

final ASN1Primitive authorityInfoAccess;
final X509CertificateHolder certificateHolder;
try {
authorityInfoAccess = JcaX509ExtensionUtils.parseExtensionValue(value);
} catch (IOException | IllegalArgumentException e) {
return null;
}
if (!(authorityInfoAccess instanceof DLSequence)) {
return null;
}

final DLSequence aiaSequence = (DLSequence) authorityInfoAccess;
final DLTaggedObject taggedObject = findObject(aiaSequence, OCSP_RESPONDER_OID, DLTaggedObject.class);
if (taggedObject == null) {
return null;
}

if (taggedObject.getTagNo() != BERTags.OBJECT_IDENTIFIER) {
return null;
}

final byte[] encoded;
try {
encoded = taggedObject.getEncoded();
} catch (IOException e) {
return null;
}
int length = encoded[1] & 0xFF;
final String uri = new String(encoded, 2, length, StandardCharsets.UTF_8);
return URI.create(uri);
}

private static <T> T findObject(DLSequence sequence, ASN1ObjectIdentifier oid, Class<T> type) {
for (final ASN1Encodable element : sequence) {
if (!(element instanceof DLSequence)) {
continue;
}

final DLSequence subSequence = (DLSequence) element;
if (subSequence.size() != 2) {
continue;
}

final ASN1Encodable key = subSequence.getObjectAt(0);
final ASN1Encodable value = subSequence.getObjectAt(1);

if (key.equals(oid) && type.isInstance(value)) {
return type.cast(value);
certificateHolder = new X509CertificateHolder(certificate.getEncoded());
final AuthorityInformationAccess authorityInformationAccess =
AuthorityInformationAccess.fromExtensions(certificateHolder.getExtensions());
for (AccessDescription accessDescription :
authorityInformationAccess.getAccessDescriptions()) {
if (accessDescription.getAccessMethod().equals(AccessDescription.id_ad_ocsp) &&
accessDescription.getAccessLocation().getTagNo() == GeneralName.uniformResourceIdentifier) {
final String accessLocationUrl = ((ASN1String) accessDescription.getAccessLocation().getName())
.getString();
return URI.create(accessLocationUrl);
}
}
} catch (IOException | CertificateEncodingException | IllegalArgumentException | NullPointerException e) {
return null;
}

return null;
}

Expand Down

0 comments on commit d7f291c

Please sign in to comment.