You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by ti...@apache.org on 2018/11/15 19:24:05 UTC
svn commit: r1846672 -
/pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/OcspHelper.java
Author: tilman
Date: Thu Nov 15 19:24:05 2018
New Revision: 1846672
URL: http://svn.apache.org/viewvc?rev=1846672&view=rev
Log:
PDFBOX-3017:
- "critical" flag should not be set for any extension, see RFC 2560 section 4.1.2
- fallback validating the OCSP response based on time
- use default security provider
- avoid memory leaks
Modified:
pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/OcspHelper.java
Modified: pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/OcspHelper.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/OcspHelper.java?rev=1846672&r1=1846671&r2=1846672&view=diff
==============================================================================
--- pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/OcspHelper.java (original)
+++ pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/OcspHelper.java Thu Nov 15 19:24:05 2018
@@ -27,12 +27,13 @@ import java.security.NoSuchAlgorithmExce
import java.security.Security;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
+import java.util.Calendar;
+import java.util.Date;
import java.util.Random;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.pdfbox.pdmodel.encryption.SecurityProvider;
-import org.apache.pdfbox.util.Hex;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DLSequence;
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
@@ -53,7 +54,6 @@ import org.bouncycastle.cert.ocsp.OCSPRe
import org.bouncycastle.cert.ocsp.OCSPResp;
import org.bouncycastle.cert.ocsp.RevokedStatus;
import org.bouncycastle.cert.ocsp.SingleResp;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentVerifierProvider;
import org.bouncycastle.operator.DigestCalculator;
import org.bouncycastle.operator.OperatorCreationException;
@@ -107,9 +107,10 @@ public class OcspHelper
* @param ocspResponse to be verified
* @throws OCSPException
* @throws RevokedCertificateException
+ * @throws IOException if the default security provider can't be instantiated
*/
private void verifyOcspResponse(OCSPResp ocspResponse)
- throws OCSPException, RevokedCertificateException
+ throws OCSPException, RevokedCertificateException, IOException
{
verifyRespStatus(ocspResponse);
@@ -118,7 +119,7 @@ public class OcspHelper
{
checkOcspSignature(basicResponse.getCerts()[0], basicResponse);
- checkNonce(basicResponse);
+ boolean nonceChecked = checkNonce(basicResponse);
SingleResp[] responses = basicResponse.getResponses();
if (responses.length == 1)
@@ -126,12 +127,12 @@ public class OcspHelper
SingleResp resp = responses[0];
Object status = resp.getCertStatus();
- //TODO check time
- // https://tools.ietf.org/html/rfc5019
- // Clients MUST check for the existence of the nextUpdate field and MUST
- // ensure the current time, expressed in GMT time as described in
- // Section 2.2.4, falls between the thisUpdate and nextUpdate times. If
- // the nextUpdate field is absent, the client MUST reject the response.
+ if (!nonceChecked)
+ {
+ // https://tools.ietf.org/html/rfc5019
+ // fall back to validating the OCSPResponse based on time
+ checkOcspResponseFresh(resp);
+ }
if (status instanceof RevokedStatus)
{
@@ -154,20 +155,54 @@ public class OcspHelper
}
}
+ private void checkOcspResponseFresh(SingleResp resp) throws OCSPException
+ {
+ // https://tools.ietf.org/html/rfc5019
+ // Clients MUST check for the existence of the nextUpdate field and MUST
+ // ensure the current time, expressed in GMT time as described in
+ // Section 2.2.4, falls between the thisUpdate and nextUpdate times. If
+ // the nextUpdate field is absent, the client MUST reject the response.
+
+ Date curDate = Calendar.getInstance().getTime();
+
+ Date thisUpdate = resp.getThisUpdate();
+ if (thisUpdate == null)
+ {
+ throw new OCSPException("OCSP: thisUpdate field is missing in response (RFC 5019 2.2.4.)");
+ }
+ Date nextUpdate = resp.getNextUpdate();
+ if (nextUpdate == null)
+ {
+ throw new OCSPException("OCSP: nextUpdate field is missing in response (RFC 5019 2.2.4.)");
+ }
+ if (curDate.compareTo(thisUpdate) < 0)
+ {
+ LOG.error(curDate + " < " + thisUpdate);
+ throw new OCSPException("OCSP: current date < thisUpdate field (RFC 5019 2.2.4.)");
+ }
+ if (curDate.compareTo(nextUpdate) > 0)
+ {
+ LOG.error(curDate + " > " + nextUpdate);
+ throw new OCSPException("OCSP: current date > nextUpdate field (RFC 5019 2.2.4.)");
+ }
+ LOG.info("OCSP response is fresh");
+ }
+
/**
* Checks whether the OCSP response is signed by the given certificate.
*
* @param certificate the certificate to check the signature
* @param basicResponse OCSP response containing the signature
* @throws OCSPException when the signature is invalid or could not be checked
+ * @throws IOException if the default security provider can't be instantiated
*/
private void checkOcspSignature(X509CertificateHolder certificate, BasicOCSPResp basicResponse)
- throws OCSPException
+ throws OCSPException, IOException
{
try
{
ContentVerifierProvider verifier = new JcaX509ContentVerifierProviderBuilder()
- .setProvider(BouncyCastleProvider.PROVIDER_NAME).build(certificate);
+ .setProvider(SecurityProvider.getProvider()).build(certificate);
if (!basicResponse.isSignatureValid(verifier))
{
@@ -181,12 +216,12 @@ public class OcspHelper
}
/**
- * Checks if the nonce in the response is correct
+ * Checks if the nonce in the response matches.
*
* @param basicResponse Response to be checked
- * @throws OCSPException if nonce is wrong or inexistent
+ * @throws OCSPException if the nonce is different
*/
- private void checkNonce(BasicOCSPResp basicResponse) throws OCSPException
+ private boolean checkNonce(BasicOCSPResp basicResponse) throws OCSPException
{
Extension nonceExt = basicResponse.getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce);
if (nonceExt != null)
@@ -194,19 +229,20 @@ public class OcspHelper
DEROctetString responseNonceString = (DEROctetString) nonceExt.getExtnValue();
if (!responseNonceString.equals(encodedNonce))
{
- throw new OCSPException("Invalid Nonce found in response!");
+ throw new OCSPException("Different nonce found in response!");
+ }
+ else
+ {
+ LOG.info("Nonce is good");
+ return true;
}
}
- else if (encodedNonce != null)
- {
- //TODO this is not correct!
- // https://tools.ietf.org/html/rfc5019
- // Clients that opt to include a nonce in the
- // request SHOULD NOT reject a corresponding OCSPResponse solely on the
- // basis of the nonexistent expected nonce, but MUST fall back to
- // validating the OCSPResponse based on time.
- throw new OCSPException("Nonce not found in response!");
- }
+ // https://tools.ietf.org/html/rfc5019
+ // Clients that opt to include a nonce in the
+ // request SHOULD NOT reject a corresponding OCSPResponse solely on the
+ // basis of the nonexistent expected nonce, but MUST fall back to
+ // validating the OCSPResponse based on time.
+ return false;
}
/**
@@ -221,22 +257,31 @@ public class OcspHelper
OCSPReq request = generateOCSPRequest();
URL url = new URL(ocspUrl);
HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
- httpConnection.setRequestProperty("Content-Type", "application/ocsp-request");
- httpConnection.setRequestProperty("Accept", "application/ocsp-response");
- httpConnection.setDoOutput(true);
- try (OutputStream out = httpConnection.getOutputStream())
+ try
{
- out.write(request.getEncoded());
- }
+ httpConnection.setRequestProperty("Content-Type", "application/ocsp-request");
+ httpConnection.setRequestProperty("Accept", "application/ocsp-response");
+ httpConnection.setDoOutput(true);
+ try (OutputStream out = httpConnection.getOutputStream())
+ {
+ out.write(request.getEncoded());
+ }
- if (httpConnection.getResponseCode() != 200)
+ if (httpConnection.getResponseCode() != 200)
+ {
+ throw new IOException("OCSP: Could not access url, ResponseCode: "
+ + httpConnection.getResponseCode());
+ }
+ // Get response
+ try (InputStream in = (InputStream) httpConnection.getContent())
+ {
+ return new OCSPResp(in);
+ }
+ }
+ finally
{
- throw new IOException("OCSP: Could not access url, ResponseCode: "
- + httpConnection.getResponseCode());
+ httpConnection.disconnect();
}
- // Get Response
- InputStream in = (InputStream) httpConnection.getContent();
- return new OCSPResp(in);
}
/**
@@ -258,11 +303,8 @@ public class OcspHelper
LOG.error("An internal error occurred in the OCSP Server!");
break;
case OCSPResponseStatus.MALFORMED_REQUEST:
- // This can also happen if the nonce extension is not supported.
- // The nonce extension is meant to prevent replay attacks.
- // Once could argue that a replay attack is less likely in document validating
- // than in ssl-certificate validating, so decide for yourself to remove
- // the nonce submission (and the check).
+ // This happened when the "critical" flag was used for extensions
+ // on a responder known by the committer of this comment.
statusInfo = "MALFORMED_REQUEST";
LOG.error("Your request did not fit the RFC 2560 syntax!");
break;
@@ -315,25 +357,24 @@ public class OcspHelper
throw new IOException("Error creating CertificateID with the Certificate encoding", e);
}
- OCSPReqBuilder builder = new OCSPReqBuilder();
+ // https://tools.ietf.org/html/rfc2560#section-4.1.2
+ // Support for any specific extension is OPTIONAL. The critical flag
+ // SHOULD NOT be set for any of them.
Extension responseExtension = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_response,
- true, new DLSequence(OCSPObjectIdentifiers.id_pkix_ocsp_basic).getEncoded());
+ false, new DLSequence(OCSPObjectIdentifiers.id_pkix_ocsp_basic).getEncoded());
Random rand = new Random();
byte[] nonce = new byte[16];
rand.nextBytes(nonce);
encodedNonce = new DEROctetString(new DEROctetString(nonce));
- Extension nonceExtension = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, true,
+ Extension nonceExtension = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false,
encodedNonce);
+ OCSPReqBuilder builder = new OCSPReqBuilder();
builder.setRequestExtensions(
new Extensions(new Extension[] { responseExtension, nonceExtension }));
-
builder.addRequest(certId);
-
- LOG.info("Nonce: " + Hex.getString(nonceExtension.getExtnValue().getEncoded()));
-
return builder.build();
}