Skip to content

Commit 9f0e824

Browse files
committed
Collect revocation data for CRL response certificates
DEVSIX-7893
1 parent 76ed412 commit 9f0e824

File tree

18 files changed

+746
-107
lines changed

18 files changed

+746
-107
lines changed

sign/src/main/java/com/itextpdf/signatures/CertificateUtil.java

Lines changed: 71 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,23 @@ public static CRL getCRL(String url) throws IOException, CertificateException, C
128128
return SignUtils.parseCrlFromStream(new URL(url).openStream());
129129
}
130130

131+
/**
132+
* Retrieves the URL for the issuer certificate for the given CRL.
133+
*
134+
* @param crl the CRL response
135+
*
136+
* @return the URL or null.
137+
*/
138+
public static String getIssuerCertURL(CRL crl) {
139+
IASN1Primitive obj;
140+
try {
141+
obj = getExtensionValue(crl, FACTORY.createExtension().getAuthorityInfoAccess().getId());
142+
return getValueFromAIAExtension(obj, SecurityIDs.ID_CA_ISSUERS);
143+
} catch (IOException e) {
144+
return null;
145+
}
146+
}
147+
131148
// Online Certificate Status Protocol
132149

133150
/**
@@ -141,22 +158,10 @@ public static String getOCSPURL(X509Certificate certificate) {
141158
IASN1Primitive obj;
142159
try {
143160
obj = getExtensionValue(certificate, FACTORY.createExtension().getAuthorityInfoAccess().getId());
144-
if (obj == null) {
145-
return null;
146-
}
147-
IASN1Sequence accessDescriptions = FACTORY.createASN1Sequence(obj);
148-
for (int i = 0; i < accessDescriptions.size(); i++) {
149-
IASN1Sequence accessDescription = FACTORY.createASN1Sequence(accessDescriptions.getObjectAt(i));
150-
IASN1ObjectIdentifier id = FACTORY.createASN1ObjectIdentifier(accessDescription.getObjectAt(0));
151-
if (accessDescription.size() == 2 && id != null && SecurityIDs.ID_OCSP.equals(id.getId())) {
152-
IASN1Primitive description = FACTORY.createASN1Primitive(accessDescription.getObjectAt(1));
153-
return getStringFromGeneralName(description);
154-
}
155-
}
161+
return getValueFromAIAExtension(obj, SecurityIDs.ID_OCSP);
156162
} catch (IOException e) {
157163
return null;
158164
}
159-
return null;
160165
}
161166

162167
// Missing certificates in chain
@@ -172,22 +177,10 @@ public static String getIssuerCertURL(X509Certificate certificate) {
172177
IASN1Primitive obj;
173178
try {
174179
obj = getExtensionValue(certificate, FACTORY.createExtension().getAuthorityInfoAccess().getId());
175-
if (obj == null) {
176-
return null;
177-
}
178-
IASN1Sequence accessDescriptions = FACTORY.createASN1Sequence(obj);
179-
for (int i = 0; i < accessDescriptions.size(); i++) {
180-
IASN1Sequence accessDescription = FACTORY.createASN1Sequence(accessDescriptions.getObjectAt(i));
181-
IASN1ObjectIdentifier id = FACTORY.createASN1ObjectIdentifier(accessDescription.getObjectAt(0));
182-
if (accessDescription.size() == 2 && id != null && SecurityIDs.ID_CA_ISSUERS.equals(id.getId())) {
183-
IASN1Primitive description = FACTORY.createASN1Primitive(accessDescription.getObjectAt(1));
184-
return getStringFromGeneralName(description);
185-
}
186-
}
180+
return getValueFromAIAExtension(obj, SecurityIDs.ID_CA_ISSUERS);
187181
} catch (IOException e) {
188182
return null;
189183
}
190-
return null;
191184
}
192185

193186
// Time Stamp Authority
@@ -245,17 +238,41 @@ static boolean isSelfSigned(X509Certificate certificate) {
245238
* @param certificate the certificate from which we need the ExtensionValue
246239
* @param oid the Object Identifier value for the extension.
247240
*
248-
* @return the extension value as an {@link IASN1Primitive} object
241+
* @return the extension value as an {@link IASN1Primitive} object.
249242
*
250243
* @throws IOException
251244
*/
252245
private static IASN1Primitive getExtensionValue(X509Certificate certificate, String oid) throws IOException {
253-
byte[] bytes = SignUtils.getExtensionValueByOid(certificate, oid);
254-
if (bytes == null) {
246+
return getExtensionValueFromByteArray(SignUtils.getExtensionValueByOid(certificate, oid));
247+
}
248+
249+
/**
250+
* @param crl the CRL from which we need the ExtensionValue
251+
* @param oid the Object Identifier value for the extension.
252+
*
253+
* @return the extension value as an {@link IASN1Primitive} object.
254+
*
255+
* @throws IOException
256+
*/
257+
private static IASN1Primitive getExtensionValue(CRL crl, String oid) throws IOException {
258+
return getExtensionValueFromByteArray(SignUtils.getExtensionValueByOid(crl, oid));
259+
}
260+
261+
/**
262+
* Converts extension value represented as byte array to {@link IASN1Primitive} object.
263+
*
264+
* @param extensionValue the extension value as byte array
265+
*
266+
* @return the extension value as an {@link IASN1Primitive} object.
267+
*
268+
* @throws IOException
269+
*/
270+
private static IASN1Primitive getExtensionValueFromByteArray(byte[] extensionValue) throws IOException {
271+
if (extensionValue == null) {
255272
return null;
256273
}
257274
IASN1OctetString octs;
258-
try (IASN1InputStream aIn = FACTORY.createASN1InputStream(new ByteArrayInputStream(bytes))) {
275+
try (IASN1InputStream aIn = FACTORY.createASN1InputStream(new ByteArrayInputStream(extensionValue))) {
259276
octs = FACTORY.createASN1OctetString(aIn.readObject());
260277
}
261278
try (IASN1InputStream aIn = FACTORY.createASN1InputStream(new ByteArrayInputStream(octs.getOctets()))) {
@@ -274,4 +291,28 @@ private static String getStringFromGeneralName(IASN1Primitive names) {
274291
IASN1TaggedObject taggedObject = FACTORY.createASN1TaggedObject(names);
275292
return new String(FACTORY.createASN1OctetString(taggedObject, false).getOctets(), StandardCharsets.ISO_8859_1);
276293
}
294+
295+
/**
296+
* Retrieves accessLocation value for specified accessMethod from the Authority Information Access extension.
297+
*
298+
* @param extensionValue Authority Information Access extension value
299+
* @param accessMethod accessMethod OID; usually id-ad-caIssuers or id-ad-ocsp
300+
*
301+
* @return the location (URI) of the information.
302+
*/
303+
private static String getValueFromAIAExtension(IASN1Primitive extensionValue, String accessMethod) {
304+
if (extensionValue == null) {
305+
return null;
306+
}
307+
IASN1Sequence accessDescriptions = FACTORY.createASN1Sequence(extensionValue);
308+
for (int i = 0; i < accessDescriptions.size(); i++) {
309+
IASN1Sequence accessDescription = FACTORY.createASN1Sequence(accessDescriptions.getObjectAt(i));
310+
IASN1ObjectIdentifier id = FACTORY.createASN1ObjectIdentifier(accessDescription.getObjectAt(0));
311+
if (accessDescription.size() == 2 && id != null && accessMethod.equals(id.getId())) {
312+
IASN1Primitive description = FACTORY.createASN1Primitive(accessDescription.getObjectAt(1));
313+
return getStringFromGeneralName(description);
314+
}
315+
}
316+
return null;
317+
}
277318
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2023 Apryse Group NV
4+
Authors: Apryse Software.
5+
6+
This program is offered under a commercial and under the AGPL license.
7+
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
8+
9+
AGPL licensing:
10+
This program is free software: you can redistribute it and/or modify
11+
it under the terms of the GNU Affero General Public License as published by
12+
the Free Software Foundation, either version 3 of the License, or
13+
(at your option) any later version.
14+
15+
This program is distributed in the hope that it will be useful,
16+
but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
GNU Affero General Public License for more details.
19+
20+
You should have received a copy of the GNU Affero General Public License
21+
along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
package com.itextpdf.signatures;
24+
25+
import java.security.cert.CRL;
26+
import java.security.cert.Certificate;
27+
28+
/**
29+
* Empty {@link IIssuingCertificateRetriever} implementation for compatibility with the older code.
30+
*/
31+
class DefaultIssuingCertificateRetriever implements IIssuingCertificateRetriever {
32+
33+
/**
34+
* Creates {@link DefaultIssuingCertificateRetriever} instance.
35+
*/
36+
public DefaultIssuingCertificateRetriever() {
37+
// Empty constructor.
38+
}
39+
40+
/**
41+
* {@inheritDoc}
42+
*
43+
* @param chain {@inheritDoc}
44+
* @return {@inheritDoc}
45+
*/
46+
@Override
47+
public Certificate[] retrieveMissingCertificates(Certificate[] chain) {
48+
return chain;
49+
}
50+
51+
/**
52+
* {@inheritDoc}
53+
*
54+
* @param crl {@inheritDoc}
55+
* @return {@inheritDoc}
56+
*/
57+
@Override
58+
public Certificate[] getCrlIssuerCertificates(CRL crl) {
59+
return new Certificate[0];
60+
}
61+
}

sign/src/main/java/com/itextpdf/signatures/IMissingCertificatesClient.java renamed to sign/src/main/java/com/itextpdf/signatures/IIssuingCertificateRetriever.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,31 @@ This file is part of the iText (R) project.
2222
*/
2323
package com.itextpdf.signatures;
2424

25+
import java.security.cert.CRL;
2526
import java.security.cert.Certificate;
2627

2728
/**
28-
* Interface client to support Certificate Chain with Missing Certificates.
29+
* Interface helper to support retrieving CAIssuers certificates from Authority Information Access (AIA) Extension in
30+
* order to support certificate chains with missing certificates and getting CRL response issuer certificates.
2931
*/
30-
public interface IMissingCertificatesClient {
32+
public interface IIssuingCertificateRetriever {
3133
/**
32-
* Retrieves missing certificates in chain using Authority Information Access (AIA) Extension.
34+
* Retrieves missing certificates in chain using certificate Authority Information Access (AIA) Extension.
3335
*
3436
* @param chain certificate chain to restore with at least signing certificate.
3537
*
3638
* @return full chain of trust or maximum chain that could be restored in case missing certificates cannot be
3739
* retrieved from AIA extension.
3840
*/
3941
Certificate[] retrieveMissingCertificates(Certificate[] chain);
42+
43+
/**
44+
* Retrieves certificates that can be used to verify the signature on the CRL response using CRL
45+
* Authority Information Access (AIA) Extension.
46+
*
47+
* @param crl CRL response to retrieve issuer for.
48+
*
49+
* @return certificates retrieved from CRL AIA extension or an empty list in case certificates cannot be retrieved.
50+
*/
51+
Certificate[] getCrlIssuerCertificates(CRL crl);
4052
}

sign/src/main/java/com/itextpdf/signatures/MissingCertificatesClient.java renamed to sign/src/main/java/com/itextpdf/signatures/IssuingCertificateRetriever.java

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ This file is part of the iText (R) project.
2727
import java.io.IOException;
2828
import java.io.InputStream;
2929
import java.net.URL;
30+
import java.security.cert.CRL;
3031
import java.security.cert.Certificate;
3132
import java.security.cert.CertificateException;
3233
import java.security.cert.X509Certificate;
@@ -37,16 +38,16 @@ This file is part of the iText (R) project.
3738
import org.slf4j.LoggerFactory;
3839

3940
/**
40-
* {@link IMissingCertificatesClient} default implementation.
41+
* {@link IIssuingCertificateRetriever} default implementation.
4142
*/
42-
public class MissingCertificatesClient implements IMissingCertificatesClient {
43+
public class IssuingCertificateRetriever implements IIssuingCertificateRetriever {
4344

44-
private static final Logger LOGGER = LoggerFactory.getLogger(MissingCertificatesClient.class);
45+
private static final Logger LOGGER = LoggerFactory.getLogger(IssuingCertificateRetriever.class);
4546

4647
/**
47-
* Creates {@link MissingCertificatesClient} instance.
48+
* Creates {@link IssuingCertificateRetriever} instance.
4849
*/
49-
public MissingCertificatesClient() {
50+
public IssuingCertificateRetriever() {
5051
// Empty constructor.
5152
}
5253

@@ -73,7 +74,8 @@ public Certificate[] retrieveMissingCertificates(Certificate[] chain) {
7374
i++;
7475
} else {
7576
// Get missing certificates using AIA Extensions
76-
Collection<Certificate> certificatesFromAIA = processCertificatesFromAIA(lastAddedCert);
77+
String url = CertificateUtil.getIssuerCertURL((X509Certificate) lastAddedCert);
78+
Collection<Certificate> certificatesFromAIA = processCertificatesFromAIA(url);
7779
if (certificatesFromAIA == null || certificatesFromAIA.isEmpty()) {
7880
// Unable to retrieve missing certificates
7981
while (i < chain.length) {
@@ -90,6 +92,28 @@ public Certificate[] retrieveMissingCertificates(Certificate[] chain) {
9092
return fullChain.toArray(new Certificate[0]);
9193
}
9294

95+
/**
96+
* {@inheritDoc}
97+
*
98+
* @param crl {@inheritDoc}
99+
*
100+
* @return {@inheritDoc}
101+
*/
102+
@Override
103+
public Certificate[] getCrlIssuerCertificates(CRL crl) {
104+
// Usually CRLs are signed using CA certificate, so we don’t need to do anything extra and the revocation data
105+
// is already collected. However, it is possible to sign it with any other certificate.
106+
107+
// IssuingDistributionPoint extension: https://datatracker.ietf.org/doc/html/rfc5280#section-5.2.5
108+
// Nothing special for the indirect CRLs.
109+
110+
// AIA Extension
111+
String url = CertificateUtil.getIssuerCertURL(crl);
112+
List<Certificate> certificatesFromAIA = (List<Certificate>) processCertificatesFromAIA(url);
113+
return certificatesFromAIA == null ? new Certificate[0] :
114+
retrieveMissingCertificates(certificatesFromAIA.toArray(new Certificate[0]));
115+
}
116+
93117
/**
94118
* Get CA issuers certificates represented as {@link InputStream}.
95119
*
@@ -117,8 +141,7 @@ protected Collection<Certificate> parseCertificates(InputStream certsData) throw
117141
return SignUtils.readAllCerts(certsData, null);
118142
}
119143

120-
private Collection<Certificate> processCertificatesFromAIA(Certificate certificate) {
121-
String url = CertificateUtil.getIssuerCertURL((X509Certificate) certificate);
144+
private Collection<Certificate> processCertificatesFromAIA(String url) {
122145
if (url == null) {
123146
// We don't have any URIs to the issuer certificates in AuthorityInfoAccess extension
124147
return null;

0 commit comments

Comments
 (0)