1 package org.bouncycastle.mime.smime;
2 
3 import java.io.IOException;
4 import java.io.OutputStream;
5 import java.util.LinkedHashMap;
6 import java.util.Map;
7 
8 import org.bouncycastle.cms.CMSAttributeTableGenerator;
9 import org.bouncycastle.cms.CMSEnvelopedDataStreamGenerator;
10 import org.bouncycastle.cms.CMSException;
11 import org.bouncycastle.cms.OriginatorInformation;
12 import org.bouncycastle.cms.RecipientInfoGenerator;
13 import org.bouncycastle.mime.Headers;
14 import org.bouncycastle.mime.MimeIOException;
15 import org.bouncycastle.mime.MimeWriter;
16 import org.bouncycastle.mime.encoding.Base64OutputStream;
17 import org.bouncycastle.operator.OutputEncryptor;
18 import org.bouncycastle.util.Strings;
19 
20 /**
21  * Writer for SMIME Enveloped objects.
22  */
23 public class SMIMEEnvelopedWriter
24     extends MimeWriter
25 {
26     public static class Builder
27     {
28         private static final String[] stdHeaders;
29         private static final String[] stdValues;
30 
31         static
32         {
33             stdHeaders = new String[]
34                 {
35                     "Content-Type",
36                     "Content-Disposition",
37                     "Content-Transfer-Encoding",
38                     "Content-Description"
39                 };
40 
41             stdValues = new String[]
42                 {
43                     "application/pkcs7-mime; name=\"smime.p7m\"; smime-type=enveloped-data",
44                     "attachment; filename=\"smime.p7m\"",
45                     "base64",
46                     "S/MIME Encrypted Message"
47                 };
48         }
49 
50         private final CMSEnvelopedDataStreamGenerator envGen = new CMSEnvelopedDataStreamGenerator();
51         private final Map<String, String> headers = new LinkedHashMap<String, String>();
52 
53         String contentTransferEncoding = "base64";
54 
Builder()55         public Builder()
56         {
57             for (int i = 0; i != stdHeaders.length; i++)
58             {
59                 headers.put(stdHeaders[i], stdValues[i]);
60             }
61         }
62 
63         /**
64          * Set the underlying string size for encapsulated data
65          *
66          * @param bufferSize length of octet strings to buffer the data.
67          */
setBufferSize( int bufferSize)68         public Builder setBufferSize(
69             int bufferSize)
70         {
71             this.envGen.setBufferSize(bufferSize);
72 
73             return this;
74         }
75 
setUnprotectedAttributeGenerator(CMSAttributeTableGenerator unprotectedAttributeGenerator)76         public Builder setUnprotectedAttributeGenerator(CMSAttributeTableGenerator unprotectedAttributeGenerator)
77         {
78             this.envGen.setUnprotectedAttributeGenerator(unprotectedAttributeGenerator);
79 
80             return this;
81         }
82 
setOriginatorInfo(OriginatorInformation originatorInfo)83         public Builder setOriginatorInfo(OriginatorInformation originatorInfo)
84         {
85             this.envGen.setOriginatorInfo(originatorInfo);
86 
87             return this;
88         }
89 
90         /**
91          * Specify a MIME header (name, value) pair for this builder. If the headerName already exists it will
92          * be overridden.
93          *
94          * @param headerName name of the MIME header.
95          * @param headerValue value of the MIME header.
96          *
97          * @return the current Builder instance.
98          */
withHeader(String headerName, String headerValue)99         public Builder withHeader(String headerName, String headerValue)
100         {
101             this.headers.put(headerName, headerValue);
102 
103             return this;
104         }
105 
106         /**
107          * Add a generator to produce the recipient info required.
108          *
109          * @param recipientGenerator a generator of a recipient info object.
110          *
111          * @return the current Builder instance.
112          */
addRecipientInfoGenerator(RecipientInfoGenerator recipientGenerator)113         public Builder addRecipientInfoGenerator(RecipientInfoGenerator recipientGenerator)
114         {
115             this.envGen.addRecipientInfoGenerator(recipientGenerator);
116 
117             return this;
118         }
119 
build(OutputStream mimeOut, OutputEncryptor outEnc)120         public SMIMEEnvelopedWriter build(OutputStream mimeOut, OutputEncryptor outEnc)
121         {
122             return new SMIMEEnvelopedWriter(this, outEnc, SMimeUtils.autoBuffer(mimeOut));
123         }
124     }
125 
126     private final CMSEnvelopedDataStreamGenerator envGen;
127 
128     private final OutputEncryptor outEnc;
129     private final OutputStream mimeOut;
130     private final String contentTransferEncoding;
131 
SMIMEEnvelopedWriter(Builder builder, OutputEncryptor outEnc, OutputStream mimeOut)132     private SMIMEEnvelopedWriter(Builder builder, OutputEncryptor outEnc, OutputStream mimeOut)
133     {
134         super(new Headers(mapToLines(builder.headers), builder.contentTransferEncoding));
135 
136         this.envGen = builder.envGen;
137         this.contentTransferEncoding = builder.contentTransferEncoding;
138         this.outEnc = outEnc;
139         this.mimeOut = mimeOut;
140     }
141 
getContentStream()142     public OutputStream getContentStream()
143         throws IOException
144     {
145         headers.dumpHeaders(mimeOut);
146 
147         mimeOut.write(Strings.toByteArray("\r\n"));
148 
149         try
150         {
151             OutputStream backing = mimeOut;
152 
153             if ("base64".equals(contentTransferEncoding))
154             {
155                 backing = new Base64OutputStream(backing);
156             }
157 
158             OutputStream main = envGen.open(SMimeUtils.createUnclosable(backing), outEnc);
159 
160             return new ContentOutputStream(main, backing);
161         }
162         catch (CMSException e)
163         {
164             throw new MimeIOException(e.getMessage(), e);
165         }
166     }
167 
168     private class ContentOutputStream
169         extends OutputStream
170     {
171         private final OutputStream main;
172         private final OutputStream backing;
173 
ContentOutputStream(OutputStream main, OutputStream backing)174         ContentOutputStream(OutputStream main, OutputStream backing)
175         {
176             this.main = main;
177             this.backing = backing;
178         }
179 
write(byte[] buf)180         public void write(byte[] buf)
181             throws IOException
182         {
183             main.write(buf);
184         }
185 
write(byte[] buf, int off, int len)186         public void write(byte[] buf, int off, int len)
187             throws IOException
188         {
189             main.write(buf, off, len);
190         }
191 
write(int i)192         public void write(int i)
193             throws IOException
194         {
195             main.write(i);
196         }
197 
close()198         public void close()
199             throws IOException
200         {
201             main.close();
202             if (backing != null)
203             {
204                 backing.close();
205             }
206         }
207     }
208 }
209