一、前言
上篇文章我们了解了根证书和校验证书有效性中的一个比较重要的渠道–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 协议的工作方式如下:
- 客户端向 OCSP 服务器发送一个查询请求,其中包含要验证的证书的序列号。
- OCSP 服务器检查该证书是否被撤销或者是否仍然有效,并将其状态返回给客户端。
- 客户端收到 OCSP 服务器的响应,根据响应中的信息来确定证书的有效性。
相比于传统的证书撤销列表(CRL),OCSP 的优势在于它能够提供更快的响应时间和更精确的证书状态信息。因为 CRL 需要定期更新,并且可能很大,所以使用 OCSP 可以避免这些问题。
值得一提的是,OCSP 也有一些缺点,如可能存在安全和隐私方面的问题,因为使用 OCSP 需要向 CA 公开某个特定证书的信息。此外,如果 OCSP 服务器无法响应,则客户端可能无法验证证书的有效性。
2.2、OCSP地址在java中的获取
在java中,我们可以通过证书的X509形式类X509Certificate去获取CRL地址,步骤如下,前两步大家可以发现其实和获取CRL地址代码差不多。
- 获取证书中的扩展信息:
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()
- 解码扩展信息,首先创建一个新的ASN1InputStream对象,用于读取传递的字节数组参数crlDistributionPointsExtension。 然后,将扩展值包装在一个ASN1OctetString对象中,这可以通过在ASN1InputStream对象上调用readObject()来获取。接下来,使用ASN1OctetString对象的八位字节创建一个新的ASN1InputStream对象,并再次调用readObject()以获取表示CRL Distribution Points扩展的ASN1Primitive对象。
ASN1InputStream aIn =newASN1InputStream(newByteArrayInputStream(crlDistributionPointsExtension));ASN1OctetString octs =(ASN1OctetString) aIn.readObject();
aIn =newASN1InputStream(newByteArrayInputStream(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 序列。
- 用之前获取的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)的表示方式。
代码如下
ASN1SequenceAccessDescriptions=(ASN1Sequence) obj;for(int i =0; i <AccessDescriptions.size(); i++){ASN1SequenceAccessDescription=(ASN1Sequence)AccessDescriptions.getObjectAt(i);if(AccessDescription.size()!=2){continue;}elseif(AccessDescription.getObjectAt(0)instanceofASN1ObjectIdentifier){//获取accessMethodASN1ObjectIdentifier id =(ASN1ObjectIdentifier)AccessDescription.getObjectAt(0);if(SecurityIDs.ID_OCSP.equals(id.getId())){//如果是OCSP类型,则获取accessLocationASN1Primitive description =(ASN1Primitive)AccessDescription.getObjectAt(1);StringAccessLocation=getStringFromGeneralName(description);// 区别于itext源码,不获取ldap协议地址if(AccessLocation.startsWith("ldap")){continue;}if(AccessLocation==null){return"";}else{returnAccessLocation;}}}}
我们debug可以看一下
在获取数字证书中的OCSP URL时,AccessDescription就是包含OCSP地址信息的ASN.1结构。具体而言,该字段的accessMethod值应该为“1.3.6.1.5.5.7.48.1”,即OCSP协议类型的标识符,而accessLocation则应该是一个URI字符串,表示OCSP服务器的地址。
完整测试代码如下:
publicstaticvoidmain(String[] args)throwsCertificateException,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",newBouncyCastleProvider());X509Certificate certificate =(X509Certificate) cf
.generateCertificate(newByteArrayInputStream(Base64Utils.decode(rootCert)));byte[] crlDistributionPointsExtension = certificate.getExtensionValue("1.3.6.1.5.5.7.1.1");ASN1InputStream aIn =newASN1InputStream(newByteArrayInputStream(crlDistributionPointsExtension));ASN1OctetString octs =(ASN1OctetString) aIn.readObject();
aIn =newASN1InputStream(newByteArrayInputStream(octs.getOctets()));ASN1Primitive asn1Primitive = aIn.readObject();ASN1SequenceAccessDescriptions=(ASN1Sequence) asn1Primitive;for(int i =0; i <AccessDescriptions.size(); i++){ASN1SequenceAccessDescription=(ASN1Sequence)AccessDescriptions.getObjectAt(i);if(AccessDescription.size()!=2){continue;}elseif(AccessDescription.getObjectAt(0)instanceofASN1ObjectIdentifier){ASN1ObjectIdentifier id =(ASN1ObjectIdentifier)AccessDescription.getObjectAt(0);if(SecurityIDs.ID_OCSP.equals(id.getId())){ASN1Primitive description =(ASN1Primitive)AccessDescription.getObjectAt(1);StringAccessLocation=getStringFromGeneralName(description);// 区别于itext源码,不获取ldap协议地址if(AccessLocation.startsWith("ldap")){continue;}if(AccessLocation==null){System.out.println("");}else{System.out.println(AccessLocation);}}}}}privatestaticStringgetStringFromGeneralName(ASN1Primitive names)throwsIOException{ASN1TaggedObject taggedObject =(ASN1TaggedObject) names ;returnnewString(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 =newCertificateID(newJcaDigestCalculatorProviderBuilder().build().get(CertificateID.HASH_SHA1),newJcaX509CertificateHolder(issuerCert), serialNumber);
在创建CertificateID时,首先获取一个用于计算SHA-1 CertHash的
JcaDigestCalculator
对象。在这里,先通过调用
JcaDigestCalculatorProviderBuilder
的
build()
方法获取一个**用于计算摘要的
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 =newOCSPReqBuilder();
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 =newExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce,false,newDEROctetString(newDEROctetString(PdfEncryption.createDocumentId()).getEncoded()));
gen.setRequestExtensions(newExtensions(newExtension[]{ ext }));
最后,调用build()方法获取OCSPReq对象,该对象表示一个完整的OCSP请求信息。完整代码如下:
privatestaticOCSPReqgenerateOCSPRequest(X509Certificate issuerCert,BigInteger serialNumber)throwsOCSPException,IOException,OperatorException,CertificateEncodingException{// Add provider BCSecurity.addProvider(neworg.bouncycastle.jce.provider.BouncyCastleProvider());// Generate the id for the certificate we are looking forCertificateID id =newCertificateID(newJcaDigestCalculatorProviderBuilder().build().get(CertificateID.HASH_SHA1),newJcaX509CertificateHolder(issuerCert), serialNumber);// basic request generation with nonceOCSPReqBuilder gen =newOCSPReqBuilder();
gen.addRequest(id);Extension ext =newExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce,false,newDEROctetString(newDEROctetString(PdfEncryption.createDocumentId()).getEncoded()));
gen.setRequestExtensions(newExtensions(newExtension[]{ ext }));return gen.build();}
2. 创建并配置一个HTTP连接,并且配置连接,用于向指定的URL发送OCSP请求消息,并等待响应消息。
byte[] array = request.getEncoded();URL urlt =newURL(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 =newDataOutputStream(newBufferedOutputStream(out));//创建一个数据输出流对象,用于将字节数组写入到输出流中。
dataOut.write(array);//将OCSP请求消息体数组(array)中的数据写入到数据输出流中。
dataOut.flush();//刷新数据输出流,将缓冲区中的数据推送到网络中。
dataOut.close();//关闭数据输出流。if(con.getResponseCode()/100!=2){thrownewIOException(MessageLocalization.getComposedMessage("invalid.http.response.1", con.getResponseCode()));}// Get ResponseInputStream in =(InputStream) con.getContent();returnnewOCSPResp(StreamUtil.inputStreamToArray(in));
完整获取回复OCSP回复代码如下:
privatestaticOCSPReqgenerateOCSPRequest(X509Certificate issuerCert,BigInteger serialNumber)throwsOCSPException,IOException,OperatorException,CertificateEncodingException{// Add provider BCSecurity.addProvider(neworg.bouncycastle.jce.provider.BouncyCastleProvider());// Generate the id for the certificate we are looking forCertificateID id =newCertificateID(newJcaDigestCalculatorProviderBuilder().build().get(CertificateID.HASH_SHA1),newJcaX509CertificateHolder(issuerCert), serialNumber);// basic request generation with nonceOCSPReqBuilder gen =newOCSPReqBuilder();
gen.addRequest(id);Extension ext =newExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce,false,newDEROctetString(newDEROctetString(PdfEncryption.createDocumentId()).getEncoded()));
gen.setRequestExtensions(newExtensions(newExtension[]{ ext }));return gen.build();}publicstaticOCSPRespgetOcspResponse(X509Certificate checkCert,X509Certificate rootCert1,String url)throwsGeneralSecurityException,OCSPException,IOException,OperatorException{if(checkCert ==null|| rootCert1 ==null){returnnull;}if(url ==null){returnnull;}OCSPReq request =generateOCSPRequest(rootCert1, checkCert.getSerialNumber());byte[] array = request.getEncoded();URL urlt =newURL(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 =newDataOutputStream(newBufferedOutputStream(out));
dataOut.write(array);
dataOut.flush();
dataOut.close();if(con.getResponseCode()/100!=2){thrownewIOException(MessageLocalization.getComposedMessage("invalid.http.response.1", con.getResponseCode()));}// Get ResponseInputStream in =(InputStream) con.getContent();returnnewOCSPResp(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);return2;}if(ocspResponse ==null){
logger.warn("未获取到ocsp响应");return2;}if(ocspResponse.getStatus()!=OCSPResp.SUCCESSFUL){//判断此次连接返回的响应结果
logger.warn("获取ocsp响应未成功");return2;}BasicOCSPResp basicResponse =null;//获取到BasicOCSPResp try{
basicResponse =(BasicOCSPResp) ocspResponse.getResponseObject();}catch(Exception e){
logger.warn(e.getMessage(), e);return2;}if(basicResponse !=null){SingleResp[] responses = basicResponse.getResponses();if(responses.length ==1){SingleResp resp = responses[0];Object status = resp.getCertStatus();if(status ==CertificateStatus.GOOD){return1;}elseif(status instanceofRevokedStatus){return0;}else{
logger.warn("ocsp校验结果:"+"ocsp.status.is.unknown");return2;}}}return2;
这里可能大家对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去验证结果了。
publicstaticvoidmain(String[] args)throwsGeneralSecurityException,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",newBouncyCastleProvider());X509Certificate certificate =(X509Certificate) cf
.generateCertificate(newByteArrayInputStream(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(newByteArrayInputStream(Base64Utils.decode(rootCert)));byte[] authorityInfoAccesses = certificate.getExtensionValue(Extension.authorityInfoAccess.getId());ASN1InputStream aIn =newASN1InputStream(newByteArrayInputStream(authorityInfoAccesses));ASN1OctetString octs =(ASN1OctetString) aIn.readObject();
aIn =newASN1InputStream(newByteArrayInputStream(octs.getOctets()));ASN1Primitive asn1Primitive = aIn.readObject();ASN1SequenceAccessDescriptions=(ASN1Sequence) asn1Primitive;StringAccessLocation=null;for(int i =0; i <AccessDescriptions.size(); i++){ASN1SequenceAccessDescription=(ASN1Sequence)AccessDescriptions.getObjectAt(i);if(AccessDescription.size()!=2){continue;}elseif(AccessDescription.getObjectAt(0)instanceofASN1ObjectIdentifier){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("证书可以正常使用");}elseif(status instanceofRevokedStatus){System.out.println("该证书已吊销");}else{
logger.warn("ocsp校验结果:"+"ocsp.status.is.unknown");System.out.println("吊销校验异常");}}}}
版权归原作者 雨~旋律 所有, 如有侵权,请联系我们删除。