NFC smart cards
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
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 theContent-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 theSignedData
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
- 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
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 theSignature
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.