Search code examples
javacertificatesubject-alternative-namecertificate-transparency

Can't pars subjectAlternativeNames in java


I try to parse extra data of certificate in java. I interested in subjectAlternativeNames section. My code is:

CertificateFactory certFactory = CertificateFactory.getInstance("X.509")
certFactory.generateCertificate(ByteArrayInputStream(x509EntryChain.leafCertificate))

X509CertInfo contains java.io.IOException: invalid URI name:DNS:*.cetrel.lu, DNS:mail.cetrel.lu, DNS:www.cetrel.lu. and subjectAlternativeNames is empty.

How can I parse it?

I am interested only in extra_data, not in leaf_input. I can find the certificate here: http://transparencyreport.google.com/https/certificates/

The DNS names section is empty, seems Google can't parse it either.

Full code to parce cert:


import org.apache.commons.codec.binary.Base64;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

import sun.security.x509.CertificateExtensions;
import sun.security.x509.X509CertImpl;
import sun.security.x509.X509CertInfo;

import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;

public class CertParser {

    public static void main(String[] args) throws CertificateException, IOException {
        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
        ByteArrayInputStream in = new ByteArrayInputStream(Base64.decodeBase64(
                "AAAAAAFXsGq8HwAAAAW+MIIFujCCBKKgAwIBAgIDAZZpMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkxVMRYwFAYDVQQKEw1MdXhUcnVzdCBTLkEuMR4wHAYDVQQDExVMdXhUcnVzdCBRdWFsaWZpZWQgQ0EwHhcNMTAwNDE1MDY0MzM0WhcNMTMwNDE1MDY0MzM0WjB8MQswCQYDVQQGEwJMVTERMA8GA1UEBxMITXVuc2JhY2gxFDASBgNVBAoTC0NFVFJFTCBTLkEuMRAwDgYDVQQLEwdJT1AtU1NTMRQwEgYDVQQDFAsqLmNldHJlbC5sdTEcMBoGCSqGSIb3DQEJARYNc3NzQGNldHJlbC5sdTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALhf+I7RshQlHMMWq/WPDLNxx+ODMd4Tn9ej14igvMEE+RYEagdMOZoeO8Bqz9qV2atzFHqz0D+Ad+cxuznBGKl4rhS9gUejgoAMox7car0+LSsv1NT4J0gAlnmH3BJlDMd9CihT0D/sRwMNfa8GYAvCuGDtWIvYb497RFy+2kmzk3cwCk3BgOO3MsT7iqhcn65Pd1Lq1vLjCCuQBoWLlcKk4uptPsyFKrHEh1/0ksY5evqBPxioVppoN+oay20RK36JzrzAl+vfpzq03WRlM2IgM0ItnesLqid9GqTUsOTq59i5aVX1EKlfgM5v7YCpYMLrJA+JBO3beR/4FSczfccCAwEAAaOCAnowggJ2MAwGA1UdEwEB/wQCMAAwYAYIKwYBBQUHAQEEVDBSMCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5sdXh0cnVzdC5sdTArBggrBgEFBQcwAoYfaHR0cDovL2NhLmx1eHRydXN0Lmx1L0xUUUNBLmNydDBBBgNVHREEOjA4hjZETlM6Ki5jZXRyZWwubHUsIEROUzptYWlsLmNldHJlbC5sdSwgRE5TOnd3dy5jZXRyZWwubHUwggEABgNVHSAEgfgwgfUwgegGCCuBKwEBAgYBMIHbMIGtBggrBgEFBQcCAjCBoBqBnUx1eFRydXN0IFNlcnZlciBDZXJ0aWZpY2F0ZS4gTm90IHN1cHBvcnRlZCBieSBTU0NELCBLZXkgR2VuZXJhdGlvbiBieSBTdWJzY3JpYmVyLiBHVEMsIENQIGFuZCBDUFMgb24gaHR0cDovL3JlcG9zaXRvcnkubHV4dHJ1c3QubHUuIFNpZ25lZCBieSBhIFF1YWxpZmllZCBDQS4wKQYIKwYBBQUHAgEWHWh0dHA6Ly9yZXBvc2l0b3J5Lmx1eHRydXN0Lmx1MAgGBgQAj3oBAzARBglghkgBhvhCAQEEBAMCBeAwDgYDVR0PAQH/BAQDAgSwMCcGA1UdJQQgMB4GCCsGAQUFBwMBBggrBgEFBQcDAgYIKwYBBQUHAwQwHwYDVR0jBBgwFoAUjZCjB90aE3eZTJKrTUPeP80pZAUwMQYDVR0fBCowKDAmoCSgIoYgaHR0cDovL2NybC5sdXh0cnVzdC5sdS9MVFFDQS5jcmwwHQYDVR0OBBYEFLhOdzNJg4CSchxSsHbwKWMVm8xnMA0GCSqGSIb3DQEBBQUAA4IBAQBrQ9bYRA5O8a+4vIfVx5izH5x9yieKuQjpF4Etc4YUxfk3h5yZieWJuZGHygUjV5TCYDdhtVf6FFWkJ4FT5l6zDQeOLrEWqL12qcT4hGFN61mwjZO7kca8IHlqPPeqtYVg/Ssbpun+bjPOsGdvrvMulqNNTz5UeQuovc/VFaoHpYCQhezrQ6E6uQ684f6LFVIbsah6pT58wEnrf6xE1aRdSk27e3bF8wns3zOVsWE2wKck5pMS5DkGwjWli27Aqt6QQCyCKC7xqqxwL8GfnmXZNdn2iYYfSyr0I7rdqxa7FsuNFkEb8/PdZlyMxQP867YnucRCyLzzjCnRy1bOLnUeAAA="
        ));
        readNumber(in, 1); // version
        readNumber(in, 1); // type
        readNumber(in, 8); // timestamp
        readNumber(in, 2); // entry type
        int length = (int) readNumber(in, 3);
        byte[]  x509 = readFixedLength(in, length);
        X509CertImpl cert = (X509CertImpl) certFactory.generateCertificate(new ByteArrayInputStream(x509));
        ((CertificateExtensions)((X509CertInfo)cert.get("x509.info")).get("extensions")).getUnparseableExtensions(); // <-- want too be empty, but it's not!!!
    }

    static byte[] readFixedLength(InputStream inputStream, int dataLength) throws IOException {
        byte[] toReturn = new byte[dataLength];
        int bytesRead = inputStream.read(toReturn);
        if (bytesRead < dataLength) {
            throw new RuntimeException();
        }
        return toReturn;
    }

    static long readNumber(InputStream inputStream, int numBytes) throws IOException {
        long toReturn = 0;
        for (int i = 0; i < numBytes; i++) {
            int valRead = inputStream.read();
            if (valRead < 0) {
                throw new RuntimeException();
            }
            toReturn = (toReturn << 8) | valRead;
        }
        return toReturn;
    }
}


Solution

  • After you shared your code to access the certificate, I could safe and inspect it.

    As was already clear from the question, the problematic extension is the subject alternative name. Its value is specified (RFC 5280) as an instance of GeneralNames which is a SEQUENCE of GeneralName which is a CHOICE:

    SubjectAltName ::= GeneralNames
    
    GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
    
    GeneralName ::= CHOICE {
         otherName                 [0]  AnotherName,
         rfc822Name                [1]  IA5String,
         dNSName                   [2]  IA5String,
         x400Address               [3]  ORAddress,
         directoryName             [4]  Name,
         ediPartyName              [5]  EDIPartyName,
         uniformResourceIdentifier [6]  IA5String,
         iPAddress                 [7]  OCTET STRING,
         registeredID              [8]  OBJECT IDENTIFIER }
    

    This is a dump of the extension in question of your certificate:

        <30 41>
     676   65: . . . . SEQUENCE {
        <06 03>
     678    3: . . . . . OBJECT IDENTIFIER subjectAltName (2 5 29 17)
             : . . . . . . (X.509 extension)
        <04 3A>
     683   58: . . . . . OCTET STRING, encapsulates {
        <30 38>
     685   56: . . . . . . SEQUENCE {
        <86 36>
     687   54: . . . . . . . [6]
             : . . . . . . . . 'DNS:*.cetrel.lu, DNS:mail.cetrel.lu, DNS:www.cet'
             : . . . . . . . . 'rel.lu'
             : . . . . . . . }
             : . . . . . . }
             : . . . . . }
    

    The value is tagged with a 6 ([6]). Thus, the uniformResourceIdentifier choice is used. RFC 5280 in the context of subject alternative names requires for the value of this choice:

    The name MUST NOT be a relative URI, and it MUST follow the URI syntax and encoding rules specified in [RFC3986]. The name MUST include both a scheme (e.g., "http" or "ftp") and a scheme-specific-part.

    Thus, the sun.security.x509.X509CertImpl attempts to parse the value as an URI. This obviously must fail as the value simply is no URI:

    java.io.IOException: invalid URI name:DNS:*.cetrel.lu, DNS:mail.cetrel.lu, DNS:www.cetrel.lu
        at sun.security.x509.URIName.<init>(URIName.java:109)
        at sun.security.x509.URIName.<init>(URIName.java:96)
        at sun.security.x509.GeneralName.<init>(GeneralName.java:122)
        at sun.security.x509.GeneralName.<init>(GeneralName.java:76)
        at sun.security.x509.GeneralNames.<init>(GeneralNames.java:68)
        at sun.security.x509.SubjectAlternativeNameExtension.<init>(SubjectAlternativeNameExtension.java:141)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
        at sun.security.x509.CertificateExtensions.parseExtension(CertificateExtensions.java:113)
        at sun.security.x509.CertificateExtensions.init(CertificateExtensions.java:88)
        at sun.security.x509.CertificateExtensions.<init>(CertificateExtensions.java:78)
        at sun.security.x509.X509CertInfo.parse(X509CertInfo.java:702)
        at sun.security.x509.X509CertInfo.<init>(X509CertInfo.java:167)
        at sun.security.x509.X509CertImpl.parse(X509CertImpl.java:1804)
        at sun.security.x509.X509CertImpl.<init>(X509CertImpl.java:195)
        at sun.security.provider.X509Factory.engineGenerateCertificate(X509Factory.java:102)
        at java.security.cert.CertificateFactory.generateCertificate(CertificateFactory.java:339)
    Caused by: java.net.URISyntaxException: Illegal character in opaque part at index 16: DNS:*.cetrel.lu, DNS:mail.cetrel.lu, DNS:www.cetrel.lu
        at java.net.URI$Parser.fail(URI.java:2848)
        at java.net.URI$Parser.checkChars(URI.java:3021)
        at java.net.URI$Parser.parse(URI.java:3058)
        at java.net.URI.<init>(URI.java:588)
        at sun.security.x509.URIName.<init>(URIName.java:107)
        ... 46 more
    

    If you want to access the value nonetheless, simply retrieve and parse it from the UnparseableExtensions map you have like this:

    Map<String, Extension> unparseables = ((CertificateExtensions)((X509CertInfo)cert.get("x509.info")).get("extensions")).getUnparseableExtensions();
    
    Extension extension = unparseables.get("2.5.29.17");
    byte[] value = extension.getValue();
    DerValue derValue = new DerValue(value);
    while (derValue.data.available() > 0) {
        DerValue encName = derValue.data.getDerValue();
        if ((encName.tag & 0x1f) == 6) {
            encName.resetTag(DerValue.tag_IA5String);
            System.out.printf("IA5String value from URI GeneralName value: %s\n", encName.getIA5String());
        }
    }
    

    for sun.security.x509.Extension and sun.security.util.DerValue.

    The output:

    IA5String value from URI GeneralName value: DNS:*.cetrel.lu, DNS:mail.cetrel.lu, DNS:www.cetrel.lu