一、前言
上篇文章我们主要了解了PKI中的数字证书和PKCS,这篇文章我们主要了解一下根证书,以及OCSP和CRL。
二、根证书
- 在公钥基础设施(PKI)中,根证书是 整个数字证书体系的最高级别证书 。根证书由受信任的第三方机构(CA)颁发,用于验证其他证书以及建立信任链。
- 当您收到一个数字证书时,您可以跟踪证书链来验证该证书是否有效。证书链是一组数字证书,其中包括一个或多个中间证书和一个根证书。每个证书都由其颁发机构签名,并包含颁发机构的公钥。
- 通过跟踪证书链,您可以验证目标证书的所有者身份以及该证书是否受信任。 证书链中的最后一个证书是根证书,它是唯一一个不需要由其他证书签名的证书 。根证书只能由被认可的CA机构颁发,并且默认情况下存储在浏览器、操作系统和应用程序的信任存储库中。
- 使用根证书,您可以验证中间证书和服务器证书的有效性。如果您信任根证书所属的CA机构,则可以信任整个证书链中的所有证书。
- 总之,在PKI中,根证书是信任链中的顶级证书。它们用于验证其他证书并建立信任关系,因此它们对于确保安全通信至关重要。
2.1、证书链
- 证书链(Certificate Chain)是由数字证书构成的一系列链接, 其中每一个数字证书都被另一个数字证书所签名认证。这些数字证书中最顶层的证书称为“根证书(Root Certificate)”,并且没有上级证书对其进行认证。它是由数字证书颁发机构(CA)签署的自己的公钥。根证书可以用于验证其他数字证书的有效性 。
- 当客户端访问一个使用SSL/TLS协议进行加密通信的网站时,它将会收到该网站的数字证书。该数字证书中包含了该网站的公钥和其它信息。客户端通过验证该数字证书的有效性,来确保与该网站进行的通信是安全可靠的。如果该数字证书无效或被篡改,则客户端将不信任该证书,并且停止与该网站进行通信。
- 在验证数字证书的有效性时, 客户端会遍历证书链上的所有数字证书,并且检查每个数字证书是否是由另一个数字证书所签名认证的。如果证书链上存在任何一个数字证书验证失败,则整个数字证书链都会被视为无效的 。并且, 客户端需要使用与之匹配的根证书来检查数字证书链的完整性和可信度。如果根证书无效或被篡改,则整个数字证书链将被视为不可信,并且通信将被终止 。
- 因此,证书链是确保数字证书有效性的重要组成部分,它可以帮助保护用户数据的安全和隐私。
2.2、CRL
2.2.1、CRL概念
- CRL代表证书撤销列表(Certificate Revocation List),它是一种用于检查数字证书是否已被吊销的机制 。
- CRL通常由证书颁发机构(CA)创建,其中包含已吊销数字证书的序列号 。数字证书可能会被吊销,例如在证书所有者私钥泄露或证书信息被篡改时,CA可能会吊销该证书。当客户端接收到数字证书时,可以通过下载和验证与该数字证书相关的CRL来确认其是否已被吊销。
- 使用CRL进行数字证书撤销检查时,客户端必须从CA获取最新的CRL,并且定期地更新本地的CRL。客户端可以通过验证数字证书的序列号是否出现在CRL中来检查数字证书的有效性 。
- CRL有一些限制,例如CA必须定期创建和发布新的CRL,否则数字证书的吊销状态将无法得到及时更新。另外,大型CRL文件可能会导致网络延迟和性能问题。
- 随着OCSP(Online Certificate Status Protocol)的出现,越来越多的系统开始采用OCSP来替代CRL进行数字证书状态检查 。相比于CRL,OCSP更加灵活高效,并且不需要下载整个CRL文件即可完成数字证书状态检查。
2.2.2、CRL在java中的获取
在java中,我们可以通过证书的X509形式类X509Certificate去获取CRL地址,步骤如下
- 获取证书中的扩展信息:
X509Certificate cert = ... // 从某处获取证书对象
byte[] crlDistributionPointsExtension = cert.getExtensionValue("2.5.29.31");
其中2.5.29.31是 X.509标准中定义的证书扩展之一 ,也称为CRL Distribution Points扩展。该扩展用于
指定证书撤销列表(CRL)的位置 ,以便验证人员可以在验证证书时检查该证书是否已被撤销。CRL Distribution
Points扩展包含一个或多个分发点,每个分发点都包含一个或多个CRL地址,其中可以获取到对应证书的CRL列表。java中也可以通过以下方法获取,同样也是2.5.29.31
X509Extensions.CRLDistributionPoints.getId()
- 解码扩展信息,首先创建一个新的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();
3.从CRL分发点扩展中获取CRL地址.
①首先通过之前的ASN1Primitive对象获取CRLDistPoint,CRLDistPoint是X.509证书中的一个扩展字段,它用于指定可撤销证书列表(CRL)的分发点。
②接着通过CRLDistPoint类中的getDistributionPoints()方法用于获取CRL分发点。具体来说,它返回一个DistributionPoint数组,其中包含了所有在CRLDistPoint扩展字段中找到的CRL分发点。
③然后通过DistributionPoint数组作增强for循环,对每一个CRL分发点进行操作,首先DistributionPoint取出DistributionPointName
DistributionPointName是Java中表示CRL分发点名称的类。它用于表示CRLDistPoint扩展字段中的一个完整的CRL分发点。
CRL分发点可以是以下三种类型之一:
名称(Name):这种类型的CRL分发点包含了CRL的发行者的名称,例如"CN=Example Root CA, O=Example Inc.,
C=US"。
完全限定名(Full Name):这种类型的CRL分发点包含了CRL的URI地址,例如"http://example.com/crl.crl"。
相对名称(Relative Name):这种类型的CRL分发点包含了相对于证书颁发机构的位置的路径,例如"crl/root-ca.crl"。
DistributionPointName类提供了几个方法来获取和设置CRL分发点名称的类型和值。其中最常用的方法是getName()
,它返回一个GeneralName对象,可以通过该对象来访问具体的CRL分发点值;然后就是getType()
,它返回一个int类型的type,含义是CRL分发点类型。如果CRL分发点类型为"名称"或者"完全限定名",那么GeneralName对象将被转换为GeneralNames对象,从而可以访问其中的所有名称;如果CRL分发点类型为"相对名称",那么GeneralName对象将被转换为RelativeDistinguishedName对象,从而可以访问具体的相对路径。
④那么我们这边只需要取type为完全限定名类型的分发点即可,因为我们的目的是为了获取URI地址,取出对应的完全限定名类型的分发点后,通过getName()并且强转成GeneralNames,然后通过getNames()返回一个由
GeneralName 对象组成的数组,所有通用名称。
⑤最后通过每一个通用名称去进行筛选,把符合条件的取出即是我们最后的CRL地址
我们这边通过2个条件去进行筛选
1.GeneralName.getTagNo()是否为GeneralName.uniformResourceIdentifier
GeneralName.getTagNo() 是 org.bouncycastle.asn1.x509.GeneralName
类中的一个方法,用于获取通用名称类型的标记号。通用名称是 X.509 标准中定义的一种标识实体的方式,例如主体名称、主体替代名称或分发点名称等。
在 ASN.1 编码规范中,每个通用名称类型都有一个唯一的标记号来标识它。getTagNo() 方法返回当前通用名称对象的标记号。
GeneralName name = ... // 获取 GeneralName 对象
int tagNo = name.getTagNo();
switch (tagNo) {
case GeneralName.dNSName:
// 处理 DNS 名称
break;
case GeneralName.iPAddress:
// 处理 IP 地址
break;
case GeneralName.directoryName:
// 处理目录名称
break;
// 其他通用名称类型
}
而GeneralName.uniformResourceIdentifier表示 X.509
证书的通用名称类型之一,它表示一个统一资源标识符(URI)。URI 是一种标识某个资源的字符串,它可以指向任何类型的资源,例如 Web
页面、电子邮件地址、文件等。
通常情况下,URI 使用的是 URL(Uniform Resource Locator)或 URN(Uniform Resource Name)格式。
URL 定位到一个资源,并且可以直接访问该资源,而 URN 只是一个命名方式,不包含具体位置信息。
在 X.509证书中,URI 通常被用作 CRL 分发点名称(CRL Distribution Point Name)或颁发者名称(Issuer
Name)等标识信息的一部分。
所以我们判断我们的通用名称标记类型是否是URI,不是则直接去找下一个通用名称,是的话接着判断该URI是否是LDAP协议,我们这边只需要http协议的URI,所以不需要LDAP协议的,最终均满足条件的就是最终的CRL
URL。
获取CRL的URL代码如下:
CRLDistPoint dist = CRLDistPoint.getInstance(asn1Primitive);
DistributionPoint[] dists = dist.getDistributionPoints();
for (DistributionPoint p : dists) {
DistributionPointName distributionPointName = p.getDistributionPoint();
if (DistributionPointName.FULL_NAME != distributionPointName.getType()) {
continue;
}
GeneralNames generalNames = (GeneralNames)distributionPointName.getName();
GeneralName[] names = generalNames.getNames();
for (GeneralName name : names) {
if (name.getTagNo() != GeneralName.uniformResourceIdentifier) {
continue;
}
DERIA5String derStr = DERIA5String.getInstance((ASN1TaggedObject)name.toASN1Primitive(), false);
String result = derStr.getString();
// 区别于itext源码,不获取ldap协议地址
if (result.startsWith("ldap")) {
continue;
}
System.out.println(derStr);
}
}
完整获取CRI的例子如下:
public static void main(String[] args) throws CertificateException, IOException {
String rootCert = "MIIFrzCCBJegAwIBAgIQebTsANjzNmcwIHFPVk62NTANBgkqhkiG9w0BAQsFADAzMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxETAPBgNVBAMMCFNIRUNBIEcyMB4XDTE2MDkwNTE2MDAwMFoXDTE3MDkwNTE2MDAwMFowQzELMAkGA1UEBhMCQ04xIDAeBgkqhkiG9w0BCQEWEXBvc3RAcWl5dWVzdW8uY29tMRIwEAYDVQQDDAnlsJrkv67mmbowggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDjK48pdmxvXMwyzRiozXayAdSClELgslZvmmg0e58royRj3UrfijEHtSiWHchGRZn6YQrZe6sj5TSlQvNSUxFN/DCX1eFnDRzr9fuA5UEGiOeZQkMJFZej0v/sRzJmsRJEGWAcXXmPzRAp1/iTR4WYtf6TzOq7xiKmz23JbGgFdlFLMQxxVJSpRuJPV8TT5nOZtoVlBkVveIDPUMC+oxOKKstqYi+qPc3iaOXq8Q1fgLPE+mutuEpjep7fxL8e8DQx0ON2BIRicNdWzGY16oFfnxEQt8jf39dCLLjDfPwBa6VxGUc13HWfMMG/lDhJJXCx1S6EhCXXpJzKkzPZtvJrAgMBAAGjggKtMIICqTAfBgNVHSMEGDAWgBRWiN7jGEOCt3KkJutEqWLQh8SsJjAdBgNVHQ4EFgQUPVpnYHAUzuNerenuuN9DGbKX86EwCwYDVR0PBAQDAgbAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDBBBgNVHSAEOjA4MDYGCCqBHAHFOIEVMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly93d3cuc2hlY2EuY29tL3BvbGljeS8wCQYDVR0TBAIwADCB3AYDVR0fBIHUMIHRMIGXoIGUoIGRhoGObGRhcDovL2xkYXAyLnNoZWNhLmNvbTozODkvY249Q1JMMC5jcmwsb3U9UkEyMDE2MDkwNSxvdT1DQTMxLG91PWNybCxvPVVuaVRydXN0P2NlcnRpZmljYXRlUmV2b2NhdGlvbkxpc3Q/YmFzZT9vYmplY3RDbGFzcz1jUkxEaXN0cmlidXRpb25Qb2ludDA1oDOgMYYvaHR0cDovL2xkYXAyLnNoZWNhLmNvbS9DQTMxL1JBMjAxNjA5MDUvQ1JMMC5jcmwwfQYIKwYBBQUHAQEEcTBvMDUGCCsGAQUFBzABhilodHRwOi8vb2NzcDMuc2hlY2EuY29tL1NoZWNhZzIvc2hlY2Eub2NzcDA2BggrBgEFBQcwAoYqaHR0cDovL2xkYXAyLnNoZWNhLmNvbS9yb290L3NoZWNhZzJzdWIuZGVyMIGOBgYqgRwBxTgEgYMwgYAwSQYIKoEcAcU4gRAEPWxkYXA6Ly9sZGFwMi5zaGVjYS5jb20vb3U9c2hlY2EgY2VydGlmaWNhdGUgY2hhaW4sbz1zaGVjYS5jb20wEQYIKoEcAcU4gRMEBTY1Nzk5MCAGCCqBHAHFOIEUBBRTRjMyMDcyMTE5ODkwNzIzMDIxWDANBgkqhkiG9w0BAQsFAAOCAQEAADBN2mmMSYiKgNqgYVJtQ5KHmzP2smphBZurYRpU2sSffdXQTxtfh0g7L4klTHDSQEBa+/dNpt/g6oaMPxXwXMQA5IYWXNpcHjEPi66v4c/melPGWvWwsuQcyBC4y5uI3+tjh9kjQrU3JKaw161nSOJ86kX5bfQwPR5kyZEW15Xuuq3wRsJl8Gv6oriPenXALIFtj0JqI4PGDx7FqWzUZowxVlX38InJ9Z+YZ+UhGRpNBZ7j7yF0Y+ApTApKCnTkUPo0ST7kY9C8ripL3bOJXv6WWt+40WJirLLcJEs4YEQ864ca3beHDACbaueJJera1iMi726pfz8L5zCmRjGK7w==";
CertificateFactory cf = CertificateFactory.getInstance("X.509", new BouncyCastleProvider());
X509Certificate certificate = (X509Certificate) cf
.generateCertificate(new ByteArrayInputStream(Base64Utils.decode(rootCert)));
byte[] crlDistributionPointsExtension = certificate.getExtensionValue("2.5.29.31");
ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(crlDistributionPointsExtension));
ASN1OctetString octs = (ASN1OctetString) aIn.readObject();
aIn = new ASN1InputStream(new ByteArrayInputStream(octs.getOctets()));
ASN1Primitive asn1Primitive = aIn.readObject();
CRLDistPoint dist = CRLDistPoint.getInstance(asn1Primitive);
DistributionPoint[] dists = dist.getDistributionPoints();
for (DistributionPoint p : dists) {
DistributionPointName distributionPointName = p.getDistributionPoint();
if (DistributionPointName.FULL_NAME != distributionPointName.getType()) {
continue;
}
GeneralNames generalNames = (GeneralNames)distributionPointName.getName();
GeneralName[] names = generalNames.getNames();
for (GeneralName name : names) {
if (name.getTagNo() != GeneralName.uniformResourceIdentifier) {
continue;
}
DERIA5String derStr = DERIA5String.getInstance((ASN1TaggedObject)name.toASN1Primitive(), false);
String result = derStr.getString();
// 区别于itext源码,不获取ldap协议地址
if (result.startsWith("ldap")) {
continue;
}
System.out.println(derStr);
}
}
}
发现可以获取成功
然后我们可以将CRL URL转化为CRL类
public static CRL getCRL(String url) throws IOException, CertificateException, CRLException {
if (url == null) {
return null;
}
InputStream is = new URL(url).openStream();
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return cf.generateCRL(is);
}
调用其isRevoked()方法判断证书是否吊销,参数接收的是要验证的证书的X509形式类
crl.isRevoked(certificate)
2.2.3、CRL的缺点
CRL是撤销证书列表的缩写,它记录了已经被吊销的证书序列号或其他标识信息。CRL的缺点包括:
- 时间延迟:CRL需要不断更新,才能保持最新状态。如果某个证书在CRL中被吊销,但是CRL还没有被更新,那么该证书仍然可以继续使用一段时间。
- 网络带宽消耗:CRL包含了所有被吊销的证书信息,因此它的大小可能非常大。客户端需要下载完整的CRL才能验证一个证书的有效性,这会消耗很多网络带宽。
- 容易被篡改:CRL是由CA签名的,但是如果CA的私钥被泄漏或者被攻击,那么攻击者就可以伪造CRL并发布虚假的被吊销的证书信息。
- 无法立即响应吊销请求:如果某个证书被盗用或者存在其他安全问题,CA会立即吊销该证书并发布CRL。但是,由于CRL需要一定的时间来更新和传播,吊销操作的效果可能要滞后一段时间,从而给攻击者留下可乘之机。
为了克服这些缺点,现在一些颁发机构采用了基于在线协议的证书吊销列表(OCSP)来验证证书的有效性。OCSP是一种更加高效的方式,它只返回特定证书的状态,而不需要下载整个CRL。这个我们后面会详细了解。
最后
从时代发展的角度看,网络安全的知识是学不完的,而且以后要学的会更多,同学们要摆正心态,既然选择入门网络安全,就不能仅仅只是入门程度而已,能力越强机会才越多。
因为入门学习阶段知识点比较杂,所以我讲得比较笼统,大家如果有不懂的地方可以找我咨询,我保证知无不言言无不尽,需要相关资料也可以找我要,我的网盘里一大堆资料都在吃灰呢。
干货主要有:
①1000+CTF历届题库(主流和经典的应该都有了)
②CTF技术文档(最全中文版)
③项目源码(四五十个有趣且经典的练手项目及源码)
④ CTF大赛、web安全、渗透测试方面的视频(适合小白学习)
⑤ 网络安全学习路线图(告别不入流的学习)
⑥ CTF/渗透测试工具镜像文件大全
⑦ 2023密码学/隐身术/PWN技术手册大全
如果你对网络安全入门感兴趣,那么你需要的话可以点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!
扫码领取
![](https://i-blog.csdnimg.cn/blog_migrate/f8165a97290a891d5cb1f858d74feef8.jpeg)