为什么IE仅拒绝127.0.0.1的自签名本地主机证书,当Chrome接受它时?

时间:2014-07-26 13:33:59

标签: java internet-explorer ssl localhost x509certificate

我们的Java 7应用程序需要在localhost上侦听HTTPS请求。它必须接受https://localhost:8112https://127.0.0.1:8112上的连接。

为此,我们以编程方式构建了自动签名的X509v3证书,我们已在 Windows-ROOT 密钥库中安装此证书,如下所示:

KeyStore.TrustedCertificateEntry trustedCert = ...;
KeyStore ks = KeyStore.getInstance("Windows-ROOT");
ks.load(null, null);
ks.setEntry("xxxx_localhost", trustedCert, null);

这使得Chrome 36在两种情况下都接受了证书(localhost和127.0.0.1),但在访问127.0.0.1时,IE 11在访问localhost时无法将证书识别为有效:

Test ok with Chrome (top), KO with IE (bottom)

为什么呢?通过sun.security.x509包使用 BasicConstraints ExtendedKeyUsage SubjectAlternativeName 扩展,构建如下证书:

KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048);
KeyPair kp = generator.generateKeyPair();

X509Certificate cert = generateCertificate("CN=localhost, OU=XXX, O=XXX", kp,
    1825, "SHA256withRSA", "ip:127.0.0.1,dns:localhost,uri:https://127.0.0.1:8112");

/**
 * Create a self-signed X.509 Certificate.
 * @param dn the X.509 Distinguished Name
 * @param pair the KeyPair
 * @param days how many days from now the Certificate is valid for
 * @param algorithm the signing algorithm, eg "SHA256withRSA"
 * @param san SubjectAlternativeName extension (optional)
 */
private static X509Certificate generateCertificate(String dn, KeyPair pair, int days, String algorithm, String san) 
    throws GeneralSecurityException, IOException {
    PrivateKey privkey = pair.getPrivate();
    X509CertInfo info = new X509CertInfo();
    Date from = new Date();
    Date to = new Date(from.getTime() + days * 86400000l);
    CertificateValidity interval = new CertificateValidity(from, to);
    BigInteger sn = new BigInteger(64, new SecureRandom());
    X500Name owner = new X500Name(dn);

    info.set(X509CertInfo.VALIDITY, interval);
    info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(sn));
    info.set(X509CertInfo.SUBJECT, new CertificateSubjectName(owner));
    info.set(X509CertInfo.ISSUER, new CertificateIssuerName(owner));
    info.set(X509CertInfo.KEY, new CertificateX509Key(pair.getPublic()));
    info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
    AlgorithmId algo = new AlgorithmId(AlgorithmId.md5WithRSAEncryption_oid);
    info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algo));

    CertificateExtensions ext = new CertificateExtensions();
    // Critical: Not CA, max path len 0
    ext.set(BasicConstraintsExtension.NAME, new BasicConstraintsExtension(true, false, 0));
    // Critical: only allow TLS ("serverAuth" = 1.3.6.1.5.5.7.3.1)
    ext.set(ExtendedKeyUsageExtension.NAME, new ExtendedKeyUsageExtension(true,
            new Vector<ObjectIdentifier>(Arrays.asList(new ObjectIdentifier("1.3.6.1.5.5.7.3.1")))));

    if (san != null) {
        int colonpos;
        String[] ps = san.split(",");
        GeneralNames gnames = new GeneralNames();
        for(String item: ps) {
            colonpos = item.indexOf(':');
            if (colonpos < 0) {
                throw new IllegalArgumentException("Illegal item " + item + " in " + san);
            }
            String t = item.substring(0, colonpos);
            String v = item.substring(colonpos+1);
            gnames.add(createGeneralName(t, v));
        }
        // Non critical
        ext.set(SubjectAlternativeNameExtension.NAME, new SubjectAlternativeNameExtension(false, gnames));
    }

    info.set(X509CertInfo.EXTENSIONS, ext);

    // Sign the cert to identify the algorithm that's used.
    X509CertImpl cert = new X509CertImpl(info);
    cert.sign(privkey, algorithm);

    // Update the algorithm, and resign.
    algo = (AlgorithmId)cert.get(X509CertImpl.SIG_ALG);
    info.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, algo);
    cert = new X509CertImpl(info);
    cert.sign(privkey, algorithm);
    return cert;
}

通过关于CAPI2诊断的this tutorial,我发现了IE报告的错误:

<CertVerifyCertificateChainPolicy>
    <Policy type="CERT_CHAIN_POLICY_SSL" constant="4" /> 
    <Certificate fileRef="XXX.cer" subjectName="127.0.0.1" /> 
    <CertificateChain chainRef="{XXX}" /> 
    <Flags value="0" /> 
    <SSLAdditionalPolicyInfo authType="server" serverName="127.0.0.1">
        <IgnoreFlags value="0" /> 
    </SSLAdditionalPolicyInfo>
    <Status chainIndex="0" elementIndex="0" /> 
    <EventAuxInfo ProcessName="iexplore.exe" /> 
    <CorrelationAuxInfo TaskId="{XXX}" SeqNumber="4" /> 
    <Result value="800B010F">The certificate's CN name does not match the passed value.</Result> 
</CertVerifyCertificateChainPolicy>

CertVerifyCertificateChainPolicyCERT_CHAIN_POLICY_STATUS上的文档对我帮助不大:看起来IE期望CN等于服务器名称,但我尝试将CN更改为{{1没有成功(相同的行为)。

2 个答案:

答案 0 :(得分:6)

IE不支持主题备用名称(SAN)中的IP地址值,仅支持DNS条目。

根据微软的说法,这是一个known limitation无法解决的问题:

  

我们不支持使用Subject Alternative名称中的IP选项来匹配服务器名称。您可以通过将IP地址添加为DNS名称选择的字符串来解决此问题。目前我们还没有计划解决这个问题。

所以处理它的正确方法是添加一个包含IP地址的DNS条目:

"dns:127.0.0.1"

不幸的是,由于Java bug 8016345,使用 keytool 或以编程方式使用sun.security.x509类是不可能的。

然而,只需复制最新版本的DNSName.java并删除此项检查即可自行修复此错误:

//DNSName components must begin with a letter A-Z or a-z
if (alpha.indexOf(name.charAt(startIndex)) < 0)
    throw new IOException("DNSName components must begin with a letter");

答案 1 :(得分:-1)

为什么不在用户使用127.0.0.1将其发送到localhost时发出HTTP 301重定向?