Chromium https 数字证书验证

2017-01-06  本文已影响0人  驳斑

一般验证数字证书我们一般分为验证其是否在有效期、是否被吊销以及是否由上级CA签发。但在浏览器的实现中,相对会比较繁琐,验证流程也复杂,因为证书也有不同等级和不同方式加密的。

一、X.509证书

x.509是现行的数字证书标准,几乎绝大多数数字证书使用,且https中的tls(ssl)验证的证书就是使用的就是x.509证书。

x.509证书格式

X.509证书现在已经到了第三版本,每下一个版本字段都会有增加,一增强其安全性。

字段 描述
版本 表示数字证书使用的X.509协议版本,目前可取1/2/3
证书序号 CA产生的唯一整数
签名算法标识符 标识CA签名数字证书时使用的算法
签名者 生成、签名证书的CA的可区分名
有效期 数字证书的有效时间范围
主体名 数字证书所指实体的可区分名,除非v3中定义了替换名,否则此字段非空
主体公钥信息 主体的公钥与密钥相关的算法
字段 描述
签发这唯一标识 在两个或者多个CA使用相同签发者名时标识CA
主体唯一标识符 在两个或多个主体使用相同签发者名时标识CA
字段 描述
机构密钥标识符 单个证书机构可能有多个公/私钥对,定义该证书的签名使用哪个密钥对
主体密钥标识符 主体可能有多个公/私钥对,定义该证书的签名使用哪个密钥对
密钥用法 该公钥的操作范围
扩展密钥用法 可补充或替代密钥用法,指定改证书可采用哪些协议,包括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<LPSTR*>(usage);

  // Get the certificatePolicies extension of the certificate.
  scoped_ptr<CERT_POLICIES_INFO, base::FreeDeleter> 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());
    
}

如果不能生成证书链,则直接返回失败,验证失败。证书过期、签名验证等在RFC3280RFC5280中定义的检查将会在此处进行,如果证书由问题,将不能生成证书链。
(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<std::string> 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);
  }
上一篇下一篇

猜你喜欢

热点阅读