org.spongycastle.openpgp.examples.ClearSignedFileProcessor Maven / Gradle / Ivy
Show all versions of pg Show documentation
package org.spongycastle.openpgp.examples;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.security.SignatureException;
import java.util.Iterator;
import org.spongycastle.bcpg.ArmoredInputStream;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.bcpg.BCPGOutputStream;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPPublicKeyRingCollection;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureGenerator;
import org.spongycastle.openpgp.PGPSignatureList;
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.spongycastle.openpgp.PGPUtil;
import org.spongycastle.openpgp.jcajce.JcaPGPObjectFactory;
import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.spongycastle.util.Strings;
/**
* A simple utility class that creates clear signed files and verifies them.
*
* To sign a file: ClearSignedFileProcessor -s fileName secretKey passPhrase.
*
* To decrypt: ClearSignedFileProcessor -v fileName signatureFile publicKeyFile.
*/
public class ClearSignedFileProcessor
{
private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn)
throws IOException
{
bOut.reset();
int lookAhead = -1;
int ch;
while ((ch = fIn.read()) >= 0)
{
bOut.write(ch);
if (ch == '\r' || ch == '\n')
{
lookAhead = readPassedEOL(bOut, ch, fIn);
break;
}
}
return lookAhead;
}
private static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn)
throws IOException
{
bOut.reset();
int ch = lookAhead;
do
{
bOut.write(ch);
if (ch == '\r' || ch == '\n')
{
lookAhead = readPassedEOL(bOut, ch, fIn);
break;
}
}
while ((ch = fIn.read()) >= 0);
if (ch < 0)
{
lookAhead = -1;
}
return lookAhead;
}
private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn)
throws IOException
{
int lookAhead = fIn.read();
if (lastCh == '\r' && lookAhead == '\n')
{
bOut.write(lookAhead);
lookAhead = fIn.read();
}
return lookAhead;
}
/*
* verify a clear text signed file
*/
private static void verifyFile(
InputStream in,
InputStream keyIn,
String resultName)
throws Exception
{
ArmoredInputStream aIn = new ArmoredInputStream(in);
OutputStream out = new BufferedOutputStream(new FileOutputStream(resultName));
//
// write out signed section using the local line separator.
// note: trailing white space needs to be removed from the end of
// each line RFC 4880 Section 7.1
//
ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
int lookAhead = readInputLine(lineOut, aIn);
byte[] lineSep = getLineSeparator();
if (lookAhead != -1 && aIn.isClearText())
{
byte[] line = lineOut.toByteArray();
out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
out.write(lineSep);
while (lookAhead != -1 && aIn.isClearText())
{
lookAhead = readInputLine(lineOut, lookAhead, aIn);
line = lineOut.toByteArray();
out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
out.write(lineSep);
}
}
else
{
// a single line file
if (lookAhead != -1)
{
byte[] line = lineOut.toByteArray();
out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
out.write(lineSep);
}
}
out.close();
PGPPublicKeyRingCollection pgpRings = new PGPPublicKeyRingCollection(keyIn, new JcaKeyFingerprintCalculator());
JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(aIn);
PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject();
PGPSignature sig = p3.get(0);
PGPPublicKey publicKey = pgpRings.getPublicKey(sig.getKeyID());
sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider("SC"), publicKey);
//
// read the input, making sure we ignore the last newline.
//
InputStream sigIn = new BufferedInputStream(new FileInputStream(resultName));
lookAhead = readInputLine(lineOut, sigIn);
processLine(sig, lineOut.toByteArray());
if (lookAhead != -1)
{
do
{
lookAhead = readInputLine(lineOut, lookAhead, sigIn);
sig.update((byte)'\r');
sig.update((byte)'\n');
processLine(sig, lineOut.toByteArray());
}
while (lookAhead != -1);
}
sigIn.close();
if (sig.verify())
{
System.out.println("signature verified.");
}
else
{
System.out.println("signature verification failed.");
}
}
private static byte[] getLineSeparator()
{
String nl = Strings.lineSeparator();
byte[] nlBytes = new byte[nl.length()];
for (int i = 0; i != nlBytes.length; i++)
{
nlBytes[i] = (byte)nl.charAt(i);
}
return nlBytes;
}
/*
* create a clear text signed file.
*/
private static void signFile(
String fileName,
InputStream keyIn,
OutputStream out,
char[] pass,
String digestName)
throws IOException, NoSuchAlgorithmException, NoSuchProviderException, PGPException, SignatureException
{
int digest;
if (digestName.equals("SHA256"))
{
digest = PGPUtil.SHA256;
}
else if (digestName.equals("SHA384"))
{
digest = PGPUtil.SHA384;
}
else if (digestName.equals("SHA512"))
{
digest = PGPUtil.SHA512;
}
else if (digestName.equals("MD5"))
{
digest = PGPUtil.MD5;
}
else if (digestName.equals("RIPEMD160"))
{
digest = PGPUtil.RIPEMD160;
}
else
{
digest = PGPUtil.SHA1;
}
PGPSecretKey pgpSecKey = PGPExampleUtil.readSecretKey(keyIn);
PGPPrivateKey pgpPrivKey = pgpSecKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("SC").build(pass));
PGPSignatureGenerator sGen = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(pgpSecKey.getPublicKey().getAlgorithm(), digest).setProvider("SC"));
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
sGen.init(PGPSignature.CANONICAL_TEXT_DOCUMENT, pgpPrivKey);
Iterator it = pgpSecKey.getPublicKey().getUserIDs();
if (it.hasNext())
{
spGen.setSignerUserID(false, (String)it.next());
sGen.setHashedSubpackets(spGen.generate());
}
InputStream fIn = new BufferedInputStream(new FileInputStream(fileName));
ArmoredOutputStream aOut = new ArmoredOutputStream(out);
aOut.beginClearText(digest);
//
// note the last \n/\r/\r\n in the file is ignored
//
ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
int lookAhead = readInputLine(lineOut, fIn);
processLine(aOut, sGen, lineOut.toByteArray());
if (lookAhead != -1)
{
do
{
lookAhead = readInputLine(lineOut, lookAhead, fIn);
sGen.update((byte)'\r');
sGen.update((byte)'\n');
processLine(aOut, sGen, lineOut.toByteArray());
}
while (lookAhead != -1);
}
fIn.close();
aOut.endClearText();
BCPGOutputStream bOut = new BCPGOutputStream(aOut);
sGen.generate().encode(bOut);
aOut.close();
}
private static void processLine(PGPSignature sig, byte[] line)
throws SignatureException, IOException
{
int length = getLengthWithoutWhiteSpace(line);
if (length > 0)
{
sig.update(line, 0, length);
}
}
private static void processLine(OutputStream aOut, PGPSignatureGenerator sGen, byte[] line)
throws SignatureException, IOException
{
// note: trailing white space needs to be removed from the end of
// each line for signature calculation RFC 4880 Section 7.1
int length = getLengthWithoutWhiteSpace(line);
if (length > 0)
{
sGen.update(line, 0, length);
}
aOut.write(line, 0, line.length);
}
private static int getLengthWithoutSeparatorOrTrailingWhitespace(byte[] line)
{
int end = line.length - 1;
while (end >= 0 && isWhiteSpace(line[end]))
{
end--;
}
return end + 1;
}
private static boolean isLineEnding(byte b)
{
return b == '\r' || b == '\n';
}
private static int getLengthWithoutWhiteSpace(byte[] line)
{
int end = line.length - 1;
while (end >= 0 && isWhiteSpace(line[end]))
{
end--;
}
return end + 1;
}
private static boolean isWhiteSpace(byte b)
{
return isLineEnding(b) || b == '\t' || b == ' ';
}
public static void main(
String[] args)
throws Exception
{
Security.addProvider(new BouncyCastleProvider());
if (args[0].equals("-s"))
{
InputStream keyIn = PGPUtil.getDecoderStream(new FileInputStream(args[2]));
FileOutputStream out = new FileOutputStream(args[1] + ".asc");
if (args.length == 4)
{
signFile(args[1], keyIn, out, args[3].toCharArray(), "SHA1");
}
else
{
signFile(args[1], keyIn, out, args[3].toCharArray(), args[4]);
}
}
else if (args[0].equals("-v"))
{
if (args[1].indexOf(".asc") < 0)
{
System.err.println("file needs to end in \".asc\"");
System.exit(1);
}
FileInputStream in = new FileInputStream(args[1]);
InputStream keyIn = PGPUtil.getDecoderStream(new FileInputStream(args[2]));
verifyFile(in, keyIn, args[1].substring(0, args[1].length() - 4));
}
else
{
System.err.println("usage: ClearSignedFileProcessor [-s file keyfile passPhrase]|[-v sigFile keyFile]");
}
}
}