一般验证数字证书我们一般分为验证其是否在有效期、是否被吊销以及是否由上级CA签发。但在浏览器的实现中,相对会比较繁琐,验证流程也复杂,因为证书也有不同等级和不同方式加密的。
一、X.509证书
x.509是现行的数字证书标准,几乎绝大多数数字证书使用,且https中的tls(ssl)验证的证书就是使用的就是x.509证书。
x.509证书格式
X.509证书现在已经到了第三版本,每下一个版本字段都会有增加,一增强其安全性。
- v1
字段 | 描述 |
---|---|
版本 | 表示数字证书使用的X.509协议版本,目前可取1/2/3 |
证书序号 | CA产生的唯一整数 |
签名算法标识符 | 标识CA签名数字证书时使用的算法 |
签名者 | 生成、签名证书的CA的可区分名 |
有效期 | 数字证书的有效时间范围 |
主体名 | 数字证书所指实体的可区分名,除非v3中定义了替换名,否则此字段非空 |
主体公钥信息 | 主体的公钥与密钥相关的算法 |
- v2
字段 | 描述 |
---|---|
签发这唯一标识 | 在两个或者多个CA使用相同签发者名时标识CA |
主体唯一标识符 | 在两个或多个主体使用相同签发者名时标识CA |
- v3
字段 | 描述 |
---|---|
机构密钥标识符 | 单个证书机构可能有多个公/私钥对,定义该证书的签名使用哪个密钥对 |
主体密钥标识符 | 主体可能有多个公/私钥对,定义该证书的签名使用哪个密钥对 |
密钥用法 | 该公钥的操作范围 |
扩展密钥用法 | 可补充或替代密钥用法,指定改证书可采用哪些协议,包括TLS、客户端认证、服务器认证、时间戳等 |
私钥使用期 | 公钥、私钥不同的使用期限 |
证书策略 | 定义证书机构对某证书指定的策略和可选限定信息 |
证书映射 | 一个证书机构向另一个证书机构签发证书,指定认证的证书机构要遵循哪些策略 |
主体替换名 | 对证书的主体定义一个或多个替换名,若主证书格式中的主体名字段为空,改字段不能为空 |
签发者替换名 | 可选择定义证书签发者的一个或多个替换名 |
主体目录属性 | 主体的其他信息,如主体电话、电子邮件等 |
基本限制 | 表示该证书主体是否可作为证书机构 |
名称限制 | 指定名字空间 |
策略限制 | 只用于CA证书 |
二、chromium浏览器实现
浏览器和操作系统往往有一个预先定义好的列表作为信任CA列表,那么验证的时候,如果能验证证书是被这些CA签发的并且没有被吊销且在有效期内,则可以认为是有效的证书。
chromium使用的是操作系统的证书库,即windows和mac或ios有自己的根证书库来验证数字证书,而unix或linux则会直接使用nss或者openssl。
1 使用黑名单检查证书,即证书已被吊销,便返回
if (IsBlacklisted(cert)) {
verify_result->cert_status |= CERT_STATUS_REVOKED;
return ERR_CERT_REVOKED;
}
IsBlacklisted函数使用被吊销证书列表检查该证书的serial number(序列号)和主体名(subject->common_name)两个字段来确定证书是否有效。
2 通过底层库检查证书
if (flags & CertVerifier::VERIFY_EV_CERT)
flags |= CertVerifier::VERIFY_REV_CHECKING_ENABLED_EV_ONLY;
// check EV cert
int rv = VerifyInternal(cert, hostname, ocsp_response, flags, crl_set,
additional_trust_anchors, verify_result);
VerifyInternal函数将取决于使用的底层库。在windows中这个函数会创建证书链来验证证书是否有效,从签署证书的信任根CA一直到被验证证书。
// Build and validate certificate chain.
CERT_CHAIN_PARA chain_para;
memset(&chain_para, 0, sizeof(chain_para));
chain_para.cbSize = sizeof(chain_para);
(1). 获取证书密钥用法,并设置检验方式
// ExtendedKeyUsage.
// We still need to request szOID_SERVER_GATED_CRYPTO and szOID_SGC_NETSCAPE
// today because some certificate chains need them. IE also requests these
// two usages.
static const LPCSTR usage[] = {
szOID_PKIX_KP_SERVER_AUTH,
szOID_SERVER_GATED_CRYPTO,
szOID_SGC_NETSCAPE
};
chain_para.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR;
chain_para.RequestedUsage.Usage.cUsageIdentifier = arraysize(usage);
chain_para.RequestedUsage.Usage.rgpszUsageIdentifier =
const_cast(usage);
// Get the certificatePolicies extension of the certificate.
scoped_ptr policies_info;
LPSTR ev_policy_oid = NULL;
if (flags & CertVerifier::VERIFY_EV_CERT) {
GetCertPoliciesInfo(cert_handle, &policies_info);
if (policies_info.get()) {
EVRootCAMetadata* metadata = EVRootCAMetadata::GetInstance();
for (DWORD i = 0; i < policies_info->cPolicyInfo; ++i) {
LPSTR policy_oid = policies_info->rgPolicyInfo[i].pszPolicyIdentifier;
if (metadata->IsEVPolicyOID(policy_oid)) {
ev_policy_oid = policy_oid;
chain_para.RequestedIssuancePolicy.dwType = USAGE_MATCH_TYPE_AND;
chain_para.RequestedIssuancePolicy.Usage.cUsageIdentifier = 1;
chain_para.RequestedIssuancePolicy.Usage.rgpszUsageIdentifier =
&ev_policy_oid;
break;
}
}
}
}
普通证书秘钥用法类型使用OR,满足其一即可。如果是EV证书(扩展验证模式),则需要严格验证,秘钥用法类型必须用AND类型,即必须满足所有的秘钥用法策略。
(2). 生成证书链
PCCERT_CHAIN_CONTEXT chain_context;
// IE passes a non-NULL pTime argument that specifies the current system
// time. IE passes CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT as the
// chain_flags argument.
if (!CertGetCertificateChain(
chain_engine,
cert_list.get(),
NULL, // current system time
cert_list->hCertStore,
&chain_para,
chain_flags,
NULL, // reserved
&chain_context)) {
verify_result->cert_status |= CERT_STATUS_INVALID;
return MapSecurityError(GetLastError());
}
如果不能生成证书链,则直接返回失败,验证失败。证书过期、签名验证等在RFC3280和RFC5280中定义的检查将会在此处进行,如果证书由问题,将不能生成证书链。
(3).使用证书吊销列表检查证书是否被吊销
CRLSetResult crl_set_result = kCRLSetUnknown;
if (crl_set)
crl_set_result = CheckRevocationWithCRLSet(chain_context, crl_set);
if (crl_set_result == kCRLSetRevoked) {
verify_result->cert_status |= CERT_STATUS_REVOKED;
} else if (crl_set_result == kCRLSetUnknown &&
(flags & CertVerifier::VERIFY_REV_CHECKING_ENABLED_EV_ONLY) &&
!rev_checking_enabled &&
ev_policy_oid != NULL) {
// We don't have fresh information about this chain from the CRLSet and
// it's probably an EV certificate. Retry with online revocation checking.
rev_checking_enabled = true;
chain_flags &= ~CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY;
verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED;
CertFreeCertificateChain(chain_context);
if (!CertGetCertificateChain(
chain_engine,
cert_list.get(),
NULL, // current system time
cert_list->hCertStore,
&chain_para,
chain_flags,
NULL, // reserved
&chain_context)) {
verify_result->cert_status |= CERT_STATUS_INVALID;
return MapSecurityError(GetLastError());
}
}
CheckRevocationWithCRLSet函数将证书链从根证书遍历到尾(被验证证书),检查每个证书的序列号的公钥信息是否被吊销。
(4). 如果密钥用法与增强密钥用法不匹配,则将密钥用法置空。
if (chain_context->TrustStatus.dwErrorStatus &
CERT_TRUST_IS_NOT_VALID_FOR_USAGE) {
ev_policy_oid = NULL;
chain_para.RequestedIssuancePolicy.Usage.cUsageIdentifier = 0;
chain_para.RequestedIssuancePolicy.Usage.rgpszUsageIdentifier = NULL;
CertFreeCertificateChain(chain_context);
if (!CertGetCertificateChain(
chain_engine,
cert_list.get(),
NULL, // current system time
cert_list->hCertStore,
&chain_para,
chain_flags,
NULL, // reserved
&chain_context)) {
verify_result->cert_status |= CERT_STATUS_INVALID;
return MapSecurityError(GetLastError());
}
}
(5).证书链中证书是否脱机或过期。
CertVerifyResult temp_verify_result = *verify_result;
GetCertChainInfo(chain_context, verify_result);
if (!verify_result->is_issued_by_known_root &&
(flags & CertVerifier::VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS)) {
*verify_result = temp_verify_result;
rev_checking_enabled = true;
verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED;
chain_flags &= ~CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY;
CertFreeCertificateChain(chain_context);
if (!CertGetCertificateChain(
chain_engine,
cert_list.get(),
NULL, // current system time
cert_list->hCertStore,
&chain_para,
chain_flags,
NULL, // reserved
&chain_context)) {
verify_result->cert_status |= CERT_STATUS_INVALID;
return MapSecurityError(GetLastError());
}
GetCertChainInfo(chain_context, verify_result);
if (chain_context->TrustStatus.dwErrorStatus &
CERT_TRUST_IS_OFFLINE_REVOCATION) {
verify_result->cert_status |= CERT_STATUS_REVOKED;
}
}
(6). 验证主体名称中common_name字段是否是空字符。
// Flag certificates that have a Subject common name with a NULL character.
if (CertSubjectCommonNameHasNull(cert_handle))
verify_result->cert_status |= CERT_STATUS_INVALID;
(7). 验证策略。
if (!CertVerifyCertificateChainPolicy(
CERT_CHAIN_POLICY_SSL,
chain_context,
&policy_para,
&policy_status)) {
return MapSecurityError(GetLastError());
}
if (policy_status.dwError) {
verify_result->cert_status |= MapNetErrorToCertStatus(
MapSecurityError(policy_status.dwError));
}
windows的CertVerifyCertificateChainPolicy函数检查证书链中的策略信息。
(8).验证主机名与本证书是否匹配
// Perform hostname verification independent of
// CertVerifyCertificateChainPolicy.
if (!cert->VerifyNameMatch(hostname,
&verify_result->common_name_fallback_used)) {
verify_result->cert_status |= CERT_STATUS_COMMON_NAME_INVALID;
}
VerifyNameMatch函数验证提交证书的hostname是否与证书中的subject字段的common_name,dns_names,ip_addrs所匹配。
(9). 验证扩展验证证书。
if (ev_policy_oid &&
CheckEV(chain_context, rev_checking_enabled, ev_policy_oid)) {
verify_result->cert_status |= CERT_STATUS_IS_EV;
}
CheckEV函数通过检查证书中的策略限制(Extension->certificate policies)来检查EV证书。
3 检查公钥是否被禁用
if (IsPublicKeyBlacklisted(verify_result->public_key_hashes)) {
verify_result->cert_status |= CERT_STATUS_REVOKED;
rv = MapCertStatusToNetError(verify_result->cert_status);
}
在第2步中会将公钥取出来,在这一步中验证是否有公钥被禁用。
4 检查common_name, dns_name是否被限制
std::vector dns_names, ip_addrs;
cert->GetSubjectAltName(&dns_names, &ip_addrs);
if (HasNameConstraintsViolation(verify_result->public_key_hashes,
cert->subject().common_name,
dns_names,
ip_addrs)) {
verify_result->cert_status |= CERT_STATUS_NAME_CONSTRAINT_VIOLATION;
rv = MapCertStatusToNetError(verify_result->cert_status);
}
5 检查是否为可信CA签发
if (IsNonWhitelistedCertificate(*verify_result->verified_cert,
verify_result->public_key_hashes)) {
verify_result->cert_status |= CERT_STATUS_AUTHORITY_INVALID;
rv = MapCertStatusToNetError(verify_result->cert_status);
}
6 检查证书链中的弱密钥
// Check for weak keys in the entire verified chain.
bool weak_key = ExaminePublicKeys(verify_result->verified_cert,
verify_result->is_issued_by_known_root);
if (weak_key) {
verify_result->cert_status |= CERT_STATUS_WEAK_KEY;
// Avoid replacing a more serious error, such as an OS/library failure,
// by ensuring that if verification failed, it failed with a certificate
// error.
if (rv == OK || IsCertificateError(rv))
rv = MapCertStatusToNetError(verify_result->cert_status);
}
7 证书签名算法检查
// Treat certificates signed using broken signature algorithms as invalid.
if (verify_result->has_md2 || verify_result->has_md4) {
verify_result->cert_status |= CERT_STATUS_INVALID;
rv = MapCertStatusToNetError(verify_result->cert_status);
}
// Flag certificates using weak signature algorithms.
if (verify_result->has_md5) {
verify_result->cert_status |= CERT_STATUS_WEAK_SIGNATURE_ALGORITHM;
// Avoid replacing a more serious error, such as an OS/library failure,
// by ensuring that if verification failed, it failed with a certificate
// error.
if (rv == OK || IsCertificateError(rv))
rv = MapCertStatusToNetError(verify_result->cert_status);
}
if (verify_result->has_sha1)
verify_result->cert_status |= CERT_STATUS_SHA1_SIGNATURE_PRESENT;
8 检查hostname是否唯一
if (verify_result->is_issued_by_known_root && IsHostnameNonUnique(hostname)) {
verify_result->cert_status |= CERT_STATUS_NON_UNIQUE_NAME;
// CERT_STATUS_NON_UNIQUE_NAME will eventually become a hard error. For
// now treat it as a warning and do not map it to an error return value.
}
9 检查证书有效期是否太长
if (verify_result->is_issued_by_known_root && HasTooLongValidity(*cert)) {
verify_result->cert_status |= CERT_STATUS_VALIDITY_TOO_LONG;
if (rv == OK)
rv = MapCertStatusToNetError(verify_result->cert_status);
}