Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why IE rejects a self-signed localhost certificate for 127.0.0.1 only, when Chrome accepts it?

Our Java 7 application needs to listen for HTTPS requests on localhost. It must accept connections on https://localhost:8112 and https://127.0.0.1:8112.

To do so we have programmatically built an auto-signed X509v3 certificate, and we have installed this certificate in the Windows-ROOT keystore, as follows:

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

This makes the certificate accepted by Chrome 36 in both cases (localhost and 127.0.0.1), but IE 11 does not recognize the certificate as valid when accessing 127.0.0.1 while it does when accessing localhost:

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

Why? The certificate is built as follows, with BasicConstraints, ExtendedKeyUsage and SubjectAlternativeName extensions, by using the sun.security.x509 package:

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;
}

By following this tutorial on CAPI2 Diagnostics, I have found the error reported by 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>

The documentation on CertVerifyCertificateChainPolicy and CERT_CHAIN_POLICY_STATUS doesn't help me much: it looks like IE expects a CN equal to server name, but I have tried to change my CN to CN=127.0.0.1 without success (same behaviour).

like image 912
vip Avatar asked Jul 26 '14 13:07

vip


1 Answers

IE does not support IP addresses values in Subject Alternative Name (SAN), only DNS entries.

This is a known limitation that won't be fixed, according to Microsoft:

We do not support using the IP choice in the Subject Alternative name to match the server name. You can work around this by adding the IP address as a string for a DNS name choice. At this time we do not plan on fixing this issue.

So the correct way to handle it is to add a DNS entry containing the IP address:

"dns:127.0.0.1"

Unfortunately, this is not possible using keytool or programmatically with sun.security.x509 classes because of Java bug 8016345.

It is however possible to fix this bug by yourself, just by copying the latest version of DNSName.java and remove this check:

//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");
like image 146
vip Avatar answered Oct 17 '22 18:10

vip