一、前言
上篇文章我們了解了根證書和校驗證書有效性中的一個比較重要的渠道–CRL,但是CRL有著時間延遲,網(wǎng)絡(luò)帶寬消耗等缺點,本篇文章我們了解另一種更高效也是目前被廣泛應(yīng)用的校驗證書有效性的另一種方式–OCSP,并且我會結(jié)合java來聊聊如何獲取OCSP地址以及如何去通過獲取的OCSP url去獲取ocsp結(jié)果
二、OCSP
2.1、OCSP概念
- OCSP(Online Certificate Status Protocol)是一種用于驗證數(shù)字證書有效性的協(xié)議。它允許一個客戶端向證書頒發(fā)機(jī)構(gòu)(CA)的OCSP服務(wù)器查詢某個特定證書是否被撤銷或者是否仍然有效。與傳統(tǒng)的證書撤銷列表(CRL)相比,OCSP 具有更快的響應(yīng)時間和更精確的證書狀態(tài)信息。OCSP協(xié)議的維護(hù)者通常是負(fù)責(zé)數(shù)字證書簽發(fā)和管理的組織或個人。例如CA機(jī)構(gòu)等。
- 具體來說,當(dāng)一個客戶端需要驗證某個證書的有效性時,它會向OCSP服務(wù)器發(fā)送一個查詢請求,詢問該證書的狀態(tài)。OCSP服務(wù)器會返回一個響應(yīng),其中包含有關(guān)該證書的信息,例如證書是否被撤銷,以及該證書是否還有效。
OCSP 協(xié)議的工作方式如下:
- 客戶端向 OCSP 服務(wù)器發(fā)送一個查詢請求,其中包含要驗證的證書的序列號。
- OCSP 服務(wù)器檢查該證書是否被撤銷或者是否仍然有效,并將其狀態(tài)返回給客戶端。
- 客戶端收到 OCSP 服務(wù)器的響應(yīng),根據(jù)響應(yīng)中的信息來確定證書的有效性。
相比于傳統(tǒng)的證書撤銷列表(CRL),OCSP 的優(yōu)勢在于它能夠提供更快的響應(yīng)時間和更精確的證書狀態(tài)信息。因為 CRL 需要定期更新,并且可能很大,所以使用 OCSP 可以避免這些問題。
值得一提的是,OCSP 也有一些缺點,如可能存在安全和隱私方面的問題,因為使用 OCSP 需要向 CA 公開某個特定證書的信息。此外,如果 OCSP 服務(wù)器無法響應(yīng),則客戶端可能無法驗證證書的有效性。
2.2、OCSP地址在java中的獲取
在java中,我們可以通過證書的X509形式類X509Certificate去獲取CRL地址,步驟如下,前兩步大家可以發(fā)現(xiàn)其實和獲取CRL地址代碼差不多。
- 獲取證書中的擴(kuò)展信息:
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標(biāo)準(zhǔn)中定義的證書擴(kuò)展之一,也稱為authorityInfoAccess擴(kuò)展。該擴(kuò)展用于指定證書頒發(fā)者(CA)的證書撤銷列表(CRL)位置和/或在線證書狀態(tài)協(xié)議(OCSP)驗證器地址等信息。這個擴(kuò)展字段允許使用者在驗證證書時獲取到關(guān)于該證書頒發(fā)者的更多信息,從而增加了證書驗證的安全性。,以便驗證人員可以在驗證證書時檢查該證書是否已被撤銷。java中也可以通過以下方法獲取,同樣也是1.3.6.1.5.5.7.1.1.所以大家感興趣的話可以用這個id去試一下獲取CRL地址,同樣也是可以獲取crl地址的,但是如果通過
Extension.authorityInfoAccess.getId()
- 解碼擴(kuò)展信息,首先創(chuàng)建一個新的ASN1InputStream對象,用于讀取傳遞的字節(jié)數(shù)組參數(shù)crlDistributionPointsExtension。 然后,將擴(kuò)展值包裝在一個ASN1OctetString對象中,這可以通過在ASN1InputStream對象上調(diào)用readObject()來獲取。接下來,使用ASN1OctetString對象的八位字節(jié)創(chuàng)建一個新的ASN1InputStream對象,并再次調(diào)用readObject()以獲取表示CRL Distribution Points擴(kuò)展的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,為什么還要多轉(zhuǎn)成一次ASN1OctetString呢。
我們實踐一下
通過上面忽略兩步操作后,發(fā)現(xiàn)報了類型轉(zhuǎn)化的異常,這個原因又是為什么呢
首先是我們獲取的證書中的擴(kuò)展信息,根據(jù) X.509 標(biāo)準(zhǔn),CRL 分發(fā)點擴(kuò)展是由一個 OCTET STRING 構(gòu)成的,其中包含了一個 ASN.1 序列。
如果 CRL 分發(fā)點擴(kuò)展由 OCTET STRING構(gòu)成,就像這個例子一樣,直接將 OCTET STRING 轉(zhuǎn)換為 ASN.1 序列(ASN1Sequence),則會拋出類型轉(zhuǎn)換異常。因為 ASN.1 編碼規(guī)范中定義,OCTET STRING 類型的數(shù)據(jù)是由一個長度和一個字節(jié)數(shù)組組成的。而 ASN.1 序列則是由一組有序的元素組成的,每個元素都有自己的標(biāo)識符和數(shù)據(jù)值。
所以我們的目的是為了從OCTET STRING中拿到ASN.1 序列,而不是直接將OCTET STRING轉(zhuǎn)化成序列
在第一次調(diào)用 ASN1InputStream.readObject()
方法時,返回的確實是一個 DEROctetString 對象。這是因為 ASN1InputStream.readObject()
方法會根據(jù)輸入流中的數(shù)據(jù)類型返回對應(yīng)的 ASN.1 原語對象。在這里,由于輸入流中的數(shù)據(jù)類型是 OCTET STRING,因此返回的就是一個 DEROctetString 對象。然后為了轉(zhuǎn)化成ASN1Sequence,我們將 OCTET STRING 中的字節(jié)流作為參數(shù)重新生成ASN1InputStream對象aIn。通過aIn.readObject()得到ASN.1原語對象,通過這樣就可以從 OCTET STRING 中提取出 ASN.1 序列。
- 用之前獲取的ASN1Primitive對象轉(zhuǎn)化成ASN1Sequence,該對象包含多個AccessDescription,遍歷每個AccessDescription,判斷其是否為OCSP協(xié)議類型,并獲取對應(yīng)的AccessLocation字符串。在獲取AccessLocation時,會跳過ldap協(xié)議地址。如果沒有找到符合條件的AccessDescription,則返回null。
這里介紹一下什么是AccessDescription
AccessDescription是一個ASN.1結(jié)構(gòu),用于描述數(shù)字證書中的訪問描述符信息。它通常用于在證書擴(kuò)展中傳遞OCSP(Online Certificate Status Protocol)或者CA Issuers的地址信息。AccessDescription本質(zhì)上是一個序列(Sequence),包含兩個元素:
1.accessMethod:用于指定AccessDescription的類型,例如OCSP或者CA Issuers等。
2.accessLocation:用于存儲對應(yīng)的訪問地址信息,可以是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);
// 區(qū)別于itext源碼,不獲取ldap協(xié)議地址
if (AccessLocation.startsWith("ldap")) {
continue;
}
if (AccessLocation == null) {
return "" ;
}
else {
return AccessLocation ;
}
}
}
}
我們debug可以看一下
在獲取數(shù)字證書中的OCSP URL時,AccessDescription就是包含OCSP地址信息的ASN.1結(jié)構(gòu)。具體而言,該字段的accessMethod值應(yīng)該為“1.3.6.1.5.5.7.48.1”,即OCSP協(xié)議類型的標(biāo)識符,而accessLocation則應(yīng)該是一個URI字符串,表示OCSP服務(wù)器的地址。
完整測試代碼如下:
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);
// 區(qū)別于itext源碼,不獲取ldap協(xié)議地址
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");
}
執(zhí)行后可以獲取OCSP URL
2.3、通過ocsp url去獲取ocsp結(jié)果響應(yīng)
通過上面的學(xué)習(xí)我們已經(jīng)可以成功獲取ocsp url了,那么接下來就來看看如何通過ocsp url獲取ocsp響應(yīng)結(jié)果,步驟如下
1. 生成ocsp請求OCSPReq
我們需要的內(nèi)容有頒發(fā)者證書(根證書),待驗證證書的序列號
步驟如下:
①創(chuàng)建CertificateID
首先根據(jù)輸入的頒發(fā)者證書和待查詢證書的序列號(serialNumber),生成代表該證書的唯一標(biāo)識——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);
在創(chuàng)建CertificateID時,首先獲取一個用于計算SHA-1 CertHash的JcaDigestCalculator
對象。在這里,先通過調(diào)用JcaDigestCalculatorProviderBuilder
的build()
方法獲取一個用于計算摘要的DigestCalculatorProvider
實例,然后再調(diào)用該實例的get()方法并傳入CertificateID.HASH_SHA1常量來獲取SHA-1算法的JcaDigestCalculator實例。
接著使用new JcaX509CertificateHolder(issuerCert)將頒發(fā)者證書轉(zhuǎn)換為Bouncy Castle庫中的X509CertificateHolder對象。
X509CertificateHolder是Bouncy Castle庫中用于表示X.509證書的一個重要類。它提供了許多有用的方法來獲取證書的各種屬性,例如:subject、issuer、Serial Number等等。通過將頒發(fā)者證書轉(zhuǎn)換為X509CertificateHolder對象,我們可以方便地從該對象中提取Issuer信息以構(gòu)建CertificateID。
在這個過程中,JcaX509CertificateHolder類是一種用于創(chuàng)建X509CertificateHolder對象的便捷方式,它實現(xiàn)了X509CertificateHolder接口并封裝了一個X.509證書。當(dāng)我們調(diào)用new JcaX509CertificateHolder(issuerCert)時,會創(chuàng)建一個新的JcaX509CertificateHolder對象,并使用issuerCert初始化該對象。最終返回的X509CertificateHolder對象包含有關(guān)頒發(fā)者證書的信息,可以用于創(chuàng)建CertificateID。
然后結(jié)合待驗證證書的序列號加上前面我們獲取的SHA-1算法的JcaDigestCalculator實例以及X509CertificateHolder就可以構(gòu)成CertificateID。
②創(chuàng)建OCSPReqBuilder
然后創(chuàng)建OCSPReqBuilder對象,并使用addRequest方法向請求中添加待查詢的證書信息,即上一步中生成的CertificateID。
// basic request generation with nonce
OCSPReqBuilder gen = new OCSPReqBuilder();
gen.addRequest(id);
③添加Nonce擴(kuò)展
為了防止重放攻擊,可以在OCSP請求中添加一個隨機(jī)數(shù)Nonce。這里使用BouncyCastle庫提供的id_pkix_ocsp_nonce擴(kuò)展來實現(xiàn),將隨機(jī)數(shù)作為DER編碼的OctetString類型數(shù)據(jù)加入到Nonce擴(kuò)展中。這個隨機(jī)數(shù)我們通過PdfEncryption.createDocumentId()
來獲取
在PDF文檔中,Nonce是一種用于生成加密密鑰的隨機(jī)數(shù)。為了確保生成的Nonce具有足夠的熵(即隨機(jī)性),應(yīng)該使用高質(zhì)量的隨機(jī)數(shù)生成器來生成它。在iText 7中,可以使用PdfEncryption.createDocumentId()方法來獲取一個具有足夠熵的隨機(jī)數(shù)作為Nonce,因為該方法使用了安全的隨機(jī)數(shù)生成器。
確保生成的Nonce是具有足夠熵的隨機(jī)數(shù)非常重要,因為如果Nonce不夠隨機(jī),則可能會出現(xiàn)加密弱點。攻擊者可能會利用這些弱點來破解加密并訪問被加密的內(nèi)容。因此,使用安全的隨機(jī)數(shù)生成器來生成Nonce是非常重要的,并且PdfEncryption.createDocumentId()方法提供了一種方便和可靠的方式來獲得這樣的隨機(jī)數(shù)。
Extension ext = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false,
new DEROctetString(new DEROctetString(PdfEncryption.createDocumentId()).getEncoded()));
gen.setRequestExtensions(new Extensions(new Extension[] { ext }));
最后,調(diào)用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. 創(chuàng)建并配置一個HTTP連接,并且配置連接,用于向指定的URL發(fā)送OCSP請求消息,并等待響應(yīng)消息。
byte[] array = request.getEncoded();
URL urlt = new URL(url);
HttpURLConnection con = (HttpURLConnection) urlt.openConnection();
con.setRequestProperty("Content-Type", "application/ocsp-request"); //設(shè)置HTTP請求頭屬性,表示請求消息體的格式是OCSP請求。
con.setRequestProperty("Accept", "application/ocsp-response"); //設(shè)置HTTP請求頭屬性,表示接受響應(yīng)消息體的格式是OCSP響應(yīng)。
con.setDoOutput(true); //設(shè)置HTTP連接可以輸出數(shù)據(jù)。
con.setConnectTimeout(3000);
con.setReadTimeout(5000);
3. 向已經(jīng)建立的HTTP連接發(fā)送數(shù)據(jù),并獲取響應(yīng)狀態(tài)碼(response code)和響應(yīng)結(jié)果
OutputStream out = con.getOutputStream(); //獲取輸出流,用于向服務(wù)器發(fā)送請求數(shù)據(jù)。
DataOutputStream dataOut = new DataOutputStream(new BufferedOutputStream(out)); //創(chuàng)建一個數(shù)據(jù)輸出流對象,用于將字節(jié)數(shù)組寫入到輸出流中。
dataOut.write(array); //將OCSP請求消息體數(shù)組(array)中的數(shù)據(jù)寫入到數(shù)據(jù)輸出流中。
dataOut.flush(); //刷新數(shù)據(jù)輸出流,將緩沖區(qū)中的數(shù)據(jù)推送到網(wǎng)絡(luò)中。
dataOut.close(); //關(guān)閉數(shù)據(jù)輸出流。
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));
完整獲取回復(fù)OCSP回復(fù)代碼如下:
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 校驗結(jié)果:0(吊銷),1(生效),2(ocsp校驗異常)
OCSPResp ocspResponse = null;
try {
ocspResponse =getOcspResponse(cert, root, ocspurl); //這個方法就是之前獲取ocspResponse的方法
} catch (Exception e) {
logger.warn(e.getMessage(), e);
return 2;
}
if (ocspResponse == null) {
logger.warn("未獲取到ocsp響應(yīng)");
return 2;
}
if (ocspResponse.getStatus() != OCSPResp.SUCCESSFUL) { //判斷此次連接返回的響應(yīng)結(jié)果
logger.warn("獲取ocsp響應(yīng)未成功");
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校驗結(jié)果:" + "ocsp.status.is.unknown");
return 2;
}
}
}
return 2;
這里可能大家對BasicOCSPResp不太熟悉
BasicOCSPResp是OCSP協(xié)議中的一個類,用于表示OCSP響應(yīng)消息體中的基本響應(yīng)消息。它包含了指定證書的響應(yīng)狀態(tài)信息,以及生成響應(yīng)的簽名等元數(shù)據(jù)。
在OCSP響應(yīng)消息體中,BasicOCSPResp是必須存在的。它的結(jié)構(gòu)如下:
ResponderID: 響應(yīng)者(OCSP服務(wù)器)的身份標(biāo)識。
ProducedAt: 響應(yīng)消息體生成的時間戳。
Responses: 包含了待驗證證書的相關(guān)信息和響應(yīng)狀態(tài)信息。
ResponseExtensions: 可選,包含了響應(yīng)消息的擴(kuò)展字段。
SignatureAlgorithmIdentifier: 簽名算法標(biāo)識符。
Signature: 對響應(yīng)消息體進(jìn)行數(shù)字簽名后的簽名值。
BasicOCSPResp類提供了一些方法來獲取響應(yīng)消息體的各個部分的內(nèi)容,如getResponderId()方法可以獲取響應(yīng)者的身份標(biāo)識,getProducedAt()方法可以獲取響應(yīng)消息生成的時間戳等。通過這些方法,可以對OCSP響應(yīng)消息體進(jìn)行解析和處理。
然后這里再解釋一下這里為什么要對
SingleResp[] responses = basicResponse.getResponses();
if (responses.length == 1) {
.....
}
在OCSP響應(yīng)消息中,BasicOCSPResp對象可以包含多個單個響應(yīng)(SingleResponse)對象,每個單個響應(yīng)對應(yīng)一個待校驗證書的OCSP響應(yīng)消息。在常規(guī)情況下,一個OCSP請求只需要校驗一個證書,因此BasicOCSPResp對象中只會包含一個單個響應(yīng)。
但是,由于網(wǎng)絡(luò)傳輸?shù)仍?,有時會發(fā)生OCSP響應(yīng)消息體格式錯誤或者損壞的情況,導(dǎo)致BasicOCSPResp對象中可能包含多個單個響應(yīng)或者不包含任何單個響應(yīng)。這種情況在實際工程中出現(xiàn)的概率較低,但仍然有可能發(fā)生。
為了避免出現(xiàn)這種情況,代碼中進(jìn)行了如下判斷:
SingleResp[] responses = basicResponse.getResponses();
獲取BasicOCSPResp對象中所有的單個響應(yīng)。
if (responses.length == 1) {...}
判斷單個響應(yīng)數(shù)量是否為1,如果不是,則表示OCSP響應(yīng)消息體格式錯誤或者損壞,無法進(jìn)行后續(xù)校驗操作,因此返回2表示校驗異常。
通過判斷單個響應(yīng)數(shù)量,可以確保OCSP響應(yīng)消息體格式正確且只包含一個單個響應(yīng)。
然后就可以通過剛才獲取的ocspUrl去驗證結(jié)果了。文章來源:http://www.zghlxwxcb.cn/news/detail-469887.html
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);
// 區(qū)別于itext源碼,不獲取ldap協(xié)議地址
if (AccessLocation.startsWith("ldap")) {
continue;
}
if (AccessLocation == null) {
System.out.println("");
} else {
System.out.println(AccessLocation);
}
}
}
}
OCSPResp ocspResponse = 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校驗結(jié)果:" + "ocsp.status.is.unknown");
System.out.println("吊銷校驗異常");
}
}
}
}
文章來源地址http://www.zghlxwxcb.cn/news/detail-469887.html
到了這里,關(guān)于數(shù)字證書的相關(guān)專業(yè)名詞(下)---OCSP及其java中的應(yīng)用的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!