PKI安全框架之Certificate数字证书(一)
概要
Public Key Infrastructure(公开密钥基础架构)简称:PKI,是一套通过公钥密码算法原理与技术提供安全服务的具有通用性的安全基础设施,是能够为电子商务提供一套安全基础平台的技术规范。它通过数字证书管理公钥,通过CA把用户的公钥与其他标识信息捆绑在一起,实现互联网上的用户身份验证。PKI的基础技术包括加密、数字签名、数据完整性机制、数字信封、双重数字签名等。
密码学上,公开密钥基础建设借着数字证书认证机构(CA)将用户的个人身份跟公开密钥链接在一起。对每个证书中心用户的身份必须是确定的。链接关系通过注册和发布过程创建,取决于担保级别,链接关系可能由CA的各种软件或在人为监督下完成。PKI的确定链接关系的这一角色称为注册管理中心(Registration Authority,RA)。RA确保公开密钥和个人身份链接,可以防抵赖。可信赖的第三者(Trusted third party,TTP)也常被用来指证书中心。PKI有时被错误地拿来代表公开密钥密码学或公开密钥算法。
PKI可以看成一套庞大的服务体系,由于涵盖的内容广泛在此不再展开详述。感兴趣的朋友可以参考PKI架构图,主要内容如下:
- CA认证机构
- 证书签发、续签
- 证书吊销列表(CRL)
- 根、二级以及证书链(Chain)管理等
- 证书状态在线查询协议(OCSP)
- 证书吊销列表分发点(CDP)
- KMC密钥管理中心
- RA注册中心管理
- 证书的注册与审批
- 用户管理
- LDAP服务:用于证书发布、查询、下载服务
- 证书用途管理等
Certificate证书
Certificate称为数字证书。数字证书是一种证明身份的电子凭证,它包含一个公钥和一些身份信息,用于验证数字签名和加密通信。数字证书在网络通信、电子签名、认证授权等场景中都有广泛应用。其特征如下:
由权威机构颁发,人们可以在互联网上用来识别对方的身份。
证书遵循国际标准 ITUTX.509,主要包括下面的内容:
- 版本信息
- 序列号,每个证书都有唯一的序列号
- 使用的签名算法
- 签发机构(命名规则一般采用 X.500格式)
- 有效期
- 证书所有人的名称
- 证书所有人的公钥 public key
- 证书发行者的签名
证书等级
- Root Cartificate:根证书
根证书:签名(Root CA’s signature)是用根私钥(Root CA’s private key)签的。所以验证根证书签名(Root CA’s signature)要用根公钥(Root CA’s public key)才能验证通过。这种情况称为自签名(self-sign)。
- Intermediate Cartificate:中介证书
中介证书是被根证书签名的证书,是被根证书的私钥进行签名的,因此要验证中介证书也需要使用根公钥。
- End-entity Certificate:终端实体证书
终端实体证书是由中介证书签发的;使用中介证书对终端实体进行认证的目的是为了减少根证书签名的负担,相当于使用一个中介来代替根实行认证的权利。
证书链
在RFC5280中,证书链或信任链被定义为“证书路径”。换句话说,信任链是指您的SSL证书以及它如何链接回受信任的证书颁发机构。为了使SSL证书可信,它必须可以追溯到它被签署的信任根,这意味着链中的所有证书——终端实体证书、中间证书和根证书,都需要得到适当的信任。
证书工具
证书的管理工具有很多,下面列举几种常用的工具:
- NET SDK:X509Certificates
- PowerShell CLI:New-SelfSignedCertificate
- CMD CLI:Certutil
- X Certificate and Key management
- CMD CLI:OpenSSL
- JDK CLI:keytool
实际上证书工具只是CA认证机构对证书的制作和签发的一个子环节,由于篇幅关系本文仅以微软开发作为主题,介绍NET SDK以及New-SelfSignedCertificate、Certutil这前三者工具展开。在开始前,我们需要了解一下证书的基础构造:
NET8 for Cryptography.X509Certificates
下面以设计一个CA根证书签发两张终端实体证书为例,对服务端和用户端进行签发证书,以满足NET8 WebApi的双向验证需求。也许有人会问,为什么不是三级证书的签发方式,别急这里后文会强调说明。
构建证书主题(头)
/// <summary>
/// 构建X.500可分辨名称
/// </summary>
/// <param name="commonName">公共证书名称</param>
/// <returns></returns>
private X500DistinguishedName BuildX500DistinguishedName(string commonName)
{
var builder = new X500DistinguishedNameBuilder();
builder.AddEmailAddress("123@abc.cn");
builder.AddCommonName(commonName);
builder.AddOrganizationalUnitName("Project Team");
builder.AddOrganizationName("Company Name");
builder.AddLocalityName("Fs");
builder.AddStateOrProvinceName("GD");
builder.AddCountryOrRegion("CN");
return builder.Build();
}
构建证书的第一步就是构建证书的主题信息(俗称:证书头信息),就是给证书起一个公开的名称(commonName),其它的信息都是可选的,完全可以省略。
起初笔者一直被它给误导,以为公共名称就是终端证书上的可选域名或IP。实际上理解完全错了,简单点讲它就是一个任意不重复的命名(比如OpenSSL指出建议以证书创建的主机名来命名)后文会有详细指出。
构建根证书
/// <summary>
/// 生成根证书
/// </summary>
/// <param name="commonName">公共证书名称</param>
/// <param name="yearValidPeriod">年限</param>
/// <returns></returns>
public X509Certificate2 GenRootCertificate(string commonName, int yearValidPeriod)
{
// Generate private-public key pair
RSA rsaKey = RSA.Create(2048);
// Describe certificate
var rootX500DistinguishedName = BuildX500DistinguishedName(commonName);
// Create certificate request
var certificateRequest = new CertificateRequest(rootX500DistinguishedName, rsaKey,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
//基本约束(2.5.29.19)
certificateRequest.CertificateExtensions.Add(
new X509BasicConstraintsExtension(
certificateAuthority: true, //是否root CA证书
hasPathLengthConstraint: false, //允许的路径级别数是否有限制
pathLengthConstraint: 0, //证书路径允许的级别数
critical: true //是否关键扩展
)
);
//使用者密钥标识符(2.5.29.14)
var ski = new X509SubjectKeyIdentifierExtension(key: certificateRequest.PublicKey, critical: