Why doesn't my key identifier match?
Asked Answered
H

3

8

I'm attempting to decrypt an S/MIME email (sent originally via Outlook), and to do that, I'm using the bouncycastle API. I'm running into a snag, though.

I have, in the Windows certificate store, the certificate for the recipient. I had previously used it to send a signed and encrypted email to the other party, and they in turn used it to send me an encrypted reply. I then exported the certificate (with private key) as a .pfx file, and I loaded this pfx file into a Java KeyStore. It doesn't work, however, and I suspect that's because the subject key identifiers don't match.

Here's the code I'm using to get the subject key id from the KeyStore:

KeyStore ks = KeyStore.getInstance("PKCS12");
char[]   pw = "password".toCharArray();

ks.load(new FileInputStream("d:\\cert_priv_key.pfx"), pw);

Enumeration en = ks.aliases();

while( en.hasMoreElements() )
{
    String alias = (String)en.nextElement();
    System.out.println(alias);

    if( ks.isKeyEntry(alias) )
    {
        Certificate[]   chain = ks.getCertificateChain(alias);
        X509Certificate cert  = (X509Certificate)chain[0];

        byte[] id = cert.getExtensionValue("2.5.29.14");

        System.out.println("  " + toHex(id));
    }
}

This prints out the following key identifier:

04 16 04 14 88 ed bb 7c 64 7b 41 63 48 0a 24 40 2b 3c d0 78 72 3c 30 b3

When I check the Windows certificate store, however, the key identifier is different:

88 ed bb 7c 64 7b 41 63 48 0a 24 40 2b 3c d0 78 72 3c 30 b3

The KeyStore returns an extra 4 bytes in the front (the subject key identifier should be the 160-bit SHA1 hash of the key, and therefore 20 bytes long, correct?).

Even more confusing is the fact that when I parse the S/MIME email using the bouncycastle API, and go through the recipients (SMIMEEnveloped.getRecipientInfos().getRecipients()), the only recipient returned (there should be only one) has this subject key identifier:

04 14 88 ed bb 7c 64 7b 41 63 48 0a 24 40 2b 3c d0 78 72 3c 30 b3

... it has only two extra bytes, not four, and I assume this is why I'm unable to decrypt the email with the certificate.

Why do none of these subject key identifiers match up? What am I doing wrong?

Hoitytoity answered 29/6, 2011 at 15:20 Comment(0)
W
17

All these answers are consistent if you understand all the specs, but of course that means they are confusing if you don't. The first place to look is in RFC 5280, section 4.2.1.2. In this case method (1) is used. Next, look at section A.2 at the definition of KeyIdentifier. It is defined as an OCTET STRING. Now look at how an ASN.1 OCTET STRING should be encoded. It should start out with hex 04 followed by the length in bytes (20 bytes, or 14 hex) followed by the actual octet string (the SHA1 hash). So the contents of the extension should be

04 14 88 ed bb 7c 64 7b 41 63 48 0a 24 40 2b 3c d0 78 72 3c 30 b3

Finally, look at the ASN.1 definition of Extension. It says that the extension value must be encoded as an OCTET STRING. In the case of this particular extension, the net effect is that is get encoded twice in a row as an OCTET STRING. At this level the OCTET STRING is the previous OCTET STRING which includes the two header bytes 04 14, so the length is hex 16, and the encoding is

04 16 04 14 88 ed bb 7c 64 7b 41 63 48 0a 24 40 2b 3c d0 78 72 3c 30 b3

This is the value returned by the X509Extension.getExtensionValue() method. Now the interesting part of the key id is just the SHA1 hash which start with 88, so this is what the Windows utility displays. Obviously the bouncycastle method you are using displays the extension without doing additional decoding.

Whelan answered 30/6, 2011 at 1:21 Comment(5)
I was able to decrypt the email by using the private key directly, but I still can't get it to work using non-deprecated methods. My guess is that X509Extension.getExtensionValue() should be stripping one layer of encoding from the value when it returns it. That would make it match.Hoitytoity
Also I assume you meant ... "additional decoding"?Hoitytoity
@Mark: re stripping a layer off. Originally that is what I thought but after a little consideration I now think getExtensionValue() does the correct thing.Whelan
@GregS: So, you're of the opinion that either the code to match the subject key ids is wrong (should be doing 2x decodes on the cert side and 1x decode on the SMIME side), or the SMIME message is encoded incorrectly?Hoitytoity
@Mark: Here is how I'd put it. The getExtensionValue() method is unaware of what the meaning of the extension you are asking for is. The source is probably very simple. The SMIME code is more type aware. It knows that this is, not just an extension, but the SubjectKeyIdentifier extension, and thus this OCTET STRING actually encodes another OCTET STRING. I confess I'm not too familiar with the bcmail api, I'm kind of guessing.Whelan
H
1

Is the S/MIME message created by Outlook 2010?

if so see http://bouncy-castle.1462172.n4.nabble.com/Re-ReadEncryptedMail-sample-and-SubjectKeyIdentifier-instead-of-IssuerSerial-Outlook-2010-Hack-td3042968.html and https://bugzilla.mozilla.org/show_bug.cgi?id=559243 for more info

Martijn Brinkers

Heaveho answered 30/6, 2011 at 10:14 Comment(1)
No, that doesn't seem to be the issue here. There is definitely a SubjectKeyIdentifier, it just doesn't match up with what the certificate has.Hoitytoity
M
1

The accepted answer from GregS helped me alot.

The code that ended up working for me is:

X509Certificate certificate = ...
byte[] encExtensionSubjectKeyIdentifier = certificate.getExtensionValue(Extension.subjectKeyIdentifier.getId());

// Unwrap first 'layer'
ASN1Primitive skiPrimitive = JcaX509ExtensionUtils.parseExtensionValue(encExtensionSubjectKeyIdentifier);

// Unwrap second 'layer'
byte[] keyIdentifier = ASN1OctetString.getInstance(skiPrimitive.getEncoded()).getOctets();

// Use keyIdentifier in e.g. CMS SignerInfo
SignerInfoGenerator signerInfoGenerator = jcaSignerInfoGeneratorBuilder.build(sha1Signer, keyIdentifier);
Melany answered 29/4, 2015 at 10:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.