上篇文章我们了解了根证书和校验证书有效性中的一个比较重要的渠道–CRL,但是CRL有着时间延迟,网络带宽消耗等缺点,本篇文章我们了解另一种更高效也是目前被广泛应用的校验证书有效性的另一种方式–OCSP,并且我会结合java来聊聊如何获取OCSP地址以及如何去通过获取的OCSP url去获取ocsp结果
OCSP 协议的工作方式如下:
相比于传统的证书撤销列表(CRL),OCSP 的优势在于它能够提供更快的响应时间和更精确的证书状态信息。因为 CRL 需要定期更新,并且可能很大,所以使用 OCSP 可以避免这些问题。
值得一提的是,OCSP 也有一些缺点,如可能存在安全和隐私方面的问题,因为使用 OCSP 需要向 CA 公开某个特定证书的信息。此外,如果 OCSP 服务器无法响应,则客户端可能无法验证证书的有效性。
在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 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 序列。
这里介绍一下什么是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) {
//获取accessMethod
ASN1ObjectIdentifier id = (ASN1ObjectIdentifier)AccessDescription.getObjectAt(0);
if (SecurityIDs.ID_OCSP.equals(id.getId())) {
//如果是OCSP类型,则获取accessLocation
ASN1Primitive 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/dYZlAqeyr4jlLWHFfVUpHKu/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了,那么接下来就来看看如何通过ocsp url获取ocsp响应结果,步骤如下
1. 生成ocsp请求OCSPReq
我们需要的内容有颁发者证书(根证书),待验证证书的序列号
步骤如下:
①创建CertificateID
首先根据输入的颁发者证书和待查询证书的序列号(serialNumber),生成代表该证书的唯一标识——CertificateID。,代码如下:
// Generate the id for the certificate we are looking for
CertificateID id = new CertificateID(new JcaDigestCalculatorProviderBuilder().build().get(CertificateID.HASH_SHA1),
new JcaX509CertificateHolder(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 nonce
OCSPReqBuilder 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 BC
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
// Generate the id for the certificate we are looking for
CertificateID id = new CertificateID(new JcaDigestCalculatorProviderBuilder().build().get(CertificateID.HASH_SHA1),
new JcaX509CertificateHolder(issuerCert), serialNumber);
// basic request generation with nonce
OCSPReqBuilder 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 Response
InputStream 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 BC
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
// Generate the id for the certificate we are looking for
CertificateID id = new CertificateID(new JcaDigestCalculatorProviderBuilder().build().get(CertificateID.HASH_SHA1),
new JcaX509CertificateHolder(issuerCert), serialNumber);
// basic request generation with nonce
OCSPReqBuilder 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 Response
InputStream 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/dYZlAqeyr4jlLWHFfVUpHKu/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; //获取到BasicOCSPResp
try {
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("吊销校验异常");
}
}
}
}