CMS and S/MIME

CMS and S MIME

Overview

Although you're probably getting comfortable with using the cryptography facilities in Java and working with the both the Sun providers and the Bouncy Castle providers, you probably don't want to rush out and develop your own messaging protocol if you don't need to. Even if you do want to, it is worth spending some time on what some of the existing practices are. A lot of work has already been done in the area and there is a great deal of benefit to be gained from it.

This chapter looks at the secure messaging standards known as Cryptographic Message Syntax (CMS) and a related standard built on top of CMS for processing MIME messages securely ”S/MIME. As well as providing some background into how these two standards work, the chapter also provides details on how to make use of the Bouncy Castle APIs that implement them.

By the end of this chapter you should

Finally, you should also know how to combine S/MIME messaging types and be able to deal with MIME multipart data when doing so.

Getting Started

The first thing you'll notice looking at the utilities class is that it is making use of the JavaMail API. So before you try entering it, you will need to download the JavaMail distribution, follow the installation instructions, and install it into your Java environment, if you have not already. Note the JavaMail API also requires you to install the JavaBeans Activation Framework (JAF) ”you'll find details on this on the JavaMail Web page as well.

As it happens, the Bouncy Castle APIs for CMS and S/MIME I'll be using in this chapter are also distributed in a different JAR file from the provider, so you will need to install that as well. You can find it at the same place you got the provider, on http://www.bouncycastle.org/latest_releases.html. Similar to the provider jar file, the Bouncy Castle mail API JAR files follow the naming convention bcmail-JdkVersion-Version.jar . So if you are looking at version 1.28 for JDK 1.4, the filename would be bcmail-jdk14-128.jar . As with the provider JAR in Chapter 1, all you need to do to install the Bouncy Castle mail JAR file is copy the JAR file into the lib/ext (or libext ) directory of the JRE you are using.

After you have installed the JavaMail and Bouncy Castle mail JARs, you can proceed with compiling the following Utils class to begin the chapter9 package:

package chapter9; import java.security.KeyStore; import java.security.cert.*; import java.util.*; import javax.mail.Address; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import javax.security.auth.x500.X500PrivateCredential; /** * Chapter 9 Utils */ public class Utils extends chapter8.Utils { public static char[] KEY_PASSWD = "keyPassword".toCharArray(); /** * Create a KeyStore containing the a private credential with * certificate chain and a trust anchor. */ public static KeyStore createCredentials() throws Exception { KeyStore store = KeyStore.getInstance("JKS"); store.load(null, null); X500PrivateCredential rootCredential = Utils.createRootCredential(); X500PrivateCredential interCredential = Utils.createIntermediateCredential( rootCredential.getPrivateKey(), rootCredential.getCertificate()); X500PrivateCredential endCredential = Utils.createEndEntityCredential( interCredential.getPrivateKey(), interCredential.getCertificate()); store.setCertificateEntry( rootCredential.getAlias(), rootCredential.getCertificate()); store.setKeyEntry( endCredential.getAlias(), endCredential.getPrivateKey(), KEY_PASSWD, new Certificate[] { endCredential.getCertificate(), interCredential.getCertificate(), rootCredential.getCertificate() }); return store; } /** * Build a path using the given root as the trust anchor, and the passed * in end constraints and certificate store. * * Note: the path is built with revocation checking turned off. */ public static PKIXCertPathBuilderResult buildPath( X509Certificate rootCert, X509CertSelector endConstraints, CertStore certsAndCRLs) throws Exception { CertPathBuilder builder = CertPathBuilder.getInstance("PKIX", "BC"); PKIXBuilderParameters buildParams = new PKIXBuilderParameters( Collections.singleton( new TrustAnchor(rootCert, null)), endConstraints); buildParams.addCertStore(certsAndCRLs); buildParams.setRevocationEnabled(false); return (PKIXCertPathBuilderResult)builder.build(buildParams); } /** * Create a MIME message from using the passed-in content. */ public static MimeMessage createMimeMessage( String subject, Object content, String contentType) throws MessagingException { Properties props = System.getProperties(); Session session = Session.getDefaultInstance(props, null); Address fromUser = new InternetAddress( ""Eric H. Echidna""); Address toUser = new InternetAddress("example@bouncycastle.org"); MimeMessage message = new MimeMessage(session); message.setFrom(fromUser); message.setRecipient(Message.RecipientType.TO, toUser); message.setSubject(subject); message.setContent(content, contentType); message.saveChanges(); return message; } }

The new Utils class adds three items of functionality. The createCredentials() method creates a KeyStore containing a private key, a validating certificate chain, and the trust anchor for the chain. The buildPath() method allows you to create certificate paths given a trust anchor, some end constraints, and a collection of certificates. Finally, the createMimeMessage() method creates a MimeMessage with sufficient headers so it will work as a mail message.

I'm not going to be giving an in-depth discussion of the JavaMail API in this chapter, except as is relevant to its use with the Bouncy Castle S/MIME API. However, if you have a look at the documentation that accompanies the JavaMail API, you will realize that not much more work is required to take a MimeMessage object such as the one created by Utils.createMimeMessage() and feed it into a mailer.

At this point, if the Utils class has compiled successfully and you have installed the JavaMail and the Bouncy Castle mail JAR files, you are ready to proceed with the first topic ”Cryptographic Message Syntax.

Cryptographic Message Syntax

Cryptographic Message Syntax, or CMS, provides an encapsulation syntax for data that is encrypted or signed. A feature of it is that it allows different types of protection mechanisms to be nested(a signed message can then be used as the plaintext for an encrypted one, or an encrypted one can be signed. Signed messages can also have attributes attached to them that can be included in the final signature. It's an important protocol because not only is it used by itself, but it also forms the basis of other protocols such as those used by S/MIME and its variants, such as AS2 (Applicability Statement 2) and RosettaNet. If you are interested, you can find some references on these in Appendix D. It also works very well with the other PKIX standards you have looked at, so you can take full advantage of things like certificate path validation and the like.

CMS was originally defined in RSA Security's PKCS #7. Along the way it was picked up as an RFC and is now represented by RFC 3852, which is the definition I will be working from here. In the Bouncy Castle APIs, there are two packages devoted to CMS: org.bouncycastle.cms contains the high-level classes that handle the creation of messages involving encryption, signing, and compression, as well as their processing; and org.bouncycastle.asn1.cms is a collection of low-level classes that provide Java object equivalents for the ASN.1 structures defined in the CMS ASN.1 module. The packages actually represent a subset of what is detailed in the RFC, but it is the subset that is most commonly used and the same subset that is required to implement S/MIME.

For the most part the high-level package is all you need to use. However, because the classes in it are related to the ASN.1 structures that make up the protocol, I'll work through them both in tandem so you have a feel for the API as well as a feel for what is going on under the covers.

Basic CMS

The basic structure in CMS is the ContentInfo object. You saw this structure in Chapter 8, where it provided a general construct for building a PKCS #12 keystore. PKCS #7 is where the ContentInfo structure is originally derived from. You probably remember the definition of it given in Chapter 8, but I'll give it again here. You'll note that this definition is different in that it shows use of the older style of ASN.1, because I am using the definition from the RFC. Although the encoded structure of the two ContentInfo objects will look the same, the definition you are now working from is as follows :

ContentInfo ::= SEQUENCE { ContentType ContentType, content [0] EXPLICIT ANY DEFINED BY contentType }

where the ContentType type is defined as:

ContentType ::= OBJECT IDENTIFIER

As you saw in Chapter 8, the idea of the ContentInfo structure is to carry other objects tagged with an object identifier. As it turns out, ContentInfo can even contain other ContentInfo objects ”in which case the OID that you will find in the contentType field is

id-ct-contentInfo OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9) smime(16) ct(1) 6 }

In the discussion here, you will be looking closely at four types that can be contained in a ContentInfo object: Data, SignedData, EnvelopedData, and CompressedData .

The last three of these message structures are versioned; the versioning is indicated by a number found at the start of the structure. Because this standard has been evolving for a while, there is a range of different version numbers for CMS structures defined in the type CMSVersion as follows:

CMSVersion ::= INTEGER { v0(0), v1(1), v2(2), v3(3), v4(4) v5(5) }

Anumber of other structures are also versioned in CMS. Usually the CMS version number associated with a particular structure depends on the presence, or not, of optional fields or CHOICE types in the structure being looked at, or it depends on the value of the CMS version numbers in fields of the structure that represent other CMS structures. If this is the case, I will generally discuss the version number value last when reviewing a structure, even though, when present, it is normally the first field.

The Data Content Type

The fundamental content type in CMS is the Data content type. Its presence in a ContentInfo structure is indicated by the contentType field being set to the following value:

id-data OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs7(7) 1 }

You will probably recognize this as being the same value given for the data OID in Chapter 8 but with a different name . This will happen a few times in this chapter, so I won't bring it up again, but if anything, it will tell you something about what tends to happen as ASN.1 modules are developed for related standards.

The Data content type indicates that the content field in the ContentInfo is simply an OCTET STRING . The octets referred to might well have some other structure hidden in them, but from the point of view of CMS, they will be treated simply as a string of octets with any further interpretation being done later by the application. Likewise, in the Bouncy Castle API, a generic interface is provided to represent the data objects for processing by the API.

The CMSProcessable Interface

The org.bouncycastle.CMSProcessable interface provides the means for introducing a stream of data into the CMS classes, in some ways providing a Java equivalent to the Data content type.

Because the interface is both quite small and very important, I've reproduced it here.

package org.bouncycastle.cms; import java.io.IOException; import java.io.OutputStream; public interface CMSProcessable { public void write(OutputStream out) throws IOException, CMSException; public Object getContent(); }

As you can see, it has two methods . The first one, write() , takes an OutputStream as a parameter and is invoked by the CMS API when it needs the data being signed, encrypted, or compressed to be made available. The write() method may be invoked multiple times, and each time it is, all the bytes representing the object being processed should be written to the stream passed in. The second method, getContent() , is provided as a means of retrieving the data in some other form and is provided as a convenience method that can be used to recover the data if it was encapsulated in the CMS message.

The CMS package also includes a class called CMSProcessableByteArray that can act as a carrier for an array of bytes to be processed by the API. If you need to process other data objects, such as files, you can use the source of CMSProcessableByteArray as a guide to implementing the appropriate object. You learn how to use CMSProcessableByteArray in the next section when you look a CMS signed-data .

CMS Signed Data

CMS signed-data consists of a content holder, zero or more signature structures, and optionally a collection of certificates and CRLs that may be associated with the signatures. The content holder can contain the data the signature structures were generated for, but it can also be empty, indicating that the signeddata message is actually detached and represents signatures created for an external data source, giving you the two processes you see in Figure 9-1. If there are no signature structures attached to the message, the message exists purely to carry the certificates and CRLs it contains ”messages of this type are created when a certificate path is encoded in PKCS #7 format.

Verifying a signature for a given signer is a matter of finding the signature structure that matches the signer you are trying to verify for. Once you have a match, the process is one of using the source data for the signature to verify the signature value using the public key of the signer you have matched.

Figure 9-1

As usual, signature values are constructed using a combination of hashing and encryption. The recommend hash algorithms for signed-data are defined in RFC 3370. Although I use different algorithms in the examples in the chapter, it is worth noting that SHA-1 is still mandated as the algorithm of choice. For your own purposes, although it is safe to use the better hash algorithms, you will probably find you will need to use SHA-1 to produce signed messages that a variety of other applications understand. It is likely that work on a revision of RFC 3370 will begin soon, but keep this warning in mind as you look at the ASN.1 structure and move on to the Java examples.

ASN 1 Structure

CMS signed-data is created by wrapping a SignedData structure in a ContentInfo structure with the contentType field set to the OID id-signedData , which is defined as:

id-signedData OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs7(7) 2 }

When the value id-signedData is found in the contentType field of a ContentInfo structure, the value present in the content field will be

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

As each signature for the data being signed is generated for each signer, the signature and attribute information are used to create a signer information structure that is accumulated in the signerInfos field, and the certificates and CRLs relevant to the signature are accumulated directly in the SignedData structure in the certificates and crls fields.

The Java equivalent for the structure is represented by the CMSSignedData class in the org.bouncycastle.cms package. As the CMSSignedData class abstracts some of the internals of the SignedData structure, I'll go through the details of how the ASN.1 structure is put together first and then look at the related Java classes.

The DigestAlgorithms Field

The digestAlgorithms field has the type DigestAlgorithmIdentifiers , which is defined as:

DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier DigestAlgorithmIdentifier ::= AlgorithmIdentifier

which is the same structure you saw in Chapter 5.

The EncapContentInfo Field

The encapContentInfo field has the type EncapsulatedContentInfo , which is defined as:

EncapsulatedContentInfo ::= SEQUENCE { eContentType ContentType, eContent [0] EXPLICIT OCTET STRING OPTIONAL }

Most signatures you deal with will be what are commonly referred to as detached or external signatures, where the signature and the data the signature is for are separate. When the data is contained in the signature, the data plus signature is referred to as encapsulated signed data . In the case of a signature where the data is encapsulated, the eContent field in the EncapsulatedContentInfo will contain an OCTET STRING that represents the BER or DER encoding of the data that was signed. In the case where the signature is external, the eContent field will be missing and the eContentType will be set with a value that is appropriate to the data that was signed ”normally id-data .

If you have a look at the ASN.1 module for PKCS #7, you'll realize that the field in the SignedData structure that would represent encapContentInfo is defined as being of the type ContentInfo , meaning that the content field is of type ANY rather than having an eContent field of the type OCTET STRING . In practice this does not generally matter, but it does mean that, technically, it is possible to produce a message with an orthodox PKCS #7 implementation that you cannot process simply using CMS as defined by RFC 3852. If you need to process messages like this, some working around is required, as described in section 5.2.1 of the RFC.

The Certificates and Crls Fields

The certificates and crls fields have the types CertificateRevocationLists and CertificateSet respectively. These two types are defined as:

RevocationInfoChoices ::= SET OF RevocationInfoChoice RevocationInfoChoice ::= CHOICE { crl CertificateList, other [1] IMPLICIT OtherRevocationInfoFormat } OtherRevocationInfoFormat ::= SEQUENCE { otherRevInfoFormat OBJECT IDENTIFIER, otherRevInfo ANY DEFINED BY otherRevInfoFormat } CertificateSet ::= SET OF CertificateChoices CertificateChoices ::= CHOICE { certificate Certificate, v2AttrCert [2] IMPLICIT AttributeCertificateV2, other [3] IMPLICIT OtherCertificateFormat } AttributeCertificateV2 ::= AttributeCertificate OtherCertificateFormat ::= SEQUENCE { otherCertFormat OBJECT IDENTIFIER, otherCert ANY DEFINED BY otherCertFormat }

If you look at the RFC you'll see I've deleted two obsolete fields from the CertificateChoices structure in order to simplify things. However, the CertificateList type is just the same one you saw for a CRL in Chapter 7, and the definition of Certificate is the same as the one given in Chapter 6. The AttributeCertificate type is defined in RFC 3281 and represents a version 2 attribute certificate. OtherCertificateFormat you can leave to your imagination !

For the purposes of this chapter I'll be ignoring certificates of type OtherCertificateFormat and crls of the type OtherRevocationInfoFormat , as the types are provided to allow later expansion to CMS. I will also ignore attribute certificates, as they are not used for validating signatures ”they don't contain a public key. Instead they contain role information and are used to represent a digitally signed identity with certain privileges rather than a digitally signed key.

The SignerInfos Field

The signerInfos field has the type SignerInfos , which, together with its associated structure, SignerInfo , is defined as follows :

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

The version number of the SignerInfo structure is determined by the type of the value that the SignerIdentifier contains. The SignerIdentifier is of type CHOICE and is defined as follows:

SignerIdentifier ::= CHOICE { issuerAndSerialNumber IssuerAndSerialNumber, subjectKeyIdentifier [0] SubjectKeyIdentifier }

Where IssuerAndSerialNumber is defined as:

IssuerAndSerialNumber ::= SEQUENCE { issuer Name , serialNumber CertificateSerialNumber }

and SubjectKeyIdentifier is the same value as you would find in the SubjectKeyIdentifier extension on the certificate associated with the signer's private key. You should be able to recognize the IssuerAndSerialNumber structure as being made up of the two values that you were using with the X509CertSelector class in Chapter 6 to specify search criteria that will match only one certificate by using the issuer X500Principal and the certificate serial number.

Now getting back to the version number, if the SignerIdentifier contains an item of the type IssuerAndSerialNumber , the version number of the SignerInfo structure will be 1. If the SignerIdentifier contains a SubjectKeyIdentifier instead, the version number will be 3.

You have already seen that the type of the digestAlgorithm field, DigestAlgorithmIdentifier reduces to an AlgorithmIdentifier , and it will probably be no surprise to find that the type of the signatureAlgorithm field, SignatureAlgorithmIdentifier does too. The actual signature field's type SignatureValue is simply an OCTET STRING .

This leaves you with the signedAttrs and unsignedAttrs fields left. The definitions of the types associated with these two implicitly tagged fields are

SignedAttributes ::= SET SIZE (1..MAX) OF Attribute UnsignedAttributes ::= SET SIZE (1..MAX) OF Attribute

with Attribute being essentially the same structure you saw in Chapter 5.

The signed attributes are worth a bit of further discussion, as, if they are present, it means that the signature contained in the SignerInfo structure is really calculated on the hash of the DER encoding of the signed attribute set, not the data that is the subject of the SignedData structure. In this case the actual hash of the data will be present in a signed attribute called the message-digest attribute , and there will also be a content-type attribute that must match the value stored in the eContentType field in the EncapsulatedContentInfo structure contained in the SignedData structure.

The message-digest attribute is identified by the OID:

id-messageDigest OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9) 4 }

and it will contain an OCTET STRING in its value set representing the bytes making up the hash.

The content-type attribute can be identified by the OID:

id-contentType OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9) 3 }

and it will contain an OBJECT IDENTIFIER in its value set that must match the one in the eContentType field.

Although you can find a complete list of possible attributes in section 11 of RFC 3852, I'll mention one other signed attribute here as you will encounter it a lot ”the signing-time attribute. It is identified by the OID:

id-signingTime OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9) 5 }

and it contains a value of the type Time in its value set. The Time type is defined as:

Time ::= CHOICE { utcTime UTCTime, generalizedTime GeneralizedTime }

which is the same structure that you saw used in X.509 certificates in Chapter 6. As the name suggests, the signing-time attribute allows the signer to include the time at which the signing process was performed in the SignerInfo structure.

The Version Field

The value of version field in a SignedData structure depends on which optional fields are present, as well as what some of them contain. If the certificates or the crls field is present and either of them contain structures representing the choice item other , then the version field will have the value 5. If the certificates field contains version 2 attribute certificates, the version field will have the value 4. Otherwise , ignoring obsolete values, if any SignerInfo structure is version 3, or the eContentType field of the structure contained in encapContentInfo is not id-data , then the version field of the SignedData structure will be 3. If none of the previous conditions apply, the version field will have the value 1.

This concludes how the internal structure of a CMS signed-data message is put together. Next you'll look at the Java classes available to support it.

The SignerInformation Class

The org.bouncycastle.cms.SignerInformation class provides the Java equivalent to the SignerInfo objects. You will never construct one of these classes directly; instead, they are created as a result of creating CMSSignedData objects or from editing other SignerInformation objects.

As I go through the methods , you will see that the structure of the class largely reflects the SignerInfo structure, with the addition of some verify() methods for checking the signature and a static convenience method to allow the creation of a new SignerInformation object from another one that has different unsigned attributes.

SignerInformation.get DigestAlgOID()

The getDigestAlgOID() method returns a String representing the OID of the algorithm used as to create the message digest used in the signature calculation.

SignerInformation.get DigestAlgParams()

The getDigestAlgParams() method returns a byte array containing the DER-encoded parameters for he message digest used or null if they are absent.

SignerInformation.get EncryptionAlgOID()

The getEncryptionAlgOID() method returns a String representing the OID of the algorithm used to encrypt the message digest to create the signature in the SignerInfo structure.

SignerInformation.get EncryptionAlgParams()

The getEncryptionAlgParams() method returns a byte array containing the DER-encoded parameters for the algorithm used to encrypt the digest or null if they are absent.

SignerInformation.get SID()

The getSID() method returns an object representing the sid field in the SignerInfo structure. The object is of the type SignerId and can be used to identify the signer ”it overrides both Object.equals() and Object.hashCode() . The SignerId class is also an extension of the X509CertSelector class, so the object returned by getSID() can be used to index a CertStore to find the X509Certificate object the SignerInfo structure's sid value refers to.

SignerInformation.get Signature()

The getSignature() method returns the actual bytes that make up the signature stored in the signatureValue field in the SignerInfo structure. If you are planning to do any processing with these bytes, remember: If the signed attributes are present, the signature returned by this method represents a signature calculated from the signed attributes, not from the actual data that is the subject of the SignerInfo structure.

SignerInformation.get SignedAttributes()

The getSignedAttributes() method returns an org.bouncycastle.asn1.cms.AttributeTable object containing org.bouncycastle.asn1.cms.Attribute objects representing the signed attributes contained in the SingerInfo structure.

SignerInformation.get UnsignedAttributes()

The getUnsignedAttributes() method returns an AttributeTable object containing Attribute objects representing the unsigned attributes contained in the SingerInfo structure.

SignerInformation.verify()

The verify() method is used to verify the signature contained in the SignerInfo structure the SignerInformation object represents. There are two versions of the method. The first takes a PublicKey and a String representing the provider name to use for creating any necessary message digest and signature objects. The other takes an X509Certificate and a String representing the provider name to use for creating any cryptographic services required. In either case, if the provider is null, the JVM's default provider is used.

Both versions of the method return true if the signature validates , false otherwise. If the method taking the X509Certificate is used and there is a signingTime attribute present in the SignerInfo object, the certificate will be checked for validity at the time indicated by the signingTime attribute.

Both methods' versions can throw NoSuchAlgorithmException if it is not possible to create objects implementing the necessary algorithms to verify the signature, NoSuchProviderException if a provider was requested and could not be found, and CMSException if some other failure takes place. If the method taking the X509Certificate is used, it may also throw a CertificateExpiredException , or a CertificateNotYetValidException if the signingTime attribute is present and the certificate was not valid at the time indicated by the attribute.

SignerInformation.replace UnsignedAttributes()

The replaceUnsignedAttributes() method is a static method that takes a SignerInformation object and an AttributeTable object and returns a new SignerInformation object. The return value is a copy of the one passed to the method, but with its unsigned attributes now being the attributes contained in the AttributeTable that the method was called with.

The SignerInformationStore Class

The org.bouncycastle.cms.SignerInformatiomStore class is a simple collection class for containing SignerInformation objects. It has a single constructor that takes a collection of SignerInformation objects as its parameters and three methods: get(), getSigners() , and size() .

SignerInformationStore.get()

The get() method takes a SignerId object as its parameter and returns the SignerInformation object that matches it. If there is no matching object, the method returns null .

SignerInformationStore.get Signers()

The getSigners() method returns a collection containing all the SignerInformation objects contained in the store.

SignerInformation Store.size()

The size() method returns the number of SignerInformation objects contained in the store.

The CMSSignedData Class

The org.bouncycastle.cms.CMSSignedData class has four public constructors for general use. Objects and objects of the class can also be made using objects of the CMSSignedDataGenerator class, which is in the same package. Two are for dealing with CMS signed-data where the data is encapsulated in the signed-data message and take either an InputStream or a byte array that provides the bytes making up a ContentInfo structure and the SignedData structure it contains. The second two are for detached, or external, signatures and take a CMSProcessable in addition to an InputStream or a byte array. In the case of these last two constructors, the only thing expected in the InputStream or byte array is the encoded signed-data message with signatures. The verification calculations will be done using the data gained from the CMSProcessable . The constructors will throw a CMSException if there is a problem parsing the ContentInfo or SignedData structure from the source provided.

The CMSSignedData class is also designed to work in conjunction with the java.security.cert.CertStore class and to work with the SignerInformationStore class. As this is the case, it doesn't provide an immediate parallel to the ASN.1 structure, as some simplifications have been possible. With this in mind, you'll look at the methods on CMSSignedData now.

CMSSignedData.get CertificatesAndCRLs()

The getCertificatesAndCRLs() method returns a CertStore containing any regular X.509 certificates and certificate revocation lists found in the SignedData structure. Note that both the certificates and crls fields are optional. Therefore, although a CertStore will always be returned, it might be empty.

CMSSignedData.get Encoded()

The getEncoded() method returns the ASN.1 binary encoding of the SignedData structure contained in the object, together with its encapsulating ContentInfo object. This will normally be encoded using the BER rules rather than the DER ones. The method will throw an IOException if a problem occurs generating the encoding.

CMSSignedData.get SignedContent()

The getSignedContent() method returns the CMSProcessable that contains the source data for the signatures contained in the SignerInfo structures making up the message. You can either recover the data by passing an OutputStream to the write() method or by calling getContent() . If the data that the signatures in the message were calculated on is encapsulated in the SignedData structure, this is the method you need to call to recover the encapsulated data.

CMSSignedData.get SignedContentOID()

The getSignedContentOID() returns a String representing the object identifier found in the eContentType field of the EncapsulatedContentInfo structure found in the SignedData structure. Generally this will just be the value id-data , but some applications that use CMS as the transport mechanisms do demand specific values, so you should not assume it always will be.

CMSSignedData.get SignerInfos()

The getSignerInfos() method returns a SignerInformationStore that contains a collection of SignerInformation objects representing the SignerInfo structures contained in the SignedData structure.

CMSSignedData.replace Signers()

The static replaceSigners() method takes a CMSSignedData object and a SignerInformation store as parameters and returns a new CMSSignedData object that is a copy of the one passed in, except its SignerInfo objects have been replaced with the ones contained in the SignerInformation store. This may seem like a strange thing to do, but if you have created some new SignerInformation objects by adding unsigned attributes to already existing ones and you want to replace the originals on the CMSSignedData object, they came from the replaceSigners() method is the one you want to use.

That covers all the classes and methods you need to create an example. Let's try doing it.

Try It Out: Creating and Validating a Detached Signature

Before showing the example, I will define a parent class, which I'll be using for the other signed-data examples appearing in the chapter as well. The object of the parent class is to provide a method for validating the first signature contained in a SignedData structure against a given trust anchor and a store of certificates. Here is the parent class:

package chapter9; import java.security.cert.*; import java.util.Iterator; import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.cms.SignerInformation; import org.bouncycastle.cms.SignerInformationStore; /** * Base class for signed examples. */ public class SignedDataProcessor { /** * Return a boolean array representing keyUsage with digitalSignature set. */ static boolean[] getKeyUsageForSignature() { boolean[] val = new boolean[9]; val[0] = true; return val; } /** * Take a CMS Signed-Data message and a trust anchor and determine if * the message is signed with a valid signature from a end entity * entity certificate recognized by the trust anchor rootCert. */ public static boolean isValid( CMSSignedData signedData, X509Certificate rootCert) throws Exception { CertStore certsAndCRLs = signedData.getCertificatesAndCRLs( "Collection", "BC"); SignerInformationStore signers = signedData.getSignerInfos(); Iterator it = signers.getSigners().iterator(); if (it.hasNext()) { SignerInformation signer = (SignerInformation)it.next(); X509CertSelector signerConstraints = signer.getSID(); signerConstraints.setKeyUsage(getKeyUsageForSignature()); PKIXCertPathBuilderResult result = Utils.buildPath( rootCert, signer.getSID(), certsAndCRLs); return signer.verify(result.getPublicKey(), "BC"); } return false; } }

Next, you have the actual example class. It uses the CMSSignedDataGenerator to create a CMSSignedData object representing a detached signature for some byte data. The CMSSignedData object then gets encoded and reconstructed. You then use the isValid() method from the parent class to verify the signature. Here is the example class:

package chapter9; import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.*; import java.util.Arrays; import org.bouncycastle.cms.CMSProcessable; import org.bouncycastle.cms.CMSProcessableByteArray; import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.cms.CMSSignedDataGenerator; /** * Example of generating a detached signature. */ public class SignedDataExample extends SignedDataProcessor { public static void main(String[] args) throws Exception { KeyStore credentials = Utils.createCredentials(); PrivateKey key = (PrivateKey)credentials.getKey( Utils.END_ENTITY_ALIAS, Utils.KEY_PASSWD); Certificate[] chain = credentials.getCertificateChain( Utils.END_ENTITY_ALIAS); CertStore certsAndCRLs = CertStore.getInstance( "Collection", new CollectionCertStoreParameters( Arrays.asList(chain)), "BC"); X509Certificate cert = (X509Certificate)chain[0]; // set up the generator CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); gen.addSigner(key, cert, CMSSignedDataGenerator.DIGEST_SHA224); gen.addCertificatesAndCRLs(certsAndCRLs); // create the signed-data object CMSProcessable data = new CMSProcessableByteArray( "Hello World!".getBytes()); CMSSignedData signed = gen.generate(data, "BC"); // re-create signed = new CMSSignedData(data, signed.getEncoded()); // verification step X509Certificate rootCert = (X509Certificate)credentials.getCertificate( Utils.ROOT_ALIAS); if (isValid(signed, rootCert)) { System.out.println("verification succeeded"); } else { System.out.println("verification failed"); } } }

Running the example, you should see the following output:

verification succeeded

This indicates that the data was signed successfully and the signature in the signed-data message was verified .

How It Works

Because this example is in two parts , I'll start by describing the SignedDataProcessor class and then review how the SignedDataExample class interacts with it.

Looking at SignedDataProcessor , you can see the isValid() method uses the buildPath() method you added to Utils for creating a validated CertPath and builds on the work you did in Chapter 7 on path validation using the CertPath API. The return value of the signer.getSID() provides the target constraints for the path building. Once the path is built, the public key for the end entity certificate is obtained from the PKIXCertPathBuilderResult object and the signature carried in the signer object is verified using a call to signer.verify() . The one extra twist is the use of the getKeyUsageForSignature() method, which returns a boolean array that will match a KeyUsage extension if the digitalSignature bit is set. Adding this to the signerConstraints object means that, if the KeyUsage extension is present in the signer's certificate, the certificate will only match if it can be used for validating digital signatures.

This brings you to the main driver in the SignedDataExample class. After the initialization code, a CMSSignedDataGenerator object is created to allow you to create the CMSSignedData object you want. A signer is added to the generator using the following code:

gen.addSigner(key, cert, CMSSignedDataGenerator.DIGEST_SHA224);

where key represents the signer's private key, cert represents the end entity certificate associated with the signer's private key, and the constant parameter at the end tells the generator that this signer wants to use the algorithm SHA-224.

Next, the certificates associated with the signer are added to the generator using

gen.addCertificatesAndCRLs(certsAndCRLs);

After a sample data object using CMSProcessableByteArray is created, the CMSSignedData object is created with the following line:

CMSSignedData signed = gen.generate(data, "BC");

Then signed is re-created using its ASN.1 binary encoding and the data object:

signed = new CMSSignedData(data, signed.getEncoded());

You have to pass the data object to the constructor because, in this case, the signature generated has been a detached one that does not encapsulate the data. If the CMSSignedData object was created with encapsulated data, the lines of code involving generation and re-creation would have read

CMSSignedData signed = gen.generate(data, true, "BC"); // re-create signed = new CMSSignedData(signed.getEncoded());

As you can see, the constructor no longer requires the data to be passed to the constructor, because the binary encoding of the message produced by signed.getEncoded() contains the data as well.

After generation and re-creation, the example tries to verify the signature included in the CMSSignedData object. This just involves passing signed and the trust anchor rootCert to the isValid() method and letting the CertPath API go to work.

One last note before you go on to enveloped-data . RFC 3852 does not require either the certificates or CRLs related to the signers to be present in the SignedData structure. Therefore, depending on where your messages are coming from, the path validation associated with signature verification might be a bit more complicated or even a lot easier. You might be using a hard-coded certificate. In any case, you should get the general idea, just keep the things that are not specifically required by the RFC in mind. It will help avoid surprises .

CMS Enveloped Data

Enveloped-data is constructed by first encrypting the data to be enveloped with a symmetric key and then encrypting the symmetric key with the algorithms appropriate for the intended recipients of the message using the process described in Figure 9-2.

Figure 9-2

The enveloped-data message is then the result of encapsulating the encrypted data in the message along with the information required for each recipient. When a recipient receives an enveloped-data message, the recipient extracts the pertinent recipient information and use it to recover the key used to encrypt the data. The recipient can then recover the encrypted data.

The recommended encryption algorithms that can be used for enveloping data were initially defined in RFC 3370 as DES-EDE and RC2. There is a wider choice than that, with mechanisms based on the ciphers CAST (RFC 2984), IDEA (RFC 3058), SKIPJACK (RFC 2876), and AES (RFC 3565) also available, to name but a few. As you'll see, there are also a variety of mechanisms available for transporting the symmetric keys used to encrypt the data.

ASN 1 Structure

CMS enveloped-data is created by wrapping an EnvelopedData structure in a ContentInfo structure with the contentType field set to the OID id-envelopedData , which is defined as:

id-envelopedData OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs7(7) 3 }

When the value id-envelopedData is found in the contentType field of a ContentInfo structure, the value present in the content field will be

EnvelopedData ::= SEQUENCE { version CMSVersion, originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL, recipientInfos RecipientInfos, encryptedContentInfo EncryptedContentInfo, unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL }

The Java class for representing enveloped-data is the CMSEnvelopedData class.

The OriginatorInfo Field

The originatorInfo field, if present, provides information about the originator of the enveloped-data. The ASN.1 definition of OriginatorInfo type is given in RFC 3852 as follows :

OriginatorInfo ::= SEQUENCE { certs [0] IMPLICIT CertificateSet OPTIONAL, crls [1] IMPLICIT RevocationInfoChoices OPTIONAL }

How the contents of the certs and the crls fields are interpreted is largely up to the application reading it. Ideally there will be enough information to validate any end entity and attribute certificates present in the CertificateSet representing the certs field. However, RFC 3852 does allow for both unnecessary certificates and CRLs to be present, as well as insufficient certificates and CRLs to be present.

The RecipientInfos Field

The recipientInfos field is a collection representing the recipients the enveloped-data has been created for its ASN.1 definition and that of its member structure. RecipientInfo is as follows:

RecipientInfos ::= SET SIZE (1..MAX) OF RecipientInfo RecipientInfo ::= CHOICE { ktri KeyTransRecipientInfo, kari [1] KeyAgreeRecipientInfo, kekri [2] KEKRecipientInfo, pwri [3] PasswordRecipientinfo, ori [4] OtherRecipientInfo }

As you can see, the definition requires that there must be at least one recipient for an enveloped-data message to be valid.

RFC 3852 mandates that conforming CMS implementations should be able to support the first three recipient choices: KeyTransRecipientInfo, KeyAgreeRecipientInfo , and KEKRecipientInfo . In practice, the first and third are the two common ones, although PasswordRecipientInfo , originally detailed in RFC 3211, is starting to generate a lot of interest these days. For the purposes of this chapter, I am just going to discuss the two most common ones ”the KeyTransRecipientInfo type and the KEKRecipientInfo type.

Amessage can have more than one recipient type, but if you are doing this, you should always remember that the message can only be regarded as secure as the weakest recipient you have included, since compromising the weakest recipient will allow an attacker to recover the key used to encrypt the message.

The reason, as already mentioned, is that the recipients stored in an EnvelopedData structure are created as key wrappers for a symmetric key. Another aspect is that, as it is a common property, you will find the ASN.1 structure of the different recipient types reflects this. If you look at the ASN.1 module at the end of RFC 3852, you will find that the structures defining the first four choices all define two fields of the following ASN.1 types at some level:

EncryptedKey ::= OCTET STRING KeyEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier

with a field of the type EncryptedKey representing the bytes making up the wrapped symmetric key used to encrypt the data enveloped in the message and the KeyEncryptionAlgorithmIdentifier representing the algorithm that was used to do the wrapping. Keep these types in mind, as you will encounter them again when you look at the Java classes that are used to represent the various recipients and how they relate to the lower-level RecipientInfo choice items they represent.

The EncryptedContentInfo Field

The encryptedContentInfo field holds the data that was enveloped and the algorithm details for the encryption algorithm used to encrypt the data as part of enveloping it. The definition of its ASN.1 type is given as follows:

EncryptedContentInfo ::= SEQUENCE { contentType ContentType, contentEncryptionAlgorithm ContentEncryptionAlgorithmIdentifier, encryptedContent [0] IMPLICIT EncryptedContent OPTIONAL }

As you can see, the structure is fairly straightforward. There is an OID hinting at what the plaintext of the encrypted content looks like ”usually this is just id-data . The ContentEncryptionAlgorithmIdentifier type is defined as AlgorithmIdentifier and provides the details of the encryption algorithm used to create the encrypted content. Finally, there is the encrypted content itself, with the EncryptedContent type, which is defined as OCTET STRING .

If you look at RFC 3370 to see some of the encryption algorithms that can be used with CMS, you will see that they are block ciphers. As this is the case, RFC 3852 also defines the padding mechanism to be applied to the data before encryption if the data is not block aligned. It is the same scheme as that specified in PKCS #7, and if you are trying to decrypt the bytes stored in the encryptedContent field by hand, and the cipher used is a block cipher, you will need to remove the pad bytes to correctly recover the data if your cipher implementation does not do so.

The UnprotectedAttrs Field

The unprotectedAttrs field provides a mechanism for carrying attributes along with the EnvelopedData structure. Like the signed and unsigned attributes associated with a SignerInfo , it is just a SET of Attribute objects. RFC 3852 does not define any specific attributes for use here, but you could use the unprotected attributes to carry things like a digital timestamp of the encrypted data or other associated information that does not need to be encrypted.

The Version Field

The value of the version field in an EnvelopedData structure depends on which optional fields are present, as well as what some of them contain. If the CertificateSet contained in the originatorInfo field contains version 2 attribute certificates or the recipientInfos field contains recipients of the types PasswordRecipientInfo or OtherRecipientInfo , the version field will have the value 3. Otherwise , if orginatorInfo field is present or the unprotectedAttrs field is present or any RecipientInfo structure is other than version 0, then the version field will be 2. If none of the previous conditions apply, the version field will have the value 0.

This brings your look at the ASN.1 structures directly involved in the construction of an EnvelopedData structure to an end. I'll now start going through the Java classes, starting with the recipient- related ones and building up to the class representing the EnvelopedData structure.

The RecipientInformation Class

As you saw earlier, RecipientInfo is actually a CHOICE type with a range of possible types under it. The org.bouncycastle.cms.RecipientInfomation class is the parent class for the classes that implement the functionality required to recover keys that have been encrypted using the techniques represented by the possible choice items for RecipientInfo .

Objects of the type RecipientInformation and its extensions do not get created directly but are instead created as a result of constructing CMSEnvelopedData objects. As you will see when the extension classes are looked at, the methods on the RecipientInformation class capture what is common in the ASN.1 structures that are valid choice items for RecipientInfo .

RecipientInformation.get Content()

The getContent() method is an abstract method that takes a Key and a String representing the name of a provider to use and attempts to decrypt the data contained in the EnvelopedData structure, returning it in a byte array if successful.

The method will throw a NoSuchProviderException if it is unable to locate the provider with the passed-in name or a CMSException if any other problems occur.

RecipientInformation.get KeyEncryptionAlgOID()

The getKeyEncryptionAlgOID() returns a String representing the OID identifying the encryption algorithm used to encrypt this RecipientInformation objects version of the data encryption key.

RecipientInformation.get KeyEncryptionAlgorithmParameters()

The getKeyEncryptionAlgorithmParameters() method takes a String representing a provider to use and attempts to generate an AlgorithmParameters object that holds the encryption parameters used to encrypt the version of the data encryption key the RecipientInformation object holds.

It will throw a NoSuchProviderException if it is unable to locate the provider with the passed-in name or a CMSException if any other problems occur.

RecipientInformation.get KeyEncryptionAlgParams()

The getKeyEncryptionAlgParams() method returns a byte array containing the DER-encoded parameters that the key encryption algorithm for this RecipientIdentifier object used. The method returns null if the parameters are absent.

RecipientInformation.getRID()

Every recipient type also has some notion of a recipient identifier attached to it. The getRID() method returns an object representing a general identifier type for recipients ”the RecipientId . A RecipientId object can be used to identify recipients, as it overrides both Object.equals() and Object.hashCode() .

Like the SignerId class, it is also an extension of the X509CertSelector class, so if the recipient type indicates a public key was used to the encrypt the data encryption key, the object returned by getRID() can be used to index a CertStore to find the X509Certificate object carrying the public key that was used.

The KeyTransRecipientInformation Class

The org.bouncycastle.cms.KeyTransRecipientInformation class is an extension of the RecipientInfomation class and provides the necessary functionality to deal with enveloped-data where the data encryption key has been encrypted with a public key using an algorithm like RSA.

Recipients where the data encryption key has been handled in this fashion are indicated by the presence of a KeyTransRecipientInfo structure in the RecipientInfo CHOICE type. The KeyTransRecipientInfo structure has the following definition:

KeyTransRecipientInfo ::= SEQUENCE { version CMSVersion, rid RecipientIdentifier, keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier, encryptedKey EncryptedKey }

where RecipientIdentifier is defined as:

RecipientIdentifier ::= CHOICE { issuerAndSerialNumber IssuerAndSerialNumber, subjectKeyIdentifier [0] SubjectKeyIdentifier }

which is identical to the SignerIdentifier type you saw earlier in the SignedData structure.

The class provides the implementation for the getContent() method required by the RecipientInformation class. The rid field becomes a RecipientId , with the fields of the type RecipientIdentifier being absorbed by that class, and the values in the keyEncryptionAlgorithm field are made available through the parent class. The version number is not exposed by the API but will either be 0 if the RecipientId.getSubjectKeyIdentifier() method returns a null, indicating the RecipientId object has been set with the details in a IssuerAndSerialNumber structure, or 2 otherwise.

The RecipientInformationStore Class

The org.bouncycastle.cms.RecipientInformationStore class is a simple collection class for containing RecipientInformation objects. It has one constructor that takes a collection of RecipientInformation objects as its parameter and three methods: get(), getRecipients() , and size() .

RecipientInformationStore.get()

The get() method takes a RecipientId object as its parameter and returns the RecipientInformation object that matches it. If there is no matching object, the method returns null .

RecipientInformationStore.get Recipients()

The getRecipients() method returns a collection containing all the RecipientInformation objects contained in the store.

RecipientInformationStore.size()

The size() method returns the number of RecipientInformation objects contained in the store.

The CMSEnvelopedData Class

The org.bouncycastle.cms.CMSEnvelopedData class has two constructors of for general use. Objects of the class can also be constructed using objects of the CMSEnvelopedDataGenerator class that can be found in the same package. The two constructors take a byte array and an input stream, respectively, where the parameter passed to the constructor is assumed to be a source for an ASN.1 binary-encoded ContentInfo structure containing an EnvelopedData object. The constructors will throw a CMSException if there is a problem parsing the ContentInfo or EnvelopedData structure from the source provided.

CMSEnvelopedData.getEncoded()

The getEncoded() method returns the ASN.1 binary encoding of the EnvelopedData structure contained in the CMSEnvelopedData object together with its encapsulating ContentInfo object. This will normally be encoded using the BER rules rather than the DER ones. The method will throw an IOException if a problem occurs generating the encoding.

CMSEnvelopedData.get EncryptionAlgOID()

The getEncryptionAlgOID() method returns a String representing the OID that identifies the algorithm used to encrypt the data contained in the object's EnvelopedData structure.

CMSEnvelopedData.get EncryptionAlgorithmParameters()

The getEncryptionAlgorithmParameters() method takes a single String representing the name of a provider to use and attempts to create an AlgorithmParameters object representing the algorithm parameters used to encrypt the data contained in the EnvelopedData structure the CMSEnvelopedData object represents.

The method will throw a NoSuchProviderException if it is unable to locate the provider with the passed-in name or a CMSException if any other problems occur.

CMSEnvelopedData.get EncryptionAlgParams()

The getEncryptionAlgParams() method returns a byte array representing the DER encoding of the algorithm parameters that were used to generate the encrypted data contained in the CMSEnvelopedData object's EnvelopedData structure.

CMSEnvelopedData.get RecipientInfos()

The getRecipientInfos() method returns a RecipientInformationStore containing all the RecipientInformation objects representing the contents of the recipientInfos field in the EnvelopedData structure represented by the CMSEnvelopedData object.

CMSEnvelopedData.get UnprotectedAttributes()

The getUnprotectedAttributes() method returns an AttributeTable of the unprotected attributes attached to the EnvelopedData structure. If the unprotectedAttrs field is missing, the method will return null .

At this point you have covered one recipient type and the support classes. Let's have a look at applying them.

Try It Out: Creating and Decoding CMS Enveloped-Data

This example shows how to create an enveloped-data message with a public key-based recipient in it. Have a look at it and try running it.

package chapter9; import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.Arrays; import org.bouncycastle.cms.CMSEnvelopedData; import org.bouncycastle.cms.CMSEnvelopedDataGenerator; import org.bouncycastle.cms.CMSProcessable; import org.bouncycastle.cms.CMSProcessableByteArray; import org.bouncycastle.cms.RecipientId; import org.bouncycastle.cms.RecipientInformation; import org.bouncycastle.cms.RecipientInformationStore; /** * Demonstrate creation and processing a public key recipient enveloped-message. */ public class KeyTransEnvelopedDataExample { public static void main(String[] args) throws Exception { KeyStore credentials = Utils.createCredentials(); PrivateKey key = (PrivateKey)credentials.getKey( Utils.END_ENTITY_ALIAS, Utils.KEY_PASSWD); Certificate[] chain = credentials.getCertificateChain( Utils.END_ENTITY_ALIAS); X509Certificate cert = (X509Certificate)chain[0]; // set up the generator CMSEnvelopedDataGenerator gen = new CMSEnvelopedDataGenerator(); gen.addKeyTransRecipient(cert); // create the enveloped-data object CMSProcessable data = new CMSProcessableByteArray( "Hello World!".getBytes()); CMSEnvelopedData enveloped = gen.generate( data, CMSEnvelopedDataGenerator.AES128_CBC, "BC"); // re-create enveloped = new CMSEnvelopedData(enveloped.getEncoded()); // look for our recipient identifier RecipientId recId = new RecipientId(); recId.setSerialNumber(cert.getSerialNumber()); recId.setIssuer(cert.getIssuerX500Principal().getEncoded()); RecipientInformationStore recipients = enveloped.getRecipientInfos(); RecipientInformation recipient = recipients.get(recId); if (recipient != null) { // decrypt the data byte[] recData = recipient.getContent(key, "BC"); // compare recovered data to the original data if (Arrays.equals((byte[])data.getContent(), recData)) { System.out.println("data recovery succeeded"); } else { System.out.println("data recovery failed"); } } else { System.out.println("could not find a matching recipient"); } } }

Running the example should produce the following output:

data recovery succeeded

indicating that the encrypted data stored in the enveloped-message was successfully recovered.

How It Works

After the basic initialization code, a CMSEnvelopedGenerator object is created and the recipient's public key information is added using the following line:

gen.addKeyTransRecipient(cert);

Adding the recipient extracts the serial number and the issuer from the certificate to store in the rid field of a KeyTransRecipientInfo structure that is created when the CMSEnvelopedData object is finally produced by the generator. It also extracts the public key so that it can be used for wrapping the symmetric key that is generated to encrypt the data when gen.generate() is finally called. For this recipient, the algorithm used for encrypting the symmetric key is derived from the type of the public key present in the certificate.

In the next step, the data is wrapped in a class that implements CMSProcessable . An EnvelopedData structure is generated and returned, wrapped in a CMSEnvelopedData object.

CMSEnvelopedData enveloped = gen.generate( data, CMSEnvelopedDataGenerator.AES128_CBC, "BC");

After that, the CMSEnvelopedData object is reconstructed from its encoded form and a RecipientId is created from the certificate to find the corresponding recipient in the enveloped message. The message is recovered, and after the decrypted data is verified , the output is printed.

Of course, there are a variety of ways you can use the RecipientId ; it depends on your circumstances. In some situations, you might have a couple of private keys that you use for decrypting messages you receive. If this is the case, you are probably better off using the information contained in the RecipientId object attached to the RecipientInformation object to determine whether you have a match against a particular private key first. Once you find a match against a RecipientId , you should be confident that the RecipientInformation object you have is one that should be used with your private key.

The following code shows one way of doing this. You add the necessary imports and replace everything following the line

// look for our recipient identifier

with

// set up to iterate through the recipients RecipientInformationStore recipients = enveloped.getRecipientInfos(); CertStore certStore = CertStore.getInstance( "Collection", new CollectionCertStoreParameters( Collections.singleton(cert)), "BC"); Iterator it = recipients.getRecipients().iterator(); RecipientInformation recipient = null; while (it.hasNext()) { recipient = (RecipientInformation)it.next(); if (recipient instanceof KeyTransRecipientInformation) { // match the recipient ID Collection matches = certStore.getCertificates(recipient.getRID()); if (!matches.isEmpty()) { // decrypt the data byte[] recData = recipient.getContent(key, "BC"); // compare recovered data to the original data if (Arrays.equals((byte[])data.getContent(), recData)) { System.out.println("data recovery succeeded"); break; } else { System.out.println("data recovery failed"); break; } } } } if (recipient == null) { System.out.println("could not find a matching recipient"); }

You should also see the data recovery succeeded message, indicating that the enveloped message was successfully decrypted and the plaintext recovered.

Still another approach when you're using a keystore is to match the certificate and then use KeyStore.getCertificateAlias() to find out the alias name of the key entry associated with the certificate. You then retrieve the key using the alias name returned. It depends a lot on how you manage your private keys and certificates, so you can think of solving this problem as being left as an exercise in imagination , rather than just as an exercise.

The KEKRecipientInformation Class

The org.bouncycastle.cms.KEKRecipientInformation is the processing class for the second kind of recipient(those protected with key-encryption keys. It is also an extension of the RecipientInfomation class, and the class provides the necessary functionality to deal with enveloped-data where the data encryption key has been encrypted with a key-encryption key (KEK) using a symmetric algorithm such Triple-DES or AES, rather than a public key as you saw with the KeyTransRecipientInformation class.

Recipients using key-encryption keys are indicated by the presence of an implicit tag of value 2 containing a structure of the KEKRecipientInfo type in the RecipientInfo CHOICE type. The KEKRecipientInfo type has the following ASN.1 definition:

KEKRecipientInfo ::= SEQUENCE { version CMSVersion, kekid KEKIdentifier, keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier, encryptedKey EncryptedKey }

with the KEKIdentifier type defined as:

KEKIdentifier ::= SEQUENCE { keyIdentifier OCTET STRING, date GeneralizedTime OPTIONAL, other OtherKeyAttribute OPTIONAL }

The class provides the implementation for the getContent() method required by the RecipientInformation class. In a similar fashion to the KeyTransRecipientInformation class, the kekid field becomes a RecipientId with the fields of the type KeyIdentifier being absorbed by that class and the values in the keyEncryptionAlgorithm field made available through the parent class. The version number is not exposed by the API but will always have the value 4.

A recipient of this type is also indicated by the getKeyIdentifier() method of its RecipientId object returning a byte array, representing the value of the keyIdentifier field, rather than null . It is the values in the returned byte array that the RecipientInformation store will use to match a KEKRecipient .

Other than the difference in the RecipientId identifier information, you'll see that using this recipient type is very similar to using the public-key-based recipient.

Try It Out: Using Key-Encrypted Keys with Enveloped-Data

This example shows the processing required for creating and using a key-encrypted key recipient in an enveloped-data message. Have a look at the code and try running it.

package chapter9; import java.util.Arrays; import javax.crypto.*; import org.bouncycastle.cms.*; /** * Demonstrate creation and processing a key-encrypted key enveloped-message. */ public class KEKEnvelopedDataExample { public static void main(String[] args) throws Exception { KeyGenerator keyGen = KeyGenerator.getInstance("DESEDE", "BC"); SecretKey key = keyGen.generateKey(); // set up the generator CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator(); byte[] kekID = new byte[] { 1, 2, 3, 4, 5 }; edGen.addKEKRecipient(key, kekID); // create the enveloped-data object CMSProcessable data = new CMSProcessableByteArray( "Hello World!".getBytes()); CMSEnvelopedData enveloped = edGen.generate( data, CMSEnvelopedDataGenerator.AES128_CBC, "BC"); // re-create enveloped = new CMSEnvelopedData(enveloped.getEncoded()); // look for our recipient RecipientId recId = new RecipientId(); recId.setKeyIdentifier(kekID); RecipientInformationStore recipients = enveloped.getRecipientInfos(); RecipientInformation recipient = recipients.get(recId); if (recipient != null) { // decrypt the data byte[] recData = recipient.getContent(key, "BC"); // compare recovered data to the original data if (Arrays.equals((byte[])data.getContent(), recData)) { System.out.println("data recovery succeeded"); } else { System.out.println("data recovery failed"); } } else { System.out.println("could not find a matching recipient"); } } }

Running the example should produce the following output:

data recovery succeeded

indicating that the symmetric key was successfully matched against the recipients and the data recovered.

How It Works

As you can see, this example is very similar to the one for the public key-based recipient, with only a few differences.

The first difference is, because you are using a symmetric key rather than an asymmetric one, the key has no certificates associated with it. To make things easy, you use the KeyGenerator class to create the key, but ordinarily you might instead fetch the key from some type of keystore. As far as the choice of algorithm goes, in this case you are using a Triple-DES key, but there are also wrapping algorithms defined for RC2 and AES, so either of those two would work as well. It is largely up to the recipient, but remember, if you are mixing key-wrapping methods, the weakest recipient is also the weakest link in security. Likewise, when you are dealing with a group of recipients, the minimum level of security any of the recipients is willing to accept is the minimum level required for the keys of all recipients.

The next difference is in the manner the entry is added to the generator:

edGen.addKEKRecipient(key, kekID);

In this case, rather than using identifying information gained from a certificate, the key is identified using the byte array referenced by keyID . As you can imagine, in a real-life situation, this would also be information that would need to be known to the recipient.

You can see that by taking advantage of the RecipientInformationStore class, the code for looking up a recipient is identical to the public key-based recipient. Likewise, if you wanted to, you could iterate through the recipients instead to find the one you want. It all depends on how you want to do it.

The last content type you will look at in CMS is the one used for creating compressed-data content. Although it is not directly related to cryptography, it can be quite useful and is included as a content type in S/MIME.

Data Compression in CMS

The compressed-data content type for CMS is defined separately from RFC 3852 ”it is defined in RFC 3274. As it does not involve encryption or authentication, it has a simpler ASN.1 structure than the others, and as you will see by the code in the example, it is also a lot more straightforward to use.

ASN 1 Structure

CMS compressed-data is created by wrapping a CompressedData structure in a ContentInfo structure with the contentType field set to the OID id-ct-compressedData , which is defined as:

id-ct-compressedData OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) ct(1) 9 }

and the CompressedData structure is defined as:

CompressedData ::= SEQUENCE { version CMSVersion, compressionAlgorithm CompressionAlgorithmIdentifier, encapContentInfo EncapsulatedContentInfo }

As you can probably guess, CompressionAlgorithmIdentifier is further defined as being of the type AlgorithmIdentifier , and EncapsulatedContentInfo is the same type you encountered when you looked at CMS signed-data . The version number is always 0.

Currently the only compression algorithm specified is ZLIB, which is identified using the following OID:

id-alg-zlibCompress OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) alg(3) 8 }

Compared to the other CMS structures you have looked at, this one is quite simple. Fortunately, the Java classes related to compressed-data reflect this.

The CMSCompressedData Class

The org.bouncycastle.cms.CMSCompressedData class has two general use constructors that can take either a byte array or an InputStream representing a binary encoding of a ContentInfo structure and the CompressedData structure it contains. As you will see in the next example, CMSCompressedData objects can also be created using objects of the CMSCompressedDataGenerator class.

CMSCompressedData.get Content()

The getContent() method returns a byte array representing the contents of the encapContentInfo field after uncompressing.

The method will throw a CMSException if a problem occurs uncompressing the data.

CMSCompressedData.get Encoded()

The getEncoded() method returns the binary ASN.1 encoding of the object. The encoding may follow the BER or DER encoding rules.

The method will throw an IOException if a problem occurs generating the encoding.

Try It Out: Using Compression with CMS

Here is a simple example showing how to use the compressed-content type. As you can see, it is much easier to deal with than the previous content types discussed.

package chapter9; import java.util.Arrays; import org.bouncycastle.cms.CMSCompressedData; import org.bouncycastle.cms.CMSCompressedDataGenerator; import org.bouncycastle.cms.CMSProcessableByteArray; /** * Basic use of CMS compressed-data. */ public class CompressedDataExample { public static void main(String args[]) throws Exception { // set up the generator CMSCompressedDataGenerator gen = new CMSCompressedDataGenerator(); //compress the data CMSProcessableByteArray data = new CMSProcessableByteArray( "Hello world!".getBytes()); CMSCompressedData compressed = gen.generate(data, CMSCompressedDataGenerator.ZLIB); // re-create and uncompress the data compressed = new CMSCompressedData(compressed.getEncoded()); byte[] recData = compressed.getContent(); // compare uncompressed data to the original data if (Arrays.equals((byte[])data.getContent(), recData)) { System.out.println("data recovery succeeded"); } else { System.out.println("data recovery failed"); } } }

Running the example produces the following message:

data recovery succeeded

indicating the data compressed without problems.

How It Works

This example follows a similar pattern to the earlier ones in that a generator is created and used to create a CMSCompressedData object from an implementation of CMSProcessable using the following line:

CMSCompressedData compressed = gen.generate(data, CMSCompressedDataGenerator.ZLIB);

where CMSCompressedDataGenerator.ZLIB is a string representing the OID for the ZLIB algorithm.

After this, the CMSCompressedData object is reconstructed from its binary encoding, and the original data is recovered from it.

One thing to note here is that, although ZLIB is a lossless compression algorithm, not all compression algorithms are ” especially those that can be used with images and sound. If you ever get to the point of using other compression algorithms and combining the compressed-content type with the signed-data type, compress the data before creating the signed-data; otherwise , the use of a "lossy" compression algorithm will mean your signatures are invalid.

CMS is not just an end in itself, but is used as the basis for a number of other protocols. Chief amongst these is S/MIME.

Secure Multipurpose Internet Mail Extensions (S MIME)

S/MIME, or the Secure/Multipurpose Internet Mail Extensions, defines a method of sending MIME data securely. If you have ever received an e-mail that indicated it was signed or encrypted, chances are it was an S/MIME message you were looking at.

Like other Internet standards, S/MIME is defined using an RFC, in this case RFC 3851, that describes S/MIME version 3.1. There are additional standards based on S/MIME, which define variants such as AS2, which are used for business-to-business electronic commerce.

In the Bouncy Castle APIs there are two packages devoted to S/MIME: org.bouncycastle.smime , which contains the high-level classes that handle creation and processing of MIME messages involving encryption, signing, and compression, and org.bouncycastle.asn1.smime , which is a collection of low-level classes that provide Java object equivalents for the ASN.1 structures defined in the S/MIME ASN.1 module. As S/MIME is a combination of MIME objects and the structures used in CMS, using S/MIME also involves using the JavaMail API, and you will also find some of the classes in the Bouncy Castle CMS API talked about earlier in this chapter will be useful from time to time.

I've written this section of the chapter in a manner that does not assume you are already familiar with the JavaMail APIthe examples will tell you the minimum you need to know. I would recommend, however, that you take some time to read the documentation accompanying the JavaMail API if you want to take full advantage of S/MIME.

Before I start on the particulars of the API for the three content types, I'll just start with two of the general classes that make up the API so I can refer to them later. They are CMSProcessableBodyPart and SMIMEUtil .

The CMSProcessableBodyPart Class

The org.bouncycastle.mail.smime.CMSProcessableBodyPart class is a general implementation of CMSProcessable that just uses the Part.writeTo() method to output the data to the stream passed in to the CMSProcessable.write() method. It is used by the classes involved with processing enveloped and compressed mime messages to feed the bytes making up the MIME message into the appropriate classes in the CMS API. Usually you will not need to use this class directly, but it is worth being aware of its existence.

The SMIMEUtil Class

The org.bouncycastle.mail.smime.SMIMEUtil class provides a couple of utility methods that are of general use when working with S/MIME messages. The two methods are toMimeBodyPart() and createIssuerAndSerialNumberFor() .

SMIMEUtil.to MimeBodyPart()

The toMimeBodyPart() method takes a byte array and returns a MimeBodyPart created from the byte array. You can use this method for recovering a MimeBodyPart from the bytes you extract from the two classes representing compressed and enveloped S/MIME message SMIMECompressed and SMIMEEnveloped .

SMIMEUtil.create IssuerAndSerialNumberFor()

The createIssuerAndSerialNumberFor() method returns an IssuerAndSerialNumber object for the X509Certificate object passed to it as a parameter. The IssuerAndSerialNumber class is in the org.bouncycastle.asn1.cms package and represents the Java equivalent to the IssuerAndSerialNumber structure in the CMS ASN.1 definitions you saw earlier in the chapter. You will mainly find this useful for creating the encryption key preference attribute, which is one of the attributes you can attach to a S/MIME signed message. You'll look at how to do this in the next section.

S MIME Signed Messages

Signed messages in S/MIME can be represented using either single MIME body part messages or MIME multipart messages. You'll encounter the MIME multipart message when the data signed has not being encapsulated in the signature. In this case the message will be in two body parts , the first part containing the contents that was signed and the second part containing the signature. When the data is encapsulated or the message is a certificate management message and only created to carry certificates, you will receive the message in a single MIME body part. In general, for signing data, you should use the multipart format whenever possible. As it is what is known as a clear signing technique, it has the advantage that the body part containing the data that was signed is readable by anyone , even if they use a MIME processor that is unable to deal with the body part containing the CMS signed-data message in the second part.

The "Content-Type" header in the MIME message will reflect the kind of signed message you are looking at. If the MIME type indicated by the header is application/pkcs7-mime , then the body part contains either a signature with encapsulated data or a certificate management message. If it is an encapsulated data message, the header will also include a smime-type parameter that will be set to signed-data . If it is a certificate management message, the smime-type parameter will be certs-only . In the case of a multipart message, the MIME type of the body part containing the signature will be application/pkcs7-signature with the smime-type parameter set to signed-data and the MIME type of the actual multipart will be multipart/signed with a protocol parameter that is set to the string application/pkcs7-signature .

The only real complication with creating S/MIME signed messages has to do with what happens to text when it passes through various mail agents . The main issue is that some platforms terminate lines with a line-feed character (LF), and others terminate them with a carriage -return character followed by a linefeed character (CRLF), and as a mail message moves around the line, endings may change from what it was created with. As a result of this decision made in S/MIME, all text is considered to end with CRLF for the purposes of signature calculation. The process of treating a text file like this is referred to as CRLF canonicalization . The Bouncy Castle API will now do this for you by default on nonbinary data, which is fine for applications built to follow RFC 3851, but not so good if you are using something like AS2 where the default transfer encoding is binary, not 7 bit as it is in RFC 3851. I'll explain how to vary the default transfer encoding a bit later, but as you can probably guess, it is simply a matter of knowing how to set your own defaults.

It doesn't stop there either. Depending on your environment, other weird and not-so-wonderful things can happen to MIME messages as they wander around the network. For example, some gateways will remove trailing whitespace between the last non-whitespace character and the end of line marker. These things tend to be the exception rather than the rule now, but if you are "lucky" enough to run into one of these gateways, the important thing to remember when signatures fail to validate for MIME text data is to check to see what's changed. If something has changed and you cannot explain it as result of CRLF canonicalization, then you may need to introduce some extra canonicalization processing of your own.

With this in mind, I'll introduce the two CMSProcessable implementations that look after canonicalization. Ordinarily you will never need to use them directly, but it is worth knowing they are there. After that I will move on to the Java class used to represent an S/MIME signed message.

The CMSProcessableBodyPartInbound Class

The org.bouncycastle.mail.smime.CMSProcessableBodyPartInbound class is an implementation of CMSProcessable , which applies CRLF canonicalization to the BodyPart object it is constructed with if the "Content-Transfer-Encoding" header does not indicate the data being processed is binary. The class is used exclusively by the signed MIME message class SMIMESigned to correctly canonicalize data during the process of signature verification.

The CMSProcessableBodyPartOutbound Class

The org.bouncycastle.mail.smime.CMSProcessableBodyPartOutbound class is also an implementation of CMSProcessable , which applies CRLF canonicalization to a nonbinary data BodyPart . In this case the class is used exclusively by the signed MIME message generator class SMIMESignedGenerator to correctly canonicalize data during the process of signature creation.

The SMIMESigned Class

The org.bouncycastle.mail.smime.SMIMESigned class is an extension of the CMSSignedData class you saw earlier and adds a few methods peculiar to the requirement of dealing with S/MIME signed messages in Java ”the need to be able to extract MIME data in the form of the MimeBodyPart and MimeMessage classes.

It has three constructors. Two take a MimeMultipart that is assumed to contain two mime body parts, one containing the data that was signed and one containing a detached CMS signed-data object, and one takes a single Part object that is assumed to contain a CMS signed-data object with encapsulated data. The first two constructors can throw MessagingException and CMSException if there is a problem processing the MimeMultipart , and the third one will also throw a SMIMEException if the MIME message encapsulated in the CMS signed-data object cannot be extracted.

The second of the MimeMultipart constructors also takes a default value for the "Content-Transfer-Encoding" header. This indicates how the message should be treated if the header is not specified in the MIME object's header. This is important to remember. Although RFC 3851 does define the default contenttransfer-encoding as "7 bit," not all S/MIME related standards use "7 bit" as the default. AS2, for example, defaults to "binary," and you can imagine how successfully signatures are created or verified if binary data they are based on gets CRLF canonicalized during the process.

The methods follow. As you can see, they just deal with MIME objects ”the real functionality required for validation of signatures is the same as you have already looked at in the CMSSignedData class.

SMIMESigned.get Content()

The getContent() method returns the MimeBodyPart representing the data that the signatures contained in the SMIMESigned object were created against.

SMIMESigned.get ContentAsMimeMessage()

The getContentAsMimeMessage() method is a convenient method that returns the data that the messages signatures were created for as a MimeMessage object.

SMIMESigned.get ContentWithSignature()

The getContentWithSignature() method returns the object that was used to create the SMIMESigned object. The object returned will be either a MimeBodyPart or a MimeMultipart depending on whether the signed message was created with encapsulated data or not.

Try It Out: Creating and Validating a S/MIME Signed Message

This example shows how to create and process a multipart S/MIME signed message. It's rather long, so I'll break it up so I can give you some commentary before getting to the full explanation in the How It Works that follows .

The first part is represented by the class header. As you'll notice, there is a CMS API class being used and several classes from the S/MIME API. The example also makes use of the JavaMail API, and the example class extends the SignedProccessor class that you created earlier in "Try It Out: Creating and Validating a Detached Signature."

package chapter9; import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.*; import java.util.Arrays; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; import org.bouncycastle.asn1.ASN1EncodableVector; import org.bouncycastle.asn1.cms.AttributeTable; import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute; import org.bouncycastle.asn1.smime.SMIMECapability; import org.bouncycastle.asn1.smime.SMIMECapabilityVector; import org.bouncycastle.asn1.smime.SMIMEEncryptionKeyPreferenceAttribute; import org.bouncycastle.mail.smime.SMIMESigned; import org.bouncycastle.mail.smime.SMIMESignedGenerator; import org.bouncycastle.mail.smime.SMIMEUtil; /** * a simple example that creates and processes a signed mail message. */ public class SignedMailExample extends SignedDataProcessor {

The second part provides the functionality that creates the multipart signed message. It defines a method called createMultipartWithSignature() , which just takes the required credentials to do the signing and the MimeBodyPart holding the data to be signed as parameters. Upon its completion, the method returns the MimeMultipart containing the MimeBodyPart passed in and its signature.

public static MimeMultipart createMultipartWithSignature( PrivateKey key, X509Certificate cert, CertStore certsAndCRLs, MimeBodyPart dataPart) throws Exception { // create some smime capabilities in case someone wants to respond ASN1EncodableVector signedAttrs = new ASN1EncodableVector(); SMIMECapabilityVector caps = new SMIMECapabilityVector(); caps.addCapability(SMIMECapability.aES256_CBC); caps.addCapability(SMIMECapability.dES_EDE3_CBC); caps.addCapability(SMIMECapability.rC2_CBC, 128); signedAttrs.add(new SMIMECapabilitiesAttribute(caps)); signedAttrs.add(new SMIMEEncryptionKeyPreferenceAttribute( SMIMEUtil.createIssuerAndSerialNumberFor(cert))); // set up the generator SMIMESignedGenerator gen = new SMIMESignedGenerator(); gen.addSigner(key, cert, SMIMESignedGenerator.DIGEST_SHA256, new AttributeTable(signedAttrs), null); gen.addCertificatesAndCRLs(certsAndCRLs); // create the signed message return gen.generate(dataPart, "BC"); }

The third, and last part, is the main driver. It uses the createMultipartWithSignature() method to create a signed multipart. It then uses the Utils method you added at the start of the chapter to produce a template for a mail message from the signed multipart you have created, recovers the multipart from the mail template, and checks the signature for validity.

public static void main(String args[]) throws Exception { KeyStore credentials = Utils.createCredentials(); PrivateKey key = (PrivateKey)credentials.getKey( Utils.END_ENTITY_ALIAS, Utils.KEY_PASSWD); Certificate[] chain = credentials.getCertificateChain( Utils.END_ENTITY_ALIAS); CertStore certsAndCRLs = CertStore.getInstance( "Collection", new CollectionCertStoreParameters( Arrays.asList(chain)), "BC"); X509Certificate cert = (X509Certificate)chain[0]; // create the message we want signed MimeBodyPart dataPart = new MimeBodyPart(); dataPart.setText("Hello world!"); // create the signed message MimeMultipart multiPart = createMultipartWithSignature( key, cert, certsAndCRLs, dataPart); // create the mail message MimeMessage mail = Utils.createMimeMessage( "example signed message", multiPart, multiPart.getContentType()); // extract the message from the mail message if (mail.isMimeType("multipart/signed")) { SMIMESigned signed = new SMIMESigned( (MimeMultipart)mail.getContent()); // verification step X509Certificate rootCert = (X509Certificate)credentials.getCertificate( Utils.ROOT_ALIAS); if (isValid(signed, rootCert)) { System.out.println("verification succeeded"); } else { System.out.println("verification failed"); } // content display step MimeBodyPart content = signed.getContent(); System.out.print("Content: "); System.out.println(content.getContent()); } else { System.out.println("wrong content found"); } } }

Running the example should produce the following output:

verification succeeded Content: Hello world!

indicating that both the signature verification succeeded and the content that was signed was successfully recovered.

How It Works

You are probably already familiar with the initialization code in the main driver by now; the only change in this case is that you are creating a MimeBodyPart to sign rather than a CMSProcessable . You can do this because, as mentioned earlier, the S/MIME API has three implementations of CMSProcessable for processing MIME objects that it makes use of internally.

After initialization, the code enters the createMultipartWithSignature() method, where it starts by creating some attributes to be included in the CMS signed-data message that will end up containing a signature for the content of dataPart . Just as there are attributes available in CMS, there are also additional attributes that can be used with signed-data if it is being used in conjunction with S/MIME. In this case, the method is creating the two most typical ones ”the S/MIME capabilities attribute and the encryption key preference attribute.

The capabilities attribute tells the receivers of the message something about what you can process if they want to respond to you. This example indicates that you are willing to accept messages encrypted using the algorithms AES (256-bit key), Triple-DES, and RC2 (128-bit key). As you can see, the capabilities are mainly indicated by OIDs with the odd parameter thrown in where needed. The order in which these appear is significant, because the receiver will assume that the first encryption algorithm identified is your preferred choice if they are going to send you an enveloped message. You can find more information about some of the others by consulting RFC 3851, but other capabilities that can be specified include which signing algorithms you can read and whether you can handle compressed data.

The encryption key preference attribute indicates which public key you would prefer people to use if they are including you in their list of recipients in an enveloped message. The encryption key preference attribute contains a SMIMEEncryptionKeyPreference structure, which is defined as follows:

SMIMEEncryptionKeyPreference ::= CHOICE { issuerAndSerialNumber [0] IssuerAndSerialNumber, recipientKeyId [1] RecipientKeyIdentifier, subjectAltKeyIdentifier [2] SubjectKeyIdentifier }

In the case of the example, you are just using the issuerAndSerialNumber choice item and you are using the certificate you are providing to validate the message with as the one you want people to use as your encryption key. The main reason I've done this is to reduce the complexity of the example. In general, it is a good idea to make sure that the certificate you give people to validate your signatures and the certificate you give people to use when they want to encrypt something with your public key are two distinct certificates with two distinct keys. Keeping a certificate single purpose reduces the possible avenues by which people can attempt to compromise the private key associated with it.

After creating the attributes, the method creates a generator for SMIMESigned objects and adds a signer to it using the following line:

gen.addSigner(key, cert, SMIMESignedGenerator.DIGEST_SHA256, new AttributeTable(signedAttrs), null);

You'll notice this method is really the same as that on the CMSSignedDataGenerator class ”not really surprising because it is to one of those that the information is being passed. The parameters mean the same as they did before. Key is the private key the signature will be created with. Cert is passed in to provide identifying information to help the entity who will eventually verify the signature locate the right public key certificate to use. The next parameter specifies the message digest to be used for signature calculation, and it is followed by the signed attributes and the unsigned attributes.

Once the generator is properly set up, gen.generate() is called with the BodyPart you want signed and the generator returns a containing a multipart signed S/MIME object.

The mulitpart signed MIME object is then wrapped in a MimeMessage , which could serve as a mail message. Then, a SMIMESigned object is created using the MimeMessage content and the example proceeds with the verification step. As SMIMESigned is an extension of CMSSignedData , this is just a matter of calling the isValid() method that is defined on the parent class and then printing the appropriate message.

That brings you to the end of S/MIME signed messages. By way of further information, the SMIMESignedGenerator also has methods for creating MIME objects representing signatures with encapsulated data and certificate management messages. You can generate these by using the generateEncapsulated() and generateCertificateManagement() methods, respectively.

Now you will take a look at S/MIME enveloped messages. These carry CMS enveloped-data in order to achieve their purpose.

S MIME Enveloped Messages

Unlike S/MIME signed messages, S/MIME enveloped messages are only represented as a single MIME body part. The reason is that, as they contain CMS enveloped-data messages, the encrypted data that they hold the details for is also carried inside them. You can identify S/MIME enveloped messages as their MIME type will be application/pkcs7-mime with a smime-type parameter of enveloped-data .

As they contain the data within them, and it is encrypted in any case, none of the canonicalization issues that you saw with S/MIME signed messages apply. For this reason, the Java classes for creating and processing them use the CMSProcessableBodyPart class for both the encryption and the decryption steps when dealing with MIME body parts that are being used in relation to S/MIME enveloped messages. The class that represents these messages in the Bouncy Castle S/MIME API is the SMIMEEnveloped class.

The SMIMEEnveloped Class

The org.bouncycastle.cms.SMIMEEnveloped class is an extension of the CMSEnvelopedData class you saw earlier in the chapter. It can be constructed with either a MimeMessage or a MimeBodyPart , which is assumed to contain a CMS enveloped-data message. The constructors can throw either a MessagingException if there is a problem using the passed-in MIME object or a CMSException if there is a problem with the processing the CMS message contained in the passed-in MIME object.

The class adds only one method getEncryptedContent() , which simply returns the MimePart it was constructed with. Like the SMIMESigned class, the methods on the parent class, CMSEnvelopedData , provide all the functionality required to process the enveloped message.

As you can see, it is a lot simpler to deal with than creating an S/MIME signed message. Fortunately, the code is simpler as well.

Try It Out: Using S/MIME Enveloped Messages

This example creates an S/MIME enveloped message from some data and then recovers the data in the second step. If you compare it to "Try It Out: Creating and Decoding CMS Enveloped-Data," you will see that, other than the use of the MIME and S/MIME objects, the two examples are quite similar.

package chapter9; import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import org.bouncycastle.cms.RecipientId; import org.bouncycastle.cms.RecipientInformation; import org.bouncycastle.cms.RecipientInformationStore; import org.bouncycastle.mail.smime.SMIMEEnveloped; import org.bouncycastle.mail.smime.SMIMEEnvelopedGenerator; import org.bouncycastle.mail.smime.SMIMEUtil; /** * a simple example that creates and processes an enveloped mail message. */ public class EnvelopedMailExample { public static void main(String args[]) throws Exception { KeyStore credentials = Utils.createCredentials(); PrivateKey key = (PrivateKey)credentials.getKey( Utils.END_ENTITY_ALIAS, Utils.KEY_PASSWD); Certificate[] chain = credentials.getCertificateChain( Utils.END_ENTITY_ALIAS); X509Certificate cert = (X509Certificate)chain[0]; // create the message we want encrypted MimeBodyPart dataPart = new MimeBodyPart(); dataPart.setText("Hello world!"); // set up the generator SMIMEEnvelopedGenerator gen = new SMIMEEnvelopedGenerator(); gen.addKeyTransRecipient(cert); // generate the enveloped message MimeBodyPart envPart = gen.generate( dataPart, SMIMEEnvelopedGenerator.AES256_CBC, "BC"); // create the mail message MimeMessage mail = Utils.createMimeMessage( "example enveloped message", envPart.getContent(), envPart.getContentType()); // create the enveloped object from the mail message SMIMEEnveloped enveloped = new SMIMEEnveloped(mail); // look for our recipient identifier RecipientId recId = new RecipientId(); recId.setSerialNumber(cert.getSerialNumber()); recId.setIssuer(cert.getIssuerX500Principal().getEncoded()); RecipientInformationStore recipients = enveloped.getRecipientInfos(); RecipientInformation recipient = recipients.get(recId); if (recipient != null) { // decryption step MimeBodyPart recoveredPart = SMIMEUtil.toMimeBodyPart( recipient.getContent(key, "BC")); // content display step System.out.print("Content: "); System.out.println(recoveredPart.getContent()); } else { System.out.println("could not find a matching recipient"); } } }

Running the example will print the original content that was used to create the message, giving the following output:

Content: Hello world!

which shows that the recipient was matched correctly and the original MIME content was successfully recovered.

How It Works

As already mentioned, the example has a strong relationship to its CMS relative ”the real difference being more that you are dealing with MimeBodyPart and MimeMessage objects than anything else. In a similar fashion to the CMS example, after the basic initialization is complete, a SMIMEEnvelopedGenerator object is created and a public key-based recipient is added to it using the gen.addKeyTransRecipient() method.

The generator object is then invoked with the following line:

MimeBodyPart envPart = gen.generate( dataPart, SMIMEEnvelopedGenerator.AES256_CBC, "BC");

which takes the object dataPart , encrypts its content, and returns a MimeBodyPart object containing an S/MIME enveloped message. After this, a MimeMessage is created from the body part, and then a SMIMEEnveloped message is created from the MimeMessage , so beginning the process of recovering the encrypted data.

You've already seen the recipient matching process in the CMS enveloped-data example. As before, a RecipientId is being created for the certificate used in the encryption and that is being used to recover a RecipientInformation object that can be processed with the private key corresponding to the public key in the certificate. The difference in the recovery step is that this time you are expecting the enveloped-data structure to contain the encoding of a MIME body part, not just a text message. You still get a byte array back from the RecipientInformation object, but as it happens, the SMIMEUtil class has a method, toMimeBodyPart() , which allows you to take a raw byte stream that represents a MIME body part and create a MimeBodyPart object that represents it. You use the method to re-create the MIME body part and then you are able to display its content, which ends the example.

One last issue you might be pondering: The example shows that as far as the generation of S/MIME enveloped messages goes, it is a case of a single body part in, a single body part out. As you've already seen, signed messages can be multipart messages. Therefore, an interesting question is how to take a multipart message and create an enveloped message from it. As you'll see in the next section, fortunately, it is quite straightforward.

Combining Signing with Encryption

Often you want to sign and encrypt data to produce a single message. In some respects how you do this depends a lot on what you are trying to do. If you encrypt then sign, it makes it possible for the signatures to be checked without decrypting the data ”you can reject data without having to decrypt it. On the other hand, perhaps you want the signatures to be protected as well.

The general philosophy, if you have no reason for bias, is to "sign what you mean." In respect of most enveloped messages, what this means is normally the data that has been enveloped. Consequently, the normal practice is to sign first and then encrypt. This is the approach taken in the next example.

Try It Out: Enveloping a Signed Message

This example first signs a message, envelopes it, and then reverses the process ”recovering the data and verifying the signature. It uses the SignedMailExample.createMultipartWithSignature() method that you created earlier in "Try It Out: Creating and Validating a S/MIME Signed Message" to perform the signing step and, also, extends the SignedProcessor class. However, it is still rather large ” mainly because there is a lot going on. As this is the case, I will split it up a bit to make sure what is happening at each step is a bit clearer.

The first stage, as always, is the initialization phase, where you collect the necessary certificates and keys. Including the class header, here is how the code looks for that:

package chapter9; import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.*; import java.util.Arrays; import javax.mail.internet.*; import org.bouncycastle.cms.*; import org.bouncycastle.mail.smime.*; /** * a simple example that creates and processes an enveloped signed mail message. */ public class EnvelopedSignedMailExample extends SignedDataProcessor { public static void main(String[] args) throws Exception { KeyStore credentials = Utils.createCredentials(); PrivateKey key = (PrivateKey)credentials.getKey( Utils.END_ENTITY_ALIAS, Utils.KEY_PASSWD); Certificate[] chain = credentials.getCertificateChain( Utils.END_ENTITY_ALIAS); CertStore certsAndCRLs = CertStore.getInstance( "Collection", new CollectionCertStoreParameters( Arrays.asList(chain)), "BC"); X509Certificate cert = (X509Certificate)chain[0];

In the next stage, the MimeBodyPart you want to process gets created, and then you create a multipart signed S/MIME object using the SignedMailExample.createMultipartWithSignature() method:

// create the message we want signed MimeBodyPart dataPart = new MimeBodyPart(); dataPart.setText("Hello world!"); // create the signed message MimeMultipart signedMulti = SignedMailExample.createMultipartWithSignature( key, cert, certsAndCRLs, dataPart);

The multipart signed message is represented by the MimeMultipart object. The first step to enveloping is to wrap it in another body part so that you reduce the problem to that of having a single MimeBodyPart to envelope. This is done in the following code:

// create the body part containing the signed message MimeBodyPart signedPart = new MimeBodyPart(); signedPart.setContent(signedMulti);

After that, all you need to do is envelope it like you did earlier and create a MimeMessage that you can then feed into a mailer:

// set up the enveloped message generator SMIMEEnvelopedGenerator gen = new SMIMEEnvelopedGenerator(); gen.addKeyTransRecipient(cert); // generate the enveloped message MimeBodyPart envPart = gen.generate( signedPart, SMIMEEnvelopedGenerator.AES256_CBC, "BC"); // create the mail message MimeMessage mail = Utils.createMimeMessage( "example signed and enveloped message", envPart.getContent(), envPart.getContentType());

Having come as far as creating the MimeMessage , you then reverse the process, first using the message to create a SMIMEEnveloped you can process and then finding a RecipientInformation object that matches your public key so you can recover the MimeBodyPart that was enveloped using your private key.

// create the enveloped object from the mail message SMIMEEnveloped enveloped = new SMIMEEnveloped(mail); // look for our recipient identifier RecipientId recId = new RecipientId(); recId.setSerialNumber(cert.getSerialNumber()); recId.setIssuer(cert.getIssuerX500Principal().getEncoded()); RecipientInformationStore recipients = enveloped.getRecipientInfos(); RecipientInformation recipient = recipients.get(recId); // decryption step MimeBodyPart res = SMIMEUtil.toMimeBodyPart( recipient.getContent(key, "BC"));

After that, it is a matter of taking the content of the MimeBodyPart and, if it is what you expect, creating a SMIMESigned object and using the isValid() method to verify the signature:

// extract the multipart from the body part. if (res.getContent() instanceof MimeMultipart) { SMIMESigned signed = new SMIMESigned( (MimeMultipart)res.getContent()); // verification step X509Certificate rootCert = (X509Certificate)credentials.getCertificate( Utils.ROOT_ALIAS); if (isValid(signed, rootCert)) { System.out.println("verification succeeded"); } else { System.out.println("verification failed"); } // content display step MimeBodyPart content = signed.getContent(); System.out.print("Content: "); System.out.println(content.getContent()); } else { System.out.println("wrong content found"); } } }

When you run the example, you should see that all the things that have been mentioned take place and you see the following output:

verification succeeded Content: Hello world!

showing that after everything was said and done, the signatures validated correctly and you recovered the original content.

How It Works

This example also contains a lot of code that you have seen before, even if it has not been organized in quite the same fashion. The signing, encryption, decryption, and verification processes are ones you are already familiar with. In many respects, the real "magic" in this example has little to do with the cryptography being used. It is more about the following lines of code using the JavaMail API:

MimeBodyPart signedPart = new MimeBodyPart(); signedPart.setContent(signedMulti);

As you've seen so far, and as will be confirmed again when you read about compression and S/MIME in the next section, S/MIME message creation involves the processing of single MIME body parts to produce various kinds of secure MIME messages. However, if you start applying this technology, you will rapidly discover that multipart messages are quite common and the previous two lines of code are the key to dealing with them. You take the multipart and wrap it in a single body part.

This explains how you are able to carry out the task in the example. From initialization, you go to generation of a signed message that returns a MimeMultipart object; you then wrap that in a MimeBodyPart object representing a single MIME body part and can pass that into the generate() method on a SMIMEEnvelopedGenerator object to get back an S/MIME enveloped message contained in another MimeBodyPart object. Likewise, because MimeBodyPart objects know something about the content they contain, when the content of the S/MIME enveloped message is decrypted, you can tell that the MimeBodyPart object created contains a MimeMultipart object and the signature verification can then proceed from that.

Important  

One thing to watch out for with creating signed messages containing other MIME objects: Some S/MIME clients will ignore certain headers in the contained MIME object when doing the signature calculation. Typically, this applies to the signature on a message containing a forwarded mail message ”some clients will ignore the standard mail headers in the MIME object representing the forwarded e-mail. If you find yourself in the situation where signatures validate successfully on one client but not on another, it could be this problem you are dealing with.

There are other uses for wrapping a MimeMultipart in a MimeBodyPart : signing a multipart MIME object, for example, or even encrypting a regular multipart MIME object ”it doesn't have to be a signed one. Another area where you could use the technique is for taking a multipart MIME object and using it to create an S/MIME compressed message using the classes you will look at in the next section.

S MIME Compressed Messages

Like S/MIME enveloped messages, compressed messages do not have to deal with canonicalization issues, as the data they carry is obscured by the compression. You can identify S/MIME enveloped messages as their MIME type will be application/pkcs7-mime with a smime-type parameter of compressed-data .

As this is the case, the Java classes for creating and processing them use the CMSProcessableBodyPart class when dealing with MIME body parts that are being compressed or decoded. The class that represents these messages in the Bouncy Castle S/MIME API is the SMIMECompressed class.

The SMIMECompressed Class

The org.bouncycastle.cms.SMIMECompressed class is an extension of the CMSCompressedData class you saw earlier in the chapter. It can be constructed with either a MimeMessage or a MimeBodyPart , which is assumed to contain a CMS compressed-data message. The constructors can throw either a MessagingException if there is a problem using the passed-in MIME object or a CMSException if there is a problem with processing the CMS message contained in the passed-in MIME object.

The class adds only one method getCompressedContent() , which simply returns the MimePart it was constructed with. As you will see in the following example, like the other S/MIME message classes, the methods on the parent class, CMSCompressedData , provide all the functionality required to process the compressed message.

Try It Out: Using S/MIME Compression

This is the last example and it is probably also the simplest. It creates a compressed message carrying a MIME body part, which is then used to create a MimeMessage . The data is then extracted from the compressed message and printed to stdout .

package chapter9; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import org.bouncycastle.mail.smime.SMIMECompressed; import org.bouncycastle.mail.smime.SMIMECompressedGenerator; import org.bouncycastle.mail.smime.SMIMEUtil; /** * a simple example that creates and processes an compressed mail message. */ public class CompressedMailExample { public static void main(String args[]) throws Exception { // create the message we want compressed MimeBodyPart dataPart = new MimeBodyPart(); dataPart.setText("Hello world!"); // set up the generator SMIMECompressedGenerator gen = new SMIMECompressedGenerator(); // generate the compressed message MimeBodyPart comPart = gen.generate( dataPart, SMIMECompressedGenerator.ZLIB); // create the mail message MimeMessage mail = Utils.createMimeMessage( "example compressed message", comPart.getContent(), comPart.getContentType()); // create the enveloped object from the mail message SMIMECompressed compressed = new SMIMECompressed(mail); // uncompression step MimeBodyPart recoveredPart = SMIMEUtil.toMimeBodyPart( compressed.getContent()); // content display step System.out.print("Content: "); System.out.println(recoveredPart.getContent()); } }

Try running the example; you should see the following output:

Content: Hello world!

showing that the compressed body part was successfully extracted.

How It Works

Not surprisingly, this example is almost identical to the CMS example you looked at for building compressed-data messages. The only difference is that, in this case, you are dealing with MIME objects.

Agenerator is created to produce MimeBodyPart objects carrying compressed data, a MimeBodyPart object is created, and the gen.generate() method is used to create a MimeBodyPart object that contains a CMS compressed-data message with the original MimeBodyPart object's contents compressed using ZLIB. This is then used to create a MimeMessage and the process is reversed . The MimeMessage is used to create an SMIMECompressed object that enables the recovery of the raw bytes making up the MIME body part that was compressed. These raw bytes are then used by SMIMEUtil.toMimeBodyPart() to re-create a MimeBodyPart object containing the original data.

Simple enough with one other point to remember. If you are signing data and then compressing it, make sure the choice of compression algorithm is such that it does not result in the data being changed, as it will be with some image, or sound, compression algorithms. If this can be the case, you need to compress first and then sign ”not the other way around.

Summary

In this chapter, you looked at two secure messaging standards: CMS, or Cryptographic Message Syntax, and a related standard for processing MIME messages securely, S/MIME. You should now have a good understanding of how messages are constructed in both standards, as well as understand what some of the variations are and what being a variant might mean to your message processing.

Over the course of this chapter, you have learned the following:

Finally, you have also seen the differences between mulitpart and single-part data in S/MIME and how these data types can be combined to create S/MIME messages that are combinations of some, or all of, the signed, encrypted, or compressed message types.

CMS and S/MIME give you ways of sending discrete messages that are either signed, encrypted, or both. The next question to ask is how would you do this for a possible unlimited amount of data? Of course, one answer is to break the data down into fix- sized packets and use CMS. A better solution is to set up a secure channel between the two points you want to send the data between and just start sending it. You will see how you can achieve this in the next chapter when you read about the Secure Sockets Layer (SSL) protocol and its successor, the Transport Layer Security protocol (TLS).

Exercises

1.  

The CMSProcessable interface is for the purpose of allowing the implementation of objects that write byte data to a CMS object for processing. How would you implement one that takes java.io.File objects?

2.  

What is the best policy to adopt with the creation of certificates for encryption and signature validation?

3.  

Under what circumstances will a CMS signed-data message not contain any signers?

4.  

How would you modify the SignedMailExample.createMultipartWithSignature() method so that it takes a MimeMultipart and signs it rather than a MimeBodyPart ?

5.  

If you wrap a mail message in another MIME body part, sign the result, and then find another application that will not validate the signature, what is most likely to be the problem?

6.  

When you are mixing signing and compression, under what circumstances is it mandatory that the signatures are calculated for the data after it has been compressed rather than before compression?

Answers

1.  

The CMSProcessable interface is for the purpose of allowing the implementation of objects that write byte data to a CMS object for processing. How would you implement one that takes java.io.File objects?

Here's one way of doing it:

package chapter9; import java.io.*; import org.bouncycastle.cms.*; /** * CMSProcessable that handles File objects. */ public class CMSProcessableFile implements CMSProcessable { private File file; private static final int BUF_SIZE = 4096; /** * Base constructor. * * @param file a File object representing the file we want processed . */ public CMSProcessableFile(File file) { this.file = file; } /** * Write the contents of the file to the passed in OutputStream * * @param out the OutputStream passed in by the CMS API. */ public void write(OutputStream out) throws IOException, CMSException { FileInputStream fIn = new FileInputStream(file); byte[] buf = new byte[BUF_SIZE]; int count = 0; while ((count = fIn.read(buf)) > 0) { out.write(buf, 0, count); } fIn.close(); } /** * Return the File object we were created with. */ public Object getContent() { return file; } }

One thing to note: Be sure to close the InputStream you are using in the write() method, as I've done here. The write() method can be called multiple times.

2.  

What is the best policy to adopt with the creation of certificates for encryption and signature validation?

Make them purpose-built. That means a certificate is for one purpose only and the public key will be used either for encryption or signature validation. There is an interesting slant on this in Chapter 13 of Practical Cryptography, where the authors point out that for a key to be different, just the public exponent needs to change; the modulus can be reused.

3.  

Under what circumstances will a CMS signeddata message not contain any signers?

A CMS signed-data message will not contain any signers if it is being used only to carry certificates and/or CRLs. Messages of this type are called certificate management messages and are also created when you encode a CertPath by passing the String to "PKCS7" to the CertPath object's getEncoded() method.

4.  

How would you modify the SignedMailExample.createMultipartWithSignature() method so that it takes a MimeMultipart and signs it rather than a MimeBodyPart?

Strictly speaking, you don't have to! Here is the modified method with the changes highlighted:

public static MimeMultipart createMultipartWithSignature( PrivateKey key, X509Certificate cert, CertStore certsAndCRLs, MimeMultipart multiPart) throws Exception { // create some smime capabilities in case someone wants to respond ASN1EncodableVector signedAttrs = new ASN1EncodableVector(); SMIMECapabilityVector caps = new SMIMECapabilityVector(); caps.addCapability(SMIMECapability.aES256_CBC); caps.addCapability(SMIMECapability.dES_EDE3_CBC); caps.addCapability(SMIMECapability.rC2_CBC, 128); signedAttrs.add(new SMIMECapabilitiesAttribute(caps)); signedAttrs.add(new SMIMEEncryptionKeyPreferenceAttribute( SMIMEUtil.createIssuerAndSerialNumberFor(cert))); // set up the generator SMIMESignedGenerator gen = new SMIMESignedGenerator(); gen.addSigner(key, cert, SMIMESignedGenerator.DIGEST_SHA256, new AttributeTable(signedAttrs), null); gen.addCertificatesAndCRLs(certsAndCRLs); MimeBodyPart dataPart = new MimeBodyPart(); dataPart.setContent(multiPart); // create the signed message return gen.generate(dataPart, "BC"); }

As you can see, this could have been more easily achieved by wrapping multiPart before passing it to the method and leaving the method's parameter list alone.

5.  

If you wrap a mail message in another MIME body part, sign the result, and then find another application that will not validate the signature, what is most likely to be the problem?

The other application is probably ignoring some of the headers in the mail message when it is doing the signature calculation. Typical headers that are ignored (when it happens) are "Reply-To," "From," and "To."

6.  

When you are mixing signing and compression, under what circumstances is it mandatory that the signatures are calculated for the data after it has been compressed rather than before compression?

If you are using a "lossy" compression technique, you have to sign the compressed data rather than the original data. Because this compression technique involves some loss of information, signatures calculated on the original data will not match the results of the verification process at the other end on the reduced data.

Категории