Skip to content

Commit 25f11f8

Browse files
committed
feat(OCSP): implement configurable designated OCSP service, verify OCSP responder certificate and response signature
WE2-432 Signed-off-by: Mart Somermaa <[email protected]>
1 parent 9275777 commit 25f11f8

File tree

53 files changed

+1327
-294
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1327
-294
lines changed

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ import java.security.cert.X509Certificate;
143143
...
144144
```
145145

146+
## 5. Add trusted OCSP responder certificates
147+
148+
- AIA
149+
- Designated
150+
146151
## 5. Configure the authentication token validator
147152

148153
Once the prerequisites have been met, the authentication token validator itself can be configured.
@@ -235,6 +240,8 @@ try {
235240
- [Nonce generation](#nonce-generation)
236241
- [Basic usage](#basic-usage-1)
237242
- [Extended configuration](#extended-configuration-1)
243+
- [Frequently asked questions](#frequently-asked-questions)
244+
- [How can I find the AIA OCSP service URLs?](#how-can-i-find-the-aia-ocsp-service-urls)
238245
239246
# Introduction
240247
@@ -356,3 +363,14 @@ NonceGenerator generator = new NonceGeneratorBuilder()
356363
.withSecureRandom(customSecureRandom)
357364
.build();
358365
```
366+
367+
## Frequently asked questions
368+
369+
### How can I find the AIA OCSP service URLs?
370+
371+
You can find the AIA OCSP service URLs from the electronic ID certificate profile documents, in the section that describes certificate extensions.
372+
The AIA OCSP extension OID is 1.3.6.1.5.5.7.48.1.
373+
374+
For example, the EstEID AIA URLs are specified in the documents
375+
[*Certificate, CRL and OCSP Profile for identification documents of the Republic of Estonia*](https://www.skidsolutions.eu/upload/files/SK-CPR-ESTEID-EN-v8_4-20200630.pdf) and
376+
[*Certificate, CRL and OCSP Profile for ID-1 Format Identity Documents Issued by the Republic of Estonia*](https://www.skidsolutions.eu/upload/files/SK-CPR-ESTEID2018-EN-v1_2_20200630.pdf).

pom.xml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<modelVersion>4.0.0</modelVersion>
66
<artifactId>authtoken-validation</artifactId>
77
<groupId>org.webeid.security</groupId>
8-
<version>1.1.0</version>
8+
<version>2.0.0</version>
99
<packaging>jar</packaging>
1010
<name>authtoken-validation</name>
1111
<description>Web eID authentication token validation library for Java</description>
@@ -16,6 +16,7 @@
1616
<java.version>1.8</java.version>
1717
<jjwt.version>0.11.2</jjwt.version>
1818
<slf4j.version>1.7.30</slf4j.version>
19+
<bouncycastle.version>1.65</bouncycastle.version>
1920
<caffeine.version>2.8.5</caffeine.version>
2021
<junit-jupiter.version>5.6.2</junit-jupiter.version>
2122
<assertj.version>3.17.2</assertj.version>
@@ -67,10 +68,15 @@
6768
<artifactId>guava</artifactId>
6869
<version>30.1-jre</version>
6970
</dependency>
71+
<dependency>
72+
<groupId>org.bouncycastle</groupId>
73+
<artifactId>bcprov-jdk15on</artifactId>
74+
<version>${bouncycastle.version}</version>
75+
</dependency>
7076
<dependency>
7177
<groupId>org.bouncycastle</groupId>
7278
<artifactId>bcpkix-jdk15on</artifactId>
73-
<version>1.65</version>
79+
<version>${bouncycastle.version}</version>
7480
</dependency>
7581
<dependency>
7682
<groupId>com.squareup.okhttp3</groupId>

src/main/java/org/webeid/security/util/CertUtil.java renamed to src/main/java/org/webeid/security/certificate/CertificateData.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
* SOFTWARE.
2121
*/
2222

23-
package org.webeid.security.util;
23+
package org.webeid.security.certificate;
2424

2525
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
2626
import org.bouncycastle.asn1.x500.RDN;
@@ -34,7 +34,7 @@
3434
import java.util.Arrays;
3535
import java.util.stream.Collectors;
3636

37-
public final class CertUtil {
37+
public final class CertificateData {
3838

3939
public static String getSubjectCN(X509Certificate certificate) throws CertificateEncodingException {
4040
return getField(certificate, BCStyle.CN);
@@ -65,7 +65,7 @@ private static String getField(X509Certificate certificate, ASN1ObjectIdentifier
6565
.collect(Collectors.joining(", "));
6666
}
6767

68-
private CertUtil() {
68+
private CertificateData() {
6969
throw new IllegalStateException("Utility class");
7070
}
7171

src/test/java/org/webeid/security/testutil/CertificateLoader.java renamed to src/main/java/org/webeid/security/certificate/CertificateLoader.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,32 +20,43 @@
2020
* SOFTWARE.
2121
*/
2222

23-
package org.webeid.security.testutil;
23+
package org.webeid.security.certificate;
2424

25+
import java.io.ByteArrayInputStream;
2526
import java.io.IOException;
2627
import java.io.InputStream;
2728
import java.security.cert.CertificateException;
2829
import java.security.cert.CertificateFactory;
2930
import java.security.cert.X509Certificate;
3031
import java.util.ArrayList;
32+
import java.util.Base64;
3133
import java.util.List;
3234

3335
public final class CertificateLoader {
3436

35-
public static X509Certificate[] loadCertificatesFromResources(String... resourceNames) throws CertificateException {
36-
List<X509Certificate> caCertificates = new ArrayList<>();
37-
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
37+
public static X509Certificate[] loadCertificatesFromResources(String... resourceNames) throws CertificateException, IOException {
38+
final List<X509Certificate> caCertificates = new ArrayList<>();
39+
final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
3840

3941
for (final String resourceName : resourceNames) {
4042
try (final InputStream resourceAsStream = ClassLoader.getSystemResourceAsStream(resourceName)) {
4143
X509Certificate caCertificate = (X509Certificate) certFactory.generateCertificate(resourceAsStream);
4244
caCertificates.add(caCertificate);
43-
} catch (IOException e) {
44-
throw new RuntimeException("Error loading certificate resource.", e);
4545
}
4646
}
4747

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

51+
public static X509Certificate loadCertificateFromBase64String(String certificate) throws CertificateException, IOException {
52+
try (final InputStream targetStream = new ByteArrayInputStream(Base64.getDecoder().decode(certificate))) {
53+
return (X509Certificate) CertificateFactory
54+
.getInstance("X509")
55+
.generateCertificate(targetStream);
56+
}
57+
}
58+
59+
private CertificateLoader() {
60+
throw new IllegalStateException("Utility class");
61+
}
5162
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package org.webeid.security.certificate;
2+
3+
import org.webeid.security.exceptions.CertificateNotTrustedException;
4+
import org.webeid.security.exceptions.JceException;
5+
import org.webeid.security.exceptions.UserCertificateExpiredException;
6+
import org.webeid.security.exceptions.UserCertificateNotYetValidException;
7+
8+
import java.security.GeneralSecurityException;
9+
import java.security.InvalidAlgorithmParameterException;
10+
import java.security.NoSuchAlgorithmException;
11+
import java.security.cert.CertPathBuilder;
12+
import java.security.cert.CertPathBuilderException;
13+
import java.security.cert.CertStore;
14+
import java.security.cert.CertificateExpiredException;
15+
import java.security.cert.CertificateNotYetValidException;
16+
import java.security.cert.CollectionCertStoreParameters;
17+
import java.security.cert.PKIXBuilderParameters;
18+
import java.security.cert.PKIXCertPathBuilderResult;
19+
import java.security.cert.TrustAnchor;
20+
import java.security.cert.X509CertSelector;
21+
import java.security.cert.X509Certificate;
22+
import java.util.Collection;
23+
import java.util.Collections;
24+
import java.util.Date;
25+
import java.util.Set;
26+
import java.util.stream.Collectors;
27+
28+
public final class CertificateValidator {
29+
30+
/**
31+
* Checks whether the certificate was valid on the given date.
32+
*/
33+
public static void certificateIsValidOnDate(X509Certificate cert, Date date) throws UserCertificateNotYetValidException, UserCertificateExpiredException {
34+
try {
35+
cert.checkValidity(date);
36+
} catch (CertificateNotYetValidException e) {
37+
throw new UserCertificateNotYetValidException(e);
38+
} catch (CertificateExpiredException e) {
39+
throw new UserCertificateExpiredException(e);
40+
}
41+
}
42+
43+
public static X509Certificate validateIsSignedByTrustedCA(X509Certificate certificate,
44+
Set<TrustAnchor> trustedCACertificateAnchors,
45+
CertStore trustedCACertificateCertStore) throws CertificateNotTrustedException, JceException {
46+
final X509CertSelector selector = new X509CertSelector();
47+
selector.setCertificate(certificate);
48+
49+
try {
50+
final PKIXBuilderParameters pkixBuilderParameters = new PKIXBuilderParameters(trustedCACertificateAnchors, selector);
51+
pkixBuilderParameters.setRevocationEnabled(false);
52+
pkixBuilderParameters.addCertStore(trustedCACertificateCertStore);
53+
54+
// See the comment in buildCertStoreFromCertificates() below why we use the default JCE provider.
55+
final CertPathBuilder certPathBuilder = CertPathBuilder.getInstance(CertPathBuilder.getDefaultType());
56+
final PKIXCertPathBuilderResult result = (PKIXCertPathBuilderResult) certPathBuilder.build(pkixBuilderParameters);
57+
58+
return result.getTrustAnchor().getTrustedCert();
59+
60+
} catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException e) {
61+
throw new JceException(e);
62+
} catch (CertPathBuilderException e) {
63+
throw new CertificateNotTrustedException(certificate, e);
64+
}
65+
}
66+
67+
public static Set<TrustAnchor> buildTrustAnchorsFromCertificates(Collection<X509Certificate> certificates) {
68+
return certificates.stream()
69+
.map(cert -> new TrustAnchor(cert, null))
70+
.collect(Collectors.toSet());
71+
}
72+
73+
public static Set<TrustAnchor> buildTrustAnchorsFromCertificate(X509Certificate certificate) {
74+
return buildTrustAnchorsFromCertificates(Collections.singleton(certificate));
75+
}
76+
77+
public static CertStore buildCertStoreFromCertificates(Collection<X509Certificate> certificates) throws JceException {
78+
// We use the default JCE provider as there is no reason to use Bouncy Castle, moreover BC requires
79+
// the validated certificate to be in the certificate store which breaks the clean immutable usage of
80+
// trustedCACertificateCertStore in SubjectCertificateTrustedValidator.
81+
try {
82+
return CertStore.getInstance("Collection", new CollectionCertStoreParameters(certificates));
83+
} catch (GeneralSecurityException e) {
84+
throw new JceException(e);
85+
}
86+
}
87+
88+
public static CertStore buildCertStoreFromCertificate(X509Certificate certificate) throws JceException {
89+
return buildCertStoreFromCertificates(Collections.singleton(certificate));
90+
}
91+
92+
private CertificateValidator() {
93+
throw new IllegalStateException("Utility class");
94+
}
95+
96+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.webeid.security.exceptions;
2+
3+
public class AiaOcspResponderConfigurationException extends TokenValidationException {
4+
public AiaOcspResponderConfigurationException(String message) {
5+
super(message);
6+
}
7+
}

src/main/java/org/webeid/security/exceptions/UserCertificateNotTrustedException.java renamed to src/main/java/org/webeid/security/exceptions/CertificateNotTrustedException.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@
2222

2323
package org.webeid.security.exceptions;
2424

25+
import java.security.cert.X509Certificate;
26+
2527
/**
26-
* Thrown when the user certificate is not trusted.
28+
* Thrown when the given certificate is not signed by a trusted CA.
2729
*/
28-
public class UserCertificateNotTrustedException extends TokenValidationException {
29-
public UserCertificateNotTrustedException() {
30-
super("User certificate is not trusted");
31-
}
30+
public class CertificateNotTrustedException extends TokenValidationException {
3231

33-
public UserCertificateNotTrustedException(String msg) {
34-
super("User certificate is not trusted: " + msg);
32+
public CertificateNotTrustedException(X509Certificate certificate, Throwable e) {
33+
super("Certificate " + certificate.getSubjectDN() + " is not trusted", e);
3534
}
35+
3636
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.webeid.security.exceptions;
2+
3+
public class OCSPCertificateException extends TokenValidationException {
4+
5+
public OCSPCertificateException(String message) {
6+
super(message);
7+
}
8+
9+
public OCSPCertificateException(String message, Throwable exception) {
10+
super(message, exception);
11+
}
12+
13+
}

src/main/java/org/webeid/security/exceptions/OriginMismatchException.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,6 @@ public OriginMismatchException() {
3535
}
3636

3737
public OriginMismatchException(Throwable cause) {
38-
super(MESSAGE + ":", cause);
38+
super(MESSAGE, cause);
3939
}
4040
}

src/main/java/org/webeid/security/exceptions/TokenExpiredException.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,6 @@
2828
public class TokenExpiredException extends TokenValidationException {
2929

3030
public TokenExpiredException(Throwable cause) {
31-
super("Token has expired:", cause);
31+
super("Token has expired", cause);
3232
}
3333
}

0 commit comments

Comments
 (0)