美文网首页
Chromium https 数字证书验证

Chromium https 数字证书验证

作者: 驳斑 | 来源:发表于2017-01-06 18:44 被阅读0次

    一般验证数字证书我们一般分为验证其是否在有效期、是否被吊销以及是否由上级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<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);
      }
    

    相关文章

      网友评论

          本文标题:Chromium https 数字证书验证

          本文链接:https://www.haomeiwen.com/subject/tunwvttx.html