Search code examples
javacertificatex509signingasn.1

Signing a X.509 certificate in Java


I want to find out how to sign a x.509 certificate.

I have set up an OpenSSL CA to create a demo certificate. The certificate is fine and I can see all relevant elements using a tool called dumpasn1. Theoretically I know I have to sign the structure called "tbsCertificate" which compounds the attributes version , serial number signature, issuer, validity, subject, subjectPublicKeyInfo and the extensions.

However, when I try to mimic that in Java, unfortunately it does not work. What I do is:

  • I read the "tbsCertificate"-portion of the DER-encoded certificate
  • then I get the private key from the issuing CA
  • last I try to sign the tbsCertificate-structure by using the following code:

.

public static void sign(byte [] data) throws Exception  
{
    String algorithm = "SHA256withECDSA";
    PrivateKey privKey;
    KeyPair keyPair;

    keyPair = getKeyPairFomPEM();
    privKey = keyPair.getPrivate();

    Signature ecdsa;
    ecdsa = Signature.getInstance(algorithm, "BC");
    ecdsa.initSign(privKey);
    ecdsa.update(data);
    byte[] baSignature = ecdsa.sign(); 
}

Unfortunately the result does not match the signature in the certificate, so obviously I made some kind of error. The signature algorithm is the same that is used in the certificate (SHA256withECDSA), so I suspect that I did not choose the right portion of the tbsCertificate structure.

My certificate is about 530 bytes long. I start reading from offset 4 (skipping the initial SEQUENCE tag and length bytes) to the end of the extensions (stopping before the begin of the certificate section).

Can anybody tell if this is the right array of bytes I have to read for the "tbsCertificate" structure? Or what other error may I have made that I just don't see right now? The certificate itself is definitively ok, I double checked the certificate and the signing algorithm using the following Java code.

public static X509Certificate loadCertificate(String fileName)
{
    InputStream in;
    byte [] signature;
    X509Certificate cert = null;
    try
    {
        in = new FileInputStream(fileName);
        CertificateFactory factory = CertificateFactory.getInstance("X.509");
        cert = (X509Certificate) factory.generateCertificate(in);
        signature = cert.getSignature();
        System.out.println("Signature Algorithm: " + cert.getSigAlgName());
        System.out.println(signature);
        return cert;
    }
    catch (FileNotFoundException e)
    {
        e.printStackTrace();
    }
    catch (CertificateException e)
    {
        e.printStackTrace();
    }
    return cert;
}

Solution

  • ECDSA doesn't produce stable signatures, so if your test is that you produced the same answer that won't work. You can verify the signature over a certificate by verifying the signature presented against the tbsCertificate data.

    Your approach would work with an RSA signature, since that's a stable signature algorithm (with PKCS1 padding, not with PSS).

    As for finding the tbsCertificate: You do seem to have an idea of what the DER encoding is, but I feel compelled to provide guidance based on the loose description in the question:

    The binary encoded form of the certificate is in DER. The first byte will be 0x30 ((constructed) SEQUENCE). The next byte is either how long the sequence is (< 0x80) or how long the length is (0x82 => the next two bytes are the big-endian length). The next byte will again be 0x30, the start of the tbsCertificate encoded value. Then you can read the length of that structure, and compute the correct end offset for the tbsCertificate.

    The certificate structure can be found in RFC 3280. (There's an update in RFC 5280 but I've heard people say "that didn't get approved as a standard".) It's also available, along with attribute certificates, in ITU-T X.509 -- which is where the X.509 in X.509 Certificate comes from. I linked to the 2012 version because the 2016 version is behind a paywall.