一、前言

上篇文章我们了解了根证书和校验证书有效性中的一个比较重要的渠道–CRL,但是CRL有着时间延迟,网络带宽消耗等缺点,本篇文章我们了解另一种更高效也是目前被广泛应用的校验证书有效性的另一种方式–OCSP,并且我会结合java来聊聊如何获取OCSP地址以及如何去通过获取的OCSP url去获取ocsp结果

二、OCSP

2.1、OCSP概念

  • OCSP(Online Certificate Status Protocol)是一种用于验证数字证书有效性的协议。它允许一个客户端向证书颁发机构(CA)的OCSP服务器查询某个特定证书是否被撤销或者是否仍然有效。与传统的证书撤销列表(CRL)相比,OCSP 具有更快的响应时间和更精确的证书状态信息。OCSP协议的维护者通常是负责数字证书签发和管理的组织或个人。例如CA机构等
  • 具体来说,当一个客户端需要验证某个证书的有效性时,它会向OCSP服务器发送一个查询请求,询问该证书的状态。OCSP服务器会返回一个响应,其中包含有关该证书的信息,例如证书是否被撤销,以及该证书是否还有效。

OCSP 协议的工作方式如下:

  1. 客户端向 OCSP 服务器发送一个查询请求,其中包含要验证的证书的序列号。
  2. OCSP 服务器检查该证书是否被撤销或者是否仍然有效,并将其状态返回给客户端。
  3. 客户端收到 OCSP 服务器的响应,根据响应中的信息来确定证书的有效性。

相比于传统的证书撤销列表(CRL),OCSP 的优势在于它能够提供更快的响应时间和更精确的证书状态信息。因为 CRL 需要定期更新,并且可能很大,所以使用 OCSP 可以避免这些问题。

值得一提的是,OCSP 也有一些缺点,如可能存在安全和隐私方面的问题,因为使用 OCSP 需要向 CA 公开某个特定证书的信息。此外,如果 OCSP 服务器无法响应,则客户端可能无法验证证书的有效性。

2.2、OCSP地址在java中的获取

在java中,我们可以通过证书的X509形式类X509Certificate去获取CRL地址,步骤如下,前两步大家可以发现其实和获取CRL地址代码差不多

  1. 获取证书中的扩展信息:
X509Certificate cert = ... // 从某处获取证书对象
byte[] crlDistributionPointsExtension = cert.getExtensionValue("1.3.6.1.5.5.7.1.1");

其中1.3.6.1.5.5.7.1.1是X.509标准中定义的证书扩展之一,也称为authorityInfoAccess扩展。该扩展用于指定证书颁发者(CA)的证书撤销列表(CRL)位置和/或在线证书状态协议(OCSP)验证器地址等信息。这个扩展字段允许使用者在验证证书时获取到关于该证书颁发者的更多信息,从而增加了证书验证的安全性。,以便验证人员可以在验证证书时检查该证书是否已被撤销。java中也可以通过以下方法获取,同样也是1.3.6.1.5.5.7.1.1.所以大家感兴趣的话可以用这个id去试一下获取CRL地址,同样也是可以获取crl地址的,但是如果通过

Extension.authorityInfoAccess.getId()
  1. 解码扩展信息,首先创建一个新的ASN1InputStream对象,用于读取传递的字节数组参数crlDistributionPointsExtension。 然后,将扩展值包装在一个ASN1OctetString对象中,这可以通过在ASN1InputStream对象上调用readObject()来获取。接下来,使用ASN1OctetString对象的八位字节创建一个新的ASN1InputStream对象,并再次调用readObject()以获取表示CRL Distribution Points扩展的ASN1Primitive对象。
        ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(crlDistributionPointsExtension));ASN1OctetString octs = (ASN1OctetString) aIn.readObject();aIn = new ASN1InputStream(new ByteArrayInputStream(octs.getOctets()));ASN1Primitive asn1Primitive = aIn.readObject();

其实我一开始看的时候会觉得中间两步是不是有点多余,我明明可以直接aIn.readObject()得到ASN1Primitive,为什么还要多转成一次ASN1OctetString呢。
我们实践一下

通过上面忽略两步操作后,发现报了类型转化的异常,这个原因又是为什么呢
首先是我们获取的证书中的扩展信息,根据 X.509 标准,CRL 分发点扩展是由一个 OCTET STRING 构成的,其中包含了一个 ASN.1 序列。
如果 CRL 分发点扩展由 OCTET STRING构成,就像这个例子一样,直接将 OCTET STRING 转换为 ASN.1 序列(ASN1Sequence),则会抛出类型转换异常。因为 ASN.1 编码规范中定义,OCTET STRING 类型的数据是由一个长度和一个字节数组组成的。而 ASN.1 序列则是由一组有序的元素组成的,每个元素都有自己的标识符和数据值。
所以我们的目的是为了从OCTET STRING中拿到ASN.1 序列,而不是直接将OCTET STRING转化成序列
在第一次调用 ASN1InputStream.readObject() 方法时,返回的确实是一个 DEROctetString 对象。这是因为 ASN1InputStream.readObject() 方法会根据输入流中的数据类型返回对应的 ASN.1 原语对象。在这里,由于输入流中的数据类型是 OCTET STRING,因此返回的就是一个 DEROctetString 对象。然后为了转化成ASN1Sequence,我们将 OCTET STRING 中的字节流作为参数重新生成ASN1InputStream对象aIn。通过aIn.readObject()得到ASN.1原语对象,通过这样就可以从 OCTET STRING 中提取出 ASN.1 序列。

  1. 用之前获取的ASN1Primitive对象转化成ASN1Sequence,该对象包含多个AccessDescription,遍历每个AccessDescription,判断其是否为OCSP协议类型,并获取对应的AccessLocation字符串。在获取AccessLocation时,会跳过ldap协议地址。如果没有找到符合条件的AccessDescription,则返回null。

这里介绍一下什么是AccessDescription

AccessDescription是一个ASN.1结构,用于描述数字证书中的访问描述符信息。它通常用于在证书扩展中传递OCSP(Online Certificate Status Protocol)或者CA Issuers的地址信息。AccessDescription本质上是一个序列(Sequence),包含两个元素:
1.accessMethod:用于指定AccessDescription的类型,例如OCSP或者CA Issuers等。
2.accessLocation:用于存储对应的访问地址信息,可以是URI字符串或者其他通用名(GeneralName)的表示方式。

代码如下

            ASN1Sequence AccessDescriptions = (ASN1Sequence) obj;for (int i = 0; i < AccessDescriptions.size(); i++) {ASN1Sequence AccessDescription = (ASN1Sequence) AccessDescriptions.getObjectAt(i);if ( AccessDescription.size() != 2 ) {continue;}else if (AccessDescription.getObjectAt(0) instanceof ASN1ObjectIdentifier) {//获取accessMethodASN1ObjectIdentifier id = (ASN1ObjectIdentifier)AccessDescription.getObjectAt(0);if (SecurityIDs.ID_OCSP.equals(id.getId())) {//如果是OCSP类型,则获取accessLocationASN1Primitive description = (ASN1Primitive)AccessDescription.getObjectAt(1);String AccessLocation =  getStringFromGeneralName(description);// 区别于itext源码,不获取ldap协议地址if (AccessLocation.startsWith("ldap")) {continue;}if (AccessLocation == null) {return "" ;}else {return AccessLocation ;}}}}

我们debug可以看一下

在获取数字证书中的OCSP URL时,AccessDescription就是包含OCSP地址信息的ASN.1结构。具体而言,该字段的accessMethod值应该为“1.3.6.1.5.5.7.48.1”,即OCSP协议类型的标识符,而accessLocation则应该是一个URI字符串,表示OCSP服务器的地址。

完整测试代码如下:

public static void main(String[] args) throws CertificateException, IOException, CRLException {String rootCert = "MIIFuDCCBKCgAwIBAgIQche7n/HhSQ+guIZEOjNvGzANBgkqhkiG9w0BAQsFADAzMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxETAPBgNVBAMMCFNIRUNBIEcyMB4XDTIzMDIwNTE3MDQ0MVoXDTIzMDUwNjE1NTk1OVowTzELMAkGA1UEBhMCQ04xGzAZBgNVBAoMEueUteWtkOetvueroOWJjeWPsDEjMCEGA1UEAwwa5rWL6K+VMDEwMTExMUA4NCoqKioqKioqMjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCHJ8088uY9p4XD77TrLu3h0tB5O4Xp32hW4zNttjHmhoXai7wngFooNXDzGwj9JnM99VE+qjQzkr8Th9pyIYeTk5eLTMmmMEo/p42uiHyGbTtn8B2g3wmPTApu2S4+MAkNbvPD5VDEfMb+1e/7oN39NTZv1mBoENpst6nwEdgQ7jla4ueOcKOWWmFT+3lGzx3tWm8lkqSnryaSjNtMynMK25PxiXLFV8dQ4Wk7DigQpveP+GH8ltTHoZqpZMcXy5qgUWeZSfNYQ1i3JajjKBdSzflrKBi3HeeQBzPSk6M12B6EE5usl1zCABXl6o29IREEy9d/zmmScyLtff+oYw19AgMBAAGjggKqMIICpjAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwfQYIKwYBBQUHAQEEcTBvMDgGCCsGAQUFBzABhixodHRwOi8vb2NzcDMuc2hlY2EuY29tL29jc3Avc2hlY2Evc2hlY2Eub2NzcDAzBggrBgEFBQcwAoYnaHR0cDovL2xkYXAyLnNoZWNhLmNvbS9yb290L3NoZWNhZzIuZGVyMB8GA1UdIwQYMBaAFFaI3uMYQ4K3cqQm60SpYtCHxKwmMB0GA1UdDgQWBBS8nQ//lcMctfZGZ1AnPCwwRPZM6TALBgNVHQ8EBAMCBsAwgYYGBiqBHAHFOAR8MHowSQYIKoEcAcU4gRAEPWxkYXA6Ly9sZGFwMi5zaGVjYS5jb20vb3U9c2hlY2EgY2VydGlmaWNhdGUgY2hhaW4sbz1zaGVjYS5jb20wEQYIKoEcAcU4gRMEBTY2MDU1MBoGCCqBHAHFOIEUBA5RVDg0OTAyMzc0MDkyMzAJBgNVHRMEAjAAMEIGA1UdIAQ7MDkwNwYJKoEcAYbvOoEVMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly93d3cuc2hlY2EuY29tL3BvbGljeS8wgeAGA1UdHwSB2DCB1TA3oDWgM4YxaHR0cDovL2xkYXAyLnNoZWNhLmNvbS9DQTIwMDExL1JBOTAzMS9DUkw1MTkxLmNybDCBmaCBlqCBk4aBkGxkYXA6Ly9sZGFwMi5zaGVjYS5jb206Mzg5L2NuPUNSTDUxOTEuY3JsLG91PVJBOTAzMSxvdT1DQTIwMDExLG91PWNybCxvPVVuaVRydXN0P2NlcnRpZmljYXRlUmV2b2NhdGlvbkxpc3Q/YmFzZT9vYmplY3RDbGFzcz1jUkxEaXN0cmlidXRpb25Qb2ludDANBgkqhkiG9w0BAQsFAAOCAQEAjQRnIYRE9SH4+leOjO9oUt++qhfefVzaZXdGQgxiUzIXv14vo9mls0COjz0YXoruEe6olh6X6rrdmaKrYw0iq2CJ3D1GkrFCutjX2P3r97Irale8w5J8hJ6dybd/rFZFZuTfYm7yWJLEcF+pAZXedGObwe4fOjS0J/A6KXqGsrdB/fJwvfHH5UIIWW3OihTr1TLEEuun/3oDbGdBDTud2+6tbiEN9daFV92TSko2DRQ/CisJoq5SCmI/dYZlAqeyr4jlLWHFfVUpHKuvpn/lHtYCU0FsGyu9ixs4/YdBw/QIDj5oES9yf/FFDzfTnS8twa8rRJKWdUKKa9sYEeJtVQ==";CertificateFactory cf = CertificateFactory.getInstance("X.509", new BouncyCastleProvider());X509Certificate certificate = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(Base64Utils.decode(rootCert)));byte[] crlDistributionPointsExtension = certificate.getExtensionValue("1.3.6.1.5.5.7.1.1");ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(crlDistributionPointsExtension));ASN1OctetString octs = (ASN1OctetString) aIn.readObject();aIn = new ASN1InputStream(new ByteArrayInputStream(octs.getOctets()));ASN1Primitive asn1Primitive = aIn.readObject();ASN1Sequence AccessDescriptions = (ASN1Sequence) asn1Primitive;for (int i = 0; i < AccessDescriptions.size(); i++) {ASN1Sequence AccessDescription = (ASN1Sequence) AccessDescriptions.getObjectAt(i);if (AccessDescription.size() != 2) {continue;} else if (AccessDescription.getObjectAt(0) instanceof ASN1ObjectIdentifier) {ASN1ObjectIdentifier id = (ASN1ObjectIdentifier) AccessDescription.getObjectAt(0);if (SecurityIDs.ID_OCSP.equals(id.getId())) {ASN1Primitive description = (ASN1Primitive) AccessDescription.getObjectAt(1);String AccessLocation = getStringFromGeneralName(description);// 区别于itext源码,不获取ldap协议地址if (AccessLocation.startsWith("ldap")) {continue;}if (AccessLocation == null) {System.out.println("");} else {System.out.println(AccessLocation);}}}}}private static String getStringFromGeneralName(ASN1Primitive names) throws IOException {ASN1TaggedObject taggedObject = (ASN1TaggedObject) names ;return new String(ASN1OctetString.getInstance(taggedObject, false).getOctets(), "ISO-8859-1");}

执行后可以获取OCSP URL

2.3、通过ocsp url去获取ocsp结果响应

通过上面的学习我们已经可以成功获取ocsp url了,那么接下来就来看看如何通过ocsp url获取ocsp响应结果,步骤如下

1. 生成ocsp请求OCSPReq

我们需要的内容有颁发者证书(根证书),待验证证书的序列号
步骤如下:
①创建CertificateID
首先根据输入的颁发者证书和待查询证书的序列号(serialNumber),生成代表该证书的唯一标识——CertificateID。,代码如下:

     // Generate the id for the certificate we are looking forCertificateID id = new CertificateID(new JcaDigestCalculatorProviderBuilder().build().get(CertificateID.HASH_SHA1),new JcaX509CertificateHolder(issuerCert), serialNumber);

在创建CertificateID时,首先获取一个用于计算SHA-1 CertHash的JcaDigestCalculator对象。在这里,先通过调用JcaDigestCalculatorProviderBuilderbuild()方法获取一个用于计算摘要的DigestCalculatorProvider实例,然后再调用该实例的get()方法并传入CertificateID.HASH_SHA1常量来获取SHA-1算法的JcaDigestCalculator实例。
接着使用new JcaX509CertificateHolder(issuerCert)将颁发者证书转换为Bouncy Castle库中的X509CertificateHolder对象。

X509CertificateHolder是Bouncy Castle库中用于表示X.509证书的一个重要类。它提供了许多有用的方法来获取证书的各种属性,例如:subject、issuer、Serial Number等等。通过将颁发者证书转换为X509CertificateHolder对象,我们可以方便地从该对象中提取Issuer信息以构建CertificateID。

在这个过程中,JcaX509CertificateHolder类是一种用于创建X509CertificateHolder对象的便捷方式,它实现了X509CertificateHolder接口并封装了一个X.509证书。当我们调用new JcaX509CertificateHolder(issuerCert)时,会创建一个新的JcaX509CertificateHolder对象,并使用issuerCert初始化该对象。最终返回的X509CertificateHolder对象包含有关颁发者证书的信息,可以用于创建CertificateID。

然后结合待验证证书的序列号加上前面我们获取的SHA-1算法的JcaDigestCalculator实例以及X509CertificateHolder就可以构成CertificateID。
②创建OCSPReqBuilder
然后创建OCSPReqBuilder对象,并使用addRequest方法向请求中添加待查询的证书信息,即上一步中生成的CertificateID。

// basic request generation with nonceOCSPReqBuilder gen = new OCSPReqBuilder();gen.addRequest(id);

③添加Nonce扩展
为了防止重放攻击,可以在OCSP请求中添加一个随机数Nonce。这里使用BouncyCastle库提供的id_pkix_ocsp_nonce扩展来实现,将随机数作为DER编码的OctetString类型数据加入到Nonce扩展中。这个随机数我们通过PdfEncryption.createDocumentId()来获取

在PDF文档中,Nonce是一种用于生成加密密钥的随机数。为了确保生成的Nonce具有足够的熵(即随机性),应该使用高质量的随机数生成器来生成它。在iText 7中,可以使用PdfEncryption.createDocumentId()方法来获取一个具有足够熵的随机数作为Nonce,因为该方法使用了安全的随机数生成器。
确保生成的Nonce是具有足够熵的随机数非常重要,因为如果Nonce不够随机,则可能会出现加密弱点。攻击者可能会利用这些弱点来破解加密并访问被加密的内容。因此,使用安全的随机数生成器来生成Nonce是非常重要的,并且PdfEncryption.createDocumentId()方法提供了一种方便和可靠的方式来获得这样的随机数。

Extension ext = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false,new DEROctetString(new DEROctetString(PdfEncryption.createDocumentId()).getEncoded()));
gen.setRequestExtensions(new Extensions(new Extension[] { ext }));

最后,调用build()方法获取OCSPReq对象,该对象表示一个完整的OCSP请求信息。完整代码如下:

private static OCSPReq generateOCSPRequest(X509Certificate issuerCert, BigInteger serialNumber)throws OCSPException, IOException, OperatorException, CertificateEncodingException {// Add provider BCSecurity.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());// Generate the id for the certificate we are looking forCertificateID id = new CertificateID(new JcaDigestCalculatorProviderBuilder().build().get(CertificateID.HASH_SHA1),new JcaX509CertificateHolder(issuerCert), serialNumber);// basic request generation with nonceOCSPReqBuilder gen = new OCSPReqBuilder();gen.addRequest(id);Extension ext = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false,new DEROctetString(new DEROctetString(PdfEncryption.createDocumentId()).getEncoded()));gen.setRequestExtensions(new Extensions(new Extension[] { ext }));return gen.build();}

2. 创建并配置一个HTTP连接,并且配置连接,用于向指定的URL发送OCSP请求消息,并等待响应消息。

byte[] array = request.getEncoded();URL urlt = new URL(url);HttpURLConnection con = (HttpURLConnection) urlt.openConnection();con.setRequestProperty("Content-Type", "application/ocsp-request");  //设置HTTP请求头属性,表示请求消息体的格式是OCSP请求。con.setRequestProperty("Accept", "application/ocsp-response"); //设置HTTP请求头属性,表示接受响应消息体的格式是OCSP响应。con.setDoOutput(true);  //设置HTTP连接可以输出数据。con.setConnectTimeout(3000);con.setReadTimeout(5000);

3. 向已经建立的HTTP连接发送数据,并获取响应状态码(response code)和响应结果

        OutputStream out = con.getOutputStream(); //获取输出流,用于向服务器发送请求数据。DataOutputStream dataOut = new DataOutputStream(new BufferedOutputStream(out)); //创建一个数据输出流对象,用于将字节数组写入到输出流中。dataOut.write(array); //将OCSP请求消息体数组(array)中的数据写入到数据输出流中。dataOut.flush();  //刷新数据输出流,将缓冲区中的数据推送到网络中。dataOut.close(); //关闭数据输出流。if (con.getResponseCode() / 100 != 2) {throw new IOException(MessageLocalization.getComposedMessage("invalid.http.response.1", con.getResponseCode()));}// Get ResponseInputStream in = (InputStream) con.getContent();return new OCSPResp(StreamUtil.inputStreamToArray(in));

完整获取回复OCSP回复代码如下:

 private static OCSPReq generateOCSPRequest(X509Certificate issuerCert, BigInteger serialNumber)throws OCSPException, IOException, OperatorException, CertificateEncodingException {// Add provider BCSecurity.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());// Generate the id for the certificate we are looking forCertificateID id = new CertificateID(new JcaDigestCalculatorProviderBuilder().build().get(CertificateID.HASH_SHA1),new JcaX509CertificateHolder(issuerCert), serialNumber);// basic request generation with nonceOCSPReqBuilder gen = new OCSPReqBuilder();gen.addRequest(id);Extension ext = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false,new DEROctetString(new DEROctetString(PdfEncryption.createDocumentId()).getEncoded()));gen.setRequestExtensions(new Extensions(new Extension[] { ext }));return gen.build();}public static OCSPResp getOcspResponse(X509Certificate checkCert, X509Certificate rootCert1, String url)throws GeneralSecurityException, OCSPException, IOException, OperatorException {if (checkCert == null || rootCert1 == null) {return null;}if (url == null) {return null;}OCSPReq request = generateOCSPRequest(rootCert1, checkCert.getSerialNumber());byte[] array = request.getEncoded();URL urlt = new URL(url);HttpURLConnection con = (HttpURLConnection) urlt.openConnection();con.setRequestProperty("Content-Type", "application/ocsp-request");con.setRequestProperty("Accept", "application/ocsp-response");con.setDoOutput(true);con.setConnectTimeout(3000);con.setReadTimeout(5000);OutputStream out = con.getOutputStream();DataOutputStream dataOut = new DataOutputStream(new BufferedOutputStream(out));dataOut.write(array);dataOut.flush();dataOut.close();if (con.getResponseCode() / 100 != 2) {throw new IOException(MessageLocalization.getComposedMessage("invalid.http.response.1", con.getResponseCode()));}// Get ResponseInputStream in = (InputStream) con.getContent();return new OCSPResp(StreamUtil.inputStreamToArray(in));}

4.通过ocspResponse判断证书是否吊销

       //ocsp 校验结果:0(吊销),1(生效),2(ocsp校验异常)OCSPResp ocspResponse = null;try {ocspResponse = CertRevokeVerifyUtil.getOcspResponse(cert, root, ocspurl); //这个方法就是之前获取ocspResponse的方法} catch (Exception e) {logger.warn(e.getMessage(), e);return 2;}if (ocspResponse == null) {logger.warn("未获取到ocsp响应");return 2;}if (ocspResponse.getStatus() != OCSPResp.SUCCESSFUL) {  //判断此次连接返回的响应结果logger.warn("获取ocsp响应未成功");return 2;}BasicOCSPResp basicResponse = null;  //获取到BasicOCSPResp try {basicResponse = (BasicOCSPResp) ocspResponse.getResponseObject();} catch (Exception e) {logger.warn(e.getMessage(), e);return 2;}if (basicResponse != null) {SingleResp[] responses = basicResponse.getResponses();if (responses.length == 1) {SingleResp resp = responses[0];Object status = resp.getCertStatus();if (status == CertificateStatus.GOOD) {return 1;} else if (status instanceof RevokedStatus) {return 0;} else {logger.warn("ocsp校验结果:" + "ocsp.status.is.unknown");return 2;}}}return 2;

这里可能大家对BasicOCSPResp不太熟悉

BasicOCSPResp是OCSP协议中的一个类,用于表示OCSP响应消息体中的基本响应消息。它包含了指定证书的响应状态信息,以及生成响应的签名等元数据。
在OCSP响应消息体中,BasicOCSPResp是必须存在的。它的结构如下:
ResponderID: 响应者(OCSP服务器)的身份标识。
ProducedAt: 响应消息体生成的时间戳。
Responses: 包含了待验证证书的相关信息和响应状态信息。
ResponseExtensions: 可选,包含了响应消息的扩展字段。
SignatureAlgorithmIdentifier: 签名算法标识符。
Signature: 对响应消息体进行数字签名后的签名值。
BasicOCSPResp类提供了一些方法来获取响应消息体的各个部分的内容,如getResponderId()方法可以获取响应者的身份标识,getProducedAt()方法可以获取响应消息生成的时间戳等。通过这些方法,可以对OCSP响应消息体进行解析和处理。

然后这里再解释一下这里为什么要对

SingleResp[] responses = basicResponse.getResponses();if (responses.length == 1) {.....}

在OCSP响应消息中,BasicOCSPResp对象可以包含多个单个响应(SingleResponse)对象,每个单个响应对应一个待校验证书的OCSP响应消息。在常规情况下,一个OCSP请求只需要校验一个证书,因此BasicOCSPResp对象中只会包含一个单个响应。

但是,由于网络传输等原因,有时会发生OCSP响应消息体格式错误或者损坏的情况,导致BasicOCSPResp对象中可能包含多个单个响应或者不包含任何单个响应。这种情况在实际工程中出现的概率较低,但仍然有可能发生。

为了避免出现这种情况,代码中进行了如下判断:

SingleResp[] responses = basicResponse.getResponses(); 获取BasicOCSPResp对象中所有的单个响应。

if (responses.length == 1) {...} 判断单个响应数量是否为1,如果不是,则表示OCSP响应消息体格式错误或者损坏,无法进行后续校验操作,因此返回2表示校验异常。

通过判断单个响应数量,可以确保OCSP响应消息体格式正确且只包含一个单个响应。
然后就可以通过刚才获取的ocspUrl去验证结果了。

 public static void main(String[] args) throws GeneralSecurityException, IOException, OperatorException, OCSPException {String verifyCert = "MIIFuDCCBKCgAwIBAgIQche7n/HhSQ+guIZEOjNvGzANBgkqhkiG9w0BAQsFADAzMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxETAPBgNVBAMMCFNIRUNBIEcyMB4XDTIzMDIwNTE3MDQ0MVoXDTIzMDUwNjE1NTk1OVowTzELMAkGA1UEBhMCQ04xGzAZBgNVBAoMEueUteWtkOetvueroOWJjeWPsDEjMCEGA1UEAwwa5rWL6K+VMDEwMTExMUA4NCoqKioqKioqMjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCHJ8088uY9p4XD77TrLu3h0tB5O4Xp32hW4zNttjHmhoXai7wngFooNXDzGwj9JnM99VE+qjQzkr8Th9pyIYeTk5eLTMmmMEo/p42uiHyGbTtn8B2g3wmPTApu2S4+MAkNbvPD5VDEfMb+1e/7oN39NTZv1mBoENpst6nwEdgQ7jla4ueOcKOWWmFT+3lGzx3tWm8lkqSnryaSjNtMynMK25PxiXLFV8dQ4Wk7DigQpveP+GH8ltTHoZqpZMcXy5qgUWeZSfNYQ1i3JajjKBdSzflrKBi3HeeQBzPSk6M12B6EE5usl1zCABXl6o29IREEy9d/zmmScyLtff+oYw19AgMBAAGjggKqMIICpjAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwfQYIKwYBBQUHAQEEcTBvMDgGCCsGAQUFBzABhixodHRwOi8vb2NzcDMuc2hlY2EuY29tL29jc3Avc2hlY2Evc2hlY2Eub2NzcDAzBggrBgEFBQcwAoYnaHR0cDovL2xkYXAyLnNoZWNhLmNvbS9yb290L3NoZWNhZzIuZGVyMB8GA1UdIwQYMBaAFFaI3uMYQ4K3cqQm60SpYtCHxKwmMB0GA1UdDgQWBBS8nQ//lcMctfZGZ1AnPCwwRPZM6TALBgNVHQ8EBAMCBsAwgYYGBiqBHAHFOAR8MHowSQYIKoEcAcU4gRAEPWxkYXA6Ly9sZGFwMi5zaGVjYS5jb20vb3U9c2hlY2EgY2VydGlmaWNhdGUgY2hhaW4sbz1zaGVjYS5jb20wEQYIKoEcAcU4gRMEBTY2MDU1MBoGCCqBHAHFOIEUBA5RVDg0OTAyMzc0MDkyMzAJBgNVHRMEAjAAMEIGA1UdIAQ7MDkwNwYJKoEcAYbvOoEVMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly93d3cuc2hlY2EuY29tL3BvbGljeS8wgeAGA1UdHwSB2DCB1TA3oDWgM4YxaHR0cDovL2xkYXAyLnNoZWNhLmNvbS9DQTIwMDExL1JBOTAzMS9DUkw1MTkxLmNybDCBmaCBlqCBk4aBkGxkYXA6Ly9sZGFwMi5zaGVjYS5jb206Mzg5L2NuPUNSTDUxOTEuY3JsLG91PVJBOTAzMSxvdT1DQTIwMDExLG91PWNybCxvPVVuaVRydXN0P2NlcnRpZmljYXRlUmV2b2NhdGlvbkxpc3Q/YmFzZT9vYmplY3RDbGFzcz1jUkxEaXN0cmlidXRpb25Qb2ludDANBgkqhkiG9w0BAQsFAAOCAQEAjQRnIYRE9SH4+leOjO9oUt++qhfefVzaZXdGQgxiUzIXv14vo9mls0COjz0YXoruEe6olh6X6rrdmaKrYw0iq2CJ3D1GkrFCutjX2P3r97Irale8w5J8hJ6dybd/rFZFZuTfYm7yWJLEcF+pAZXedGObwe4fOjS0J/A6KXqGsrdB/fJwvfHH5UIIWW3OihTr1TLEEuun/3oDbGdBDTud2+6tbiEN9daFV92TSko2DRQ/CisJoq5SCmI/dYZlAqeyr4jlLWHFfVUpHKuvpn/lHtYCU0FsGyu9ixs4/YdBw/QIDj5oES9yf/FFDzfTnS8twa8rRJKWdUKKa9sYEeJtVQ==";CertificateFactory cf = CertificateFactory.getInstance("X.509", new BouncyCastleProvider());X509Certificate certificate = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(Base64Utils.decode(verifyCert)));String rootCert = "MIIEGDCCAwCgAwIBAgIQag48Y/PJFceE2VmIFXZ9qDANBgkqhkiG9w0BAQsFADAzMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxETAPBgNVBAMMCFVDQSBSb290MB4XDTE1MDMxMzAwMDAwMFoXDTI5MTIzMTAwMDAwMFowMzELMAkGA1UEBhMCQ04xETAPBgNVBAoMCFVuaVRydXN0MREwDwYDVQQDDAhTSEVDQSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN6IIHj7qZIYrz466zxTGCPURSI6GZqbFCTtxBrPnTE5SKtCIyqRNwj/+3A9f/cCyWYHptLeGeY80WORDGuZBBHiVTEsIHSnXZvfqCnQf9KmAisVOOTIGCPWJvCRnfMWLdAcENNaZDIxpkc31ZejALBNPHJDDhxmt6PqyvdX5/cF6gkXO2OOzCa/EF5+x9LwWUKAGR/b+x5j5vt637AQjNmt5Xym63sQdwEaAHqTuPCbcwl+Y1eKXmWuFUXcMk+JdbOhXmjqbOIhup5yrx+hyXc+dtRBJzuSEpvC7WkXLJInR2dqb+Bc2ReJd6zM1deM1MPRmqdJQKEDyT7lEXST53UCAwEAAaOCASYwggEiMEEGA1UdIAQ6MDgwNgYIKoEchu86gRUwKjAoBggrBgEFBQcCARYcaHR0cDovL3d3dy5zaGVjYS5jb20vcG9saWN5LzAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBDBggrBgEFBQcBAQQ3MDUwMwYIKwYBBQUHMAKGJ2h0dHA6Ly9sZGFwMi5zaGVjYS5jb20vcm9vdC91Y2Fyb290LmRlcjAdBgNVHQ4EFgQUVoje4xhDgrdypCbrRKli0IfErCYwHwYDVR0jBBgwFoAU2x8182tM/0IxZJvNu1oeHUgQt+4wNwYDVR0fBDAwLjAsoCqgKIYmaHR0cDovL2xkYXAyLnNoZWNhLmNvbS9yb290L3VjYXN1Yi5jcmwwDQYJKoZIhvcNAQELBQADggEBAHj7LkurkcVg8NqoXTg8skl96hhVN06jx2OuiEUFda8NVOfxvaCIc7Ep009/CHZnFUaQO3DYZejpTPAMlsJa18luc12xrOhLZxP4ht2TY+UfcskrjiyrrczJ+95dXT+ChYcGtDGfYXFKDOrgsxekEahSIs+fS/H4LA3Y3z8SeK4tFRigaWZQWV2kW+YNRAtXPoNYUPbPCq3UP4dLtm35DHPvdn/h1iVo5/GU+P02F+SBd6J4AO+wcVw5izs6LRXNRnfgSERM7vP8WLt+lX14umZXJPMPh+WoAH9WU6KnXFwLxpltCayueWsLOzDsX6sUbLcp/vPPrkA20CzeMerxY9E=";X509Certificate rootCertificate = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(Base64Utils.decode(rootCert)));byte[] authorityInfoAccesses = certificate.getExtensionValue(Extension.authorityInfoAccess.getId());ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(authorityInfoAccesses));ASN1OctetString octs = (ASN1OctetString) aIn.readObject();aIn = new ASN1InputStream(new ByteArrayInputStream(octs.getOctets()));ASN1Primitive asn1Primitive = aIn.readObject();ASN1Sequence AccessDescriptions = (ASN1Sequence) asn1Primitive;String AccessLocation = null;for (int i = 0; i < AccessDescriptions.size(); i++) {ASN1Sequence AccessDescription = (ASN1Sequence) AccessDescriptions.getObjectAt(i);if (AccessDescription.size() != 2) {continue;} else if (AccessDescription.getObjectAt(0) instanceof ASN1ObjectIdentifier) {ASN1ObjectIdentifier id = (ASN1ObjectIdentifier) AccessDescription.getObjectAt(0);if (SecurityIDs.ID_OCSP.equals(id.getId())) {ASN1Primitive description = (ASN1Primitive) AccessDescription.getObjectAt(1);AccessLocation = getStringFromGeneralName(description);// 区别于itext源码,不获取ldap协议地址if (AccessLocation.startsWith("ldap")) {continue;}if (AccessLocation == null) {System.out.println("");} else {System.out.println(AccessLocation);}}}}OCSPResp ocspResponse = CertRevokeVerifyUtil.getOcspResponse(certificate, rootCertificate, AccessLocation);System.out.println(ocspResponse);BasicOCSPResp basicResponse = null;  //获取到BasicOCSPResptry {basicResponse = (BasicOCSPResp) ocspResponse.getResponseObject();} catch (Exception e) {logger.warn(e.getMessage(), e);System.out.println("吊销校验异常");}if (basicResponse != null) {SingleResp[] responses = basicResponse.getResponses();if (responses.length == 1) {SingleResp resp = responses[0];Object status = resp.getCertStatus();if (status == CertificateStatus.GOOD) {System.out.println("证书可以正常使用");} else if (status instanceof RevokedStatus) {System.out.println("该证书已吊销");} else {logger.warn("ocsp校验结果:" + "ocsp.status.is.unknown");System.out.println("吊销校验异常");}}}}

数字证书的相关专业名词(下)---OCSP及其java中的应用相关推荐

  1. http、https和数字证书的相关知识

    http协议 http协议全称超文本传输协议,是一个基于请求与响应,无状态的,应用层的协议,常基于TCP/IP协议传输数据,互联网上应用最为广泛的一种网络协议,所有的WWW文件都必须遵守这个标准.设计 ...

  2. JAVA如何处理上一篇下一篇,Java中如何实现分页功能

    Java中如何实现分页功能 时间:2017-10-10     来源:华清远见Java培训中心 内容多了,我们就会想要去做分页,既能提升用户体验,又减少页面体积,提升加载速度.那么Java中怎么实现分 ...

  3. 【​区块链】相关专业名词术语

    区块链 区块链是一个共享数据库,存储于其中的数据或信息,具有不可伪造.全程留痕.可以追溯.公开透明和集体维护等特征.可以把区块链理解为一个共享的.不可更改的电子账本,能够在网络中记录交易和跟踪资产.这 ...

  4. 5G相关专业名词解释

    3GPP的提案的时候的一些关于5G中的英文缩写解释 NR(New Radio,新空口):通过电磁波来承载所需要发送的信息的一系列规范 BLER(blockerror rate)误块率 CB (code ...

  5. 广告曝光相关专业名词

    广告行业中常说的 CPC,CPM,CPD,CPT,CPA,CPS 等词的意思是什么?|广告入门 广告 广告主:投放广告的金主,有钱的. 流量主:有流量,接广告变现的. 广告行业内存在的几种方式主要是C ...

  6. 广告投放相关专业名词整理

    DSP(Demand Side Platform) 需求方平台 Ad Exchange 互联网广告交易平台 SSP(Supply-Side Platform) 供应方平台 DMP(Data-Manag ...

  7. java判断键盘按键按下_在Java中检测并操作键盘方向键

    vladr.. 5 不幸的是,这是不可能以便携的方式: 在Windows上,从System.in读取将被阻止,直到enter被按下,即使您不使用BufferedReader.箭头将循环显示命令历史记录 ...

  8. PKI/CA与数字证书

    写在前面 现在开始接触CA相关的内容,对一些名词还是不甚了解,在遇到一些问题的时候也不能理解,刚好最近看了一本<PKI/CA与数字证书技术大全>,里面介绍的比较系统全面,也对刚接触这方面的 ...

  9. 深入浅出 SSL/CA 证书及其相关证书文件(pem、crt、cer、key、csr)

    互联网是虚拟的,通过互联网我们无法正确获取对方真实身份.数字证书是网络世界中的身份证,数字证书为实现双方安全通信提供了电子认证.数字证书中含有密钥对所有者的识别信息,通过验证识别信息的真伪实现对证书持 ...

最新文章

  1. vuejs出的手机app有哪些_详解Vue webapp项目通过HBulider打包原生APP
  2. 解决AttributeError: XXX instance has no attribute ‘xxx‘的问题(新手必备)
  3. CTFshow 信息收集 web5
  4. Nginx HTTP之请求行解析函数ngx_http_parse_request_line
  5. cesium billboard 点击 不想显示infobox
  6. 数字能查出笔迹吗_大家都知道文字可以做笔迹鉴定,我想问一下专家阿拉伯数字是否也可以做笔迹鉴定正确率高吗?...
  7. android自动回复退订,Android实现短信自动回复,挂电话
  8. 【C语言笔记初级篇】第七章:结构体相关
  9. python基础逻辑判断语句(九)
  10. mysql unauthenticated user原因分析以及解决方法
  11. 第四:RobotFramework+Allure2生成精美测试报告
  12. DOM节点的属性和方法
  13. 【广东大学生网络攻防大赛-WriteUp(非官方)】Crypto | crypto-xor2
  14. android actionbar setCustomView时布局整体右移解决方案
  15. 《Lost》大结局最权威最彻底解读
  16. itunes将m4a转mp3
  17. SZTUOJ 1025.怪物入侵
  18. 2021-5-25-今日收获
  19. 创业者自述:都2020年了,我为什么还在做翻盖手机?
  20. JavaScript代码具体是怎么引入到HTML中的?

热门文章

  1. 分手了就不要联系了吗?
  2. 随机迷宫生成算法浅析
  3. OpenCV实战之人脸美颜美型(二)——人脸检测
  4. 开关电源电感电压波形过冲和下冲原理以及处理办法
  5. 快速搭建后台框架D2admin项目实战(1)
  6. Swagger UI汉化
  7. linux c prctl 进程相关 调用指令 简介
  8. sqlite源码剖析(一)
  9. C语言中const用法详解
  10. Vue 颜色选择器组件