001: package org.bouncycastle.openpgp.examples;
002:
003: import org.bouncycastle.bcpg.ArmoredInputStream;
004: import org.bouncycastle.bcpg.ArmoredOutputStream;
005: import org.bouncycastle.bcpg.BCPGOutputStream;
006: import org.bouncycastle.jce.provider.BouncyCastleProvider;
007: import org.bouncycastle.openpgp.PGPException;
008: import org.bouncycastle.openpgp.PGPObjectFactory;
009: import org.bouncycastle.openpgp.PGPPrivateKey;
010: import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
011: import org.bouncycastle.openpgp.PGPSecretKey;
012: import org.bouncycastle.openpgp.PGPSecretKeyRing;
013: import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
014: import org.bouncycastle.openpgp.PGPSignature;
015: import org.bouncycastle.openpgp.PGPSignatureGenerator;
016: import org.bouncycastle.openpgp.PGPSignatureList;
017: import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
018: import org.bouncycastle.openpgp.PGPUtil;
019:
020: import java.io.BufferedInputStream;
021: import java.io.BufferedOutputStream;
022: import java.io.ByteArrayOutputStream;
023: import java.io.FileInputStream;
024: import java.io.FileOutputStream;
025: import java.io.IOException;
026: import java.io.InputStream;
027: import java.io.OutputStream;
028: import java.security.NoSuchAlgorithmException;
029: import java.security.NoSuchProviderException;
030: import java.security.Security;
031: import java.security.SignatureException;
032: import java.util.Iterator;
033:
034: /**
035: * A simple utility class that creates clear signed files and verifies them.
036: * <p>
037: * To sign a file: ClearSignedFileProcessor -s fileName secretKey passPhrase.<br>
038: * If -a is specified the output file will be "ascii-armored".
039: * <p>
040: * To decrypt: ClearSignedFileProcessor -v fileName signatureFile publicKeyFile.
041: */
042: public class ClearSignedFileProcessor {
043: /**
044: * A simple routine that opens a key ring file and loads the first available key suitable for
045: * signature generation.
046: *
047: * @param in stream to read the secret key ring collection from.
048: * @return a secret key.
049: * @throws IOException on a problem with using the input stream.
050: * @throws PGPException if there is an issue parsing the input stream.
051: */
052: private static PGPSecretKey readSecretKey(InputStream in)
053: throws IOException, PGPException {
054: PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(
055: in);
056:
057: //
058: // we just loop through the collection till we find a key suitable for encryption, in the real
059: // world you would probably want to be a bit smarter about this.
060: //
061: PGPSecretKey key = null;
062:
063: //
064: // iterate through the key rings.
065: //
066: Iterator rIt = pgpSec.getKeyRings();
067:
068: while (key == null && rIt.hasNext()) {
069: PGPSecretKeyRing kRing = (PGPSecretKeyRing) rIt.next();
070: Iterator kIt = kRing.getSecretKeys();
071:
072: while (key == null && kIt.hasNext()) {
073: PGPSecretKey k = (PGPSecretKey) kIt.next();
074:
075: if (k.isSigningKey()) {
076: key = k;
077: }
078: }
079: }
080:
081: if (key == null) {
082: throw new IllegalArgumentException(
083: "Can't find signing key in key ring.");
084: }
085:
086: return key;
087: }
088:
089: private static int readInputLine(ByteArrayOutputStream bOut,
090: InputStream fIn) throws IOException {
091: bOut.reset();
092:
093: int lookAhead = -1;
094: int ch;
095:
096: while ((ch = fIn.read()) >= 0) {
097: bOut.write(ch);
098: if (ch == '\r' || ch == '\n') {
099: lookAhead = readPassedEOL(bOut, ch, fIn);
100: break;
101: }
102: }
103:
104: return lookAhead;
105: }
106:
107: private static int readInputLine(ByteArrayOutputStream bOut,
108: int lookAhead, InputStream fIn) throws IOException {
109: bOut.reset();
110:
111: int ch = lookAhead;
112:
113: do {
114: bOut.write(ch);
115: if (ch == '\r' || ch == '\n') {
116: lookAhead = readPassedEOL(bOut, ch, fIn);
117: break;
118: }
119: } while ((ch = fIn.read()) >= 0);
120:
121: return lookAhead;
122: }
123:
124: private static int readPassedEOL(ByteArrayOutputStream bOut,
125: int lastCh, InputStream fIn) throws IOException {
126: int lookAhead = fIn.read();
127:
128: if (lastCh == '\r' && lookAhead == '\n') {
129: bOut.write(lookAhead);
130: lookAhead = fIn.read();
131: }
132:
133: return lookAhead;
134: }
135:
136: /*
137: * verify a clear text signed file
138: */
139: private static void verifyFile(InputStream in, InputStream keyIn,
140: String resultName) throws Exception {
141: ArmoredInputStream aIn = new ArmoredInputStream(in);
142: OutputStream out = new BufferedOutputStream(
143: new FileOutputStream(resultName));
144:
145: //
146: // write out signed section using the local line separator.
147: // note: although we leave it in trailing white space as it is not verifiable.
148: // Some people prefer to remove it.
149: //
150: ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
151: int lookAhead = readInputLine(lineOut, aIn);
152: byte[] lineSep = getLineSeparator();
153:
154: if (lookAhead != -1 && aIn.isClearText()) {
155: byte[] line = lineOut.toByteArray();
156: out.write(line, 0, getLengthWithoutSeperator(line));
157: out.write(lineSep);
158:
159: while (lookAhead != -1 && aIn.isClearText()) {
160: lookAhead = readInputLine(lineOut, lookAhead, aIn);
161:
162: line = lineOut.toByteArray();
163: out.write(line, 0, getLengthWithoutSeperator(line));
164: out.write(lineSep);
165: }
166: }
167:
168: out.close();
169:
170: PGPPublicKeyRingCollection pgpRings = new PGPPublicKeyRingCollection(
171: keyIn);
172:
173: PGPObjectFactory pgpFact = new PGPObjectFactory(aIn);
174: PGPSignatureList p3 = (PGPSignatureList) pgpFact.nextObject();
175: PGPSignature sig = p3.get(0);
176:
177: sig.initVerify(pgpRings.getPublicKey(sig.getKeyID()), "BC");
178:
179: //
180: // read the input, making sure we ingore the last newline.
181: //
182:
183: InputStream sigIn = new BufferedInputStream(
184: new FileInputStream(resultName));
185:
186: lookAhead = readInputLine(lineOut, sigIn);
187:
188: processLine(sig, lineOut.toByteArray());
189:
190: if (lookAhead != -1) {
191: do {
192: lookAhead = readInputLine(lineOut, lookAhead, sigIn);
193:
194: sig.update((byte) '\r');
195: sig.update((byte) '\n');
196:
197: processLine(sig, lineOut.toByteArray());
198: } while (lookAhead != -1);
199: }
200:
201: if (sig.verify()) {
202: System.out.println("signature verified.");
203: } else {
204: System.out.println("signature verification failed.");
205: }
206: }
207:
208: private static byte[] getLineSeparator() {
209: String nl = System.getProperty("line.separator");
210: byte[] nlBytes = new byte[nl.length()];
211:
212: for (int i = 0; i != nlBytes.length; i++) {
213: nlBytes[i] = (byte) nl.charAt(i);
214: }
215:
216: return nlBytes;
217: }
218:
219: /*
220: * create a clear text signed file.
221: */
222: private static void signFile(String fileName, InputStream keyIn,
223: OutputStream out, char[] pass, String digestName)
224: throws IOException, NoSuchAlgorithmException,
225: NoSuchProviderException, PGPException, SignatureException {
226: int digest;
227:
228: if (digestName.equals("SHA256")) {
229: digest = PGPUtil.SHA256;
230: } else if (digestName.equals("SHA384")) {
231: digest = PGPUtil.SHA384;
232: } else if (digestName.equals("SHA512")) {
233: digest = PGPUtil.SHA512;
234: } else if (digestName.equals("MD5")) {
235: digest = PGPUtil.MD5;
236: } else if (digestName.equals("RIPEMD160")) {
237: digest = PGPUtil.RIPEMD160;
238: } else {
239: digest = PGPUtil.SHA1;
240: }
241:
242: PGPSecretKey pgpSecKey = readSecretKey(keyIn);
243: PGPPrivateKey pgpPrivKey = pgpSecKey.extractPrivateKey(pass,
244: "BC");
245: PGPSignatureGenerator sGen = new PGPSignatureGenerator(
246: pgpSecKey.getPublicKey().getAlgorithm(), digest, "BC");
247: PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
248:
249: sGen.initSign(PGPSignature.CANONICAL_TEXT_DOCUMENT, pgpPrivKey);
250:
251: Iterator it = pgpSecKey.getPublicKey().getUserIDs();
252: if (it.hasNext()) {
253: spGen.setSignerUserID(false, (String) it.next());
254: sGen.setHashedSubpackets(spGen.generate());
255: }
256:
257: FileInputStream fIn = new FileInputStream(fileName);
258: ArmoredOutputStream aOut = new ArmoredOutputStream(out);
259:
260: aOut.beginClearText(digest);
261:
262: //
263: // note the last \n/\r/\r\n in the file is ignored
264: //
265: ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
266: int lookAhead = readInputLine(lineOut, fIn);
267:
268: processLine(aOut, sGen, lineOut.toByteArray());
269:
270: if (lookAhead != -1) {
271: do {
272: lookAhead = readInputLine(lineOut, lookAhead, fIn);
273:
274: sGen.update((byte) '\r');
275: sGen.update((byte) '\n');
276:
277: processLine(aOut, sGen, lineOut.toByteArray());
278: } while (lookAhead != -1);
279: }
280:
281: aOut.endClearText();
282:
283: BCPGOutputStream bOut = new BCPGOutputStream(aOut);
284:
285: sGen.generate().encode(bOut);
286:
287: aOut.close();
288: }
289:
290: private static void processLine(PGPSignature sig, byte[] line)
291: throws SignatureException, IOException {
292: int length = getLengthWithoutWhiteSpace(line);
293: if (length > 0) {
294: sig.update(line, 0, length);
295: }
296: }
297:
298: private static void processLine(OutputStream aOut,
299: PGPSignatureGenerator sGen, byte[] line)
300: throws SignatureException, IOException {
301: int length = getLengthWithoutWhiteSpace(line);
302: if (length > 0) {
303: sGen.update(line, 0, length);
304: }
305:
306: aOut.write(line, 0, line.length);
307: }
308:
309: private static int getLengthWithoutSeperator(byte[] line) {
310: int end = line.length - 1;
311:
312: while (end >= 0 && isLineEnding(line[end])) {
313: end--;
314: }
315:
316: return end + 1;
317: }
318:
319: private static boolean isLineEnding(byte b) {
320: return b == '\r' || b == '\n';
321: }
322:
323: private static int getLengthWithoutWhiteSpace(byte[] line) {
324: int end = line.length - 1;
325:
326: while (end >= 0 && isWhiteSpace(line[end])) {
327: end--;
328: }
329:
330: return end + 1;
331: }
332:
333: private static boolean isWhiteSpace(byte b) {
334: return b == '\r' || b == '\n' || b == '\t' || b == ' ';
335: }
336:
337: public static void main(String[] args) throws Exception {
338: Security.addProvider(new BouncyCastleProvider());
339:
340: if (args[0].equals("-s")) {
341: InputStream keyIn = PGPUtil
342: .getDecoderStream(new FileInputStream(args[2]));
343: FileOutputStream out = new FileOutputStream(args[1]
344: + ".asc");
345:
346: if (args.length == 4) {
347: signFile(args[1], keyIn, out, args[3].toCharArray(),
348: "SHA1");
349: } else {
350: signFile(args[1], keyIn, out, args[3].toCharArray(),
351: args[4]);
352: }
353: } else if (args[0].equals("-v")) {
354: if (args[1].indexOf(".asc") < 0) {
355: System.err.println("file needs to end in \".asc\"");
356: System.exit(1);
357: }
358: FileInputStream in = new FileInputStream(args[1]);
359: InputStream keyIn = PGPUtil
360: .getDecoderStream(new FileInputStream(args[2]));
361:
362: verifyFile(in, keyIn, args[1].substring(0,
363: args[1].length() - 4));
364: } else {
365: System.err
366: .println("usage: ClearSignedFileProcessor [-s file keyfile passPhrase]|[-v sigFile keyFile]");
367: }
368: }
369: }
|