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