001: package org.bouncycastle.bcpg;
002:
003: import java.io.*;
004: import java.util.Enumeration;
005: import java.util.Hashtable;
006:
007: /**
008: * Basic output stream.
009: */
010: public class ArmoredOutputStream extends OutputStream {
011: private static final byte[] encodingTable = { (byte) 'A',
012: (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
013: (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
014: (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
015: (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
016: (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
017: (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
018: (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
019: (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
020: (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
021: (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
022: (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
023: (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
024: (byte) '9', (byte) '+', (byte) '/' };
025:
026: /**
027: * encode the input data producing a base 64 encoded byte array.
028: */
029: private void encode(OutputStream out, int[] data, int len)
030: throws IOException {
031: int d1, d2, d3;
032:
033: switch (len) {
034: case 0: /* nothing left to do */
035: break;
036: case 1:
037: d1 = data[0];
038:
039: out.write(encodingTable[(d1 >>> 2) & 0x3f]);
040: out.write(encodingTable[(d1 << 4) & 0x3f]);
041: out.write('=');
042: out.write('=');
043: break;
044: case 2:
045: d1 = data[0];
046: d2 = data[1];
047:
048: out.write(encodingTable[(d1 >>> 2) & 0x3f]);
049: out.write(encodingTable[((d1 << 4) | (d2 >>> 4)) & 0x3f]);
050: out.write(encodingTable[(d2 << 2) & 0x3f]);
051: out.write('=');
052: break;
053: case 3:
054: d1 = data[0];
055: d2 = data[1];
056: d3 = data[2];
057:
058: out.write(encodingTable[(d1 >>> 2) & 0x3f]);
059: out.write(encodingTable[((d1 << 4) | (d2 >>> 4)) & 0x3f]);
060: out.write(encodingTable[((d2 << 2) | (d3 >>> 6)) & 0x3f]);
061: out.write(encodingTable[d3 & 0x3f]);
062: break;
063: default:
064: throw new IOException("unknown length in encode");
065: }
066: }
067:
068: OutputStream out;
069: int[] buf = new int[3];
070: int bufPtr = 0;
071: CRC24 crc = new CRC24();
072: int chunkCount = 0;
073: int lastb;
074:
075: boolean start = true;
076: boolean clearText = false;
077: boolean newLine = false;
078:
079: String nl = System.getProperty("line.separator");
080:
081: String type;
082: String headerStart = "-----BEGIN PGP ";
083: String headerTail = "-----";
084: String footerStart = "-----END PGP ";
085: String footerTail = "-----";
086:
087: String version = "BCPG v1.37";
088:
089: Hashtable headers = new Hashtable();
090:
091: public ArmoredOutputStream(OutputStream out) {
092: this .out = out;
093:
094: if (nl == null) {
095: nl = "\r\n";
096: }
097:
098: resetHeaders();
099: }
100:
101: public ArmoredOutputStream(OutputStream out, Hashtable headers) {
102: this (out);
103:
104: Enumeration e = headers.keys();
105:
106: while (e.hasMoreElements()) {
107: Object key = e.nextElement();
108:
109: this .headers.put(key, headers.get(key));
110: }
111: }
112:
113: /**
114: * Set an additional header entry.
115: *
116: * @param name the name of the header entry.
117: * @param value the value of the header entry.
118: */
119: public void setHeader(String name, String value) {
120: this .headers.put(name, value);
121: }
122:
123: /**
124: * Reset the headers to only contain a Version string.
125: */
126: public void resetHeaders() {
127: headers.clear();
128: headers.put("Version", version);
129: }
130:
131: /**
132: * Start a clear text signed message.
133: * @param hashAlgorithm
134: */
135: public void beginClearText(int hashAlgorithm) throws IOException {
136: String hash;
137:
138: switch (hashAlgorithm) {
139: case HashAlgorithmTags.SHA1:
140: hash = "SHA1";
141: break;
142: case HashAlgorithmTags.SHA256:
143: hash = "SHA256";
144: break;
145: case HashAlgorithmTags.SHA384:
146: hash = "SHA384";
147: break;
148: case HashAlgorithmTags.SHA512:
149: hash = "SHA512";
150: break;
151: case HashAlgorithmTags.MD2:
152: hash = "MD2";
153: break;
154: case HashAlgorithmTags.MD5:
155: hash = "MD5";
156: break;
157: case HashAlgorithmTags.RIPEMD160:
158: hash = "RIPEMD160";
159: break;
160: default:
161: throw new IOException(
162: "unknown hash algorithm tag in beginClearText: "
163: + hashAlgorithm);
164: }
165:
166: String armorHdr = "-----BEGIN PGP SIGNED MESSAGE-----" + nl;
167: String hdrs = "Hash: " + hash + nl + nl;
168:
169: for (int i = 0; i != armorHdr.length(); i++) {
170: out.write(armorHdr.charAt(i));
171: }
172:
173: for (int i = 0; i != hdrs.length(); i++) {
174: out.write(hdrs.charAt(i));
175: }
176:
177: clearText = true;
178: newLine = true;
179: lastb = 0;
180: }
181:
182: public void endClearText() {
183: clearText = false;
184: }
185:
186: private void writeHeaderEntry(String name, String value)
187: throws IOException {
188: for (int i = 0; i != name.length(); i++) {
189: out.write(name.charAt(i));
190: }
191:
192: out.write(':');
193: out.write(' ');
194:
195: for (int i = 0; i != value.length(); i++) {
196: out.write(value.charAt(i));
197: }
198:
199: for (int i = 0; i != nl.length(); i++) {
200: out.write(nl.charAt(i));
201: }
202: }
203:
204: public void write(int b) throws IOException {
205: if (clearText) {
206: out.write(b);
207:
208: if (newLine) {
209: if (!(b == '\n' && lastb == '\r')) {
210: newLine = false;
211: }
212: if (b == '-') {
213: out.write(' ');
214: out.write('-'); // dash escape
215: }
216: }
217: if (b == '\r' || (b == '\n' && lastb != '\r')) {
218: newLine = true;
219: }
220: lastb = b;
221: return;
222: }
223:
224: if (start) {
225: boolean newPacket = (b & 0x40) != 0;
226: int tag = 0;
227:
228: if (newPacket) {
229: tag = b & 0x3f;
230: } else {
231: tag = (b & 0x3f) >> 2;
232: }
233:
234: switch (tag) {
235: case PacketTags.PUBLIC_KEY:
236: type = "PUBLIC KEY BLOCK";
237: break;
238: case PacketTags.SECRET_KEY:
239: type = "PRIVATE KEY BLOCK";
240: break;
241: case PacketTags.SIGNATURE:
242: type = "SIGNATURE";
243: break;
244: default:
245: type = "MESSAGE";
246: }
247:
248: for (int i = 0; i != headerStart.length(); i++) {
249: out.write(headerStart.charAt(i));
250: }
251:
252: for (int i = 0; i != type.length(); i++) {
253: out.write(type.charAt(i));
254: }
255:
256: for (int i = 0; i != headerTail.length(); i++) {
257: out.write(headerTail.charAt(i));
258: }
259:
260: for (int i = 0; i != nl.length(); i++) {
261: out.write(nl.charAt(i));
262: }
263:
264: writeHeaderEntry("Version", (String) headers.get("Version"));
265:
266: Enumeration e = headers.keys();
267: while (e.hasMoreElements()) {
268: String key = (String) e.nextElement();
269:
270: if (!key.equals("Version")) {
271: writeHeaderEntry(key, (String) headers.get(key));
272: }
273: }
274:
275: for (int i = 0; i != nl.length(); i++) {
276: out.write(nl.charAt(i));
277: }
278:
279: start = false;
280: }
281:
282: if (bufPtr == 3) {
283: encode(out, buf, bufPtr);
284: bufPtr = 0;
285: if ((++chunkCount & 0xf) == 0) {
286: for (int i = 0; i != nl.length(); i++) {
287: out.write(nl.charAt(i));
288: }
289: }
290: }
291:
292: crc.update(b);
293: buf[bufPtr++] = b & 0xff;
294: }
295:
296: public void flush() throws IOException {
297: }
298:
299: /**
300: * <b>Note</b>: close does nor close the underlying stream. So it is possible to write
301: * multiple objects using armoring to a single stream.
302: */
303: public void close() throws IOException {
304: if (type != null) {
305: encode(out, buf, bufPtr);
306:
307: for (int i = 0; i != nl.length(); i++) {
308: out.write(nl.charAt(i));
309: }
310: out.write('=');
311:
312: int crcV = crc.getValue();
313:
314: buf[0] = ((crcV >> 16) & 0xff);
315: buf[1] = ((crcV >> 8) & 0xff);
316: buf[2] = (crcV & 0xff);
317:
318: encode(out, buf, 3);
319:
320: for (int i = 0; i != nl.length(); i++) {
321: out.write(nl.charAt(i));
322: }
323:
324: for (int i = 0; i != footerStart.length(); i++) {
325: out.write(footerStart.charAt(i));
326: }
327:
328: for (int i = 0; i != type.length(); i++) {
329: out.write(type.charAt(i));
330: }
331:
332: for (int i = 0; i != footerTail.length(); i++) {
333: out.write(footerTail.charAt(i));
334: }
335:
336: for (int i = 0; i != nl.length(); i++) {
337: out.write(nl.charAt(i));
338: }
339:
340: out.flush();
341:
342: type = null;
343: start = true;
344: }
345: }
346: }
|