Skip to content

Commit c277cd7

Browse files
committed
Add support for revocation data in SignedData structure
DEVSIX-7971
1 parent 4dcad82 commit c277cd7

File tree

19 files changed

+705
-552
lines changed

19 files changed

+705
-552
lines changed

bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/BouncyCastleFactory.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ This file is part of the iText (R) project.
254254
import java.util.Set;
255255
import javax.crypto.Cipher;
256256
import org.bouncycastle.asn1.ASN1BitString;
257+
import org.bouncycastle.asn1.ASN1Enumerated;
257258
import org.bouncycastle.asn1.ASN1GeneralizedTime;
258259
import org.bouncycastle.asn1.ASN1Integer;
259260
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
@@ -697,6 +698,18 @@ public IASN1Enumerated createASN1Enumerated(int i) {
697698
return new ASN1EnumeratedBC(i);
698699
}
699700

701+
/**
702+
* {@inheritDoc}
703+
*/
704+
@Override
705+
public IASN1Enumerated createASN1Enumerated(IASN1Encodable object) {
706+
ASN1EncodableBC encodable = (ASN1EncodableBC) object;
707+
if (encodable.getEncodable() instanceof ASN1Enumerated) {
708+
return new ASN1EnumeratedBC((ASN1Enumerated) encodable.getEncodable());
709+
}
710+
return null;
711+
}
712+
700713
/**
701714
* {@inheritDoc}
702715
*/

bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/ASN1EnumeratedBC.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,14 @@ public ASN1EnumeratedBC(int i) {
5656
public ASN1Enumerated getASN1Enumerated() {
5757
return (ASN1Enumerated) getEncodable();
5858
}
59+
60+
/**
61+
* {@inheritDoc}
62+
*
63+
* @return {@inheritDoc}
64+
*/
65+
@Override
66+
public int intValueExact() {
67+
return getASN1Enumerated().intValueExact();
68+
}
5969
}

bouncy-castle-connector/src/main/java/com/itextpdf/bouncycastleconnector/BouncyCastleDefaultFactory.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,11 @@ public IASN1Enumerated createASN1Enumerated(int i) {
344344
throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
345345
}
346346

347+
@Override
348+
public IASN1Enumerated createASN1Enumerated(IASN1Encodable object) {
349+
throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
350+
}
351+
347352
@Override
348353
public IASN1Encoding createASN1Encoding() {
349354
throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);

bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/BouncyCastleFipsFactory.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ This file is part of the iText (R) project.
255255
import javax.crypto.Cipher;
256256
import javax.crypto.spec.SecretKeySpec;
257257
import org.bouncycastle.asn1.ASN1BitString;
258+
import org.bouncycastle.asn1.ASN1Enumerated;
258259
import org.bouncycastle.asn1.ASN1GeneralizedTime;
259260
import org.bouncycastle.asn1.ASN1Integer;
260261
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
@@ -703,6 +704,18 @@ public IASN1Enumerated createASN1Enumerated(int i) {
703704
return new ASN1EnumeratedBCFips(i);
704705
}
705706

707+
/**
708+
* {@inheritDoc}
709+
*/
710+
@Override
711+
public IASN1Enumerated createASN1Enumerated(IASN1Encodable object) {
712+
ASN1EncodableBCFips encodable = (ASN1EncodableBCFips) object;
713+
if (encodable.getEncodable() instanceof ASN1Enumerated) {
714+
return new ASN1EnumeratedBCFips((ASN1Enumerated) encodable.getEncodable());
715+
}
716+
return null;
717+
}
718+
706719
/**
707720
* {@inheritDoc}
708721
*/

bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/ASN1EnumeratedBCFips.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,14 @@ public ASN1EnumeratedBCFips(int i) {
5656
public ASN1Enumerated getASN1Enumerated() {
5757
return (ASN1Enumerated) getEncodable();
5858
}
59+
60+
/**
61+
* {@inheritDoc}
62+
*
63+
* @return {@inheritDoc}
64+
*/
65+
@Override
66+
public int intValueExact() {
67+
return getASN1Enumerated().getValue().intValueExact();
68+
}
5969
}

commons/src/main/java/com/itextpdf/commons/bouncycastle/IBouncyCastleFactory.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,15 @@ public interface IBouncyCastleFactory {
497497
*/
498498
IASN1Enumerated createASN1Enumerated(int i);
499499

500+
/**
501+
* Create ASN1 Enumerated wrapper from {@code IASN1Encodable} value.
502+
*
503+
* @param object {@code IASN1Encodable} to create ASN1 Enumerated wrapper from
504+
*
505+
* @return created ASN1 Enumerated wrapper.
506+
*/
507+
IASN1Enumerated createASN1Enumerated(IASN1Encodable object);
508+
500509
/**
501510
* Create ASN1 Encoding without parameters.
502511
*

commons/src/main/java/com/itextpdf/commons/bouncycastle/asn1/IASN1Enumerated.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,11 @@ This file is part of the iText (R) project.
2727
* to switch between bouncy-castle and bouncy-castle FIPS implementations.
2828
*/
2929
public interface IASN1Enumerated extends IASN1Primitive {
30+
31+
/**
32+
* Calls actual {@code intValueExact()} method for the wrapped ASN1Enumerated object.
33+
*
34+
* @return integer value of the wrapped ASN1Enumerated object.
35+
*/
36+
int intValueExact();
3037
}

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

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ This file is part of the iText (R) project.
2424

2525
import com.itextpdf.bouncycastleconnector.BouncyCastleFactoryCreator;
2626
import com.itextpdf.commons.bouncycastle.IBouncyCastleFactory;
27+
import com.itextpdf.commons.bouncycastle.asn1.IASN1EncodableVector;
28+
import com.itextpdf.commons.bouncycastle.asn1.IASN1Enumerated;
2729
import com.itextpdf.commons.bouncycastle.asn1.IASN1InputStream;
2830
import com.itextpdf.commons.bouncycastle.asn1.IASN1ObjectIdentifier;
2931
import com.itextpdf.commons.bouncycastle.asn1.IASN1OctetString;
@@ -32,11 +34,17 @@ This file is part of the iText (R) project.
3234
import com.itextpdf.commons.bouncycastle.asn1.IASN1TaggedObject;
3335
import com.itextpdf.commons.bouncycastle.asn1.IDERIA5String;
3436
import com.itextpdf.commons.bouncycastle.asn1.IDEROctetString;
37+
import com.itextpdf.commons.bouncycastle.asn1.IDERSet;
38+
import com.itextpdf.commons.bouncycastle.asn1.ocsp.IBasicOCSPResponse;
39+
import com.itextpdf.commons.bouncycastle.asn1.ocsp.IOCSPObjectIdentifiers;
3540
import com.itextpdf.commons.bouncycastle.asn1.x509.ICRLDistPoint;
3641
import com.itextpdf.commons.bouncycastle.asn1.x509.IDistributionPoint;
3742
import com.itextpdf.commons.bouncycastle.asn1.x509.IDistributionPointName;
3843
import com.itextpdf.commons.bouncycastle.asn1.x509.IGeneralName;
3944
import com.itextpdf.commons.bouncycastle.asn1.x509.IGeneralNames;
45+
import com.itextpdf.signatures.logs.SignLogMessageConstant;
46+
import org.slf4j.Logger;
47+
import org.slf4j.LoggerFactory;
4048

4149
import java.io.ByteArrayInputStream;
4250
import java.io.IOException;
@@ -48,6 +56,9 @@ This file is part of the iText (R) project.
4856
import java.security.cert.Certificate;
4957
import java.security.cert.CertificateException;
5058
import java.security.cert.X509Certificate;
59+
import java.security.cert.X509CRL;
60+
import java.util.Collection;
61+
import java.util.Enumeration;
5162

5263
/**
5364
* This class contains a series of static methods that
@@ -56,6 +67,7 @@ This file is part of the iText (R) project.
5667
public class CertificateUtil {
5768

5869
private static final IBouncyCastleFactory FACTORY = BouncyCastleFactoryCreator.getFactory();
70+
private static final Logger LOGGER = LoggerFactory.getLogger(CertificateUtil.class);
5971

6072
// Certificate Revocation Lists
6173

@@ -224,6 +236,119 @@ public static Certificate generateCertificate(InputStream data) throws Certifica
224236
return SignUtils.generateCertificate(data, FACTORY.getProvider());
225237
}
226238

239+
/**
240+
* Try to retrieve CRL and OCSP responses from the signed data crls field.
241+
*
242+
* @param taggedObj signed data crls field as {@link IASN1TaggedObject}.
243+
*
244+
* @param crls collection to store retrieved CRL responses.
245+
* @param ocsps collection of {@link IBasicOCSPResponse} wrappers to store retrieved
246+
* OCSP responses.
247+
* @param otherRevocationInfoFormats collection of revocation info other than OCSP and CRL responses,
248+
* e.g. SCVP Request and Response, stored as {@link IASN1Sequence}.
249+
*
250+
* @throws IOException if some I/O error occurred.
251+
* @throws CertificateException if CertificateFactory instance wasn't created.
252+
*/
253+
public static void retrieveRevocationInfoFromSignedData(IASN1TaggedObject taggedObj, Collection<CRL> crls,
254+
Collection<IBasicOCSPResponse> ocsps,
255+
Collection<IASN1Sequence> otherRevocationInfoFormats)
256+
throws IOException, CertificateException {
257+
Enumeration revInfo = FACTORY.createASN1Set(taggedObj, false).getObjects();
258+
while (revInfo.hasMoreElements()) {
259+
IASN1Sequence s = FACTORY.createASN1Sequence(revInfo.nextElement());
260+
IASN1ObjectIdentifier o = FACTORY.createASN1ObjectIdentifier(s.getObjectAt(0));
261+
if (o != null && SecurityIDs.ID_RI_OCSP_RESPONSE.equals(o.getId())) {
262+
IASN1Sequence ocspResp = FACTORY.createASN1Sequence(s.getObjectAt(1));
263+
IASN1Enumerated respStatus = FACTORY.createASN1Enumerated(ocspResp.getObjectAt(0));
264+
if (respStatus.intValueExact() == FACTORY.createOCSPRespBuilderInstance().getSuccessful()) {
265+
IASN1Sequence responseBytes = FACTORY.createASN1Sequence(ocspResp.getObjectAt(1));
266+
if (responseBytes != null) {
267+
ocsps.add(CertificateUtil.createOcsp(responseBytes));
268+
}
269+
}
270+
} else {
271+
try {
272+
crls.addAll(SignUtils.readAllCRLs(s.getEncoded()));
273+
} catch (CRLException ignored) {
274+
LOGGER.warn(SignLogMessageConstant.UNABLE_TO_PARSE_REV_INFO);
275+
otherRevocationInfoFormats.add(s);
276+
}
277+
}
278+
}
279+
}
280+
281+
/**
282+
* Creates the revocation info (crls field) for SignedData structure:
283+
* RevocationInfoChoices ::= SET OF RevocationInfoChoice
284+
*
285+
* RevocationInfoChoice ::= CHOICE {
286+
* crl CertificateList,
287+
* other [1] IMPLICIT OtherRevocationInfoFormat }
288+
*
289+
* OtherRevocationInfoFormat ::= SEQUENCE {
290+
* otherRevInfoFormat OBJECT IDENTIFIER,
291+
* otherRevInfo ANY DEFINED BY otherRevInfoFormat }
292+
*
293+
* CertificateList ::= SEQUENCE {
294+
* tbsCertList TBSCertList,
295+
* signatureAlgorithm AlgorithmIdentifier,
296+
* signatureValue BIT STRING }
297+
*
298+
* @see <a href="https://datatracker.ietf.org/doc/html/rfc5652#section-10.2.1">RFC 5652 §10.2.1</a>
299+
*
300+
* @param crls collection of CRL revocation status information.
301+
* @param ocsps collection of OCSP revocation status information.
302+
* @param otherRevocationInfoFormats collection of revocation info other than OCSP and CRL responses,
303+
* e.g. SCVP Request and Response, stored as {@link IASN1Sequence}.
304+
*
305+
* @return {@code crls [1] RevocationInfoChoices} field of SignedData structure. Null if SignedData has
306+
* no revocation data.
307+
*
308+
* @throws CRLException if an encoding error occurs.
309+
* @throws IOException if an I/O error occurs.
310+
*/
311+
public static IDERSet createRevocationInfoChoices(Collection<CRL> crls, Collection<IBasicOCSPResponse> ocsps,
312+
Collection<IASN1Sequence> otherRevocationInfoFormats)
313+
throws CRLException, IOException {
314+
if (crls.size() == 0 && ocsps.size() == 0) {
315+
return null;
316+
}
317+
IASN1EncodableVector revocationInfoChoices = FACTORY.createASN1EncodableVector();
318+
319+
// Add CRLs
320+
for (CRL element : crls) {
321+
// Add crl CertificateList (crl RevocationInfoChoice)
322+
revocationInfoChoices.add(FACTORY.createASN1Sequence(((X509CRL) element).getEncoded()));
323+
}
324+
325+
// Add OCSPs
326+
for (IBasicOCSPResponse element : ocsps) {
327+
IASN1EncodableVector ocspResponseRevInfo = FACTORY.createASN1EncodableVector();
328+
// Add otherRevInfoFormat (ID_RI_OCSP_RESPONSE)
329+
ocspResponseRevInfo.add(FACTORY.createASN1ObjectIdentifier(SecurityIDs.ID_RI_OCSP_RESPONSE));
330+
331+
IASN1EncodableVector ocspResponse = FACTORY.createASN1EncodableVector();
332+
ocspResponse.add(FACTORY.createOCSPResponseStatus(
333+
FACTORY.createOCSPRespBuilderInstance().getSuccessful()).toASN1Primitive());
334+
ocspResponse.add(FACTORY.createResponseBytes(
335+
FACTORY.createOCSPObjectIdentifiers().getIdPkixOcspBasic(),
336+
FACTORY.createDEROctetString(element.toASN1Primitive().getEncoded())).toASN1Primitive());
337+
// Add otherRevInfo (ocspResponse)
338+
ocspResponseRevInfo.add(FACTORY.createDERSequence(ocspResponse));
339+
340+
// Add other [1] IMPLICIT OtherRevocationInfoFormat (ocsp RevocationInfoChoice)
341+
revocationInfoChoices.add(FACTORY.createDERSequence(ocspResponseRevInfo));
342+
}
343+
344+
// Add other RevocationInfo formats
345+
for (IASN1Sequence revInfo : otherRevocationInfoFormats) {
346+
revocationInfoChoices.add(revInfo);
347+
}
348+
349+
return FACTORY.createDERSet(revocationInfoChoices);
350+
}
351+
227352
/**
228353
* Checks if the certificate is signed by provided issuer certificate.
229354
*
@@ -330,4 +455,27 @@ private static String getValueFromAIAExtension(IASN1Primitive extensionValue, St
330455
}
331456
return null;
332457
}
458+
459+
/**
460+
* Helper method that creates the {@link IBasicOCSPResponse} object from the response bytes.
461+
*
462+
* @param seq response bytes.
463+
*
464+
* @return {@link IBasicOCSPResponse} object.
465+
*
466+
* @throws IOException if some I/O error occurred.
467+
*/
468+
private static IBasicOCSPResponse createOcsp(IASN1Sequence seq) throws IOException {
469+
IASN1ObjectIdentifier objectIdentifier = FACTORY.createASN1ObjectIdentifier(
470+
seq.getObjectAt(0));
471+
IOCSPObjectIdentifiers ocspObjectIdentifiers = FACTORY.createOCSPObjectIdentifiers();
472+
if (objectIdentifier != null
473+
&& objectIdentifier.getId().equals(ocspObjectIdentifiers.getIdPkixOcspBasic().getId())) {
474+
IASN1OctetString os = FACTORY.createASN1OctetString(seq.getObjectAt(1));
475+
try (IASN1InputStream inp = FACTORY.createASN1InputStream(os.getOctets())) {
476+
return FACTORY.createBasicOCSPResponse(inp.readObject());
477+
}
478+
}
479+
return null;
480+
}
333481
}

0 commit comments

Comments
 (0)