Skip to content

Commit 50609c6

Browse files
committed
Improve ocsp verifier: now we check ocsp response strongly by RFC 6960
DEVSIX-520
1 parent 889541c commit 50609c6

File tree

3 files changed

+206
-118
lines changed

3 files changed

+206
-118
lines changed

io/src/main/java/com/itextpdf/io/LogMessageConstant.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,17 @@ public class LogMessageConstant {
119119
*/
120120
public static final String MAKE_COPY_OF_CATALOG_DICTIONARY_IS_FORBIDDEN = "Make copy of Catalog dictionary is forbidden.";
121121

122+
/**
123+
* Log message.
124+
*/
125+
public static final String OCSP_STATUS_IS_UNKNOWN = "OCSP status is unknown.";
126+
127+
/**
128+
* Log message.
129+
*/
130+
public static final String OCSP_STATUS_IS_REVOKED = "OCSP status is revoked.";
131+
132+
122133
/**
123134
* Log message.
124135
*/

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

Lines changed: 123 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,19 @@ This file is part of the iText (R) project.
4646

4747
import java.io.IOException;
4848
import java.security.GeneralSecurityException;
49+
import java.security.KeyStoreException;
50+
import java.security.cert.CRL;
4951
import java.security.cert.Certificate;
52+
import java.security.cert.CertificateParsingException;
53+
import java.security.cert.X509CRL;
5054
import java.security.cert.X509Certificate;
5155
import java.text.MessageFormat;
5256
import java.util.ArrayList;
5357
import java.util.Date;
5458
import java.util.Enumeration;
5559
import java.util.List;
5660

61+
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
5762
import org.bouncycastle.cert.X509CertificateHolder;
5863
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
5964
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
@@ -76,6 +81,8 @@ public class OCSPVerifier extends RootStoreVerifier {
7681
/** The Logger instance */
7782
protected static final Logger LOGGER = LoggerFactory.getLogger(OCSPVerifier.class);
7883

84+
protected final static String id_kp_OCSPSigning = "1.3.6.1.5.5.7.3.9";
85+
7986
/** The list of OCSP responses. */
8087
protected List<BasicOCSPResp> ocsps;
8188

@@ -97,7 +104,7 @@ public OCSPVerifier(CertificateVerifier verifier, List<BasicOCSPResp> ocsps) {
97104
* @param issuerCert its issuer
98105
* @return a list of <code>VerificationOK</code> objects.
99106
* The list will be empty if the certificate couldn't be verified.
100-
* @see com.itextpdf.text.pdf.security.RootStoreVerifier#verify(java.security.cert.X509Certificate, java.security.cert.X509Certificate, java.util.Date)
107+
* @see com.itextpdf.signatures.RootStoreVerifier#verify(java.security.cert.X509Certificate, java.security.cert.X509Certificate, java.util.Date)
101108
*/
102109
public List<VerificationOK> verify(X509Certificate signCert,
103110
X509Certificate issuerCert, Date signDate)
@@ -132,11 +139,11 @@ public List<VerificationOK> verify(X509Certificate signCert,
132139

133140
/**
134141
* Verifies a certificate against a single OCSP response
135-
* @param ocspResp the OCSP response
136-
* @param serialNumber the serial number of the certificate that needs to be checked
137-
* @param issuerCert
138-
* @param signDate
139-
* @return
142+
* @param ocspResp the OCSP response
143+
* @param signCert the certificate that needs to be checked
144+
* @param issuerCert the certificate of CA
145+
* @param signDate sign date
146+
* @return {@code true}, in case successful check, otherwise false.
140147
* @throws GeneralSecurityException
141148
* @throws IOException
142149
*/
@@ -183,65 +190,126 @@ public boolean verify(BasicOCSPResp ocspResp, X509Certificate signCert, X509Cert
183190

184191
/**
185192
* Verifies if an OCSP response is genuine
186-
* @param ocspResp the OCSP response
187-
* @param issuerCert the issuer certificate
193+
* If it doesn't verify against the issuer certificate and response's certificates, it may verify
194+
* using a trusted anchor or cert.
195+
* @param ocspResp the OCSP response
196+
* @param issuerCert the issuer certificate
188197
* @throws GeneralSecurityException
189198
* @throws IOException
190199
*/
191200
public void isValidResponse(BasicOCSPResp ocspResp, X509Certificate issuerCert) throws GeneralSecurityException, IOException {
192-
// by default the OCSP responder certificate is the issuer certificate
193-
X509Certificate responderCert = issuerCert;
194-
// check if there's a responder certificate
195-
X509CertificateHolder[] certHolders = ocspResp.getCerts();
196-
if (certHolders.length > 0) {
197-
responderCert = new JcaX509CertificateConverter().setProvider( "BC" ).getCertificate(certHolders[0]);
201+
//OCSP response might be signed by the issuer certificate or
202+
//the Authorized OCSP responder certificate containing the id-kp-OCSPSigning extended key usage extension
203+
X509Certificate responderCert = null;
204+
205+
//first check if the issuer certificate signed the response
206+
//since it is expected to be the most common case
207+
if (isSignatureValid(ocspResp, issuerCert)) {
208+
responderCert = issuerCert;
209+
}
210+
211+
//if the issuer certificate didn't sign the ocsp response, look for authorized ocsp responses
212+
// from properties or from certificate chain received with response
213+
if (responderCert == null) {
214+
if (ocspResp.getCerts() != null) {
215+
//look for existence of Authorized OCSP responder inside the cert chain in ocsp response
216+
X509CertificateHolder[] certs = ocspResp.getCerts();
217+
for (X509CertificateHolder cert : certs) {
218+
X509Certificate tempCert;
219+
try {
220+
tempCert = new JcaX509CertificateConverter().getCertificate(cert);
221+
} catch (Exception ex) {
222+
continue;
223+
}
224+
List<String> keyPurposes = null;
225+
try {
226+
keyPurposes = tempCert.getExtendedKeyUsage();
227+
if ((keyPurposes != null) && keyPurposes.contains(id_kp_OCSPSigning) && isSignatureValid(ocspResp, tempCert)) {
228+
responderCert = tempCert;
229+
break;
230+
}
231+
} catch (CertificateParsingException ignored) {
232+
}
233+
}
234+
// Certificate signing the ocsp response is not found in ocsp response's certificate chain received
235+
// and is not signed by the issuer certificate.
236+
if (responderCert == null) {
237+
throw new VerificationException(issuerCert, "OCSP response could not be verified");
238+
}
239+
} else {
240+
//certificate chain is not present in response received
241+
//try to verify using rootStore
242+
if (rootStore != null) {
243+
try {
244+
for (Enumeration<String> aliases = rootStore.aliases(); aliases.hasMoreElements(); ) {
245+
String alias = aliases.nextElement();
246+
try {
247+
if (!rootStore.isCertificateEntry(alias))
248+
continue;
249+
X509Certificate anchor = (X509Certificate) rootStore.getCertificate(alias);
250+
if (isSignatureValid(ocspResp, anchor)) {
251+
responderCert = anchor;
252+
break;
253+
}
254+
} catch (GeneralSecurityException ignored) {
255+
}
256+
}
257+
} catch (KeyStoreException e) {
258+
responderCert = null;
259+
}
260+
}
261+
262+
// OCSP Response does not contain certificate chain, and response is not signed by any
263+
// of the rootStore or the issuer certificate.
264+
if (responderCert == null) {
265+
throw new VerificationException(issuerCert, "OCSP response could not be verified");
266+
}
267+
}
268+
}
269+
270+
//check "This certificate MUST be issued directly by the CA that issued the certificate in question".
271+
responderCert.verify(issuerCert.getPublicKey());
272+
273+
// validating ocsp signers certificate
274+
// Check if responders certificate has id-pkix-ocsp-nocheck extension,
275+
// in which case we do not validate (perform revocation check on) ocsp certs for lifetime of certificate
276+
if (responderCert.getExtensionValue(OCSPObjectIdentifiers.id_pkix_ocsp_nocheck.getId()) == null) {
277+
CRL crl;
198278
try {
199-
responderCert.verify(issuerCert.getPublicKey());
279+
crl = CertificateUtil.getCRL(responderCert);
280+
} catch (Exception ignored) {
281+
crl = null;
200282
}
201-
catch(GeneralSecurityException e) {
202-
if (super.verify(responderCert, issuerCert, null).size() == 0)
203-
throw new VerificationException(responderCert, "Responder certificate couldn't be verified");
283+
if (crl != null && crl instanceof X509CRL) {
284+
CRLVerifier crlVerifier = new CRLVerifier(null, null);
285+
crlVerifier.setRootStore(rootStore);
286+
crlVerifier.setOnlineCheckingAllowed(onlineCheckingAllowed);
287+
crlVerifier.verify((X509CRL)crl, responderCert, issuerCert, new Date());
288+
return;
204289
}
205290
}
206-
// verify if the signature of the response is valid
207-
if (!verifyResponse(ocspResp, responderCert))
208-
throw new VerificationException(responderCert, "OCSP response could not be verified");
291+
292+
//check if lifetime of certificate is ok
293+
responderCert.checkValidity();
209294
}
210295

211296
/**
212-
* Verifies if the signature of the response is valid.
213-
* If it doesn't verify against the responder certificate, it may verify
214-
* using a trusted anchor.
297+
* Verifies if the response is valid.
298+
* If it doesn't verify against the issuer certificate and response's certificates, it may verify
299+
* using a trusted anchor or cert.
300+
* NOTE. Use {@code isValidResponse()} instead.
215301
* @param ocspResp the response object
216-
* @param responderCert the certificate that may be used to sign the response
302+
* @param issuerCert the issuer certificate
217303
* @return true if the response can be trusted
218304
*/
219-
public boolean verifyResponse(BasicOCSPResp ocspResp, X509Certificate responderCert) {
220-
// testing using the responder certificate
221-
if (isSignatureValid(ocspResp, responderCert))
222-
return true;
223-
// testing using trusted anchors
224-
if (rootStore == null)
225-
return false;
305+
@Deprecated
306+
public boolean verifyResponse(BasicOCSPResp ocspResp, X509Certificate issuerCert) {
226307
try {
227-
// loop over the certificates in the root store
228-
for (Enumeration<String> aliases = rootStore.aliases(); aliases.hasMoreElements();) {
229-
String alias = aliases.nextElement();
230-
try {
231-
if (!rootStore.isCertificateEntry(alias))
232-
continue;
233-
X509Certificate anchor = (X509Certificate)rootStore.getCertificate(alias);
234-
if (isSignatureValid(ocspResp, anchor))
235-
return true;
236-
} catch (GeneralSecurityException e) {
237-
continue;
238-
}
239-
}
240-
}
241-
catch (GeneralSecurityException e) {
308+
isValidResponse(ocspResp, issuerCert);
309+
return true;
310+
} catch (Exception e) {
242311
return false;
243312
}
244-
return false;
245313
}
246314

247315
/**
@@ -252,7 +320,8 @@ public boolean verifyResponse(BasicOCSPResp ocspResp, X509Certificate responderC
252320
*/
253321
public boolean isSignatureValid(BasicOCSPResp ocspResp, Certificate responderCert) {
254322
try {
255-
ContentVerifierProvider verifierProvider = new JcaContentVerifierProviderBuilder().setProvider("BC").build(responderCert.getPublicKey());
323+
ContentVerifierProvider verifierProvider = new JcaContentVerifierProviderBuilder()
324+
.setProvider("BC").build(responderCert.getPublicKey());
256325
return ocspResp.isSignatureValid(verifierProvider);
257326
} catch (OperatorCreationException e) {
258327
return false;
@@ -263,7 +332,7 @@ public boolean isSignatureValid(BasicOCSPResp ocspResp, Certificate responderCer
263332

264333
/**
265334
* Gets an OCSP response online and returns it if the status is GOOD
266-
* (without further checking).
335+
* (without further checking!).
267336
* @param signCert the signing certificate
268337
* @param issuerCert the issuer certificate
269338
* @return an OCSP response
@@ -272,14 +341,14 @@ public BasicOCSPResp getOcspResponse(X509Certificate signCert, X509Certificate i
272341
if (signCert == null && issuerCert == null) {
273342
return null;
274343
}
275-
OcspClientBouncyCastle ocsp = new OcspClientBouncyCastle();
344+
OcspClientBouncyCastle ocsp = new OcspClientBouncyCastle(null);
276345
BasicOCSPResp ocspResp = ocsp.getBasicOCSPResp(signCert, issuerCert, null);
277346
if (ocspResp == null) {
278347
return null;
279348
}
280-
SingleResp[] resp = ocspResp.getResponses();
281-
for (int i = 0; i < resp.length; i++) {
282-
Object status = resp[i].getCertStatus();
349+
SingleResp[] resps = ocspResp.getResponses();
350+
for (SingleResp resp : resps) {
351+
Object status = resp.getCertStatus();
283352
if (status == CertificateStatus.GOOD) {
284353
return ocspResp;
285354
}

0 commit comments

Comments
 (0)