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

news2024/11/24 5:23:40

一、前言

上篇文章我们了解了根证书和校验证书有效性中的一个比较重要的渠道–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) {
	        	//获取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/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 for
		CertificateID 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 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/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;  //获取到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("吊销校验异常");
				}
			}
		}
	}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/418089.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

4年测试工程师经历,下一步转开发还是继续测试?

​测试四年&#xff0c;没有积累编程脚本能力和自动化经验&#xff0c;找工作时都要求语言能力&#xff0c;自动化框架。感觉开发同事积累的经历容易找工作。下一步&#xff0c;想办法转开发岗还是继续测试&#xff1f;&#xff1f;&#xff1f;正常情况下&#xff0c;有了四年…

matlab图像处理系列:图片圈数识别+编号标记位置

matlab图像处理系列&#xff1a;图片圈数识别编号标记位置一、app界面介绍二、实现过程step1图像二值化step2 图像close 做差step3 像素阈值处理step4 清除小区域step5 识别联通区域 并在原图上标记三、项目分享一、app界面介绍 读取图片按钮&#xff1a;使用ui交互工具&#x…

单词管理系统【GUI/Swing+MySQL】(Java课设)

系统类型 Swing窗口类型Mysql数据库存储数据 使用范围 适合作为Java课设&#xff01;&#xff01;&#xff01; 部署环境 jdk1.8Mysql8.0Idea或eclipsejdbc 运行效果 本系统源码地址&#xff1a;https://download.csdn.net/download/qq_50954361/87682568 更多系统资源库…

Prometheus - Grafana 监控 MysqlD Linux服务器 详细版

readme 其实上一篇文章已经把如何操作&#xff0c;基本上是写全了&#xff08;因为是从本文精炼出来的&#xff09;本文可能更多的是补充一些关于 Prometheus 和 Grafana 的理论 &#xff0c;关系等。 操作 标签&#xff1a;是必看&#xff0c;要操作的。 非必要看 标签&…

DP7416国产192K数字音频接收芯片兼容替代CS8416

目录192K 数字音频应用DP7416简介芯片特性192K 数字音频应用 采样率192khz&#xff0c;能将192,000hz以下的频率都录下来&#xff0c;而且对声波每秒连续采样192,000次。在回放的时候&#xff0c;这192,000个采样点按顺序播放&#xff0c;从而还原原来的声音。   过采样技术除…

微信小程序开发 | 综合项目-点餐系统

综合项目-点餐系统8.1 开发前准备8.1.1 项目展示8.1.2 项目分析8.1.3 项目初始化8.1.4 封装网络请求8.2 【任务1】商家首页8.2.1 任务分析8.2.2 焦点图切换8.2.3 中间区域单击跳转到菜单列表8.2.4 底部商品展示8.3 【任务2】菜单列表8.3.1 任务分析8.3.2 折扣信息区8.3.3 设计菜…

关系数据库及其设计

目录 一、关系数据库 二、关系数据库设计 1、需求分析 2&#xff0e;概念结构设计 3&#xff0e;逻辑结构设计 4&#xff0e;数据库表的优化与规范化 5、规范化的大学数据库 6、数据库中表间关系 三、关系数据库的完整性 1&#xff0e;实体完整性约束&#xff08;PRI…

51电动车报警器.md

1.项目接线 接线示意图和实物图 示意图&#xff1a; 实物图&#xff1a; 信号传输路线 路线1&#xff1a; 433遥控信号 ——> 433接收模块D0引脚以及D1引脚 ——> 单片机P1^2引脚以及P1^3引脚 ——> 单片机P1^1引脚 ——> 继电器IN引脚 ——> 继电器COM口和NO口…

安全防御 --- 防火墙高可靠技术

防火墙高可靠技术&#xff08;双机热备&#xff09; VRRP&#xff1a;负责的单个接口的故障检测和流量引导。每个VRRP备份组拥有一个虚拟的IP地址&#xff0c;作为网络的网关地址&#xff1b;在VRRP主备倒换时通过发送免费的ARP来刷新对接设备的MAC地址转发表来引导流量。VGMP&…

SSVEP解码算法 - 多变量同步指数(MSI)

1 算法来源 该算法来自电子科技大学张杨松博士,针对该算法的计算在张博士的博士论文中有详细介绍,有兴趣的读者可以下载阅读,本文重点在对该方法的代码实现。Zhang, Yangsong, et al. “Multivariate synchronization index for frequency recognition of SSVEP-based brain…

Mysql 8 VS Mariadb 10.6 他们有多不一样 (声译)

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…

获取List集合中的最大值和最小值

实现获取List集合中的最大值和最小值共有以下两种方式&#xff0c;根据自己的场景选择合适的方法。 目录 Collections 1、String、Integer、Double类型 2、对象类型 排序 1、Integer类型 2、 基本排序方式&#xff1a; Collections 1、String、Integer、Double类型 当…

如何使用Edge Copilot

在ChatGPT红得发紫年代&#xff0c;也应该了解下微软microsoft edge浏览器中的Copilot。 通过这个Copilot - 副驾驶&#xff0c;Edge浏览器发生了革命性变化。 借助AI副驾驶强大的功能&#xff0c;在Edge浏览器中可以轻松完成AI聊天、AI写作、AI网页分析&#xff0c;和AI绘图…

【C++要笑着学】搜索二叉树 (SBTree) | K 模型 | KV 模型

C 表情包趣味教程 &#x1f449; 《C要笑着学》 &#x1f4ad; 写在前面&#xff1a;半年没更 C 专栏了&#xff0c;上一次更新还是去年九月份&#xff0c;被朋友催更很久了hhh 本章倒回数据结构专栏去讲解搜索二叉树&#xff0c;主要原因是讲解 map 和 set 的特性需要二叉搜索…

RPC框架一,RMI远程调用实例

RPC框架一&#xff0c;RMI远程调用实例 网上找了好久关于RMI调用的实例&#xff0c;大多都是本地调用的&#xff0c;远程调用的示例很少&#xff0c;所以自己整理一版。 首先 从server端开始&#xff1a; 服务端############### 具体步骤&#xff1a; 1&#xff0c;写个RM…

【从零学Python基础】Python中的条件判断与循环

文章目录条件语句语法格式缩进和代码块空语句pass循环语句while循环for循环continue与break条件语句 条件语句能够表达如果...则...否则...这样的语义&#xff0c;这即是计算机基础中的逻辑判定&#xff0c;条件语句也叫分支语句 如果 我好好学习&#xff1a;   我一定会找到…

wav2lip:Accurately Lip-syncing Videos In The Wild

飞桨AI Studio - 人工智能学习与实训社区集开放数据、开源算法、免费算力三位一体&#xff0c;为开发者提供高效学习和开发环境、高价值高奖金竞赛项目&#xff0c;支撑高校老师轻松实现AI教学&#xff0c;并助力开发者学习交流&#xff0c;加速落地AI业务场景https://aistudio…

CUDA编程基础与Triton模型部署实践

作者&#xff1a;王辉 阿里智能互联工程技术团队 近年来人工智能发展迅速&#xff0c;模型参数量随着模型功能的增长而快速增加&#xff0c;对模型推理的计算性能提出了更高的要求&#xff0c;GPU作为一种可以执行高度并行任务的处理器&#xff0c;非常适用于神经网络的推理计算…

电脑有自带的录屏功能吗?电脑录屏如何录人脸

案例&#xff1a;所有电脑都有自带的录屏功能吗&#xff1f; “在网上了解到电脑有录屏功能&#xff0c;但是我在我的电脑上又找不到。想问问小伙伴们是所有的电脑都有自带的录屏功能吗&#xff1f;怎样才能找到电脑自带的录屏功能&#xff1f;” 在日常使用电脑时&#xff0…

在 Visual Studio 中设置指针星号的位置

作为一个完美主义者&#xff0c;如果写出来的代码&#xff0c;让自己感觉到不那么舒服&#xff0c;你需要好好研究研究&#xff0c;如何解决这个问题。 在写代码的过程中&#xff0c;我碰到了这样的一个小问题。 一直以来&#xff0c;我对指针的星号的位置比较敏感&#xff0…