When I got my Android phone, I tried to make it work with my e-mail server which supports TLS and SSL using a CAcert certificate. CAcert is not yet present in the Android list of root certificates, and an FAQ entry of the official CAcert site along with two Android issues (6207 and 11231) state, that since this list is writeable to root only, it requires rooting of the phone, which I've considered an overkill. I also found that the stock Mail application had only two options for TLS or SSL security: either accept all certificates, thus creating a false sense of security, or accept certificates only from the read-only list.
I tried an alternative mail client called K-9 mail, which had more options available, as it allowed the user to examine the certificate, and accept it to be used in future communications. I configured the mailbox, and got a nice dialog box asking me to confirm the details of the certificate.
Unfortunately, the only problem with this dialog box was making the whole feature useless: it didn't contain the checksum (also known as fingerprint) of the certificate, which is the only value not spoofable by a man in the middle (MITM) attacker. There are point-and-click tools available on the web that can create a rogue certificate matching the original one in every detail, and the only difference is the public key, which affects the checksum. I developed a patch to fix this problem, which was accepted after a short discussion, and later made its way into the Android Market.
First, I found, that the checksum algorithm is SHA-1 most of the time, since MD5 is considered broken for this purpose. To calculate the SHA-1 hashes of the certificates in the chain, I imported the necessary classes and instantiated the SHA-1 MessageDigest class.
import java.security.NoSuchAlgorithmException;
import java.security.MessageDigest;
...
MessageDigest sha1 = null;
try {
sha1 = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException e) {
Log.e(K9.LOG_TAG, "Error while initializing MessageDigest", e);
}
I spent most of the time figuring out how to extract the content of the
certificate that needs to be hashed. As Roman D figured out in a
post on Stack Overflow, “the hash is calculated over the DER-encoded
(again, not the PEM string) TBS part only, including its ASN.1 header
(the ID 0x30 == ASN1_SEQUENCE | ASN1_CONSTRUCTED
and the length field)”.
Knowing this, I just needed to extract this information form the instances of the X509Certificate interface. I tried the getTBSCertificate method with no luck, but later found that there's a method inherited from Certificate called getEncoded, which does just what I needed. There was only one task to do: since the MessageDigest object returns the digest in raw bytes, I needed to convert them to hex digits in order to display it in the dialog box. It turned out, that the project already had a class for that, so I could avoid reinventing the wheel.
import com.fsck.k9.mail.filter.Hex;
import java.security.cert.CertificateEncodingException;
...
if (sha1 != null) {
sha1.reset();
try {
char[] sha1sum = Hex.encodeHex(sha1.digest(chain[i].getEncoded()));
chainInfo.append("Fingerprint (SHA-1): " + new String(sha1sum) + "\n");
} catch (CertificateEncodingException e) {
Log.e(K9.LOG_TAG, "Error while encoding certificate", e);
}
}
After development and building, there was only one task: testing. I installed the modified version into an emulator and tried to configure an account on my mail server. It worked, and as you can see on the screenshot below, the checksum matches the one you can CAcert fingerprint page.