Signing email with an NFC smart card on Android

Last time we discussed how to access the SIM card and use it as a secure element to enhance Android applications. One of the main problems with this approach is that since SIM cards are controlled by the MNO any applets running on a commercial SIM have to be approved by them. Needless to say, that considerably limits flexibility. Fortunately, NFC-enabled Android devices can communicate with practically any external contactless smart card, and you can install anything on those. Let's explore how an NFC smart card can be used to sign email on Android.

NFC smart cards

As discussed in previous posts, a smart card is a secure execution environment on a single chip, typically packaged in a credit-card sized plastic package or the smaller 2FF/3FF/4FF form factors when used as a SIM card. Traditionally, smart cards connect with a card reader using a number of gold-plated contact pads. The pads are used to both provide power to the card and establish serial communication with its I/O interface. Size, electrical characteristics and communication protocols are defined in the 7816 series of ISO standards. Those traditional cards are referred to as 'contact smart cards'. Contactless cards on the other hand do not need to have physical contact with the reader. They draw power and communicate with the reader using RF induction. The communication protocol (T=CL) they use is defined in ISO 14443 and is very similar to the T1 protocol used by contact cards. While smart cards that have only a contactless interface do exist, dual-interface cards that have both contacts and an antenna for RF communication are the majority. The underlying RF standard used varies by manufacturer, and both Type A and Type B are common. 

As we know, NFC has three standard modes of operation: reader/writer (R/W), peer-to-peer (P2P) and card emulation (CE) mode. All NFC-enabled Android devices support R/W and P2P mode, and some can provide CE, either using a physical secure element (SE) or software emulation. All that is needed to communicate with a contactless smart card is the basic R/W mode, so they can be used on practically all Android devices with NFC support. This functionality is provided by the IsoDep class. It provides only basic command-response exchange functionality with the transceive() method, any higher level protocol need to be implemented by the client application.

Securing email

There have been quite a few new services that are trying to reinvent secure email in recent years. They are trying to make it 'easy' for users by taking care of key management and shifting all cryptographic operations to the server. As recent events have reconfirmed, introducing an intermediary is not a very good idea if communication between two parties is to be and remain secure. Secure email itself is hardly a new idea, and the 'old-school' way of implementing it relies on pubic key cryptography. Each party is responsible for both protecting their private key and verifying that the public key of their counterpart matches their actual identity. The method used to verify identity is the biggest difference between the two major secure email standards in use today, PGP and S/MIME. PGP relies on the so called 'web of trust', where everyone can vouch for the identity of someone by signing their key (usually after meeting them in person), and keys with more signatures can be considered trustworthy. S/MIME, on the other hand, relies on PKI and X.509 certificates, where the issuing authority (CA) is relied upon to verify identity when issuing a certificate. PGP has the advantage of being decentralized, which makes it harder to break the system by compromising  a single entity, as has happened with a number of public CAs in recent years. However, it requires much more user involvement and is especially challenging to new users. Additionally, while many commercial and open source PGP implementations do exist, most mainstream email clients do not support PGP out of the box and require the installation of plugins and additional software. On the other hand, all major proprietary (Outlook variants, Mail.app, etc) and open source (Thunderbird) email clients have built-in and mature S/MIME implementations. We will use S/MIME for this example because it is a lot easier to get started with and test, but the techniques described can be used to implement PGP-secured email as well. Let's first discuss how S/MIME is implemented.

Signing with S/MIME

The S/MIME, or Secure/Multipurpose Internet Mail Extensions, standard defines how to include signed and/or encrypted content in email messages. It specified both the procedures for creating  signed or encrypted (enveloped) content and the MIME media types to use when adding them to the message. For example, a signed message would have a part with the Content-Type: application/pkcs7-signature; name=smime.p7s; smime-type=signed-data which contains the message signature and any associated attributes. To an email client that does not support S/MIME, like most Web mail apps, this would look like an attachment called smime.p7s. S/MIME-compliant clients would instead parse and verify the signature and display some visual indication showing the signature verification status.

The more interesting question however is what's in smime.p7s? The 'p7' stands for PKCS#7, which is the predecessor of the current Cryptographic Message Syntax (CMS). CMS defines structures used to package signed, authenticated or encrypted content and related attributes. As with most PKI X.509-derived standards, those structures are ASN.1 based and encoded into binary using DER, just like certificates and CRLs. They are sequences of other structures, which are in turn composed of yet other ASN.1 structures, which are..., basically sequences all the way down. Let's try to look at the higher-level ones used for signed email. The CMS structure describing signed content is predictably called SignedData and looks like this:

SignedData ::= SEQUENCE {
        version CMSVersion,
        digestAlgorithms DigestAlgorithmIdentifiers,
        encapContentInfo EncapsulatedContentInfo,
        certificates [0] IMPLICIT CertificateSet OPTIONAL,
        crls [1] IMPLICIT RevocationInfoChoices OPTIONAL,
        signerInfos SignerInfos }

Here digestAlgorithms contains the OIDs of the hash algorithms used to produce the signature (one for each signer) and encapContentInfo describes the data that was signed, and can optionally contain the actual data. The optional certificates and crls fields are intended to help verify the signer certificate. If absent, the verifier is responsible for collecting them by other means. The most interesting part, signerInfos, contains the actual signature and information about the signer. It looks like this:

SignerInfo ::= SEQUENCE {
        version CMSVersion,
        sid SignerIdentifier,
        digestAlgorithm DigestAlgorithmIdentifier,
        signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL,
        signatureAlgorithm SignatureAlgorithmIdentifier,
        signature SignatureValue,
        unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL }

Besides the signature value and algorithms used, SignedInfo contains signer identifier used to find the exact certificate that was used and a number of optional signed and unsigned attributes. Signed attributes are included when producing the signature value and can contain additional information about the signature, such as signing time. Unsigned attribute are not covered by the signature value, but can contain signed data themselves, such as counter signature (an additional signature over the signature value).

To sum this up, in order to produce a S/MIME signed message, we need to sign the email contents and any attributes, generate the SignedInfo structure, wrap it into a SignedData, DER encode the result and add it to the message using the appropriate MIME type. Sound easy, right? Let's how this can be done on Android.

Using S/MIME on Android

On any platform, you need two things in order to generate an S/MIME message: a cryptographic provider that can perform the actual signing using an asymmetric key and an ASN.1 parser/generator in order to generate the SignedData structure. Android has JCE providers that support RSA, recently even with hardware-backed keys. What's left is an ASN.1 generator. While ASN.1 and DER/BER have been around for ages, and there are quite a few parsers/generators, the practically useful choices  are not that many. No one really generates code directly from the ASN.1 modules found in related standards, most libraries implement only the necessary parts, building on available components. Both of Android's major cryptographic libraries, OpenSSL and Bouncy Castle contain ASN.1 parser/generators and have support for CMS. The related API's are not public though, so we need to include our own libraries.

As usual we turn to Spongy Castle, which is provides all of Bouncy Castle's functionality under a different namespace. In order to be able process CMS and generate S/MIME messages, we need the optional scpkix and scmail packages. The first one contains PKIX and CMS related classes, and the second one implements S/MIME. However, there is a twist: Android lacks some of the classes required for generating S/MIME messages. As you may know, Android has implementations for most standard Java APIs, with a few exceptions, most notably the GUI widget related AWT and Swing packages. Those are rarely missed, because Android has its own widget and graphics libraries. However, besides widgets AWT contains classes related to MIME media types as well. Unfortunately, some of those are  used in libraries that deal with MIME objects, such as JavaMail and the Bouncy Castle S/MIME implementation. JavaMail versions that include alternative AWT implementations, repackaged for Android have been available for some time, but since they use some non-standard package names, they are not a drop-in replacement. That applies to Spongy Castle as well: some source code modifications are required in order to get scmail to work with the javamail-android library.

With that sorted out, generating an S/MIME message on Android is just a matter of finding the signer key and certificate and using the proper Bouncy Castle and JavaMail APIs to generate and send the message:

PrivateKey signerKey = KeyChain.getPrivateKey(ctx, "smime");
X509Certificate[] chain = KeyChain.getCertificateChain(ctx, "smime");
X509Certificate signerCert = chain[0];
X509Certificate caCert = chain[1];

SMIMESignedGenerator gen = new SMIMESignedGenerator();
gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder()
                    .setProvider("AndroidOpenSSL")
                    .setSignedAttributeGenerator(
                            new AttributeTable(signedAttrs))
                    .build("SHA512withRSA", signerKey, signerCert));
Store certs = new JcaCertStore(Arrays.asList(signerCert, caCert));
gen.addCertificates(certs);

MimeMultipart mm = gen.generate(mimeMsg, "SC");
MimeMessage signedMessage = new MimeMessage(session);
Enumeration headers = mimeMsg.getAllHeaderLines();
while (headers.hasMoreElements()) {
    signedMessage.addHeaderLine((String) headers.nextElement());
}
signedMessage.setContent(mm);
signedMessage.saveChanges();

Transport.send(signedMessage);

Here we first get the signer key and certificate using the KeyChain API and then create an S/MIME generator by specifying the key, certificate, signature algorithm and signed attributes. Note that we specify the AndroidOpenSSL provider explicitly which is the only one that can use hardware-backed keys. This is only required if you changed the default provider order when installing Spongy Castle, by default AndroidOpenSSL is the preferred JCE provider. We then add the certificates we want to include in the generated SignedData and generate a multi-part MIME message that includes both the original message (mimeMsg) and the signature. Finally we send the message using the JavaMail Transport class. The JavaMail Session initialization is omitted from the example above, see the sample app for how to set it up to use Gmail's SMTP server. This requires the Gmail account password to be specified, but with a little more work it can be replaced with an OAuth token you can obtain from the system AccountManager.

So what about smart cards?

Using a MuscleCard to sign email

In order to sign email using keys stored on a smart card we need a few things: 
  • a dual-interface smart cards that supports RSA keys
  • a crypto applet that allows us to sign data with those keys
  • some sort of middleware that exposes card functionality through a standard crypto API
Most recent dual-interface JavaCards fulfill our requirements, but we will be using a NXP J3A081 which supports JavaCard 2.2.2 and 2048-bit RSA keys. When it comes to open source crypto applets though, unfortunately the choices are quite limited. Just about the only one that is both full-featured and well supported in middleware libraries is the venerable MuscleCard applet. We will be using one of the fairly recent forks, updated to support JavaCard 2.2 and extended APDUs. To load the applet on the card you need a GlobalPlatform-compatible loader application, like GPJ, and of course the CardManager keys. Once you have initialized it, you can personalize it by generating or importing keys and certificates. After that the card can be used in any application that supports PKCS#11, for example Thunderbird and Firefox. Because the card is dual-interface, practically any smart card reader can be used on desktops. When the OpenSC PKCS#11 module is loaded in Thunderbird the card will show up in the Security Devices dialog like this:


If the certificate installed in the card has your email in the Subject Alternative Name extension, you should be able send signed and encrypted emails (if you have the recipient's certificate, of course). But how to achieve the same thing in Android?

Using MuscleCard on Android

Android doesn't support PKCS#11 modules, so in order to expose the cards crypto functionality we could implement a custom JCE provider that provides card-backed implementations of the Signature and KeyStrore engine classes. That is quite a bit of work though, and since we are only targeting the Bouncy Castle S/MIME API, we can get away by implementing the ContentSigner interface. It provides an OutputStream clients write data to be signed to, an AlgorithmIdentifer for the signature method used and a getSignature() method that returns the actual signature value. Our MuscleCard-backed implementation could look like this:

class MuscleCardContentSigner implements ContentSigner {

    private ByteArrayOutputStream baos = new ByteArrayOutputStream();
    private MuscleCard msc;
    private String pin;
...
    @Override
    public byte[] getSignature() {
        msc.select();
        msc.verifyPin(pin);

        byte[] data = baos.toByteArray();
        baos.reset();
        return msc.sign(data);
    }
}

Here the MuscleCard class is our 'middleware' and encapsulates the card's RSA signature functionality. It is implemented by sending the required command APDUs for each operation using Android's IsoDep API and aggregating and converting the result as needed. For example, the verifyPin() is implemented like this:

class MuscleCard {

   private IsoDep tag;

   public boolean verifyPin(String pin) throws IOException {
      String cmd = String.format("B0 42 01 00 %02x %s", pin.length(),
                                 toHex(pin.getBytes("ASCII")));
      ResponseApdu rapdu = new ResponseApdu(tag.transceive(fromHex(cmd)));
      if (rapdu.getSW() != SW_SUCCESS) {
         return false;
      }

      return true;
   }
}

Signing is a little more complicated because it involves creating and updating temporary I/O objects, but follows the same principle. Since the applet does not support padding or hashing, we need to generate and pad the PKCS#1 (or PSS) signature block on Android and send the complete data to the card. Finally, we need to plug our signer implementation into the Bouncy Castle CMS generator:

ContentSigner mscCs = new MuscleCardContentSigner(muscleCard, pin);
gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(
                           new JcaDigestCalculatorProviderBuilder()
                           .setProvider("SC")
                           .build()).build(mscCs, cardCert));

After that the signed message can be generated exactly like when using local key store keys. Of course, there are a few caveats. Since apps cannot control when an NFC connection is established, we can only sign data after the card has been picked up by the device and we have received an Intent with a live IsoDep instance. Additionally, since signing can take a few seconds, we need to make sure the connection is not broken by placing the device on top of the card (or use some sort of awkward case with a card slot). Our implementation also takes a few shortcuts by hard-coding the certificate object ID and size, as well as the card PIN, but those can be remedied with a little more code. The UI of our homebrew S/MIME client is shown below.


After you import a PKCS#12 file in the system credential store you can sign emails using the imported keys. The 'Sign with NFC' button is only enabled when a compatible card has been detected. The easiest way to verify the email signature is to send a message to a desktop client that supports S/MIME. There are also a few Android email apps that support S/MIME, but setup can be a bit challenging because they often use their own trust and key stores. You can also dump the generated message to external storage using MimeMessage.writeTo() and then parse the CMS structure using the OpenSSL cms command:

$ openssl cms -cmsout -in signed.message -noout -print
CMS_ContentInfo: 
  contentType: pkcs7-signedData (1.2.840.113549.1.7.2)
  d.signedData: 
    version: 1
    digestAlgorithms:
        algorithm: sha512 (2.16.840.1.101.3.4.2.3)
        parameter: NULL
    encapContentInfo: 
      eContentType: pkcs7-data (1.2.840.113549.1.7.1)
      eContent: <absent>
    certificates:
      d.certificate: 
        cert_info: 
          version: 2
          serialNumber: 4
          signature: 
            algorithm: sha1WithRSAEncryption (1.2.840.113549.1.1.5)
            ...
    crls:
      <empty>
    signerInfos:
        version: 1
        d.issuerAndSerialNumber: 
          issuer: C=JP, ST=Tokyo, CN=keystore-test-CA
          serialNumber: 3
        digestAlgorithm: 
          algorithm: sha512 (2.16.840.1.101.3.4.2.3)
          parameter: NULL
        signedAttrs:
            object: contentType (1.2.840.113549.1.9.3)
            value.set:
              OBJECT:pkcs7-data (1.2.840.113549.1.7.1)

            object: signingTime (1.2.840.113549.1.9.5)
            value.set:
              UTCTIME:Oct 25 16:25:29 2013 GMT

            object: messageDigest (1.2.840.113549.1.9.4)
            value.set:
              OCTET STRING:
                0000 - 88 bd 87 84 15 53 3d d8-72 64 c7 36 f8   .....S=.rd.6.
                000d - b0 f3 39 90 b2 a4 77 56-5c 9f e4 2e 7c   ..9...wV\...|
                001a - 7d 2e 0b 08 b4 b7 e7 6c-e9 b6 61 00 13   }......l..a..
                0027 - 25 62 69 2a bc 08 5b 4c-4f c9 73 cf d3   %bi*..[LO.s..
                0034 - c6 1e 51 c2 5f c1 64 77-3b 45 e2 cb      ..Q._.dw;E..
        signatureAlgorithm: 
          algorithm: rsaEncryption (1.2.840.113549.1.1.1)
          parameter: NULL
        signature: 
          0000 - a0 d0 ce 35 46 8c f9 cd-e5 db ed d8 e3 f0 08   ...5F..........
          ...
        unsignedAttrs:
          <empty>

Email encryption using the NFC smart card can be implemented in a similar fashion, but this time the card will be required when decrypting the message.

Summary

Practically all NFC-enabled Android devices can be used to communicate with a contactless or dual-interface smart card. If the interface of card applications is known, it is fairly easy to implement an Android component that exposes card functionality via a custom interface, or even as a standard JCE provider. The card's cryptographic functionality can then be used to secure email or provide HTTPS and VPN authentication. This could be especially useful when dealing with keys that have been generated on the card and cannot be extracted. If a PKCS#12 backup file is available, importing the file in the system credential store can provide a better user experience and comparable security levels if the device has a hardware-backed credential store.