001: /*
002: * Title: Oyster Project
003: * Description: S/MIME email sending capabilities
004: * @Author Vladimir Radisic
005: * @Version 2.1.6
006: */
007:
008: package org.enhydra.oyster.activation;
009:
010: import org.enhydra.oyster.cms.*;
011: import org.enhydra.oyster.cms.consts.CapabilitiesConstants;
012: import org.enhydra.oyster.util.MimeAssist;
013: import org.enhydra.oyster.util.PFXUtils;
014: import org.enhydra.oyster.util.MimeAssist;
015: import org.enhydra.oyster.exception.SMIMEException;
016: import org.enhydra.oyster.exception.SMIMEIOException;
017: import javax.mail.internet.MimeMessage;
018: import javax.activation.DataSource;
019: import java.io.*;
020: import java.util.Vector;
021: import java.security.PrivateKey;
022: import java.security.KeyStore;
023: import java.security.cert.X509Certificate;
024:
025: /**
026: * CMSSignedDataSource represents implementation of DataSource interfaces. It is
027: * used within MimeMessage as a source of data. Also, object of this class is
028: * used to create DER encoded Cryptographic Message Syntax (CMS) object
029: * represented in ASN.1 notation according to RFC2630. This object (CMS) is used
030: * as the source of data for MimeMessage in the process of sending signed message.
031: */
032: public class CMSSignedDataSource implements DataSource {
033:
034: /**
035: * Container for messages.
036: */
037: private byte[] message;
038:
039: /**
040: * Container for SignerInfos.
041: */
042: private SignerInfos sInfo;
043:
044: /**
045: * Certificates container.
046: */
047: private Certificates certif;
048:
049: /**
050: * Number of certificates stored in certificates container.
051: */
052: private int countCert = 0;
053:
054: /**
055: * Capabilities attributes and their order
056: */
057: private Vector capabilities;
058:
059: /**
060: * Decision: external (true) / internal (false) signing
061: */
062: private boolean externalSignature = true;
063:
064: /**
065: * Used in setting digestAlgorithms field in Signed Data sequence:
066: * {SHA1,MD2,MD5}
067: */
068: private boolean[] typeOfDigest = { false, false, false };
069:
070: /**
071: * Boundary used in process of generation external signed message.
072: */
073: private String boundary = null;
074:
075: /**
076: * Constructs CMS object for signing with Mime Message in form of
077: * byte array and with given value for type of CMSSignedDataSource (type of signing).
078: * Type can be external or internal signing.
079: * @param message0 message for encryption
080: * @param externalSignature0 true = external signing, false = internal
081: * signing
082: * @exception SMIMEException in case of failure in MimeMessageConvertor
083: * class which performes transformation from MimeMessage object to byte array.
084: * Also, it can be caused by problems in construction or work with some
085: * inner objects instantiated from classes that belong to
086: * org.enhydra.oyster.der or org.enhydra.oyster.cms packages used
087: * in other CMSEnvelopedObject constructor.
088: */
089: public CMSSignedDataSource(byte[] message0,
090: boolean externalSignature0) throws SMIMEException {
091: message = message0;
092: externalSignature = externalSignature0;
093: if (externalSignature)
094: boundary = "smime_bondary_"
095: + Long.toString(System.currentTimeMillis());
096: sInfo = new SignerInfos();
097: certif = new Certificates();
098: capabilities = new Vector(0, 1);
099: }
100:
101: /**
102: * Constructs CMS object for signing with Mime Message in form of
103: * instance of MimeMessage class and with given value for type of CMSSignedDataSource
104: * (type of signing). Type can be external or internal signing.
105: * @param message0 message for encryption
106: * @param externalSignature0 true = external signing, false = internal
107: * signing
108: * @exception SMIMEException caused by problems in construction or work with
109: * some inner objects instantiated from classes that belong to
110: * org.enhydra.oyster.der or org.enhydra.oyster.cms packages used
111: * in other CMSEnvelopedObject constructor.
112: */
113: public CMSSignedDataSource(MimeMessage message0,
114: boolean externalSignature0) throws SMIMEException {
115: this (MimeAssist.messageConvertor(message0), externalSignature0);
116: }
117:
118: /**
119: * Sets Capabilities Attributes (method is optional, but if exists, must be
120: * performed before addSigner method). Depending on parameter type0 (algorithm
121: * group type), other parameter contains array of algoriithms from specific
122: * group of algorithms in client prefered usage order. Groups of algorithms
123: * are:<BR>
124: * SIGNATURE - MD2 with RSA, MD5 with RSA, SHA1 with RSA, SHA1 with DSA<BR>
125: * SYMMETRIC - RC2 40 bits, RC2 64 bits, RC2 128 bits, DES, DES_EDE3<BR>
126: * ENCIPHER - RSA<BR>
127: * DEFAULT - sets the default values for all three algorithm group types<BR>
128: * <BR>
129: * It is free to decide which algorithm will be included, or which group of algorithm
130: * will be included in Capabilities Attributes. If no groups are added, capabilities
131: * attributes won't be added to Signed Attributes. If two or more signers will
132: * sign the message, and their capabilities are different, this method should
133: * be performed before every signing if we wish to specify Capabilities
134: * Attributes for all particular signers. If type0 parameter is set as:<BR>
135: * <BR>
136: * setCapabilities (CapabilitiesConstants.DEFAULT, new String[0])<BR>
137: * <BR>
138: * it is equivalent to:<BR>
139: * <BR>
140: * setCapabilities(CapabilitiesConstants.SYMMETRIC, new String[] {CapabilitiesConstants.RC2_CBC_40});<BR>
141: * setCapabilities(CapabilitiesConstants.ENCIPHER, new String[] {CapabilitiesConstants.RSA });<BR>
142: * setCapabilities(CapabilitiesConstants.SIGNATURE, new String[] {CapabilitiesConstants.SHA1_WITH_RSA });<BR>
143: * <BR>
144: * @param type0 sets group of algorithms for capabilities attributes. It can be set
145: * with values: SIGNATURE, SYMMETRIC, ENCIPHER or DEFAULT.
146: * @param capabil0 array of user prefered algorithms in user prrefered
147: * order for each capabilityes group.
148: * @exception SMIMEException if same group is added more than once, invalid
149: * group type is used, or group is added after DEFAULT option.
150: */
151: public void setCapabilities(String type0, String[] capabil0)
152: throws SMIMEException {
153:
154: if (type0.equalsIgnoreCase(CapabilitiesConstants.SYMMETRIC)
155: || type0
156: .equalsIgnoreCase(CapabilitiesConstants.SIGNATURE)
157: || type0
158: .equalsIgnoreCase(CapabilitiesConstants.ENCIPHER)) {
159:
160: for (int i = 0; i < capabil0.length; i++) {
161: if (capabil0[i] == null)
162: continue;
163: capabilities.add(capabil0[i]);
164: }
165: } else if (type0.equals(CapabilitiesConstants.DEFAULT)) {
166:
167: capabilities.add(CapabilitiesConstants.RC2_CBC_40); // RC2 40 bits algorithm
168: capabilities.add(CapabilitiesConstants.SHA1_WITH_RSA); // SHA1 with RSA algorithm
169: capabilities.add(CapabilitiesConstants.RSA); // RSA Encription
170: } else
171: throw new SMIMEException(1030);
172: }
173:
174: /**
175: * Adds Signer. This method must be performed at least once.
176: * @param pfx0 contains information from signer's .pfx or .p12 file
177: * @param includingCert0 true = automatically including all certificates from pfx0
178: * false = no certificate will be added
179: * @param includingSignAttrib0 true = signed attributes will be included, false
180: * = signed attributes will not be included
181: * @param signingAlg0 used for signing (can be SHA1_WITH_RSA, MD2_WITH_RSA,
182: * MD5_WITH_RSA or SHA1_WITH_DSA)
183: * @exception SMIMEException in case of wrong type of digest algorithm, or in
184: * case of problems with manipulation with .pfx or .p12 file in PFXUtils class.
185: * Also, it can be caused by problems in construction or work with some inner
186: * objects from org.enhydra.oyster.der or org.enhydra.oyster.cms package.
187: */
188: public void addSigner(KeyStore pfx0, boolean includingCert0,
189: boolean includingSignAttrib0, String signingAlg0)
190: throws SMIMEException {
191:
192: X509Certificate[] chain = PFXUtils.getCertificateChain(pfx0);
193: if (chain == null)
194: chain = PFXUtils.getAllX509Certificate(pfx0);
195:
196: PrivateKey pKey = PFXUtils.getPrivateKey(pfx0);
197:
198: this .addSigner(chain, pKey, includingCert0,
199: includingSignAttrib0, signingAlg0);
200: }
201:
202: /**
203: * Adds Signer. This method must be performed at least once.
204: * @param chain0 signer's certificates chain. First certificate in chain
205: * must be owner's.
206: * @param privKey0 signer's private key (DSA or RSA depend on type of signing)
207: * @param includingCert0 true = automatically including all certificates from pfx0
208: * false = no certificate will be added
209: * @param includingSignAttrib0 true = signed attributes will be included, false
210: * = signed attributes will not be included
211: * @param signingAlg0 used for signing (can be SHA1_WITH_RSA, MD2_WITH_RSA,
212: * MD5_WITH_RSA or SHA1_WITH_DSA)
213: * @exception SMIMEException in case of wrong type of digest algorithm. Also,
214: * it can be caused by problems in construction or work with some inner
215: * objects from org.enhydra.oyster.der or org.enhydra.oyster.cms package.
216: */
217: public void addSigner(X509Certificate[] chain0,
218: PrivateKey privKey0, boolean includingCert0,
219: boolean includingSignAttrib0, String signingAlg0)
220: throws SMIMEException {
221: String digestAlg = null;
222: if (signingAlg0.equalsIgnoreCase("SHA1_WITH_RSA")) {
223: typeOfDigest[0] = true;
224: digestAlg = "SHA1";
225: } else if (signingAlg0.equalsIgnoreCase("SHA1_WITH_DSA")) {
226: typeOfDigest[0] = true;
227: digestAlg = "SHA1";
228: } else if (signingAlg0.equalsIgnoreCase("MD2_WITH_RSA")) {
229: typeOfDigest[1] = true;
230: digestAlg = "MD2";
231: } else if (signingAlg0.equalsIgnoreCase("MD5_WITH_RSA")) {
232: typeOfDigest[2] = true;
233: digestAlg = "MD5";
234: } else
235: throw new SMIMEException(1031);
236: SignedAttributes attrib = null; // Signed attributes remain null if includingSignAttrib0==false
237: if (includingSignAttrib0 == true) { // Creating signers info with signed attributes
238: attrib = new SignedAttributes(); // Creating container of signed attrinutes
239: ContentTypeAttribute cont = new ContentTypeAttribute(
240: "ID_DATA", "NAME_STRING");
241: attrib.addSignedAttribute(cont.getDEREncoded());
242: SigningTimeAttribute time = new SigningTimeAttribute();
243: attrib.addSignedAttribute(time.getDEREncoded());
244: boolean include = false; // Check for capabilities attributes existing
245: if (capabilities.size() > 0)
246: include = true;
247:
248: if (include == true) {
249: CapabilitiesAttribute cap = new CapabilitiesAttribute(
250: (String[]) (capabilities.toArray(new String[1])));
251: attrib.addSignedAttribute(cap.getDEREncoded());
252: capabilities.clear(); // resets capabilities
253: }
254: MessageDigestAttribute dig = new MessageDigestAttribute(
255: message, digestAlg);
256: attrib.addSignedAttribute(dig.getDEREncoded());
257: }
258: sInfo.addSigner(message, chain0[0], privKey0, attrib,
259: signingAlg0); // First certificate in chain must be certificate asociated with owners of private key
260: if (includingCert0 == true) {
261: for (int i = 0; i != chain0.length; i++) { // Adding certificates from certificate chain
262: certif.addCertificate(chain0[i]);
263: countCert++;
264: }
265: }
266: }
267:
268: /**
269: * Adds the Certificate
270: * @param cert0 X509 certificate
271: * @exception SMIMEException thrown in inner object which is instance of the class
272: * org.enhydra.oyster.cms.Certificates.
273: */
274: public void addCertificate(X509Certificate cert0)
275: throws SMIMEException {
276: certif.addCertificate(cert0);
277: countCert++;
278: }
279:
280: /**
281: * Returns complete DER encoded CMS Signed Object
282: * @return DER encoded CMS Signed Object represented as byte array
283: * @exception SMIMEException caused by problems in construction or dealing
284: * with some inner objects instantiated from classes that belong to
285: * org.enhydra.oyster.der or org.enhydra.oyster.cms packages.
286: */
287: public byte[] getCMSSignedObject() throws SMIMEException {
288: SignedData signData = new SignedData(); // Container for signed data sub object
289: signData.addCMSVersion(new CMSVersion(1).getDEREncoded());
290: DigestAlgorithmIdentifiers digAlg = new DigestAlgorithmIdentifiers();
291: if (typeOfDigest[0] == true)
292: digAlg.addDigestAlgIdNullPar("SHA1", "NAME_STRING");
293: if (typeOfDigest[1] == true)
294: digAlg.addDigestAlgIdNullPar("MD2", "NAME_STRING");
295: if (typeOfDigest[2] == true)
296: digAlg.addDigestAlgIdNullPar("MD5", "NAME_STRING");
297: signData.addDigestAlgorithm(digAlg.getDEREncoded());
298: EncapsulatedContentInfo enc = new EncapsulatedContentInfo();
299: ContentTypeIdentifier cont = new ContentTypeIdentifier(
300: "ID_DATA", "NAME_STRING");
301: enc.addContentType(cont.getDEREncoded());
302: if (externalSignature == false) {
303: enc.addEncapsulatedContent(message);
304: }
305: signData.addEncapsulatedContentInfo(enc.getDEREncoded());
306: if (countCert != 0)
307: signData.addCertificate(certif.getDEREncoded());
308: signData.addSignerInfos(sInfo.getDEREncoded());
309: Content signedContent = new Content(signData.getDEREncoded(),
310: true); // Filling signed data content in context specific DER object
311: ContentInfo cmsObjectSignedData = new ContentInfo();
312: ContentTypeIdentifier contentTypeSignDataId = new ContentTypeIdentifier(
313: "ID_SIGNEDDATA", "NAME_STRING"); // Creating the Content Type
314: cmsObjectSignedData.addContentType(contentTypeSignDataId
315: .getDEREncoded());
316: cmsObjectSignedData.addContent(signedContent.getDEREncoded());
317: return cmsObjectSignedData.getDEREncoded();
318: }
319:
320: /**
321: * Returns complete DER encoded CMS Signed Object with BASE64 encoding
322: * @return DER encoded CMS Signed Object represented as byte array with
323: * performed BASE64 encoding.
324: * @exception SMIMEException in case of failure in Base64 encoding performed
325: * on the generated SMIME message byte array by method ofMimeAssist class. Also,
326: * it can be caused by problems in construction or work with some inner objects
327: * instantiated from classes that belong to org.enhydra.oyster.der or
328: * org.enhydra.oyster.cms packages used in getCMSSignedDataSource() method.
329: */
330: public byte[] getBASE64CMSSignedObject() throws SMIMEException {
331: return MimeAssist.getBASE64WithBreakOn76(this
332: .getCMSSignedObject());
333: }
334:
335: /**
336: * Returns "micalg" parameter used in Content-Type of external signing
337: * @return String representation of micalg parameter.
338: */
339: private String getMicalg() {
340: int sum = 0;
341: if (typeOfDigest[0])
342: sum = sum + 1;
343: if (typeOfDigest[1])
344: sum = sum + 10;
345: if (typeOfDigest[2])
346: sum = sum + 100;
347:
348: switch (sum) {
349: case 0:
350: return "\"unknown\"";
351: case 1:
352: return "\"sha1\"";
353: case 10:
354: return "\"md2\"";
355: case 100:
356: return "\"md5\"";
357: case 11:
358: return "\"sha1,md2\"";
359: case 101:
360: return "\"sha1,md5\"";
361: case 110:
362: return "\"md2,md5\"";
363: case 111:
364: return "\"sha1,md2,md5\"";
365: }
366: return "\"unknown\"";
367: }
368:
369: /**
370: * Returns composed message for external signing.
371: * @return External signed message represented as byte array.
372: * @exception SMIMEException in case of failure in Base64 encoding performed
373: * on the generated SMIME message byte array by method ofMimeAssist class. Also,
374: * it can be caused by problems in construction or work with some inner objects
375: * instantiated from classes that belong to org.enhydra.oyster.der or
376: * org.enhydra.oyster.cms packages used in getCMSSignedDataSource() method.
377: */
378: private byte[] getExternalSignedMessage() throws SMIMEException {
379:
380: try {
381: String extMessage = "--"
382: + boundary
383: + "\r\n"
384: + new String(this .message, "ISO-8859-1")
385: + "\r\n"
386: + "--"
387: + boundary
388: + "\r\n"
389: + "Content-Type: application/x-pkcs7-signature; name=smime.p7s"
390: + "\r\n"
391: + "Content-Transfer-Encoding: base64"
392: + "\r\n"
393: + "Content-Disposition: attachment; filename=smime.p7s"
394: + "\r\n"
395: + "\r\n"
396: + new String(this .getBASE64CMSSignedObject(),
397: "ISO-8859-1") + "\r\n" + "--" + boundary
398: + "--";
399:
400: return extMessage.getBytes("ISO-8859-1");
401: } catch (Exception e) {
402: throw new SMIMEException(e);
403: }
404: }
405:
406: /**
407: * Implements getContentType method from DataSource interface
408: * @return Content-Type for MIME message header field
409: */
410: public String getContentType() {
411: if (!externalSignature)
412:
413: // For new version of mail clients: "application/pkcs7-mime; smime-type=signed-data; name=\"smime.p7m\""
414: return "application/x-pkcs7-mime; smime-type=signed-data; name=\"smime.p7m\"";
415: else
416:
417: // For new version of mail clients protocol: "application/pkcs7-signature; name=\"smime.p7s\""
418: return "multipart/signed;"
419: + "\r\n"
420: + " protocol=\"application/x-pkcs7-signature\";"
421: + "\r\n" + " micalg=" + this .getMicalg()
422: + "; boundary=" + boundary;
423:
424: }
425:
426: /**
427: * Implements getInputStream method from DataSource interface
428: * @return CMS signed object
429: * @exception SMIMEIOException thrown as result of SMIMEException
430: */
431: public InputStream getInputStream() throws SMIMEIOException {
432: try {
433: if (!externalSignature)
434: return new ByteArrayInputStream(getCMSSignedObject());
435: else
436: return new ByteArrayInputStream(
437: getExternalSignedMessage());
438: } catch (SMIMEException e) {
439: throw new SMIMEIOException(e);
440: }
441: }
442:
443: /**
444: * Implements getName method from DataSource interface
445: * @return Name: SignedDataContentInfo
446: */
447: public String getName() {
448: return "SignedDataContentInfo";
449: }
450:
451: /**
452: * Implements getOutputStream method from DataSource interface. This
453: * method is not in use.
454: * @return nothing
455: * @exception IOException is always thrown when this method is used.
456: */
457: public OutputStream getOutputStream() throws IOException {
458: throw new IOException(
459: "SignedDataContentInfo does not support getOutputStream()");
460: }
461: }
|