Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/quite/humla-spongycastle.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'pg/src/main/java/org/spongycastle/openpgp')
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPAlgorithmParameters.java5
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPCompressedData.java153
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPCompressedDataGenerator.java236
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPDataValidationException.java17
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPEncryptedData.java165
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPEncryptedDataGenerator.java398
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPEncryptedDataList.java110
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPException.java35
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPKdfParameters.java24
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPKeyFlags.java19
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPKeyPair.java54
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPKeyRing.java125
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPKeyRingGenerator.java151
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPKeyValidationException.java16
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPLiteralData.java90
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPLiteralDataGenerator.java231
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPMarker.java34
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPObjectFactory.java175
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPOnePassSignature.java222
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPOnePassSignatureList.java40
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPPBEEncryptedData.java145
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPPrivateKey.java65
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPPublicKey.java977
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPPublicKeyEncryptedData.java168
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPPublicKeyRing.java252
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPPublicKeyRingCollection.java391
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPRuntimeOperationException.java19
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPSecretKey.java945
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPSecretKeyRing.java401
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPSecretKeyRingCollection.java389
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPSignature.java559
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPSignatureGenerator.java443
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPSignatureList.java40
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPSignatureSubpacketGenerator.java207
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPSignatureSubpacketVector.java308
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPUserAttributeSubpacketVector.java93
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPUserAttributeSubpacketVectorGenerator.java27
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPUtil.java403
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/PGPV3SignatureGenerator.java201
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/SXprUtils.java101
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/StreamGenerator.java16
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/WrappedGeneratorStream.java46
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/bc/BcPGPObjectFactory.java35
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/bc/BcPGPPublicKeyRing.java26
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/bc/BcPGPPublicKeyRingCollection.java32
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/bc/BcPGPSecretKeyRing.java27
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/bc/BcPGPSecretKeyRingCollection.java32
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/examples/ByteArrayHandler.java206
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java391
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/examples/DSAElGamalKeyRingGenerator.java139
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/examples/DetachedSignatureProcessor.java199
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/examples/DirectKeySignature.java115
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/examples/KeyBasedFileProcessor.java280
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/examples/KeyBasedLargeFileProcessor.java284
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/examples/PBEFileProcessor.java214
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/examples/PGPExampleUtil.java155
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/examples/PubringDump.java100
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/examples/RSAKeyPairGenerator.java112
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/examples/SignedFileProcessor.java216
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/jcajce/JcaPGPObjectFactory.java35
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/jcajce/JcaPGPPublicKeyRing.java26
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/jcajce/JcaPGPPublicKeyRingCollection.java32
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/jcajce/JcaPGPSecretKeyRing.java27
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/jcajce/JcaPGPSecretKeyRingCollection.java32
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/KeyFingerPrintCalculator.java10
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/PBEDataDecryptorFactory.java57
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/PBEKeyEncryptionMethodGenerator.java134
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/PBEProtectionRemoverFactory.java9
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/PBESecretKeyDecryptor.java31
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/PBESecretKeyEncryptor.java104
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentSigner.java20
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentSignerBuilder.java10
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifier.java20
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifierBuilder.java10
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifierBuilderProvider.java9
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptor.java30
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptorFactory.java25
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptorProvider.java5
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataEncryptor.java39
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataEncryptorBuilder.java36
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/PGPDigestCalculator.java40
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/PGPDigestCalculatorProvider.java21
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/PGPKeyEncryptionMethodGenerator.java23
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/PGPPad.java50
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/PGPUtil.java229
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/PublicKeyDataDecryptorFactory.java10
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/PublicKeyKeyEncryptionMethodGenerator.java100
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/RFC6637KDFCalculator.java115
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcImplProvider.java174
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcKeyFingerprintCalculator.java68
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java68
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBEKeyEncryptionMethodGenerator.java95
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilder.java43
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBESecretKeyEncryptorBuilder.java142
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPContentSignerBuilder.java98
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPContentVerifierBuilderProvider.java75
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPDataEncryptorBuilder.java131
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPDigestCalculatorProvider.java82
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPKeyConverter.java239
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPKeyPair.java33
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java139
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPublicKeyKeyEncryptionMethodGenerator.java139
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcUtil.java75
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/bc/SHA1PGPDigestCalculator.java68
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/bc/SignerOutputStream.java35
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaKeyFingerprintCalculator.java74
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPContentSignerBuilder.java156
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPContentVerifierBuilderProvider.java113
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPDigestCalculatorProviderBuilder.java149
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPKeyConverter.java377
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPKeyPair.java48
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPPrivateKey.java34
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java109
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEKeyEncryptionMethodGenerator.java142
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEProtectionRemoverFactory.java106
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilder.java100
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBESecretKeyEncryptorBuilder.java180
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePGPDataEncryptorBuilder.java175
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java239
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePublicKeyKeyEncryptionMethodGenerator.java166
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/OperatorHelper.java200
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/PGPUtil.java124
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/SHA1PGPDigestCalculator.java81
-rw-r--r--pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/SignatureOutputStream.java56
124 files changed, 16681 insertions, 0 deletions
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPAlgorithmParameters.java b/pg/src/main/java/org/spongycastle/openpgp/PGPAlgorithmParameters.java
new file mode 100644
index 00000000..a9628afb
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPAlgorithmParameters.java
@@ -0,0 +1,5 @@
+package org.spongycastle.openpgp;
+
+public interface PGPAlgorithmParameters
+{
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPCompressedData.java b/pg/src/main/java/org/spongycastle/openpgp/PGPCompressedData.java
new file mode 100644
index 00000000..9269dfee
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPCompressedData.java
@@ -0,0 +1,153 @@
+package org.spongycastle.openpgp;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+
+import org.spongycastle.apache.bzip2.CBZip2InputStream;
+import org.spongycastle.bcpg.BCPGInputStream;
+import org.spongycastle.bcpg.CompressedDataPacket;
+import org.spongycastle.bcpg.CompressionAlgorithmTags;
+import org.spongycastle.bcpg.PacketTags;
+
+/**
+ * A PGP compressed data object.
+ */
+public class PGPCompressedData
+ implements CompressionAlgorithmTags
+{
+ CompressedDataPacket data;
+
+ /**
+ * Construct a compressed data object, reading a single {@link PacketTags#COMPRESSED_DATA}
+ * packet from the stream.
+ *
+ * @param pIn a PGP input stream, with a compressed data packet as the current packet.
+ * @throws IOException if an error occurs reading the packet from the stream.
+ */
+ public PGPCompressedData(
+ BCPGInputStream pIn)
+ throws IOException
+ {
+ data = (CompressedDataPacket)pIn.readPacket();
+ }
+
+ /**
+ * Return the {@link CompressionAlgorithmTags compression algorithm} used for this packet.
+ *
+ * @return the compression algorithm code
+ */
+ public int getAlgorithm()
+ {
+ return data.getAlgorithm();
+ }
+
+ /**
+ * Return the raw input stream contained in the object.
+ * <p/>
+ * Note that this stream is shared with the decompression stream, so consuming the returned
+ * stream will affect decompression.
+ *
+ * @return the raw data in the compressed data packet.
+ */
+ public InputStream getInputStream()
+ {
+ return data.getInputStream();
+ }
+
+ /**
+ * Return an input stream that decompresses and returns data in the compressed packet.
+ *
+ * @return a stream over the uncompressed data.
+ * @throws PGPException if an error occurs constructing the decompression stream.
+ */
+ public InputStream getDataStream()
+ throws PGPException
+ {
+ if (this.getAlgorithm() == UNCOMPRESSED)
+ {
+ return this.getInputStream();
+ }
+ if (this.getAlgorithm() == ZIP)
+ {
+ return new InflaterInputStream(this.getInputStream(), new Inflater(true))
+ {
+ // If the "nowrap" inflater option is used the stream can
+ // apparently overread - we override fill() and provide
+ // an extra byte for the end of the input stream to get
+ // around this.
+ //
+ // Totally weird...
+ //
+ protected void fill() throws IOException
+ {
+ if (eof)
+ {
+ throw new EOFException("Unexpected end of ZIP input stream");
+ }
+
+ len = this.in.read(buf, 0, buf.length);
+
+ if (len == -1)
+ {
+ buf[0] = 0;
+ len = 1;
+ eof = true;
+ }
+
+ inf.setInput(buf, 0, len);
+ }
+
+ private boolean eof = false;
+ };
+ }
+ if (this.getAlgorithm() == ZLIB)
+ {
+ return new InflaterInputStream(this.getInputStream())
+ {
+ // If the "nowrap" inflater option is used the stream can
+ // apparently overread - we override fill() and provide
+ // an extra byte for the end of the input stream to get
+ // around this.
+ //
+ // Totally weird...
+ //
+ protected void fill() throws IOException
+ {
+ if (eof)
+ {
+ throw new EOFException("Unexpected end of ZIP input stream");
+ }
+
+ len = this.in.read(buf, 0, buf.length);
+
+ if (len == -1)
+ {
+ buf[0] = 0;
+ len = 1;
+ eof = true;
+ }
+
+ inf.setInput(buf, 0, len);
+ }
+
+ private boolean eof = false;
+ };
+ }
+ if (this.getAlgorithm() == BZIP2)
+ {
+ try
+ {
+ return new CBZip2InputStream(this.getInputStream());
+ }
+ catch (IOException e)
+ {
+ throw new PGPException("I/O problem with stream: " + e, e);
+ }
+ }
+
+ throw new PGPException("can't recognise compression algorithm: " + this.getAlgorithm());
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPCompressedDataGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/PGPCompressedDataGenerator.java
new file mode 100644
index 00000000..ae9bd043
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPCompressedDataGenerator.java
@@ -0,0 +1,236 @@
+package org.spongycastle.openpgp;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+
+import org.spongycastle.apache.bzip2.CBZip2OutputStream;
+import org.spongycastle.bcpg.BCPGOutputStream;
+import org.spongycastle.bcpg.CompressionAlgorithmTags;
+import org.spongycastle.bcpg.PacketTags;
+
+/**
+ * Generator for producing compressed data packets.
+ * <p/>
+ * A PGPCompressedDataGenerator is used by invoking one of the open functions to create an
+ * OutputStream that raw data can be supplied to for compression:
+ * <ul>
+ * <li>If the length of the data to be written is known in advance, use
+ * {@link #open(OutputStream, long)} to create a packet containing a single compressed object.</li>
+ * <li>If the length of the data is unknown, use {@link #open(OutputStream, byte[])} to create a
+ * packet consisting of a series of compressed data objects (partials).</li>
+ * </ul>
+ * <p/>
+ * A PGPCompressedDataGenerator is usually used to wrap the OutputStream
+ * {@link PGPEncryptedDataGenerator#open(OutputStream, byte[]) obtained} from a
+ * {@link PGPEncryptedDataGenerator} (i.e. to compress data prior to encrypting it).
+ * <p/>
+ * Raw data is not typically written directly to the OutputStream obtained from a
+ * PGPCompressedDataGenerator. The OutputStream is usually wrapped by a
+ * {@link PGPLiteralDataGenerator}, which encodes the raw data prior to compression.
+ * <p/>
+ * Once data for compression has been written to the constructed OutputStream, writing of the object
+ * stream is completed by closing the OutputStream obtained from the <code>open()</code> method, or
+ * equivalently invoking {@link #close()} on this generator.
+ */
+public class PGPCompressedDataGenerator
+ implements CompressionAlgorithmTags, StreamGenerator
+{
+ private int algorithm;
+ private int compression;
+
+ private OutputStream dOut;
+ private BCPGOutputStream pkOut;
+
+ /**
+ * Construct a new compressed data generator.
+ *
+ * @param algorithm the identifier of the {@link CompressionAlgorithmTags compression algorithm}
+ * to use.
+ */
+ public PGPCompressedDataGenerator(
+ int algorithm)
+ {
+ this(algorithm, Deflater.DEFAULT_COMPRESSION);
+ }
+
+ /**
+ * Construct a new compressed data generator.
+ *
+ * @param algorithm the identifier of the {@link CompressionAlgorithmTags compression algorithm}
+ * to use.
+ * @param compression the {@link Deflater} compression level to use.
+ */
+ public PGPCompressedDataGenerator(
+ int algorithm,
+ int compression)
+ {
+ switch (algorithm)
+ {
+ case CompressionAlgorithmTags.UNCOMPRESSED:
+ case CompressionAlgorithmTags.ZIP:
+ case CompressionAlgorithmTags.ZLIB:
+ case CompressionAlgorithmTags.BZIP2:
+ break;
+ default:
+ throw new IllegalArgumentException("unknown compression algorithm");
+ }
+
+ if (compression != Deflater.DEFAULT_COMPRESSION)
+ {
+ if ((compression < Deflater.NO_COMPRESSION) || (compression > Deflater.BEST_COMPRESSION))
+ {
+ throw new IllegalArgumentException("unknown compression level: " + compression);
+ }
+ }
+
+ this.algorithm = algorithm;
+ this.compression = compression;
+ }
+
+ /**
+ * Return an OutputStream which will save the data being written to
+ * the compressed object.
+ * <p>
+ * The stream created can be closed off by either calling close()
+ * on the stream or close() on the generator. Closing the returned
+ * stream does not close off the OutputStream parameter out.
+ *
+ * @param out underlying OutputStream to be used.
+ * @return OutputStream
+ * @throws IOException, IllegalStateException
+ */
+ public OutputStream open(
+ OutputStream out)
+ throws IOException
+ {
+ if (dOut != null)
+ {
+ throw new IllegalStateException("generator already in open state");
+ }
+
+ this.pkOut = new BCPGOutputStream(out, PacketTags.COMPRESSED_DATA);
+
+ doOpen();
+
+ return new WrappedGeneratorStream(dOut, this);
+ }
+
+ /**
+ * Return an OutputStream which will compress the data as it is written to it. The stream will
+ * be written out in chunks (partials) according to the size of the passed in buffer.
+ * <p>
+ * The stream created can be closed off by either calling close() on the stream or close() on
+ * the generator. Closing the returned stream does not close off the OutputStream parameter out.
+ * <p>
+ * <b>Note</b>: if the buffer is not a power of 2 in length only the largest power of 2 bytes
+ * worth of the buffer will be used.
+ * </p>
+ * <p>
+ * <b>Note</b>: using this may break compatibility with RFC 1991 compliant tools. Only recent
+ * OpenPGP implementations are capable of accepting these streams.
+ * </p>
+ *
+ * @param out the stream to write compressed packets to.
+ * @param buffer a buffer to use to buffer and write partial packets. The returned stream takes
+ * ownership of the buffer and will use it to buffer plaintext data for compression.
+ * @return the output stream to write data to.
+ * @throws IOException if an error occurs writing stream header information to the provider
+ * output stream.
+ * @throws PGPException
+ * @throws IllegalStateException if this generator already has an open OutputStream.
+ */
+ public OutputStream open(
+ OutputStream out,
+ byte[] buffer)
+ throws IOException, PGPException
+ {
+ if (dOut != null)
+ {
+ throw new IllegalStateException("generator already in open state");
+ }
+
+ this.pkOut = new BCPGOutputStream(out, PacketTags.COMPRESSED_DATA, buffer);
+
+ doOpen();
+
+ return new WrappedGeneratorStream(dOut, this);
+ }
+
+ private void doOpen() throws IOException
+ {
+ pkOut.write(algorithm);
+
+ switch (algorithm)
+ {
+ case CompressionAlgorithmTags.UNCOMPRESSED:
+ dOut = pkOut;
+ break;
+ case CompressionAlgorithmTags.ZIP:
+ dOut = new SafeDeflaterOutputStream(pkOut, compression, true);
+ break;
+ case CompressionAlgorithmTags.ZLIB:
+ dOut = new SafeDeflaterOutputStream(pkOut, compression, false);
+ break;
+ case CompressionAlgorithmTags.BZIP2:
+ dOut = new SafeCBZip2OutputStream(pkOut);
+ break;
+ default:
+ // Constructor should guard against this possibility
+ throw new IllegalStateException();
+ }
+ }
+
+ /**
+ * Close the compressed object - this is equivalent to calling close on the stream
+ * returned by the open() method.
+ *
+ * @throws IOException
+ */
+ public void close()
+ throws IOException
+ {
+ if (dOut != null)
+ {
+ if (dOut != pkOut)
+ {
+ dOut.close();
+ dOut.flush();
+ }
+
+ dOut = null;
+
+ pkOut.finish();
+ pkOut.flush();
+ pkOut = null;
+ }
+ }
+
+ private static class SafeCBZip2OutputStream extends CBZip2OutputStream
+ {
+ public SafeCBZip2OutputStream(OutputStream output) throws IOException
+ {
+ super(output);
+ }
+
+ public void close() throws IOException
+ {
+ finish();
+ }
+ }
+
+ private class SafeDeflaterOutputStream extends DeflaterOutputStream
+ {
+ public SafeDeflaterOutputStream(OutputStream output, int compression, boolean nowrap)
+ {
+ super(output, new Deflater(compression, nowrap));
+ }
+
+ public void close() throws IOException
+ {
+ finish();
+ def.end();
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPDataValidationException.java b/pg/src/main/java/org/spongycastle/openpgp/PGPDataValidationException.java
new file mode 100644
index 00000000..1b6fb10b
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPDataValidationException.java
@@ -0,0 +1,17 @@
+package org.spongycastle.openpgp;
+
+/**
+ * Thrown if the iv at the start of a data stream indicates the wrong key
+ * is being used.
+ */
+public class PGPDataValidationException
+ extends PGPException
+{
+ /**
+ * @param message
+ */
+ public PGPDataValidationException(String message)
+ {
+ super(message);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPEncryptedData.java b/pg/src/main/java/org/spongycastle/openpgp/PGPEncryptedData.java
new file mode 100644
index 00000000..6f5f4043
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPEncryptedData.java
@@ -0,0 +1,165 @@
+package org.spongycastle.openpgp;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.spongycastle.bcpg.InputStreamPacket;
+import org.spongycastle.bcpg.SymmetricEncIntegrityPacket;
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.openpgp.operator.PGPDataDecryptor;
+import org.spongycastle.openpgp.operator.PGPDataDecryptorFactory;
+import org.spongycastle.openpgp.operator.PGPDigestCalculator;
+import org.spongycastle.util.Arrays;
+
+/**
+ * A PGP encrypted data object.
+ * <p/>
+ * Encrypted data packets are decrypted using a {@link PGPDataDecryptor} obtained from a
+ * {@link PGPDataDecryptorFactory}.
+ */
+public abstract class PGPEncryptedData
+ implements SymmetricKeyAlgorithmTags
+{
+ protected class TruncatedStream extends InputStream
+ {
+ int[] lookAhead = new int[22];
+ int bufPtr;
+ InputStream in;
+
+ TruncatedStream(
+ InputStream in)
+ throws IOException
+ {
+ for (int i = 0; i != lookAhead.length; i++)
+ {
+ if ((lookAhead[i] = in.read()) < 0)
+ {
+ throw new EOFException();
+ }
+ }
+
+ bufPtr = 0;
+ this.in = in;
+ }
+
+ public int read()
+ throws IOException
+ {
+ int ch = in.read();
+
+ if (ch >= 0)
+ {
+ int c = lookAhead[bufPtr];
+
+ lookAhead[bufPtr] = ch;
+ bufPtr = (bufPtr + 1) % lookAhead.length;
+
+ return c;
+ }
+
+ return -1;
+ }
+
+ int[] getLookAhead()
+ {
+ int[] tmp = new int[lookAhead.length];
+ int count = 0;
+
+ for (int i = bufPtr; i != lookAhead.length; i++)
+ {
+ tmp[count++] = lookAhead[i];
+ }
+ for (int i = 0; i != bufPtr; i++)
+ {
+ tmp[count++] = lookAhead[i];
+ }
+
+ return tmp;
+ }
+ }
+
+ InputStreamPacket encData;
+ InputStream encStream;
+ TruncatedStream truncStream;
+ PGPDigestCalculator integrityCalculator;
+
+ PGPEncryptedData(
+ InputStreamPacket encData)
+ {
+ this.encData = encData;
+ }
+
+ /**
+ * Return the raw input stream for the data stream.
+ * <p/>
+ * Note this stream is shared with all other encryption methods in the same
+ * {@link PGPEncryptedDataList} and with any decryption methods in sub-classes, so consuming
+ * this stream will affect decryption.
+ *
+ * @return the encrypted data in this packet.
+ */
+ public InputStream getInputStream()
+ {
+ return encData.getInputStream();
+ }
+
+ /**
+ * Checks whether the packet is integrity protected.
+ *
+ * @return <code>true</code> if there is a modification detection code package associated with
+ * this stream
+ */
+ public boolean isIntegrityProtected()
+ {
+ return (encData instanceof SymmetricEncIntegrityPacket);
+ }
+
+ /**
+ * Verifies the integrity of the packet against the modification detection code associated with
+ * it in the stream.
+ * <p/>
+ * Note: This can only be called after the message has been read.
+ *
+ * @return <code>true</code> if the message verifies, <code>false</code> otherwise.
+ * @throws PGPException if the message is not {@link #isIntegrityProtected() integrity
+ * protected}.
+ */
+ public boolean verify()
+ throws PGPException, IOException
+ {
+ if (!this.isIntegrityProtected())
+ {
+ throw new PGPException("data not integrity protected.");
+ }
+
+ //
+ // make sure we are at the end.
+ //
+ while (encStream.read() >= 0)
+ {
+ // do nothing
+ }
+
+ //
+ // process the MDC packet
+ //
+ int[] lookAhead = truncStream.getLookAhead();
+
+ OutputStream dOut = integrityCalculator.getOutputStream();
+
+ dOut.write((byte)lookAhead[0]);
+ dOut.write((byte)lookAhead[1]);
+
+ byte[] digest = integrityCalculator.getDigest();
+ byte[] streamDigest = new byte[digest.length];
+
+ for (int i = 0; i != streamDigest.length; i++)
+ {
+ streamDigest[i] = (byte)lookAhead[i + 2];
+ }
+
+ return Arrays.constantTimeAreEqual(digest, streamDigest);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPEncryptedDataGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/PGPEncryptedDataGenerator.java
new file mode 100644
index 00000000..6300d001
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPEncryptedDataGenerator.java
@@ -0,0 +1,398 @@
+package org.spongycastle.openpgp;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.spongycastle.bcpg.BCPGOutputStream;
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.bcpg.PacketTags;
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
+import org.spongycastle.openpgp.operator.PGPDataEncryptor;
+import org.spongycastle.openpgp.operator.PGPDataEncryptorBuilder;
+import org.spongycastle.openpgp.operator.PGPDigestCalculator;
+import org.spongycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator;
+import org.spongycastle.util.io.TeeOutputStream;
+
+/**
+ * Generator for encrypted objects.
+ * <p/>
+ * A PGPEncryptedDataGenerator is used by configuring one or more {@link #methods encryption
+ * methods}, and then invoking one of the open functions to create an OutputStream that raw data can
+ * be supplied to for encryption:
+ * <ul>
+ * <li>If the length of the data to be written is known in advance, use
+ * {@link #open(OutputStream, long)} to create a packet containing a single encrypted object.</li>
+ * <li>If the length of the data is unknown, use {@link #open(OutputStream, byte[])} to create an
+ * packet consisting of a series of encrypted objects (partials).</li>
+ * </ul>
+ * <p/>
+ * Raw data is not typically written directly to the OutputStream obtained from a
+ * PGPEncryptedDataGenerator. The OutputStream is usually wrapped by a
+ * {@link PGPLiteralDataGenerator}, and often with a {@link PGPCompressedDataGenerator} between.
+ * <p/>
+ * Once plaintext data for encryption has been written to the constructed OutputStream, writing of
+ * the encrypted object stream is completed by closing the OutputStream obtained from the
+ * <code>open()</code> method, or equivalently invoking {@link #close()} on this generator.
+ */
+public class PGPEncryptedDataGenerator
+ implements SymmetricKeyAlgorithmTags, StreamGenerator
+{
+ // TODO: These seem to belong on the PBE classes. Are they even used now?
+ /**
+ * Specifier for SHA-1 S2K PBE generator.
+ */
+ public static final int S2K_SHA1 = HashAlgorithmTags.SHA1;
+
+ /**
+ * Specifier for SHA-224 S2K PBE generator.
+ */
+ public static final int S2K_SHA224 = HashAlgorithmTags.SHA224;
+
+ /**
+ * Specifier for SHA-256 S2K PBE generator.
+ */
+ public static final int S2K_SHA256 = HashAlgorithmTags.SHA256;
+
+ /**
+ * Specifier for SHA-384 S2K PBE generator.
+ */
+ public static final int S2K_SHA384 = HashAlgorithmTags.SHA384;
+
+ /**
+ * Specifier for SHA-512 S2K PBE generator.
+ */
+ public static final int S2K_SHA512 = HashAlgorithmTags.SHA512;
+
+ private BCPGOutputStream pOut;
+ private OutputStream cOut;
+ private boolean oldFormat = false;
+ private PGPDigestCalculator digestCalc;
+ private OutputStream genOut;
+ private PGPDataEncryptorBuilder dataEncryptorBuilder;
+
+ private List methods = new ArrayList();
+ private int defAlgorithm;
+ private SecureRandom rand;
+
+ /**
+ * Base constructor.
+ *
+ * @param encryptorBuilder builder to create actual data encryptor.
+ */
+ public PGPEncryptedDataGenerator(PGPDataEncryptorBuilder encryptorBuilder)
+ {
+ this(encryptorBuilder, false);
+ }
+
+ /**
+ * Base constructor with the option to turn on formatting for PGP 2.6.x compatibility.
+ *
+ * @param encryptorBuilder builder to create actual data encryptor.
+ * @param oldFormat PGP 2.6.x compatibility required.
+ */
+ public PGPEncryptedDataGenerator(PGPDataEncryptorBuilder encryptorBuilder, boolean oldFormat)
+ {
+ this.dataEncryptorBuilder = encryptorBuilder;
+ this.oldFormat = oldFormat;
+
+ this.defAlgorithm = dataEncryptorBuilder.getAlgorithm();
+ this.rand = dataEncryptorBuilder.getSecureRandom();
+ }
+
+ /**
+ * Add a key encryption method to be used to encrypt the session data associated with this
+ * encrypted data.
+ *
+ * @param method key encryption method to use.
+ */
+ public void addMethod(PGPKeyEncryptionMethodGenerator method)
+ {
+ methods.add(method);
+ }
+
+ private void addCheckSum(
+ byte[] sessionInfo)
+ {
+ int check = 0;
+
+ for (int i = 1; i != sessionInfo.length - 2; i++)
+ {
+ check += sessionInfo[i] & 0xff;
+ }
+
+ sessionInfo[sessionInfo.length - 2] = (byte)(check >> 8);
+ sessionInfo[sessionInfo.length - 1] = (byte)(check);
+ }
+
+ private byte[] createSessionInfo(
+ int algorithm,
+ byte[] keyBytes)
+ {
+ byte[] sessionInfo = new byte[keyBytes.length + 3];
+ sessionInfo[0] = (byte) algorithm;
+ System.arraycopy(keyBytes, 0, sessionInfo, 1, keyBytes.length);
+ addCheckSum(sessionInfo);
+ return sessionInfo;
+ }
+
+ /**
+ * Create an OutputStream based on the configured methods.
+ *
+ * If the supplied buffer is non <code>null</code> the stream returned will write a sequence of
+ * partial packets, otherwise the length will be used to output a fixed length packet.
+ * <p>
+ * The stream created can be closed off by either calling close() on the stream or close() on
+ * the generator. Closing the returned stream does not close off the OutputStream parameter out.
+ *
+ * @param out the stream to write encrypted packets to.
+ * @param length the length of the data to be encrypted. Ignored if buffer is non
+ * <code>null</code>.
+ * @param buffer a buffer to use to buffer and write partial packets.
+ * @return the generator's output stream.
+ * @throws IOException if an error occurs writing stream header information to the provider
+ * output stream.
+ * @throws PGPException if an error occurs initialising PGP encryption for the configured
+ * encryption methods.
+ * @throws IllegalStateException if this generator already has an open OutputStream, or no
+ * {@link #addMethod(PGPKeyEncryptionMethodGenerator) encryption methods} are
+ * configured.
+ */
+ private OutputStream open(
+ OutputStream out,
+ long length,
+ byte[] buffer)
+ throws IOException, PGPException, IllegalStateException
+ {
+ if (cOut != null)
+ {
+ throw new IllegalStateException("generator already in open state");
+ }
+
+ if (methods.size() == 0)
+ {
+ throw new IllegalStateException("no encryption methods specified");
+ }
+
+ byte[] key = null;
+
+ pOut = new BCPGOutputStream(out);
+
+ defAlgorithm = dataEncryptorBuilder.getAlgorithm();
+ rand = dataEncryptorBuilder.getSecureRandom();
+
+ if (methods.size() == 1)
+ {
+
+ if (methods.get(0) instanceof PBEKeyEncryptionMethodGenerator)
+ {
+ PBEKeyEncryptionMethodGenerator m = (PBEKeyEncryptionMethodGenerator)methods.get(0);
+
+ key = m.getKey(dataEncryptorBuilder.getAlgorithm());
+
+ pOut.writePacket(((PGPKeyEncryptionMethodGenerator)methods.get(0)).generate(defAlgorithm, null));
+ }
+ else
+ {
+ key = PGPUtil.makeRandomKey(defAlgorithm, rand);
+ byte[] sessionInfo = createSessionInfo(defAlgorithm, key);
+ PGPKeyEncryptionMethodGenerator m = (PGPKeyEncryptionMethodGenerator)methods.get(0);
+
+ pOut.writePacket(m.generate(defAlgorithm, sessionInfo));
+ }
+ }
+ else // multiple methods
+ {
+ key = PGPUtil.makeRandomKey(defAlgorithm, rand);
+ byte[] sessionInfo = createSessionInfo(defAlgorithm, key);
+
+ for (int i = 0; i != methods.size(); i++)
+ {
+ PGPKeyEncryptionMethodGenerator m = (PGPKeyEncryptionMethodGenerator)methods.get(i);
+
+ pOut.writePacket(m.generate(defAlgorithm, sessionInfo));
+ }
+ }
+
+ try
+ {
+ PGPDataEncryptor dataEncryptor = dataEncryptorBuilder.build(key);
+
+ digestCalc = dataEncryptor.getIntegrityCalculator();
+
+ if (buffer == null)
+ {
+ //
+ // we have to add block size + 2 for the generated IV and + 1 + 22 if integrity protected
+ //
+ if (digestCalc != null)
+ {
+ pOut = new ClosableBCPGOutputStream(out, PacketTags.SYM_ENC_INTEGRITY_PRO, length + dataEncryptor.getBlockSize() + 2 + 1 + 22);
+
+ pOut.write(1); // version number
+ }
+ else
+ {
+ pOut = new ClosableBCPGOutputStream(out, PacketTags.SYMMETRIC_KEY_ENC, length + dataEncryptor.getBlockSize() + 2, oldFormat);
+ }
+ }
+ else
+ {
+ if (digestCalc != null)
+ {
+ pOut = new ClosableBCPGOutputStream(out, PacketTags.SYM_ENC_INTEGRITY_PRO, buffer);
+ pOut.write(1); // version number
+ }
+ else
+ {
+ pOut = new ClosableBCPGOutputStream(out, PacketTags.SYMMETRIC_KEY_ENC, buffer);
+ }
+ }
+
+ genOut = cOut = dataEncryptor.getOutputStream(pOut);
+
+ if (digestCalc != null)
+ {
+ genOut = new TeeOutputStream(digestCalc.getOutputStream(), cOut);
+ }
+
+ byte[] inLineIv = new byte[dataEncryptor.getBlockSize() + 2];
+ rand.nextBytes(inLineIv);
+ inLineIv[inLineIv.length - 1] = inLineIv[inLineIv.length - 3];
+ inLineIv[inLineIv.length - 2] = inLineIv[inLineIv.length - 4];
+
+ genOut.write(inLineIv);
+
+ return new WrappedGeneratorStream(genOut, this);
+ }
+ catch (Exception e)
+ {
+ throw new PGPException("Exception creating cipher", e);
+ }
+ }
+
+ /**
+ * Create an OutputStream based on the configured methods to write a single encrypted object of
+ * known length.
+ *
+ * <p>
+ * The stream created can be closed off by either calling close() on the stream or close() on
+ * the generator. Closing the returned stream does not close off the OutputStream parameter out.
+ *
+ * @param out the stream to write encrypted packets to.
+ * @param length the length of the data to be encrypted.
+ * @return the output stream to write data to for encryption.
+ * @throws IOException if an error occurs writing stream header information to the provider
+ * output stream.
+ * @throws PGPException if an error occurs initialising PGP encryption for the configured
+ * encryption methods.
+ * @throws IllegalStateException if this generator already has an open OutputStream, or no
+ * {@link #addMethod(PGPKeyEncryptionMethodGenerator) encryption methods} are
+ * configured.
+ */
+ public OutputStream open(
+ OutputStream out,
+ long length)
+ throws IOException, PGPException
+ {
+ return this.open(out, length, null);
+ }
+
+ /**
+ * Create an OutputStream which will encrypt the data as it is written to it. The stream of
+ * encrypted data will be written out in chunks (partial packets) according to the size of the
+ * passed in buffer.
+ * <p>
+ * The stream created can be closed off by either calling close() on the stream or close() on
+ * the generator. Closing the returned stream does not close off the OutputStream parameter out.
+ * <p>
+ * <b>Note</b>: if the buffer is not a power of 2 in length only the largest power of 2 bytes
+ * worth of the buffer will be used.
+ *
+ * @param out the stream to write encrypted packets to.
+ * @param buffer a buffer to use to buffer and write partial packets. The returned stream takes
+ * ownership of the buffer and will use it to buffer plaintext data for encryption.
+ * @return the output stream to write data to for encryption.
+ * @throws IOException if an error occurs writing stream header information to the provider
+ * output stream.
+ * @throws PGPException if an error occurs initialising PGP encryption for the configured
+ * encryption methods.
+ * @throws IllegalStateException if this generator already has an open OutputStream, or no
+ * {@link #addMethod(PGPKeyEncryptionMethodGenerator) encryption methods} are
+ * configured.
+ */
+ public OutputStream open(
+ OutputStream out,
+ byte[] buffer)
+ throws IOException, PGPException
+ {
+ return this.open(out, 0, buffer);
+ }
+
+ /**
+ * Close off the encrypted object - this is equivalent to calling close on the stream returned
+ * by the <code>open()</code> methods.
+ * <p>
+ * <b>Note</b>: This does not close the underlying output stream, only the stream on top of it
+ * created by the <code>open()</code> method.
+ *
+ * @throws IOException if an error occurs writing trailing information (such as integrity check
+ * information) to the underlying stream.
+ */
+ public void close()
+ throws IOException
+ {
+ if (cOut != null)
+ {
+ if (digestCalc != null)
+ {
+ //
+ // hand code a mod detection packet
+ //
+ BCPGOutputStream bOut = new BCPGOutputStream(genOut, PacketTags.MOD_DETECTION_CODE, 20);
+
+ bOut.flush();
+
+ byte[] dig = digestCalc.getDigest();
+
+ cOut.write(dig);
+ }
+
+ cOut.close();
+
+ cOut = null;
+ pOut = null;
+ }
+ }
+
+ private class ClosableBCPGOutputStream
+ extends BCPGOutputStream
+ {
+ public ClosableBCPGOutputStream(OutputStream out, int symmetricKeyEnc, byte[] buffer)
+ throws IOException
+ {
+ super(out, symmetricKeyEnc, buffer);
+ }
+
+ public ClosableBCPGOutputStream(OutputStream out, int symmetricKeyEnc, long length, boolean oldFormat)
+ throws IOException
+ {
+ super(out, symmetricKeyEnc, length, oldFormat);
+ }
+
+ public ClosableBCPGOutputStream(OutputStream out, int symEncIntegrityPro, long length)
+ throws IOException
+ {
+ super(out, symEncIntegrityPro, length);
+ }
+
+ public void close()
+ throws IOException
+ {
+ this.finish();
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPEncryptedDataList.java b/pg/src/main/java/org/spongycastle/openpgp/PGPEncryptedDataList.java
new file mode 100644
index 00000000..7942d1ef
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPEncryptedDataList.java
@@ -0,0 +1,110 @@
+package org.spongycastle.openpgp;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.spongycastle.bcpg.BCPGInputStream;
+import org.spongycastle.bcpg.InputStreamPacket;
+import org.spongycastle.bcpg.PacketTags;
+import org.spongycastle.bcpg.PublicKeyEncSessionPacket;
+import org.spongycastle.bcpg.SymmetricKeyEncSessionPacket;
+
+/**
+ * A holder for a list of PGP encryption method packets and the encrypted data associated with them.
+ * <p/>
+ * This holder supports reading a sequence of the following encryption methods, followed by an
+ * encrypted data packet:
+ * <ul>
+ * <li>{@link PacketTags#SYMMETRIC_KEY_ENC_SESSION} - produces a {@link PGPPBEEncryptedData}</li>
+ * <li>{@link PacketTags#PUBLIC_KEY_ENC_SESSION} - produces a {@link PGPPublicKeyEncryptedData}</li>
+ * </ul>
+ * <p/>
+ * All of the objects returned from this holder share a reference to the same encrypted data input
+ * stream, which can only be consumed once.
+ */
+public class PGPEncryptedDataList
+{
+ List list = new ArrayList();
+ InputStreamPacket data;
+
+ /**
+ * Construct an encrypted data packet holder, reading PGP encrypted method packets and an
+ * encrytped data packet from the stream.
+ * <p/>
+ * The next packet in the stream should be one of {@link PacketTags#SYMMETRIC_KEY_ENC_SESSION}
+ * or {@link PacketTags#PUBLIC_KEY_ENC_SESSION}.
+ *
+ * @param pIn the PGP object stream being read.
+ * @throws IOException if an error occurs reading from the PGP input.
+ */
+ public PGPEncryptedDataList(
+ BCPGInputStream pIn)
+ throws IOException
+ {
+ while (pIn.nextPacketTag() == PacketTags.PUBLIC_KEY_ENC_SESSION
+ || pIn.nextPacketTag() == PacketTags.SYMMETRIC_KEY_ENC_SESSION)
+ {
+ list.add(pIn.readPacket());
+ }
+
+ data = (InputStreamPacket)pIn.readPacket();
+
+ for (int i = 0; i != list.size(); i++)
+ {
+ if (list.get(i) instanceof SymmetricKeyEncSessionPacket)
+ {
+ list.set(i, new PGPPBEEncryptedData((SymmetricKeyEncSessionPacket)list.get(i), data));
+ }
+ else
+ {
+ list.set(i, new PGPPublicKeyEncryptedData((PublicKeyEncSessionPacket)list.get(i), data));
+ }
+ }
+ }
+
+ /**
+ * Gets the encryption method object at the specified index.
+ *
+ * @param index the encryption method to obtain (0 based).
+ */
+ public Object get(
+ int index)
+ {
+ return list.get(index);
+ }
+
+ /**
+ * Gets the number of encryption methods in this list.
+ */
+ public int size()
+ {
+ return list.size();
+ }
+
+ /**
+ * Returns <code>true</code> iff there are 0 encryption methods in this list.
+ */
+ public boolean isEmpty()
+ {
+ return list.isEmpty();
+ }
+
+ /**
+ * @deprecated misspelt - use getEncryptedDataObjects()
+ */
+ public Iterator getEncyptedDataObjects()
+ {
+ return list.iterator();
+ }
+
+ /**
+ * Returns an iterator over the encryption method objects held in this list, in the order they
+ * appeared in the stream they are read from.
+ */
+ public Iterator getEncryptedDataObjects()
+ {
+ return list.iterator();
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPException.java b/pg/src/main/java/org/spongycastle/openpgp/PGPException.java
new file mode 100644
index 00000000..220672ac
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPException.java
@@ -0,0 +1,35 @@
+package org.spongycastle.openpgp;
+
+/**
+ * generic exception class for PGP encoding/decoding problems
+ */
+public class PGPException
+ extends Exception
+{
+ Exception underlying;
+
+ public PGPException(
+ String message)
+ {
+ super(message);
+ }
+
+ public PGPException(
+ String message,
+ Exception underlying)
+ {
+ super(message);
+ this.underlying = underlying;
+ }
+
+ public Exception getUnderlyingException()
+ {
+ return underlying;
+ }
+
+
+ public Throwable getCause()
+ {
+ return underlying;
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPKdfParameters.java b/pg/src/main/java/org/spongycastle/openpgp/PGPKdfParameters.java
new file mode 100644
index 00000000..fc37af74
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPKdfParameters.java
@@ -0,0 +1,24 @@
+package org.spongycastle.openpgp;
+
+public class PGPKdfParameters
+ implements PGPAlgorithmParameters
+{
+ private final int hashAlgorithm;
+ private final int symmetricWrapAlgorithm;
+
+ public PGPKdfParameters(int hashAlgorithm, int symmetricWrapAlgorithm)
+ {
+ this.hashAlgorithm = hashAlgorithm;
+ this.symmetricWrapAlgorithm = symmetricWrapAlgorithm;
+ }
+
+ public int getSymmetricWrapAlgorithm()
+ {
+ return symmetricWrapAlgorithm;
+ }
+
+ public int getHashAlgorithm()
+ {
+ return hashAlgorithm;
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPKeyFlags.java b/pg/src/main/java/org/spongycastle/openpgp/PGPKeyFlags.java
new file mode 100644
index 00000000..2c169126
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPKeyFlags.java
@@ -0,0 +1,19 @@
+package org.spongycastle.openpgp;
+
+/**
+ * key flag values for the KeyFlags subpacket.
+ */
+public interface PGPKeyFlags
+{
+ public static final int CAN_CERTIFY = 0x01; // This key may be used to certify other keys.
+
+ public static final int CAN_SIGN = 0x02; // This key may be used to sign data.
+
+ public static final int CAN_ENCRYPT_COMMS = 0x04; // This key may be used to encrypt communications.
+
+ public static final int CAN_ENCRYPT_STORAGE = 0x08; // This key may be used to encrypt storage.
+
+ public static final int MAYBE_SPLIT = 0x10; // The private component of this key may have been split by a secret-sharing mechanism.
+
+ public static final int MAYBE_SHARED = 0x80; // The private component of this key may be in the possession of more than one person.
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPKeyPair.java b/pg/src/main/java/org/spongycastle/openpgp/PGPKeyPair.java
new file mode 100644
index 00000000..9ac974e2
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPKeyPair.java
@@ -0,0 +1,54 @@
+package org.spongycastle.openpgp;
+
+/**
+ * General class to handle JCA key pairs and convert them into OpenPGP ones.
+ * <p>
+ * A word for the unwary, the KeyID for a OpenPGP public key is calculated from
+ * a hash that includes the time of creation, if you pass a different date to the
+ * constructor below with the same public private key pair the KeyID will not be the
+ * same as for previous generations of the key, so ideally you only want to do
+ * this once.
+ */
+public class PGPKeyPair
+{
+ protected PGPPublicKey pub;
+ protected PGPPrivateKey priv;
+
+ /**
+ * Create a key pair from a PGPPrivateKey and a PGPPublicKey.
+ *
+ * @param pub the public key
+ * @param priv the private key
+ */
+ public PGPKeyPair(
+ PGPPublicKey pub,
+ PGPPrivateKey priv)
+ {
+ this.pub = pub;
+ this.priv = priv;
+ }
+
+ protected PGPKeyPair()
+ {
+ }
+
+ /**
+ * Return the keyID associated with this key pair.
+ *
+ * @return keyID
+ */
+ public long getKeyID()
+ {
+ return pub.getKeyID();
+ }
+
+ public PGPPublicKey getPublicKey()
+ {
+ return pub;
+ }
+
+ public PGPPrivateKey getPrivateKey()
+ {
+ return priv;
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPKeyRing.java b/pg/src/main/java/org/spongycastle/openpgp/PGPKeyRing.java
new file mode 100644
index 00000000..f2018a07
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPKeyRing.java
@@ -0,0 +1,125 @@
+package org.spongycastle.openpgp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.spongycastle.bcpg.BCPGInputStream;
+import org.spongycastle.bcpg.Packet;
+import org.spongycastle.bcpg.PacketTags;
+import org.spongycastle.bcpg.SignaturePacket;
+import org.spongycastle.bcpg.TrustPacket;
+import org.spongycastle.bcpg.UserAttributePacket;
+import org.spongycastle.bcpg.UserIDPacket;
+
+public abstract class PGPKeyRing
+{
+ PGPKeyRing()
+ {
+ }
+
+ static BCPGInputStream wrap(InputStream in)
+ {
+ if (in instanceof BCPGInputStream)
+ {
+ return (BCPGInputStream)in;
+ }
+
+ return new BCPGInputStream(in);
+ }
+
+ static TrustPacket readOptionalTrustPacket(
+ BCPGInputStream pIn)
+ throws IOException
+ {
+ return (pIn.nextPacketTag() == PacketTags.TRUST)
+ ? (TrustPacket) pIn.readPacket()
+ : null;
+ }
+
+ static List readSignaturesAndTrust(
+ BCPGInputStream pIn)
+ throws IOException
+ {
+ try
+ {
+ List sigList = new ArrayList();
+
+ while (pIn.nextPacketTag() == PacketTags.SIGNATURE)
+ {
+ SignaturePacket signaturePacket = (SignaturePacket)pIn.readPacket();
+ TrustPacket trustPacket = readOptionalTrustPacket(pIn);
+
+ sigList.add(new PGPSignature(signaturePacket, trustPacket));
+ }
+
+ return sigList;
+ }
+ catch (PGPException e)
+ {
+ throw new IOException("can't create signature object: " + e.getMessage()
+ + ", cause: " + e.getUnderlyingException().toString());
+ }
+ }
+
+ static void readUserIDs(
+ BCPGInputStream pIn,
+ List ids,
+ List idTrusts,
+ List idSigs)
+ throws IOException
+ {
+ while (pIn.nextPacketTag() == PacketTags.USER_ID
+ || pIn.nextPacketTag() == PacketTags.USER_ATTRIBUTE)
+ {
+ Packet obj = pIn.readPacket();
+ if (obj instanceof UserIDPacket)
+ {
+ UserIDPacket id = (UserIDPacket)obj;
+ ids.add(id);
+ }
+ else
+ {
+ UserAttributePacket user = (UserAttributePacket)obj;
+ ids.add(new PGPUserAttributeSubpacketVector(user.getSubpackets()));
+ }
+
+ idTrusts.add(readOptionalTrustPacket(pIn));
+ idSigs.add(readSignaturesAndTrust(pIn));
+ }
+ }
+
+ /**
+ * Return the first public key in the ring. In the case of a {@link PGPSecretKeyRing}
+ * this is also the public key of the master key pair.
+ *
+ * @return PGPPublicKey
+ */
+ public abstract PGPPublicKey getPublicKey();
+
+ /**
+ * Return an iterator containing all the public keys.
+ *
+ * @return Iterator
+ */
+ public abstract Iterator getPublicKeys();
+
+ /**
+ * Return the public key referred to by the passed in keyID if it
+ * is present.
+ *
+ * @param keyID
+ * @return PGPPublicKey
+ */
+ public abstract PGPPublicKey getPublicKey(long keyID);
+
+ public abstract void encode(OutputStream outStream)
+ throws IOException;
+
+ public abstract byte[] getEncoded()
+ throws IOException;
+
+} \ No newline at end of file
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPKeyRingGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/PGPKeyRingGenerator.java
new file mode 100644
index 00000000..b631bb8b
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPKeyRingGenerator.java
@@ -0,0 +1,151 @@
+package org.spongycastle.openpgp;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.spongycastle.bcpg.PublicSubkeyPacket;
+import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor;
+import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
+import org.spongycastle.openpgp.operator.PGPDigestCalculator;
+
+/**
+ * Generator for a PGP master and subkey ring. This class will generate
+ * both the secret and public key rings
+ */
+public class PGPKeyRingGenerator
+{
+ List keys = new ArrayList();
+
+ private PBESecretKeyEncryptor keyEncryptor;
+ private PGPDigestCalculator checksumCalculator;
+ private PGPKeyPair masterKey;
+ private PGPSignatureSubpacketVector hashedPcks;
+ private PGPSignatureSubpacketVector unhashedPcks;
+ private PGPContentSignerBuilder keySignerBuilder;
+
+ /**
+ * Create a new key ring generator.
+ *
+ * @param certificationLevel
+ * @param masterKey
+ * @param id
+ * @param checksumCalculator
+ * @param hashedPcks
+ * @param unhashedPcks
+ * @param keySignerBuilder
+ * @param keyEncryptor
+ * @throws PGPException
+ */
+ public PGPKeyRingGenerator(
+ int certificationLevel,
+ PGPKeyPair masterKey,
+ String id,
+ PGPDigestCalculator checksumCalculator,
+ PGPSignatureSubpacketVector hashedPcks,
+ PGPSignatureSubpacketVector unhashedPcks,
+ PGPContentSignerBuilder keySignerBuilder,
+ PBESecretKeyEncryptor keyEncryptor)
+ throws PGPException
+ {
+ this.masterKey = masterKey;
+ this.keyEncryptor = keyEncryptor;
+ this.checksumCalculator = checksumCalculator;
+ this.keySignerBuilder = keySignerBuilder;
+ this.hashedPcks = hashedPcks;
+ this.unhashedPcks = unhashedPcks;
+
+ keys.add(new PGPSecretKey(certificationLevel, masterKey, id, checksumCalculator, hashedPcks, unhashedPcks, keySignerBuilder, keyEncryptor));
+ }
+
+ /**
+ * Add a sub key to the key ring to be generated with default certification and inheriting
+ * the hashed/unhashed packets of the master key.
+ *
+ * @param keyPair
+ * @throws PGPException
+ */
+ public void addSubKey(
+ PGPKeyPair keyPair)
+ throws PGPException
+ {
+ addSubKey(keyPair, hashedPcks, unhashedPcks);
+ }
+
+ /**
+ * Add a subkey with specific hashed and unhashed packets associated with it and default
+ * certification.
+ *
+ * @param keyPair public/private key pair.
+ * @param hashedPcks hashed packet values to be included in certification.
+ * @param unhashedPcks unhashed packets values to be included in certification.
+ * @throws PGPException
+ */
+ public void addSubKey(
+ PGPKeyPair keyPair,
+ PGPSignatureSubpacketVector hashedPcks,
+ PGPSignatureSubpacketVector unhashedPcks)
+ throws PGPException
+ {
+ try
+ {
+ //
+ // generate the certification
+ //
+ PGPSignatureGenerator sGen = new PGPSignatureGenerator(keySignerBuilder);
+
+ sGen.init(PGPSignature.SUBKEY_BINDING, masterKey.getPrivateKey());
+
+ sGen.setHashedSubpackets(hashedPcks);
+ sGen.setUnhashedSubpackets(unhashedPcks);
+
+ List subSigs = new ArrayList();
+
+ subSigs.add(sGen.generateCertification(masterKey.getPublicKey(), keyPair.getPublicKey()));
+
+ keys.add(new PGPSecretKey(keyPair.getPrivateKey(), new PGPPublicKey(keyPair.getPublicKey(), null, subSigs), checksumCalculator, keyEncryptor));
+ }
+ catch (PGPException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new PGPException("exception adding subkey: ", e);
+ }
+ }
+
+ /**
+ * Return the secret key ring.
+ *
+ * @return a secret key ring.
+ */
+ public PGPSecretKeyRing generateSecretKeyRing()
+ {
+ return new PGPSecretKeyRing(keys);
+ }
+
+ /**
+ * Return the public key ring that corresponds to the secret key ring.
+ *
+ * @return a public key ring.
+ */
+ public PGPPublicKeyRing generatePublicKeyRing()
+ {
+ Iterator it = keys.iterator();
+ List pubKeys = new ArrayList();
+
+ pubKeys.add(((PGPSecretKey)it.next()).getPublicKey());
+
+ while (it.hasNext())
+ {
+ PGPPublicKey k = new PGPPublicKey(((PGPSecretKey)it.next()).getPublicKey());
+
+ k.publicPk = new PublicSubkeyPacket(k.getAlgorithm(), k.getCreationTime(), k.publicPk.getKey());
+
+ pubKeys.add(k);
+ }
+
+ return new PGPPublicKeyRing(pubKeys);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPKeyValidationException.java b/pg/src/main/java/org/spongycastle/openpgp/PGPKeyValidationException.java
new file mode 100644
index 00000000..63245c51
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPKeyValidationException.java
@@ -0,0 +1,16 @@
+package org.spongycastle.openpgp;
+
+/**
+ * Thrown if the key checksum is invalid.
+ */
+public class PGPKeyValidationException
+ extends PGPException
+{
+ /**
+ * @param message
+ */
+ public PGPKeyValidationException(String message)
+ {
+ super(message);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPLiteralData.java b/pg/src/main/java/org/spongycastle/openpgp/PGPLiteralData.java
new file mode 100644
index 00000000..d640bab0
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPLiteralData.java
@@ -0,0 +1,90 @@
+package org.spongycastle.openpgp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Date;
+
+import org.spongycastle.bcpg.BCPGInputStream;
+import org.spongycastle.bcpg.LiteralDataPacket;
+
+/**
+ * A single literal data packet in a PGP object stream.
+ */
+public class PGPLiteralData
+{
+ /** Format tag for binary literal data */
+ public static final char BINARY = 'b';
+ /** Format tag for textual literal data */
+ public static final char TEXT = 't';
+ /** Format tag for UTF-8 encoded textual literal data */
+ public static final char UTF8 = 'u';
+
+ /**
+ * The special name indicating a "for your eyes only" packet.
+ */
+ public static final String CONSOLE = "_CONSOLE";
+
+ /**
+ * The special time for a modification time of "now" or
+ * the present time.
+ */
+ public static final Date NOW = new Date(0L);
+
+ LiteralDataPacket data;
+
+ public PGPLiteralData(
+ BCPGInputStream pIn)
+ throws IOException
+ {
+ data = (LiteralDataPacket)pIn.readPacket();
+ }
+
+ /**
+ * Return the format of the data packet. One of {@link #BINARY}, {@link #TEXT} or {@link #UTF8}
+ */
+ public int getFormat()
+ {
+ return data.getFormat();
+ }
+
+ /**
+ * Return the file name associated with the data packet.
+ */
+ public String getFileName()
+ {
+ return data.getFileName();
+ }
+
+ /**
+ * Return the file name as an uninterpreted (UTF-8 encoded) byte array.
+ */
+ public byte[] getRawFileName()
+ {
+ return data.getRawFileName();
+ }
+
+ /**
+ * Return the modification time for the file (at second level precision).
+ */
+ public Date getModificationTime()
+ {
+ return new Date(data.getModificationTime());
+ }
+
+ /**
+ * Return the raw input stream for the data packet.
+ */
+ public InputStream getInputStream()
+ {
+ return data.getInputStream();
+ }
+
+ /**
+ * Return the input stream representing the data stream. <br/>
+ * Equivalent to {@link #getInputStream()}.
+ */
+ public InputStream getDataStream()
+ {
+ return this.getInputStream();
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPLiteralDataGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/PGPLiteralDataGenerator.java
new file mode 100644
index 00000000..9487238e
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPLiteralDataGenerator.java
@@ -0,0 +1,231 @@
+package org.spongycastle.openpgp;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Date;
+
+import org.spongycastle.bcpg.BCPGOutputStream;
+import org.spongycastle.bcpg.PacketTags;
+import org.spongycastle.util.Strings;
+
+/**
+ * Generator for producing literal data packets.
+ * <p/>
+ * A PGPLiteralData is used by invoking one of the open functions to create an OutputStream that raw
+ * data can be supplied to for encoding:
+ * <ul>
+ * <li>If the length of the data to be written is known in advance, use
+ * {@link #open(OutputStream, char, String, long, Date)} to create a packet containing a single
+ * literal data object.</li>
+ * <li>If the length of the data is unknown, use
+ * {@link #open(OutputStream, char, String, Date, byte[])} to create a packet consisting of a series
+ * of literal data objects (partials).</li>
+ * </ul>
+ * <p/>
+ * A PGPLiteralDataGenerator is usually used to wrap the OutputStream
+ * {@link PGPEncryptedDataGenerator#open(OutputStream, byte[]) obtained} from a
+ * {@link PGPEncryptedDataGenerator} or a {@link PGPCompressedDataGenerator}.
+ * <p/>
+ * Once literal data has been written to the constructed OutputStream, writing of the object stream
+ * is completed by closing the OutputStream obtained from the <code>open()</code> method, or
+ * equivalently invoking {@link #close()} on this generator.
+ */
+public class PGPLiteralDataGenerator implements StreamGenerator
+{
+ /** Format tag for binary literal data */
+ public static final char BINARY = PGPLiteralData.BINARY;
+ /** Format tag for textual literal data */
+ public static final char TEXT = PGPLiteralData.TEXT;
+ /** Format tag for UTF-8 encoded textual literal data */
+ public static final char UTF8 = PGPLiteralData.UTF8;
+
+ /**
+ * The special name indicating a "for your eyes only" packet.
+ */
+ // TODO: Not used?
+ public static final String CONSOLE = PGPLiteralData.CONSOLE;
+
+ /**
+ * The special time for a modification time of "now" or
+ * the present time.
+ */
+ public static final Date NOW = PGPLiteralData.NOW;
+
+ private BCPGOutputStream pkOut;
+ private boolean oldFormat = false;
+
+ /**
+ * Constructs a generator for literal data objects.
+ */
+ public PGPLiteralDataGenerator()
+ {
+ }
+
+ /**
+ * Constructs a generator for literal data objects, specifying to use new or old (PGP 2.6.x
+ * compatible) format.
+ * <p/>
+ * This can be used for compatibility with PGP 2.6.x.
+ *
+ * @param oldFormat <code>true</code> to use PGP 2.6.x compatible format.
+ */
+ public PGPLiteralDataGenerator(
+ boolean oldFormat)
+ {
+ this.oldFormat = oldFormat;
+ }
+
+ private void writeHeader(
+ OutputStream out,
+ char format,
+ byte[] encName,
+ long modificationTime)
+ throws IOException
+ {
+ out.write(format);
+
+ out.write((byte)encName.length);
+
+ for (int i = 0; i != encName.length; i++)
+ {
+ out.write(encName[i]);
+ }
+
+ long modDate = modificationTime / 1000;
+
+ out.write((byte)(modDate >> 24));
+ out.write((byte)(modDate >> 16));
+ out.write((byte)(modDate >> 8));
+ out.write((byte)(modDate));
+ }
+
+ /**
+ * Open a literal data packet, returning a stream to store the data inside the packet.
+ * <p>
+ * The stream created can be closed off by either calling close() on the stream or close() on
+ * the generator. Closing the returned stream does not close off the OutputStream parameter out.
+ *
+ * @param out the underlying output stream to write the literal data packet to.
+ * @param format the format of the literal data that will be written to the output stream (one
+ * of {@link #BINARY}, {@link #TEXT} or {@link #UTF8}).
+ * @param name the name of the "file" to encode in the literal data object.
+ * @param length the length of the data that will be written.
+ * @param modificationTime the time of last modification we want stored.
+ */
+ public OutputStream open(
+ OutputStream out,
+ char format,
+ String name,
+ long length,
+ Date modificationTime)
+ throws IOException
+ {
+ if (pkOut != null)
+ {
+ throw new IllegalStateException("generator already in open state");
+ }
+
+ byte[] encName = Strings.toUTF8ByteArray(name);
+
+ pkOut = new BCPGOutputStream(out, PacketTags.LITERAL_DATA, length + 2 + encName.length + 4, oldFormat);
+
+ writeHeader(pkOut, format, encName, modificationTime.getTime());
+
+ return new WrappedGeneratorStream(pkOut, this);
+ }
+
+ /**
+ * Open a literal data packet, returning a stream to store the data inside the packet as an
+ * indefinite length stream. The stream is written out as a series of partial packets with a
+ * chunk size determined by the size of the passed in buffer.
+ * <p>
+ * The stream created can be closed off by either calling close() on the stream or close() on
+ * the generator. Closing the returned stream does not close off the OutputStream parameter out.
+ *
+ * <p>
+ * <b>Note</b>: if the buffer is not a power of 2 in length only the largest power of 2 bytes
+ * worth of the buffer will be used.
+ *
+ * @param out the underlying output stream to write the literal data packet to.
+ * @param format the format of the literal data that will be written to the output stream (one
+ * of {@link #BINARY}, {@link #TEXT} or {@link #UTF8}).
+ * @param name the name of the "file" to encode in the literal data object.
+ * @param modificationTime the time of last modification we want stored (will be stored to
+ * second level precision).
+ * @param buffer a buffer to use to buffer and write partial packets. The returned stream takes
+ * ownership of the buffer.
+ *
+ * @return the output stream to write data to.
+ * @throws IOException if an error occurs writing stream header information to the provider
+ * output stream.
+ * @throws IllegalStateException if this generator already has an open OutputStream.
+ */
+ public OutputStream open(
+ OutputStream out,
+ char format,
+ String name,
+ Date modificationTime,
+ byte[] buffer)
+ throws IOException
+ {
+ if (pkOut != null)
+ {
+ throw new IllegalStateException("generator already in open state");
+ }
+
+ pkOut = new BCPGOutputStream(out, PacketTags.LITERAL_DATA, buffer);
+
+ byte[] encName = Strings.toUTF8ByteArray(name);
+
+ writeHeader(pkOut, format, encName, modificationTime.getTime());
+
+ return new WrappedGeneratorStream(pkOut, this);
+ }
+
+ /**
+ * Open a literal data packet for the passed in File object, returning an output stream for
+ * saving the file contents.
+ * <p/>
+ * This method configures the generator to store the file contents in a single literal data
+ * packet, taking the filename and modification time from the file, but does not store the
+ * actual file data.
+ * <p/>
+ * The stream created can be closed off by either calling close() on the stream or close() on
+ * the generator. Closing the returned stream does not close off the OutputStream parameter out.
+ *
+ * @param out the underlying output stream to write the literal data packet to.
+ * @param format the format of the literal data that will be written to the output stream (one
+ * of {@link #BINARY}, {@link #TEXT} or {@link #UTF8}).
+ * @param file the file to determine the length and filename from.
+ * @return the output stream to write data to.
+ * @throws IOException if an error occurs writing stream header information to the provider
+ * output stream.
+ * @throws IllegalStateException if this generator already has an open OutputStream.
+ */
+ public OutputStream open(
+ OutputStream out,
+ char format,
+ File file)
+ throws IOException
+ {
+ return open(out, format, file.getName(), file.length(), new Date(file.lastModified()));
+ }
+
+ /**
+ * Close the literal data packet - this is equivalent to calling close on the stream
+ * returned by the open() method.
+ *
+ * @throws IOException
+ */
+ public void close()
+ throws IOException
+ {
+ if (pkOut != null)
+ {
+ pkOut.finish();
+ pkOut.flush();
+ pkOut = null;
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPMarker.java b/pg/src/main/java/org/spongycastle/openpgp/PGPMarker.java
new file mode 100644
index 00000000..5719e90d
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPMarker.java
@@ -0,0 +1,34 @@
+/*
+ * Created on Mar 6, 2004
+ *
+ * To change this generated comment go to
+ * Window>Preferences>Java>Code Generation>Code and Comments
+ */
+package org.spongycastle.openpgp;
+
+import java.io.IOException;
+
+import org.spongycastle.bcpg.BCPGInputStream;
+import org.spongycastle.bcpg.MarkerPacket;
+
+/**
+ * a PGP marker packet - in general these should be ignored other than where
+ * the idea is to preserve the original input stream.
+ */
+public class PGPMarker
+{
+ private MarkerPacket p;
+
+ /**
+ * Default constructor.
+ *
+ * @param in
+ * @throws IOException
+ */
+ public PGPMarker(
+ BCPGInputStream in)
+ throws IOException
+ {
+ p = (MarkerPacket)in.readPacket();
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPObjectFactory.java b/pg/src/main/java/org/spongycastle/openpgp/PGPObjectFactory.java
new file mode 100644
index 00000000..948a491a
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPObjectFactory.java
@@ -0,0 +1,175 @@
+package org.spongycastle.openpgp;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.spongycastle.bcpg.BCPGInputStream;
+import org.spongycastle.bcpg.PacketTags;
+import org.spongycastle.openpgp.bc.BcPGPObjectFactory;
+import org.spongycastle.openpgp.jcajce.JcaPGPObjectFactory;
+import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator;
+import org.spongycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
+
+/**
+ * General class for reading a PGP object stream.
+ * <p/>
+ * Note: if this class finds a {@link PGPPublicKey} or a {@link PGPSecretKey} it will create a
+ * {@link PGPPublicKeyRing}, or a {@link PGPSecretKeyRing} for each key found. If all you are trying
+ * to do is read a key ring file use either {@link PGPPublicKeyRingCollection} or
+ * {@link PGPSecretKeyRingCollection}.
+ * <p/>
+ * This factory supports reading the following types of objects:
+ * <ul>
+ * <li>{@link PacketTags#SIGNATURE} - produces a {@link PGPSignatureList}</li>
+ * <li>{@link PacketTags#SECRET_KEY} - produces a {@link PGPSecretKeyRing}</li>
+ * <li>{@link PacketTags#PUBLIC_KEY} - produces a {@link PGPPublicKeyRing}</li>
+ * <li>{@link PacketTags#PUBLIC_SUBKEY} - produces a {@link PGPPublicKey}</li>
+ * <li>{@link PacketTags#COMPRESSED_DATA} - produces a {@link PGPCompressedData}</li>
+ * <li>{@link PacketTags#LITERAL_DATA} - produces a {@link PGPLiteralData}</li>
+ * <li>{@link PacketTags#PUBLIC_KEY_ENC_SESSION} - produces a {@link PGPEncryptedDataList}</li>
+ * <li>{@link PacketTags#SYMMETRIC_KEY_ENC_SESSION} - produces a {@link PGPEncryptedDataList}</li>
+ * <li>{@link PacketTags#ONE_PASS_SIGNATURE} - produces a {@link PGPOnePassSignatureList}</li>
+ * <li>{@link PacketTags#MARKER} - produces a {@link PGPMarker}</li>
+ * </ul>
+ */
+public class PGPObjectFactory
+{
+ private BCPGInputStream in;
+ private KeyFingerPrintCalculator fingerPrintCalculator;
+
+ /**
+ * @deprecated use {@link JcaPGPObjectFactory} or {@link BcPGPObjectFactory}
+ */
+ public PGPObjectFactory(
+ InputStream in)
+ {
+ this(in, new BcKeyFingerprintCalculator());
+ }
+
+ /**
+ * Create an object factory suitable for reading PGP objects such as keys, key rings and key
+ * ring collections, or PGP encrypted data.
+ *
+ * @param in stream to read PGP data from.
+ * @param fingerPrintCalculator calculator to use in key finger print calculations.
+ */
+ public PGPObjectFactory(
+ InputStream in,
+ KeyFingerPrintCalculator fingerPrintCalculator)
+ {
+ this.in = new BCPGInputStream(in);
+ this.fingerPrintCalculator = fingerPrintCalculator;
+ }
+
+ /**
+ * @deprecated use JcaPGPObjectFactory or BcPGPObjectFactory
+ */
+ public PGPObjectFactory(
+ byte[] bytes)
+ {
+ this(new ByteArrayInputStream(bytes));
+ }
+
+ /**
+ * Create an object factory suitable for reading PGP objects such as keys, key rings and key
+ * ring collections, or PGP encrypted data.
+ *
+ * @param bytes PGP encoded data.
+ * @param fingerPrintCalculator calculator to use in key finger print calculations.
+ */
+ public PGPObjectFactory(
+ byte[] bytes,
+ KeyFingerPrintCalculator fingerPrintCalculator)
+ {
+ this(new ByteArrayInputStream(bytes), fingerPrintCalculator);
+ }
+
+ /**
+ * Return the next object in the stream, or <code>null</code> if the end of stream is reached.
+ *
+ * @return one of the supported objects - see class docs for details.
+ * @throws IOException if an error occurs reading from the wrapped stream or parsing data.
+ */
+ public Object nextObject()
+ throws IOException
+ {
+ List l;
+
+ switch (in.nextPacketTag())
+ {
+ case -1:
+ return null;
+ case PacketTags.SIGNATURE:
+ l = new ArrayList();
+
+ while (in.nextPacketTag() == PacketTags.SIGNATURE)
+ {
+ try
+ {
+ l.add(new PGPSignature(in));
+ }
+ catch (PGPException e)
+ {
+ throw new IOException("can't create signature object: " + e);
+ }
+ }
+
+ return new PGPSignatureList((PGPSignature[])l.toArray(new PGPSignature[l.size()]));
+ case PacketTags.SECRET_KEY:
+ try
+ {
+ return new PGPSecretKeyRing(in, fingerPrintCalculator);
+ }
+ catch (PGPException e)
+ {
+ throw new IOException("can't create secret key object: " + e);
+ }
+ case PacketTags.PUBLIC_KEY:
+ return new PGPPublicKeyRing(in, fingerPrintCalculator);
+ case PacketTags.PUBLIC_SUBKEY:
+ try
+ {
+ return PGPPublicKeyRing.readSubkey(in, fingerPrintCalculator);
+ }
+ catch (PGPException e)
+ {
+ throw new IOException("processing error: " + e.getMessage());
+ }
+ case PacketTags.COMPRESSED_DATA:
+ return new PGPCompressedData(in);
+ case PacketTags.LITERAL_DATA:
+ return new PGPLiteralData(in);
+ case PacketTags.PUBLIC_KEY_ENC_SESSION:
+ case PacketTags.SYMMETRIC_KEY_ENC_SESSION:
+ return new PGPEncryptedDataList(in);
+ case PacketTags.ONE_PASS_SIGNATURE:
+ l = new ArrayList();
+
+ while (in.nextPacketTag() == PacketTags.ONE_PASS_SIGNATURE)
+ {
+ try
+ {
+ l.add(new PGPOnePassSignature(in));
+ }
+ catch (PGPException e)
+ {
+ throw new IOException("can't create one pass signature object: " + e);
+ }
+ }
+
+ return new PGPOnePassSignatureList((PGPOnePassSignature[])l.toArray(new PGPOnePassSignature[l.size()]));
+ case PacketTags.MARKER:
+ return new PGPMarker(in);
+ case PacketTags.EXPERIMENTAL_1:
+ case PacketTags.EXPERIMENTAL_2:
+ case PacketTags.EXPERIMENTAL_3:
+ case PacketTags.EXPERIMENTAL_4:
+ return in.readPacket();
+ }
+
+ throw new IOException("unknown object in stream: " + in.nextPacketTag());
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPOnePassSignature.java b/pg/src/main/java/org/spongycastle/openpgp/PGPOnePassSignature.java
new file mode 100644
index 00000000..8e1dbcf6
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPOnePassSignature.java
@@ -0,0 +1,222 @@
+package org.spongycastle.openpgp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.spongycastle.bcpg.BCPGInputStream;
+import org.spongycastle.bcpg.BCPGOutputStream;
+import org.spongycastle.bcpg.OnePassSignaturePacket;
+import org.spongycastle.openpgp.operator.PGPContentVerifier;
+import org.spongycastle.openpgp.operator.PGPContentVerifierBuilder;
+import org.spongycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
+
+/**
+ * A one pass signature object.
+ */
+public class PGPOnePassSignature
+{
+ private OnePassSignaturePacket sigPack;
+ private int signatureType;
+
+ private PGPContentVerifier verifier;
+ private byte lastb;
+ private OutputStream sigOut;
+
+ PGPOnePassSignature(
+ BCPGInputStream pIn)
+ throws IOException, PGPException
+ {
+ this((OnePassSignaturePacket)pIn.readPacket());
+ }
+
+ PGPOnePassSignature(
+ OnePassSignaturePacket sigPack)
+ throws PGPException
+ {
+ this.sigPack = sigPack;
+ this.signatureType = sigPack.getSignatureType();
+ }
+
+ /**
+ * Initialise the signature object for verification.
+ *
+ * @param verifierBuilderProvider provider for a content verifier builder for the signature type of interest.
+ * @param pubKey the public key to use for verification
+ * @throws PGPException if there's an issue with creating the verifier.
+ */
+ public void init(PGPContentVerifierBuilderProvider verifierBuilderProvider, PGPPublicKey pubKey)
+ throws PGPException
+ {
+ PGPContentVerifierBuilder verifierBuilder = verifierBuilderProvider.get(sigPack.getKeyAlgorithm(), sigPack.getHashAlgorithm());
+
+ verifier = verifierBuilder.build(pubKey);
+
+ lastb = 0;
+ sigOut = verifier.getOutputStream();
+ }
+
+ public void update(
+ byte b)
+ {
+ if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
+ {
+ if (b == '\r')
+ {
+ byteUpdate((byte)'\r');
+ byteUpdate((byte)'\n');
+ }
+ else if (b == '\n')
+ {
+ if (lastb != '\r')
+ {
+ byteUpdate((byte)'\r');
+ byteUpdate((byte)'\n');
+ }
+ }
+ else
+ {
+ byteUpdate(b);
+ }
+
+ lastb = b;
+ }
+ else
+ {
+ byteUpdate(b);
+ }
+ }
+
+ public void update(
+ byte[] bytes)
+ {
+ if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
+ {
+ for (int i = 0; i != bytes.length; i++)
+ {
+ this.update(bytes[i]);
+ }
+ }
+ else
+ {
+ blockUpdate(bytes, 0, bytes.length);
+ }
+ }
+
+ public void update(
+ byte[] bytes,
+ int off,
+ int length)
+ {
+ if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
+ {
+ int finish = off + length;
+
+ for (int i = off; i != finish; i++)
+ {
+ this.update(bytes[i]);
+ }
+ }
+ else
+ {
+ blockUpdate(bytes, off, length);
+ }
+ }
+
+ private void byteUpdate(byte b)
+ {
+ try
+ {
+ sigOut.write(b);
+ }
+ catch (IOException e)
+ {
+ throw new PGPRuntimeOperationException(e.getMessage(), e);
+ }
+ }
+
+ private void blockUpdate(byte[] block, int off, int len)
+ {
+ try
+ {
+ sigOut.write(block, off, len);
+ }
+ catch (IOException e)
+ {
+ throw new PGPRuntimeOperationException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Verify the calculated signature against the passed in PGPSignature.
+ *
+ * @param pgpSig
+ * @return boolean
+ * @throws PGPException
+ */
+ public boolean verify(
+ PGPSignature pgpSig)
+ throws PGPException
+ {
+ try
+ {
+ sigOut.write(pgpSig.getSignatureTrailer());
+
+ sigOut.close();
+ }
+ catch (IOException e)
+ {
+ throw new PGPException("unable to add trailer: " + e.getMessage(), e);
+ }
+
+ return verifier.verify(pgpSig.getSignature());
+ }
+
+ public long getKeyID()
+ {
+ return sigPack.getKeyID();
+ }
+
+ public int getSignatureType()
+ {
+ return sigPack.getSignatureType();
+ }
+
+ public int getHashAlgorithm()
+ {
+ return sigPack.getHashAlgorithm();
+ }
+
+ public int getKeyAlgorithm()
+ {
+ return sigPack.getKeyAlgorithm();
+ }
+
+ public byte[] getEncoded()
+ throws IOException
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ this.encode(bOut);
+
+ return bOut.toByteArray();
+ }
+
+ public void encode(
+ OutputStream outStream)
+ throws IOException
+ {
+ BCPGOutputStream out;
+
+ if (outStream instanceof BCPGOutputStream)
+ {
+ out = (BCPGOutputStream)outStream;
+ }
+ else
+ {
+ out = new BCPGOutputStream(outStream);
+ }
+
+ out.writePacket(sigPack);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPOnePassSignatureList.java b/pg/src/main/java/org/spongycastle/openpgp/PGPOnePassSignatureList.java
new file mode 100644
index 00000000..471c64e9
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPOnePassSignatureList.java
@@ -0,0 +1,40 @@
+package org.spongycastle.openpgp;
+
+/**
+ * Holder for a list of PGPOnePassSignatures
+ */
+public class PGPOnePassSignatureList
+{
+ PGPOnePassSignature[] sigs;
+
+ public PGPOnePassSignatureList(
+ PGPOnePassSignature[] sigs)
+ {
+ this.sigs = new PGPOnePassSignature[sigs.length];
+
+ System.arraycopy(sigs, 0, this.sigs, 0, sigs.length);
+ }
+
+ public PGPOnePassSignatureList(
+ PGPOnePassSignature sig)
+ {
+ this.sigs = new PGPOnePassSignature[1];
+ this.sigs[0] = sig;
+ }
+
+ public PGPOnePassSignature get(
+ int index)
+ {
+ return sigs[index];
+ }
+
+ public int size()
+ {
+ return sigs.length;
+ }
+
+ public boolean isEmpty()
+ {
+ return (sigs.length == 0);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPPBEEncryptedData.java b/pg/src/main/java/org/spongycastle/openpgp/PGPPBEEncryptedData.java
new file mode 100644
index 00000000..fffb5d93
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPPBEEncryptedData.java
@@ -0,0 +1,145 @@
+package org.spongycastle.openpgp;
+
+import java.io.EOFException;
+import java.io.InputStream;
+
+import org.spongycastle.bcpg.BCPGInputStream;
+import org.spongycastle.bcpg.InputStreamPacket;
+import org.spongycastle.bcpg.SymmetricEncIntegrityPacket;
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.bcpg.SymmetricKeyEncSessionPacket;
+import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory;
+import org.spongycastle.openpgp.operator.PGPDataDecryptor;
+import org.spongycastle.util.io.TeeInputStream;
+
+/**
+ * A password based encryption object.
+ * <p/>
+ * PBE encrypted data objects can be {@link #getDataStream(PBEDataDecryptorFactory) decrypted }
+ * using a {@link PBEDataDecryptorFactory}.
+ */
+public class PGPPBEEncryptedData
+ extends PGPEncryptedData
+{
+ SymmetricKeyEncSessionPacket keyData;
+
+ /**
+ * Construct a PBE encryped data object.
+ *
+ * @param keyData the PBE key data packet associated with the encrypted data in the PGP object
+ * stream.
+ * @param encData the encrypted data.
+ */
+ PGPPBEEncryptedData(
+ SymmetricKeyEncSessionPacket keyData,
+ InputStreamPacket encData)
+ {
+ super(encData);
+
+ this.keyData = keyData;
+ }
+
+ /**
+ * Return the symmetric key algorithm required to decrypt the data protected by this object.
+ *
+ * @param dataDecryptorFactory decryptor factory to use to recover the session data.
+ * @return the identifier of the {@link SymmetricKeyAlgorithmTags encryption algorithm} used to
+ * encrypt this object.
+ * @throws PGPException if the session data cannot be recovered.
+ */
+ public int getSymmetricAlgorithm(
+ PBEDataDecryptorFactory dataDecryptorFactory)
+ throws PGPException
+ {
+ byte[] key = dataDecryptorFactory.makeKeyFromPassPhrase(keyData.getEncAlgorithm(), keyData.getS2K());
+ byte[] sessionData = dataDecryptorFactory.recoverSessionData(keyData.getEncAlgorithm(), key, keyData.getSecKeyData());
+
+ return sessionData[0];
+ }
+
+ /**
+ * Open an input stream which will provide the decrypted data protected by this object.
+ *
+ * @param dataDecryptorFactory decryptor factory to use to recover the session data and provide
+ * the stream.
+ * @return the resulting decrypted input stream, probably containing a sequence of PGP data
+ * objects.
+ * @throws PGPException if the session data cannot be recovered or the stream cannot be created.
+ */
+ public InputStream getDataStream(
+ PBEDataDecryptorFactory dataDecryptorFactory)
+ throws PGPException
+ {
+ try
+ {
+ int keyAlgorithm = keyData.getEncAlgorithm();
+ byte[] key = dataDecryptorFactory.makeKeyFromPassPhrase(keyAlgorithm, keyData.getS2K());
+ boolean withIntegrityPacket = encData instanceof SymmetricEncIntegrityPacket;
+
+ byte[] sessionData = dataDecryptorFactory.recoverSessionData(keyData.getEncAlgorithm(), key, keyData.getSecKeyData());
+ byte[] sessionKey = new byte[sessionData.length - 1];
+
+ System.arraycopy(sessionData, 1, sessionKey, 0, sessionKey.length);
+
+ PGPDataDecryptor dataDecryptor = dataDecryptorFactory.createDataDecryptor(withIntegrityPacket, sessionData[0] & 0xff, sessionKey);
+
+ encStream = new BCPGInputStream(dataDecryptor.getInputStream(encData.getInputStream()));
+
+ if (withIntegrityPacket)
+ {
+ truncStream = new TruncatedStream(encStream);
+
+ integrityCalculator = dataDecryptor.getIntegrityCalculator();
+
+ encStream = new TeeInputStream(truncStream, integrityCalculator.getOutputStream());
+ }
+
+ byte[] iv = new byte[dataDecryptor.getBlockSize()];
+ for (int i = 0; i != iv.length; i++)
+ {
+ int ch = encStream.read();
+
+ if (ch < 0)
+ {
+ throw new EOFException("unexpected end of stream.");
+ }
+
+ iv[i] = (byte)ch;
+ }
+
+ int v1 = encStream.read();
+ int v2 = encStream.read();
+
+ if (v1 < 0 || v2 < 0)
+ {
+ throw new EOFException("unexpected end of stream.");
+ }
+
+
+ // Note: the oracle attack on "quick check" bytes is not deemed
+ // a security risk for PBE (see PGPPublicKeyEncryptedData)
+
+ boolean repeatCheckPassed = iv[iv.length - 2] == (byte) v1
+ && iv[iv.length - 1] == (byte) v2;
+
+ // Note: some versions of PGP appear to produce 0 for the extra
+ // bytes rather than repeating the two previous bytes
+ boolean zeroesCheckPassed = v1 == 0 && v2 == 0;
+
+ if (!repeatCheckPassed && !zeroesCheckPassed)
+ {
+ throw new PGPDataValidationException("data check failed.");
+ }
+
+ return encStream;
+ }
+ catch (PGPException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new PGPException("Exception creating cipher", e);
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPPrivateKey.java b/pg/src/main/java/org/spongycastle/openpgp/PGPPrivateKey.java
new file mode 100644
index 00000000..8ba973d7
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPPrivateKey.java
@@ -0,0 +1,65 @@
+package org.spongycastle.openpgp;
+
+import org.spongycastle.bcpg.BCPGKey;
+import org.spongycastle.bcpg.PublicKeyPacket;
+
+/**
+ * general class to contain a private key for use with other openPGP
+ * objects.
+ */
+public class PGPPrivateKey
+{
+ private long keyID;
+ private PublicKeyPacket publicKeyPacket;
+ private BCPGKey privateKeyDataPacket;
+
+ /**
+ * Base constructor.
+ *
+ * Create a PGPPrivateKey from a keyID and the associated public/private data packets needed
+ * to fully describe it.
+ *
+ * @param keyID keyID associated with the public key.
+ * @param publicKeyPacket the public key data packet to be associated with this private key.
+ * @param privateKeyDataPacket the private key data packet to be associate with this private key.
+ */
+ public PGPPrivateKey(
+ long keyID,
+ PublicKeyPacket publicKeyPacket,
+ BCPGKey privateKeyDataPacket)
+ {
+ this.keyID = keyID;
+ this.publicKeyPacket = publicKeyPacket;
+ this.privateKeyDataPacket = privateKeyDataPacket;
+ }
+
+ /**
+ * Return the keyID associated with the contained private key.
+ *
+ * @return long
+ */
+ public long getKeyID()
+ {
+ return keyID;
+ }
+
+ /**
+ * Return the public key packet associated with this private key, if available.
+ *
+ * @return associated public key packet, null otherwise.
+ */
+ public PublicKeyPacket getPublicKeyPacket()
+ {
+ return publicKeyPacket;
+ }
+
+ /**
+ * Return the private key packet associated with this private key, if available.
+ *
+ * @return associated private key packet, null otherwise.
+ */
+ public BCPGKey getPrivateKeyDataPacket()
+ {
+ return privateKeyDataPacket;
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPPublicKey.java b/pg/src/main/java/org/spongycastle/openpgp/PGPPublicKey.java
new file mode 100644
index 00000000..8f4d84a5
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPPublicKey.java
@@ -0,0 +1,977 @@
+package org.spongycastle.openpgp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
+import org.spongycastle.bcpg.BCPGKey;
+import org.spongycastle.bcpg.BCPGOutputStream;
+import org.spongycastle.bcpg.ContainedPacket;
+import org.spongycastle.bcpg.DSAPublicBCPGKey;
+import org.spongycastle.bcpg.ElGamalPublicBCPGKey;
+import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
+import org.spongycastle.bcpg.PublicKeyPacket;
+import org.spongycastle.bcpg.RSAPublicBCPGKey;
+import org.spongycastle.bcpg.TrustPacket;
+import org.spongycastle.bcpg.UserAttributePacket;
+import org.spongycastle.bcpg.UserIDPacket;
+import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator;
+import org.spongycastle.util.Arrays;
+
+/**
+ * general class to handle a PGP public key object.
+ */
+public class PGPPublicKey
+ implements PublicKeyAlgorithmTags
+{
+ private static final int[] MASTER_KEY_CERTIFICATION_TYPES = new int[] { PGPSignature.POSITIVE_CERTIFICATION, PGPSignature.CASUAL_CERTIFICATION, PGPSignature.NO_CERTIFICATION, PGPSignature.DEFAULT_CERTIFICATION };
+
+ PublicKeyPacket publicPk;
+ TrustPacket trustPk;
+ List keySigs = new ArrayList();
+ List ids = new ArrayList();
+ List idTrusts = new ArrayList();
+ List idSigs = new ArrayList();
+
+ List subSigs = null;
+
+ private long keyID;
+ private byte[] fingerprint;
+ private int keyStrength;
+
+ private void init(KeyFingerPrintCalculator fingerPrintCalculator)
+ throws PGPException
+ {
+ BCPGKey key = publicPk.getKey();
+
+ this.fingerprint = fingerPrintCalculator.calculateFingerprint(publicPk);
+
+ if (publicPk.getVersion() <= 3)
+ {
+ RSAPublicBCPGKey rK = (RSAPublicBCPGKey)key;
+
+ this.keyID = rK.getModulus().longValue();
+ this.keyStrength = rK.getModulus().bitLength();
+ }
+ else
+ {
+ this.keyID = ((long)(fingerprint[fingerprint.length - 8] & 0xff) << 56)
+ | ((long)(fingerprint[fingerprint.length - 7] & 0xff) << 48)
+ | ((long)(fingerprint[fingerprint.length - 6] & 0xff) << 40)
+ | ((long)(fingerprint[fingerprint.length - 5] & 0xff) << 32)
+ | ((long)(fingerprint[fingerprint.length - 4] & 0xff) << 24)
+ | ((long)(fingerprint[fingerprint.length - 3] & 0xff) << 16)
+ | ((long)(fingerprint[fingerprint.length - 2] & 0xff) << 8)
+ | ((fingerprint[fingerprint.length - 1] & 0xff));
+
+ if (key instanceof RSAPublicBCPGKey)
+ {
+ this.keyStrength = ((RSAPublicBCPGKey)key).getModulus().bitLength();
+ }
+ else if (key instanceof DSAPublicBCPGKey)
+ {
+ this.keyStrength = ((DSAPublicBCPGKey)key).getP().bitLength();
+ }
+ else if (key instanceof ElGamalPublicBCPGKey)
+ {
+ this.keyStrength = ((ElGamalPublicBCPGKey)key).getP().bitLength();
+ }
+ }
+ }
+
+ /**
+ * Create a PGP public key from a packet descriptor using the passed in fingerPrintCalculator to do calculate
+ * the fingerprint and keyID.
+ *
+ * @param publicKeyPacket packet describing the public key.
+ * @param fingerPrintCalculator calculator providing the digest support ot create the key fingerprint.
+ * @throws PGPException if the packet is faulty, or the required calculations fail.
+ */
+ public PGPPublicKey(PublicKeyPacket publicKeyPacket, KeyFingerPrintCalculator fingerPrintCalculator)
+ throws PGPException
+ {
+ this.publicPk = publicKeyPacket;
+ this.ids = new ArrayList();
+ this.idSigs = new ArrayList();
+
+ init(fingerPrintCalculator);
+ }
+
+ /*
+ * Constructor for a sub-key.
+ */
+ PGPPublicKey(
+ PublicKeyPacket publicPk,
+ TrustPacket trustPk,
+ List sigs,
+ KeyFingerPrintCalculator fingerPrintCalculator)
+ throws PGPException
+ {
+ this.publicPk = publicPk;
+ this.trustPk = trustPk;
+ this.subSigs = sigs;
+
+ init(fingerPrintCalculator);
+ }
+
+ PGPPublicKey(
+ PGPPublicKey key,
+ TrustPacket trust,
+ List subSigs)
+ {
+ this.publicPk = key.publicPk;
+ this.trustPk = trust;
+ this.subSigs = subSigs;
+
+ this.fingerprint = key.fingerprint;
+ this.keyID = key.keyID;
+ this.keyStrength = key.keyStrength;
+ }
+
+ /**
+ * Copy constructor.
+ * @param pubKey the public key to copy.
+ */
+ PGPPublicKey(
+ PGPPublicKey pubKey)
+ {
+ this.publicPk = pubKey.publicPk;
+
+ this.keySigs = new ArrayList(pubKey.keySigs);
+ this.ids = new ArrayList(pubKey.ids);
+ this.idTrusts = new ArrayList(pubKey.idTrusts);
+ this.idSigs = new ArrayList(pubKey.idSigs.size());
+ for (int i = 0; i != pubKey.idSigs.size(); i++)
+ {
+ this.idSigs.add(new ArrayList((ArrayList)pubKey.idSigs.get(i)));
+ }
+
+ if (pubKey.subSigs != null)
+ {
+ this.subSigs = new ArrayList(pubKey.subSigs.size());
+ for (int i = 0; i != pubKey.subSigs.size(); i++)
+ {
+ this.subSigs.add(pubKey.subSigs.get(i));
+ }
+ }
+
+ this.fingerprint = pubKey.fingerprint;
+ this.keyID = pubKey.keyID;
+ this.keyStrength = pubKey.keyStrength;
+ }
+
+ PGPPublicKey(
+ PublicKeyPacket publicPk,
+ TrustPacket trustPk,
+ List keySigs,
+ List ids,
+ List idTrusts,
+ List idSigs,
+ KeyFingerPrintCalculator fingerPrintCalculator)
+ throws PGPException
+ {
+ this.publicPk = publicPk;
+ this.trustPk = trustPk;
+ this.keySigs = keySigs;
+ this.ids = ids;
+ this.idTrusts = idTrusts;
+ this.idSigs = idSigs;
+
+ init(fingerPrintCalculator);
+ }
+
+ /**
+ * @return the version of this key.
+ */
+ public int getVersion()
+ {
+ return publicPk.getVersion();
+ }
+
+ /**
+ * @return creation time of key.
+ */
+ public Date getCreationTime()
+ {
+ return publicPk.getTime();
+ }
+
+ /**
+ * @return number of valid days from creation time - zero means no
+ * expiry.
+ */
+ public int getValidDays()
+ {
+ if (publicPk.getVersion() > 3)
+ {
+ return (int)(this.getValidSeconds() / (24 * 60 * 60));
+ }
+ else
+ {
+ return publicPk.getValidDays();
+ }
+ }
+
+ /**
+ * Return the trust data associated with the public key, if present.
+ * @return a byte array with trust data, null otherwise.
+ */
+ public byte[] getTrustData()
+ {
+ if (trustPk == null)
+ {
+ return null;
+ }
+
+ return Arrays.clone(trustPk.getLevelAndTrustAmount());
+ }
+
+ /**
+ * @return number of valid seconds from creation time - zero means no
+ * expiry.
+ */
+ public long getValidSeconds()
+ {
+ if (publicPk.getVersion() > 3)
+ {
+ if (this.isMasterKey())
+ {
+ for (int i = 0; i != MASTER_KEY_CERTIFICATION_TYPES.length; i++)
+ {
+ long seconds = getExpirationTimeFromSig(true, MASTER_KEY_CERTIFICATION_TYPES[i]);
+
+ if (seconds >= 0)
+ {
+ return seconds;
+ }
+ }
+ }
+ else
+ {
+ long seconds = getExpirationTimeFromSig(false, PGPSignature.SUBKEY_BINDING);
+
+ if (seconds >= 0)
+ {
+ return seconds;
+ }
+ }
+
+ return 0;
+ }
+ else
+ {
+ return (long)publicPk.getValidDays() * 24 * 60 * 60;
+ }
+ }
+
+ private long getExpirationTimeFromSig(
+ boolean selfSigned,
+ int signatureType)
+ {
+ Iterator signatures = this.getSignaturesOfType(signatureType);
+ long expiryTime = -1;
+
+ while (signatures.hasNext())
+ {
+ PGPSignature sig = (PGPSignature)signatures.next();
+
+ if (!selfSigned || sig.getKeyID() == this.getKeyID())
+ {
+ PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets();
+
+ if (hashed != null)
+ {
+ long current = hashed.getKeyExpirationTime();
+
+ if (current == 0 || current > expiryTime)
+ {
+ expiryTime = current;
+ }
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ }
+
+ return expiryTime;
+ }
+
+ /**
+ * Return the keyID associated with the public key.
+ *
+ * @return long
+ */
+ public long getKeyID()
+ {
+ return keyID;
+ }
+
+ /**
+ * Return the fingerprint of the key.
+ *
+ * @return key fingerprint.
+ */
+ public byte[] getFingerprint()
+ {
+ byte[] tmp = new byte[fingerprint.length];
+
+ System.arraycopy(fingerprint, 0, tmp, 0, tmp.length);
+
+ return tmp;
+ }
+
+ /**
+ * Return true if this key has an algorithm type that makes it suitable to use for encryption.
+ * <p>
+ * Note: with version 4 keys KeyFlags subpackets should also be considered when present for
+ * determining the preferred use of the key.
+ *
+ * @return true if the key algorithm is suitable for encryption.
+ */
+ public boolean isEncryptionKey()
+ {
+ int algorithm = publicPk.getAlgorithm();
+
+ return ((algorithm == RSA_GENERAL) || (algorithm == RSA_ENCRYPT)
+ || (algorithm == ELGAMAL_ENCRYPT) || (algorithm == ELGAMAL_GENERAL) || algorithm == ECDH);
+ }
+
+ /**
+ * Return true if this is a master key.
+ * @return true if a master key.
+ */
+ public boolean isMasterKey()
+ {
+ return (subSigs == null);
+ }
+
+ /**
+ * Return the algorithm code associated with the public key.
+ *
+ * @return int
+ */
+ public int getAlgorithm()
+ {
+ return publicPk.getAlgorithm();
+ }
+
+ /**
+ * Return the strength of the key in bits.
+ *
+ * @return bit strength of key.
+ */
+ public int getBitStrength()
+ {
+ return keyStrength;
+ }
+
+ /**
+ * Return any userIDs associated with the key.
+ *
+ * @return an iterator of Strings.
+ */
+ public Iterator getUserIDs()
+ {
+ List temp = new ArrayList();
+
+ for (int i = 0; i != ids.size(); i++)
+ {
+ if (ids.get(i) instanceof UserIDPacket)
+ {
+ temp.add(((UserIDPacket)ids.get(i)).getID());
+ }
+ }
+
+ return temp.iterator();
+ }
+
+ /**
+ * Return any userIDs associated with the key in raw byte form. No attempt is made
+ * to convert the IDs into Strings.
+ *
+ * @return an iterator of Strings.
+ */
+ public Iterator getRawUserIDs()
+ {
+ List temp = new ArrayList();
+
+ for (int i = 0; i != ids.size(); i++)
+ {
+ if (ids.get(i) instanceof UserIDPacket)
+ {
+ temp.add(((UserIDPacket)ids.get(i)).getRawID());
+ }
+ }
+
+ return temp.iterator();
+ }
+
+ /**
+ * Return any user attribute vectors associated with the key.
+ *
+ * @return an iterator of PGPUserAttributeSubpacketVector objects.
+ */
+ public Iterator getUserAttributes()
+ {
+ List temp = new ArrayList();
+
+ for (int i = 0; i != ids.size(); i++)
+ {
+ if (ids.get(i) instanceof PGPUserAttributeSubpacketVector)
+ {
+ temp.add(ids.get(i));
+ }
+ }
+
+ return temp.iterator();
+ }
+
+ /**
+ * Return any signatures associated with the passed in id.
+ *
+ * @param id the id to be matched.
+ * @return an iterator of PGPSignature objects.
+ */
+ public Iterator getSignaturesForID(
+ String id)
+ {
+ return getSignaturesForID(new UserIDPacket(id));
+ }
+
+ /**
+ * Return any signatures associated with the passed in id.
+ *
+ * @param rawID the id to be matched in raw byte form.
+ * @return an iterator of PGPSignature objects.
+ */
+ public Iterator getSignaturesForID(
+ byte[] rawID)
+ {
+ return getSignaturesForID(new UserIDPacket(rawID));
+ }
+
+ private Iterator getSignaturesForID(
+ UserIDPacket id)
+ {
+ for (int i = 0; i != ids.size(); i++)
+ {
+ if (id.equals(ids.get(i)))
+ {
+ return ((ArrayList)idSigs.get(i)).iterator();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return an iterator of signatures associated with the passed in user attributes.
+ *
+ * @param userAttributes the vector of user attributes to be matched.
+ * @return an iterator of PGPSignature objects.
+ */
+ public Iterator getSignaturesForUserAttribute(
+ PGPUserAttributeSubpacketVector userAttributes)
+ {
+ for (int i = 0; i != ids.size(); i++)
+ {
+ if (userAttributes.equals(ids.get(i)))
+ {
+ return ((ArrayList)idSigs.get(i)).iterator();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return signatures of the passed in type that are on this key.
+ *
+ * @param signatureType the type of the signature to be returned.
+ * @return an iterator (possibly empty) of signatures of the given type.
+ */
+ public Iterator getSignaturesOfType(
+ int signatureType)
+ {
+ List l = new ArrayList();
+ Iterator it = this.getSignatures();
+
+ while (it.hasNext())
+ {
+ PGPSignature sig = (PGPSignature)it.next();
+
+ if (sig.getSignatureType() == signatureType)
+ {
+ l.add(sig);
+ }
+ }
+
+ return l.iterator();
+ }
+
+ /**
+ * Return all signatures/certifications associated with this key.
+ *
+ * @return an iterator (possibly empty) with all signatures/certifications.
+ */
+ public Iterator getSignatures()
+ {
+ if (subSigs == null)
+ {
+ List sigs = new ArrayList();
+
+ sigs.addAll(keySigs);
+
+ for (int i = 0; i != idSigs.size(); i++)
+ {
+ sigs.addAll((Collection)idSigs.get(i));
+ }
+
+ return sigs.iterator();
+ }
+ else
+ {
+ return subSigs.iterator();
+ }
+ }
+
+ public PublicKeyPacket getPublicKeyPacket()
+ {
+ return publicPk;
+ }
+
+ public byte[] getEncoded()
+ throws IOException
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ this.encode(bOut);
+
+ return bOut.toByteArray();
+ }
+
+ public void encode(
+ OutputStream outStream)
+ throws IOException
+ {
+ BCPGOutputStream out;
+
+ if (outStream instanceof BCPGOutputStream)
+ {
+ out = (BCPGOutputStream)outStream;
+ }
+ else
+ {
+ out = new BCPGOutputStream(outStream);
+ }
+
+ out.writePacket(publicPk);
+ if (trustPk != null)
+ {
+ out.writePacket(trustPk);
+ }
+
+ if (subSigs == null) // not a sub-key
+ {
+ for (int i = 0; i != keySigs.size(); i++)
+ {
+ ((PGPSignature)keySigs.get(i)).encode(out);
+ }
+
+ for (int i = 0; i != ids.size(); i++)
+ {
+ if (ids.get(i) instanceof UserIDPacket)
+ {
+ UserIDPacket id = (UserIDPacket)ids.get(i);
+
+ out.writePacket(id);
+ }
+ else
+ {
+ PGPUserAttributeSubpacketVector v = (PGPUserAttributeSubpacketVector)ids.get(i);
+
+ out.writePacket(new UserAttributePacket(v.toSubpacketArray()));
+ }
+
+ if (idTrusts.get(i) != null)
+ {
+ out.writePacket((ContainedPacket)idTrusts.get(i));
+ }
+
+ List sigs = (List)idSigs.get(i);
+ for (int j = 0; j != sigs.size(); j++)
+ {
+ ((PGPSignature)sigs.get(j)).encode(out);
+ }
+ }
+ }
+ else
+ {
+ for (int j = 0; j != subSigs.size(); j++)
+ {
+ ((PGPSignature)subSigs.get(j)).encode(out);
+ }
+ }
+ }
+
+ /**
+ * Check whether this (sub)key has a revocation signature on it.
+ *
+ * @return boolean indicating whether this (sub)key has been revoked.
+ */
+ public boolean isRevoked()
+ {
+ int ns = 0;
+ boolean revoked = false;
+
+ if (this.isMasterKey()) // Master key
+ {
+ while (!revoked && (ns < keySigs.size()))
+ {
+ if (((PGPSignature)keySigs.get(ns++)).getSignatureType() == PGPSignature.KEY_REVOCATION)
+ {
+ revoked = true;
+ }
+ }
+ }
+ else // Sub-key
+ {
+ while (!revoked && (ns < subSigs.size()))
+ {
+ if (((PGPSignature)subSigs.get(ns++)).getSignatureType() == PGPSignature.SUBKEY_REVOCATION)
+ {
+ revoked = true;
+ }
+ }
+ }
+
+ return revoked;
+ }
+
+ /**
+ * Add a certification for an id to the given public key.
+ *
+ * @param key the key the certification is to be added to.
+ * @param rawID the raw bytes making up the user id..
+ * @param certification the new certification.
+ * @return the re-certified key.
+ */
+ public static PGPPublicKey addCertification(
+ PGPPublicKey key,
+ byte[] rawID,
+ PGPSignature certification)
+ {
+ return addCert(key, new UserIDPacket(rawID), certification);
+ }
+
+ /**
+ * Add a certification for an id to the given public key.
+ *
+ * @param key the key the certification is to be added to.
+ * @param id the id the certification is associated with.
+ * @param certification the new certification.
+ * @return the re-certified key.
+ */
+ public static PGPPublicKey addCertification(
+ PGPPublicKey key,
+ String id,
+ PGPSignature certification)
+ {
+ return addCert(key, new UserIDPacket(id), certification);
+ }
+
+ /**
+ * Add a certification for the given UserAttributeSubpackets to the given public key.
+ *
+ * @param key the key the certification is to be added to.
+ * @param userAttributes the attributes the certification is associated with.
+ * @param certification the new certification.
+ * @return the re-certified key.
+ */
+ public static PGPPublicKey addCertification(
+ PGPPublicKey key,
+ PGPUserAttributeSubpacketVector userAttributes,
+ PGPSignature certification)
+ {
+ return addCert(key, userAttributes, certification);
+ }
+
+ private static PGPPublicKey addCert(
+ PGPPublicKey key,
+ Object id,
+ PGPSignature certification)
+ {
+ PGPPublicKey returnKey = new PGPPublicKey(key);
+ List sigList = null;
+
+ for (int i = 0; i != returnKey.ids.size(); i++)
+ {
+ if (id.equals(returnKey.ids.get(i)))
+ {
+ sigList = (List)returnKey.idSigs.get(i);
+ }
+ }
+
+ if (sigList != null)
+ {
+ sigList.add(certification);
+ }
+ else
+ {
+ sigList = new ArrayList();
+
+ sigList.add(certification);
+ returnKey.ids.add(id);
+ returnKey.idTrusts.add(null);
+ returnKey.idSigs.add(sigList);
+ }
+
+ return returnKey;
+ }
+
+ /**
+ * Remove any certifications associated with a given user attribute subpacket
+ * on a key.
+ *
+ * @param key the key the certifications are to be removed from.
+ * @param userAttributes the attributes to be removed.
+ * @return the re-certified key, null if the user attribute subpacket was not found on the key.
+ */
+ public static PGPPublicKey removeCertification(
+ PGPPublicKey key,
+ PGPUserAttributeSubpacketVector userAttributes)
+ {
+ return removeCert(key, userAttributes);
+ }
+
+ /**
+ * Remove any certifications associated with a given id on a key.
+ *
+ * @param key the key the certifications are to be removed from.
+ * @param id the id that is to be removed.
+ * @return the re-certified key, null if the id was not found on the key.
+ */
+ public static PGPPublicKey removeCertification(
+ PGPPublicKey key,
+ String id)
+ {
+ return removeCert(key, new UserIDPacket(id));
+ }
+
+ /**
+ * Remove any certifications associated with a given id on a key.
+ *
+ * @param key the key the certifications are to be removed from.
+ * @param rawID the id that is to be removed in raw byte form.
+ * @return the re-certified key, null if the id was not found on the key.
+ */
+ public static PGPPublicKey removeCertification(
+ PGPPublicKey key,
+ byte[] rawID)
+ {
+ return removeCert(key, new UserIDPacket(rawID));
+ }
+
+ private static PGPPublicKey removeCert(
+ PGPPublicKey key,
+ Object id)
+ {
+ PGPPublicKey returnKey = new PGPPublicKey(key);
+ boolean found = false;
+
+ for (int i = 0; i < returnKey.ids.size(); i++)
+ {
+ if (id.equals(returnKey.ids.get(i)))
+ {
+ found = true;
+ returnKey.ids.remove(i);
+ returnKey.idTrusts.remove(i);
+ returnKey.idSigs.remove(i);
+ }
+ }
+
+ if (!found)
+ {
+ return null;
+ }
+
+ return returnKey;
+ }
+
+ /**
+ * Remove a certification associated with a given id on a key.
+ *
+ * @param key the key the certifications are to be removed from.
+ * @param id the id that the certification is to be removed from (in its raw byte form)
+ * @param certification the certification to be removed.
+ * @return the re-certified key, null if the certification was not found.
+ */
+ public static PGPPublicKey removeCertification(
+ PGPPublicKey key,
+ byte[] id,
+ PGPSignature certification)
+ {
+ return removeCert(key, new UserIDPacket(id), certification);
+ }
+
+ /**
+ * Remove a certification associated with a given id on a key.
+ *
+ * @param key the key the certifications are to be removed from.
+ * @param id the id that the certification is to be removed from.
+ * @param certification the certification to be removed.
+ * @return the re-certified key, null if the certification was not found.
+ */
+ public static PGPPublicKey removeCertification(
+ PGPPublicKey key,
+ String id,
+ PGPSignature certification)
+ {
+ return removeCert(key, new UserIDPacket(id), certification);
+ }
+
+ /**
+ * Remove a certification associated with a given user attributes on a key.
+ *
+ * @param key the key the certifications are to be removed from.
+ * @param userAttributes the user attributes that the certification is to be removed from.
+ * @param certification the certification to be removed.
+ * @return the re-certified key, null if the certification was not found.
+ */
+ public static PGPPublicKey removeCertification(
+ PGPPublicKey key,
+ PGPUserAttributeSubpacketVector userAttributes,
+ PGPSignature certification)
+ {
+ return removeCert(key, userAttributes, certification);
+ }
+
+ private static PGPPublicKey removeCert(
+ PGPPublicKey key,
+ Object id,
+ PGPSignature certification)
+ {
+ PGPPublicKey returnKey = new PGPPublicKey(key);
+ boolean found = false;
+
+ for (int i = 0; i < returnKey.ids.size(); i++)
+ {
+ if (id.equals(returnKey.ids.get(i)))
+ {
+ found = ((List)returnKey.idSigs.get(i)).remove(certification);
+ }
+ }
+
+ if (!found)
+ {
+ return null;
+ }
+
+ return returnKey;
+ }
+
+ /**
+ * Add a revocation or some other key certification to a key.
+ *
+ * @param key the key the revocation is to be added to.
+ * @param certification the key signature to be added.
+ * @return the new changed public key object.
+ */
+ public static PGPPublicKey addCertification(
+ PGPPublicKey key,
+ PGPSignature certification)
+ {
+ if (key.isMasterKey())
+ {
+ if (certification.getSignatureType() == PGPSignature.SUBKEY_REVOCATION)
+ {
+ throw new IllegalArgumentException("signature type incorrect for master key revocation.");
+ }
+ }
+ else
+ {
+ if (certification.getSignatureType() == PGPSignature.KEY_REVOCATION)
+ {
+ throw new IllegalArgumentException("signature type incorrect for sub-key revocation.");
+ }
+ }
+
+ PGPPublicKey returnKey = new PGPPublicKey(key);
+
+ if (returnKey.subSigs != null)
+ {
+ returnKey.subSigs.add(certification);
+ }
+ else
+ {
+ returnKey.keySigs.add(certification);
+ }
+
+ return returnKey;
+ }
+
+ /**
+ * Remove a certification from the key.
+ *
+ * @param key the key the certifications are to be removed from.
+ * @param certification the certification to be removed.
+ * @return the modified key, null if the certification was not found.
+ */
+ public static PGPPublicKey removeCertification(
+ PGPPublicKey key,
+ PGPSignature certification)
+ {
+ PGPPublicKey returnKey = new PGPPublicKey(key);
+ boolean found;
+
+ if (returnKey.subSigs != null)
+ {
+ found = returnKey.subSigs.remove(certification);
+ }
+ else
+ {
+ found = returnKey.keySigs.remove(certification);
+ }
+
+ if (!found)
+ {
+ for (Iterator it = key.getUserIDs(); it.hasNext();)
+ {
+ UserIDPacket id = (UserIDPacket)it.next();
+ for (Iterator sIt = key.getSignaturesForID(id); sIt.hasNext();)
+ {
+ if (certification == sIt.next())
+ {
+ found = true;
+ returnKey = PGPPublicKey.removeCertification(returnKey, id.getRawID(), certification);
+ }
+ }
+ }
+
+ if (!found)
+ {
+ for (Iterator it = key.getUserAttributes(); it.hasNext();)
+ {
+ PGPUserAttributeSubpacketVector id = (PGPUserAttributeSubpacketVector)it.next();
+ for (Iterator sIt = key.getSignaturesForUserAttribute(id); sIt.hasNext();)
+ {
+ if (certification == sIt.next())
+ {
+ found = true;
+ returnKey = PGPPublicKey.removeCertification(returnKey, id, certification);
+ }
+ }
+ }
+ }
+ }
+
+ return returnKey;
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPPublicKeyEncryptedData.java b/pg/src/main/java/org/spongycastle/openpgp/PGPPublicKeyEncryptedData.java
new file mode 100644
index 00000000..5cf015d5
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPPublicKeyEncryptedData.java
@@ -0,0 +1,168 @@
+package org.spongycastle.openpgp;
+
+import java.io.EOFException;
+import java.io.InputStream;
+
+import org.spongycastle.bcpg.BCPGInputStream;
+import org.spongycastle.bcpg.InputStreamPacket;
+import org.spongycastle.bcpg.PublicKeyEncSessionPacket;
+import org.spongycastle.bcpg.SymmetricEncIntegrityPacket;
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.openpgp.operator.PGPDataDecryptor;
+import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
+import org.spongycastle.util.io.TeeInputStream;
+
+/**
+ * A public key encrypted data object.
+ */
+public class PGPPublicKeyEncryptedData
+ extends PGPEncryptedData
+{
+ PublicKeyEncSessionPacket keyData;
+
+ PGPPublicKeyEncryptedData(
+ PublicKeyEncSessionPacket keyData,
+ InputStreamPacket encData)
+ {
+ super(encData);
+
+ this.keyData = keyData;
+ }
+
+ private boolean confirmCheckSum(
+ byte[] sessionInfo)
+ {
+ int check = 0;
+
+ for (int i = 1; i != sessionInfo.length - 2; i++)
+ {
+ check += sessionInfo[i] & 0xff;
+ }
+
+ return (sessionInfo[sessionInfo.length - 2] == (byte)(check >> 8))
+ && (sessionInfo[sessionInfo.length - 1] == (byte)(check));
+ }
+
+ /**
+ * Return the keyID for the key used to encrypt the data.
+ *
+ * @return long
+ */
+ public long getKeyID()
+ {
+ return keyData.getKeyID();
+ }
+
+ /**
+ * Return the symmetric key algorithm required to decrypt the data protected by this object.
+ *
+ * @param dataDecryptorFactory decryptor factory to use to recover the session data.
+ * @return the identifier of the {@link SymmetricKeyAlgorithmTags encryption algorithm} used to
+ * encrypt this object.
+ * @throws PGPException if the session data cannot be recovered.
+ */
+ public int getSymmetricAlgorithm(
+ PublicKeyDataDecryptorFactory dataDecryptorFactory)
+ throws PGPException
+ {
+ byte[] plain = dataDecryptorFactory.recoverSessionData(keyData.getAlgorithm(), keyData.getEncSessionKey());
+
+ return plain[0];
+ }
+
+ /**
+ * Open an input stream which will provide the decrypted data protected by this object.
+ *
+ * @param dataDecryptorFactory decryptor factory to use to recover the session data and provide the stream.
+ * @return the resulting input stream
+ * @throws PGPException if the session data cannot be recovered or the stream cannot be created.
+ */
+ public InputStream getDataStream(
+ PublicKeyDataDecryptorFactory dataDecryptorFactory)
+ throws PGPException
+ {
+ byte[] sessionData = dataDecryptorFactory.recoverSessionData(keyData.getAlgorithm(), keyData.getEncSessionKey());
+
+ if (!confirmCheckSum(sessionData))
+ {
+ throw new PGPKeyValidationException("key checksum failed");
+ }
+
+ if (sessionData[0] != SymmetricKeyAlgorithmTags.NULL)
+ {
+ try
+ {
+ boolean withIntegrityPacket = encData instanceof SymmetricEncIntegrityPacket;
+ byte[] sessionKey = new byte[sessionData.length - 3];
+
+ System.arraycopy(sessionData, 1, sessionKey, 0, sessionKey.length);
+
+ PGPDataDecryptor dataDecryptor = dataDecryptorFactory.createDataDecryptor(withIntegrityPacket, sessionData[0] & 0xff, sessionKey);
+
+ encStream = new BCPGInputStream(dataDecryptor.getInputStream(encData.getInputStream()));
+
+ if (withIntegrityPacket)
+ {
+ truncStream = new TruncatedStream(encStream);
+
+ integrityCalculator = dataDecryptor.getIntegrityCalculator();
+
+ encStream = new TeeInputStream(truncStream, integrityCalculator.getOutputStream());
+ }
+
+ byte[] iv = new byte[dataDecryptor.getBlockSize()];
+
+ for (int i = 0; i != iv.length; i++)
+ {
+ int ch = encStream.read();
+
+ if (ch < 0)
+ {
+ throw new EOFException("unexpected end of stream.");
+ }
+
+ iv[i] = (byte)ch;
+ }
+
+ int v1 = encStream.read();
+ int v2 = encStream.read();
+
+ if (v1 < 0 || v2 < 0)
+ {
+ throw new EOFException("unexpected end of stream.");
+ }
+
+ //
+ // some versions of PGP appear to produce 0 for the extra
+ // bytes rather than repeating the two previous bytes
+ //
+ /*
+ * Commented out in the light of the oracle attack.
+ if (iv[iv.length - 2] != (byte)v1 && v1 != 0)
+ {
+ throw new PGPDataValidationException("data check failed.");
+ }
+
+ if (iv[iv.length - 1] != (byte)v2 && v2 != 0)
+ {
+ throw new PGPDataValidationException("data check failed.");
+ }
+ */
+
+ return encStream;
+ }
+ catch (PGPException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new PGPException("Exception starting decryption", e);
+ }
+ }
+ else
+ {
+ return encData.getInputStream();
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPPublicKeyRing.java b/pg/src/main/java/org/spongycastle/openpgp/PGPPublicKeyRing.java
new file mode 100644
index 00000000..35620ff2
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPPublicKeyRing.java
@@ -0,0 +1,252 @@
+package org.spongycastle.openpgp;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.spongycastle.bcpg.BCPGInputStream;
+import org.spongycastle.bcpg.PacketTags;
+import org.spongycastle.bcpg.PublicKeyPacket;
+import org.spongycastle.bcpg.TrustPacket;
+import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator;
+
+/**
+ * Class to hold a single master public key and its subkeys.
+ * <p>
+ * Often PGP keyring files consist of multiple master keys, if you are trying to process
+ * or construct one of these you should use the PGPPublicKeyRingCollection class.
+ */
+public class PGPPublicKeyRing
+ extends PGPKeyRing
+{
+ List keys;
+
+ public PGPPublicKeyRing(
+ byte[] encoding,
+ KeyFingerPrintCalculator fingerPrintCalculator)
+ throws IOException
+ {
+ this(new ByteArrayInputStream(encoding), fingerPrintCalculator);
+ }
+
+ /**
+ * @param pubKeys
+ */
+ PGPPublicKeyRing(
+ List pubKeys)
+ {
+ this.keys = pubKeys;
+ }
+
+ public PGPPublicKeyRing(
+ InputStream in,
+ KeyFingerPrintCalculator fingerPrintCalculator)
+ throws IOException
+ {
+ this.keys = new ArrayList();
+
+ BCPGInputStream pIn = wrap(in);
+
+ int initialTag = pIn.nextPacketTag();
+ if (initialTag != PacketTags.PUBLIC_KEY && initialTag != PacketTags.PUBLIC_SUBKEY)
+ {
+ throw new IOException(
+ "public key ring doesn't start with public key tag: " +
+ "tag 0x" + Integer.toHexString(initialTag));
+ }
+
+ PublicKeyPacket pubPk = (PublicKeyPacket)pIn.readPacket();
+ TrustPacket trustPk = readOptionalTrustPacket(pIn);
+
+ // direct signatures and revocations
+ List keySigs = readSignaturesAndTrust(pIn);
+
+ List ids = new ArrayList();
+ List idTrusts = new ArrayList();
+ List idSigs = new ArrayList();
+ readUserIDs(pIn, ids, idTrusts, idSigs);
+
+ try
+ {
+ keys.add(new PGPPublicKey(pubPk, trustPk, keySigs, ids, idTrusts, idSigs, fingerPrintCalculator));
+
+ // Read subkeys
+ while (pIn.nextPacketTag() == PacketTags.PUBLIC_SUBKEY)
+ {
+ keys.add(readSubkey(pIn, fingerPrintCalculator));
+ }
+ }
+ catch (PGPException e)
+ {
+ throw new IOException("processing exception: " + e.toString());
+ }
+ }
+
+ /**
+ * Return the first public key in the ring.
+ *
+ * @return PGPPublicKey
+ */
+ public PGPPublicKey getPublicKey()
+ {
+ return (PGPPublicKey)keys.get(0);
+ }
+
+ /**
+ * Return the public key referred to by the passed in keyID if it
+ * is present.
+ *
+ * @param keyID
+ * @return PGPPublicKey
+ */
+ public PGPPublicKey getPublicKey(
+ long keyID)
+ {
+ for (int i = 0; i != keys.size(); i++)
+ {
+ PGPPublicKey k = (PGPPublicKey)keys.get(i);
+
+ if (keyID == k.getKeyID())
+ {
+ return k;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return an iterator containing all the public keys.
+ *
+ * @return Iterator
+ */
+ public Iterator getPublicKeys()
+ {
+ return Collections.unmodifiableList(keys).iterator();
+ }
+
+ public byte[] getEncoded()
+ throws IOException
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ this.encode(bOut);
+
+ return bOut.toByteArray();
+ }
+
+ public void encode(
+ OutputStream outStream)
+ throws IOException
+ {
+ for (int i = 0; i != keys.size(); i++)
+ {
+ PGPPublicKey k = (PGPPublicKey)keys.get(i);
+
+ k.encode(outStream);
+ }
+ }
+
+ /**
+ * Returns a new key ring with the public key passed in
+ * either added or replacing an existing one.
+ *
+ * @param pubRing the public key ring to be modified
+ * @param pubKey the public key to be inserted.
+ * @return a new keyRing
+ */
+ public static PGPPublicKeyRing insertPublicKey(
+ PGPPublicKeyRing pubRing,
+ PGPPublicKey pubKey)
+ {
+ List keys = new ArrayList(pubRing.keys);
+ boolean found = false;
+ boolean masterFound = false;
+
+ for (int i = 0; i != keys.size();i++)
+ {
+ PGPPublicKey key = (PGPPublicKey)keys.get(i);
+
+ if (key.getKeyID() == pubKey.getKeyID())
+ {
+ found = true;
+ keys.set(i, pubKey);
+ }
+ if (key.isMasterKey())
+ {
+ masterFound = true;
+ }
+ }
+
+ if (!found)
+ {
+ if (pubKey.isMasterKey())
+ {
+ if (masterFound)
+ {
+ throw new IllegalArgumentException("cannot add a master key to a ring that already has one");
+ }
+
+ keys.add(0, pubKey);
+ }
+ else
+ {
+ keys.add(pubKey);
+ }
+ }
+
+ return new PGPPublicKeyRing(keys);
+ }
+
+ /**
+ * Returns a new key ring with the public key passed in
+ * removed from the key ring.
+ *
+ * @param pubRing the public key ring to be modified
+ * @param pubKey the public key to be removed.
+ * @return a new keyRing, null if pubKey is not found.
+ */
+ public static PGPPublicKeyRing removePublicKey(
+ PGPPublicKeyRing pubRing,
+ PGPPublicKey pubKey)
+ {
+ List keys = new ArrayList(pubRing.keys);
+ boolean found = false;
+
+ for (int i = 0; i < keys.size();i++)
+ {
+ PGPPublicKey key = (PGPPublicKey)keys.get(i);
+
+ if (key.getKeyID() == pubKey.getKeyID())
+ {
+ found = true;
+ keys.remove(i);
+ }
+ }
+
+ if (!found)
+ {
+ return null;
+ }
+
+ return new PGPPublicKeyRing(keys);
+ }
+
+ static PGPPublicKey readSubkey(BCPGInputStream in, KeyFingerPrintCalculator fingerPrintCalculator)
+ throws IOException, PGPException
+ {
+ PublicKeyPacket pk = (PublicKeyPacket)in.readPacket();
+ TrustPacket kTrust = readOptionalTrustPacket(in);
+
+ // PGP 8 actually leaves out the signature.
+ List sigList = readSignaturesAndTrust(in);
+
+ return new PGPPublicKey(pk, kTrust, sigList, fingerPrintCalculator);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPPublicKeyRingCollection.java b/pg/src/main/java/org/spongycastle/openpgp/PGPPublicKeyRingCollection.java
new file mode 100644
index 00000000..74b61cfd
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPPublicKeyRingCollection.java
@@ -0,0 +1,391 @@
+package org.spongycastle.openpgp;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.spongycastle.bcpg.BCPGOutputStream;
+import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator;
+import org.spongycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
+import org.spongycastle.util.Strings;
+
+/**
+ * Often a PGP key ring file is made up of a succession of master/sub-key key rings.
+ * If you want to read an entire public key file in one hit this is the class for you.
+ */
+public class PGPPublicKeyRingCollection
+{
+ private Map pubRings = new HashMap();
+ private List order = new ArrayList();
+
+ private PGPPublicKeyRingCollection(
+ Map pubRings,
+ List order)
+ {
+ this.pubRings = pubRings;
+ this.order = order;
+ }
+
+ /**
+ * @deprecated use JcePGPPublicKeyRingCollection or BcPGPPublicKeyRingCollection.
+ */
+ public PGPPublicKeyRingCollection(byte[] encoding)
+ throws IOException, PGPException
+ {
+ this(encoding, new BcKeyFingerprintCalculator());
+ }
+
+ /**
+ * @deprecated use JcePGPPublicKeyRingCollection or BcPGPPublicKeyRingCollection.
+ */
+ public PGPPublicKeyRingCollection(InputStream in)
+ throws IOException, PGPException
+ {
+ this(in, new BcKeyFingerprintCalculator());
+ }
+
+ public PGPPublicKeyRingCollection(
+ byte[] encoding,
+ KeyFingerPrintCalculator fingerPrintCalculator)
+ throws IOException, PGPException
+ {
+ this(new ByteArrayInputStream(encoding), fingerPrintCalculator);
+ }
+
+ /**
+ * Build a PGPPublicKeyRingCollection from the passed in input stream.
+ *
+ * @param in input stream containing data
+ * @throws IOException if a problem parsing the base stream occurs
+ * @throws PGPException if an object is encountered which isn't a PGPPublicKeyRing
+ */
+ public PGPPublicKeyRingCollection(
+ InputStream in,
+ KeyFingerPrintCalculator fingerPrintCalculator)
+ throws IOException, PGPException
+ {
+ PGPObjectFactory pgpFact = new PGPObjectFactory(in, fingerPrintCalculator);
+ Object obj;
+
+ while ((obj = pgpFact.nextObject()) != null)
+ {
+ if (!(obj instanceof PGPPublicKeyRing))
+ {
+ throw new PGPException(obj.getClass().getName() + " found where PGPPublicKeyRing expected");
+ }
+
+ PGPPublicKeyRing pgpPub = (PGPPublicKeyRing)obj;
+ Long key = new Long(pgpPub.getPublicKey().getKeyID());
+
+ pubRings.put(key, pgpPub);
+ order.add(key);
+ }
+ }
+
+ public PGPPublicKeyRingCollection(
+ Collection collection)
+ throws IOException, PGPException
+ {
+ Iterator it = collection.iterator();
+
+ while (it.hasNext())
+ {
+ PGPPublicKeyRing pgpPub = (PGPPublicKeyRing)it.next();
+
+ Long key = new Long(pgpPub.getPublicKey().getKeyID());
+
+ pubRings.put(key, pgpPub);
+ order.add(key);
+ }
+ }
+
+ /**
+ * Return the number of rings in this collection.
+ *
+ * @return size of the collection
+ */
+ public int size()
+ {
+ return order.size();
+ }
+
+ /**
+ * return the public key rings making up this collection.
+ */
+ public Iterator getKeyRings()
+ {
+ return pubRings.values().iterator();
+ }
+
+ /**
+ * Return an iterator of the key rings associated with the passed in userID.
+ *
+ * @param userID the user ID to be matched.
+ * @return an iterator (possibly empty) of key rings which matched.
+ * @throws PGPException
+ */
+ public Iterator getKeyRings(
+ String userID)
+ throws PGPException
+ {
+ return getKeyRings(userID, false, false);
+ }
+
+ /**
+ * Return an iterator of the key rings associated with the passed in userID.
+ * <p>
+ *
+ * @param userID the user ID to be matched.
+ * @param matchPartial if true userID need only be a substring of an actual ID string to match.
+ * @return an iterator (possibly empty) of key rings which matched.
+ * @throws PGPException
+ */
+ public Iterator getKeyRings(
+ String userID,
+ boolean matchPartial)
+ throws PGPException
+ {
+ return getKeyRings(userID, matchPartial, false);
+ }
+
+ /**
+ * Return an iterator of the key rings associated with the passed in userID.
+ * <p>
+ *
+ * @param userID the user ID to be matched.
+ * @param matchPartial if true userID need only be a substring of an actual ID string to match.
+ * @param ignoreCase if true case is ignored in user ID comparisons.
+ * @return an iterator (possibly empty) of key rings which matched.
+ * @throws PGPException
+ */
+ public Iterator getKeyRings(
+ String userID,
+ boolean matchPartial,
+ boolean ignoreCase)
+ throws PGPException
+ {
+ Iterator it = this.getKeyRings();
+ List rings = new ArrayList();
+
+ if (ignoreCase)
+ {
+ userID = Strings.toLowerCase(userID);
+ }
+
+ while (it.hasNext())
+ {
+ PGPPublicKeyRing pubRing = (PGPPublicKeyRing)it.next();
+ Iterator uIt = pubRing.getPublicKey().getUserIDs();
+
+ while (uIt.hasNext())
+ {
+ String next = (String)uIt.next();
+ if (ignoreCase)
+ {
+ next = Strings.toLowerCase(next);
+ }
+
+ if (matchPartial)
+ {
+ if (next.indexOf(userID) > -1)
+ {
+ rings.add(pubRing);
+ }
+ }
+ else
+ {
+ if (next.equals(userID))
+ {
+ rings.add(pubRing);
+ }
+ }
+ }
+ }
+
+ return rings.iterator();
+ }
+
+ /**
+ * Return the PGP public key associated with the given key id.
+ *
+ * @param keyID
+ * @return the PGP public key
+ * @throws PGPException
+ */
+ public PGPPublicKey getPublicKey(
+ long keyID)
+ throws PGPException
+ {
+ Iterator it = this.getKeyRings();
+
+ while (it.hasNext())
+ {
+ PGPPublicKeyRing pubRing = (PGPPublicKeyRing)it.next();
+ PGPPublicKey pub = pubRing.getPublicKey(keyID);
+
+ if (pub != null)
+ {
+ return pub;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the public key ring which contains the key referred to by keyID.
+ *
+ * @param keyID key ID to match against
+ * @return the public key ring
+ * @throws PGPException
+ */
+ public PGPPublicKeyRing getPublicKeyRing(
+ long keyID)
+ throws PGPException
+ {
+ Long id = new Long(keyID);
+
+ if (pubRings.containsKey(id))
+ {
+ return (PGPPublicKeyRing)pubRings.get(id);
+ }
+
+ Iterator it = this.getKeyRings();
+
+ while (it.hasNext())
+ {
+ PGPPublicKeyRing pubRing = (PGPPublicKeyRing)it.next();
+ PGPPublicKey pub = pubRing.getPublicKey(keyID);
+
+ if (pub != null)
+ {
+ return pubRing;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return true if a key matching the passed in key ID is present, false otherwise.
+ *
+ * @param keyID key ID to look for.
+ * @return true if keyID present, false otherwise.
+ */
+ public boolean contains(long keyID)
+ throws PGPException
+ {
+ return getPublicKey(keyID) != null;
+ }
+
+ public byte[] getEncoded()
+ throws IOException
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ this.encode(bOut);
+
+ return bOut.toByteArray();
+ }
+
+ public void encode(
+ OutputStream outStream)
+ throws IOException
+ {
+ BCPGOutputStream out;
+
+ if (outStream instanceof BCPGOutputStream)
+ {
+ out = (BCPGOutputStream)outStream;
+ }
+ else
+ {
+ out = new BCPGOutputStream(outStream);
+ }
+
+ Iterator it = order.iterator();
+ while (it.hasNext())
+ {
+ PGPPublicKeyRing sr = (PGPPublicKeyRing)pubRings.get(it.next());
+
+ sr.encode(out);
+ }
+ }
+
+
+ /**
+ * Return a new collection object containing the contents of the passed in collection and
+ * the passed in public key ring.
+ *
+ * @param ringCollection the collection the ring to be added to.
+ * @param publicKeyRing the key ring to be added.
+ * @return a new collection merging the current one with the passed in ring.
+ * @exception IllegalArgumentException if the keyID for the passed in ring is already present.
+ */
+ public static PGPPublicKeyRingCollection addPublicKeyRing(
+ PGPPublicKeyRingCollection ringCollection,
+ PGPPublicKeyRing publicKeyRing)
+ {
+ Long key = new Long(publicKeyRing.getPublicKey().getKeyID());
+
+ if (ringCollection.pubRings.containsKey(key))
+ {
+ throw new IllegalArgumentException("Collection already contains a key with a keyID for the passed in ring.");
+ }
+
+ Map newPubRings = new HashMap(ringCollection.pubRings);
+ List newOrder = new ArrayList(ringCollection.order);
+
+ newPubRings.put(key, publicKeyRing);
+ newOrder.add(key);
+
+ return new PGPPublicKeyRingCollection(newPubRings, newOrder);
+ }
+
+ /**
+ * Return a new collection object containing the contents of this collection with
+ * the passed in public key ring removed.
+ *
+ * @param ringCollection the collection the ring to be removed from.
+ * @param publicKeyRing the key ring to be removed.
+ * @return a new collection not containing the passed in ring.
+ * @exception IllegalArgumentException if the keyID for the passed in ring not present.
+ */
+ public static PGPPublicKeyRingCollection removePublicKeyRing(
+ PGPPublicKeyRingCollection ringCollection,
+ PGPPublicKeyRing publicKeyRing)
+ {
+ Long key = new Long(publicKeyRing.getPublicKey().getKeyID());
+
+ if (!ringCollection.pubRings.containsKey(key))
+ {
+ throw new IllegalArgumentException("Collection does not contain a key with a keyID for the passed in ring.");
+ }
+
+ Map newPubRings = new HashMap(ringCollection.pubRings);
+ List newOrder = new ArrayList(ringCollection.order);
+
+ newPubRings.remove(key);
+
+ for (int i = 0; i < newOrder.size(); i++)
+ {
+ Long r = (Long)newOrder.get(i);
+
+ if (r.longValue() == key.longValue())
+ {
+ newOrder.remove(i);
+ break;
+ }
+ }
+
+ return new PGPPublicKeyRingCollection(newPubRings, newOrder);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPRuntimeOperationException.java b/pg/src/main/java/org/spongycastle/openpgp/PGPRuntimeOperationException.java
new file mode 100644
index 00000000..8e64ce7e
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPRuntimeOperationException.java
@@ -0,0 +1,19 @@
+package org.spongycastle.openpgp;
+
+public class PGPRuntimeOperationException
+ extends RuntimeException
+{
+ private final Throwable cause;
+
+ public PGPRuntimeOperationException(String message, Throwable cause)
+ {
+ super(message);
+
+ this.cause = cause;
+ }
+
+ public Throwable getCause()
+ {
+ return cause;
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPSecretKey.java b/pg/src/main/java/org/spongycastle/openpgp/PGPSecretKey.java
new file mode 100644
index 00000000..a3e583ea
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPSecretKey.java
@@ -0,0 +1,945 @@
+package org.spongycastle.openpgp;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
+import org.spongycastle.asn1.x9.ECNamedCurveTable;
+import org.spongycastle.bcpg.BCPGInputStream;
+import org.spongycastle.bcpg.BCPGObject;
+import org.spongycastle.bcpg.BCPGOutputStream;
+import org.spongycastle.bcpg.ContainedPacket;
+import org.spongycastle.bcpg.DSASecretBCPGKey;
+import org.spongycastle.bcpg.ECDSAPublicBCPGKey;
+import org.spongycastle.bcpg.ECSecretBCPGKey;
+import org.spongycastle.bcpg.ElGamalSecretBCPGKey;
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
+import org.spongycastle.bcpg.PublicKeyPacket;
+import org.spongycastle.bcpg.RSASecretBCPGKey;
+import org.spongycastle.bcpg.S2K;
+import org.spongycastle.bcpg.SecretKeyPacket;
+import org.spongycastle.bcpg.SecretSubkeyPacket;
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.bcpg.UserAttributePacket;
+import org.spongycastle.bcpg.UserIDPacket;
+import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator;
+import org.spongycastle.openpgp.operator.PBEProtectionRemoverFactory;
+import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
+import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor;
+import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
+import org.spongycastle.openpgp.operator.PGPDigestCalculator;
+
+/**
+ * general class to handle and construct a PGP secret key object.
+ */
+public class PGPSecretKey
+{
+ SecretKeyPacket secret;
+ PGPPublicKey pub;
+
+ PGPSecretKey(
+ SecretKeyPacket secret,
+ PGPPublicKey pub)
+ {
+ this.secret = secret;
+ this.pub = pub;
+ }
+
+ PGPSecretKey(
+ PGPPrivateKey privKey,
+ PGPPublicKey pubKey,
+ PGPDigestCalculator checksumCalculator,
+ PBESecretKeyEncryptor keyEncryptor)
+ throws PGPException
+ {
+ this(privKey, pubKey, checksumCalculator, false, keyEncryptor);
+ }
+
+ /**
+ * Construct a PGPSecretKey using the passed in private key and public key. This constructor will not add any
+ * certifications but assumes that pubKey already has what is required.
+ *
+ * @param privKey the private key component.
+ * @param pubKey the public key component.
+ * @param checksumCalculator a calculator for the private key checksum
+ * @param isMasterKey true if the key is a master key, false otherwise.
+ * @param keyEncryptor an encryptor for the key if required (null otherwise).
+ * @throws PGPException if there is an issue creating the secret key packet.
+ */
+ public PGPSecretKey(
+ PGPPrivateKey privKey,
+ PGPPublicKey pubKey,
+ PGPDigestCalculator checksumCalculator,
+ boolean isMasterKey,
+ PBESecretKeyEncryptor keyEncryptor)
+ throws PGPException
+ {
+ this.pub = pubKey;
+ this.secret = buildSecretKeyPacket(isMasterKey, privKey, pubKey, keyEncryptor, checksumCalculator);
+ }
+
+ private static SecretKeyPacket buildSecretKeyPacket(boolean isMasterKey, PGPPrivateKey privKey, PGPPublicKey pubKey, PBESecretKeyEncryptor keyEncryptor, PGPDigestCalculator checksumCalculator)
+ throws PGPException
+ {
+ BCPGObject secKey = (BCPGObject)privKey.getPrivateKeyDataPacket();
+
+ if (secKey == null)
+ {
+ if (isMasterKey)
+ {
+ return new SecretKeyPacket(pubKey.publicPk, SymmetricKeyAlgorithmTags.NULL, null, null, new byte[0]);
+ }
+ else
+ {
+ return new SecretSubkeyPacket(pubKey.publicPk, SymmetricKeyAlgorithmTags.NULL, null, null, new byte[0]);
+ }
+ }
+
+ try
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ BCPGOutputStream pOut = new BCPGOutputStream(bOut);
+
+ pOut.writeObject(secKey);
+
+ byte[] keyData = bOut.toByteArray();
+
+ pOut.write(checksum(checksumCalculator, keyData, keyData.length));
+
+ int encAlgorithm = (keyEncryptor != null) ? keyEncryptor.getAlgorithm() : SymmetricKeyAlgorithmTags.NULL;
+
+ if (encAlgorithm != SymmetricKeyAlgorithmTags.NULL)
+ {
+ keyData = bOut.toByteArray(); // include checksum
+
+ byte[] encData = keyEncryptor.encryptKeyData(keyData, 0, keyData.length);
+ byte[] iv = keyEncryptor.getCipherIV();
+
+ S2K s2k = keyEncryptor.getS2K();
+
+ int s2kUsage;
+
+ if (checksumCalculator != null)
+ {
+ if (checksumCalculator.getAlgorithm() != HashAlgorithmTags.SHA1)
+ {
+ throw new PGPException("only SHA1 supported for key checksum calculations.");
+ }
+ s2kUsage = SecretKeyPacket.USAGE_SHA1;
+ }
+ else
+ {
+ s2kUsage = SecretKeyPacket.USAGE_CHECKSUM;
+ }
+
+ if (isMasterKey)
+ {
+ return new SecretKeyPacket(pubKey.publicPk, encAlgorithm, s2kUsage, s2k, iv, encData);
+ }
+ else
+ {
+ return new SecretSubkeyPacket(pubKey.publicPk, encAlgorithm, s2kUsage, s2k, iv, encData);
+ }
+ }
+ else
+ {
+ if (isMasterKey)
+ {
+ return new SecretKeyPacket(pubKey.publicPk, encAlgorithm, null, null, bOut.toByteArray());
+ }
+ else
+ {
+ return new SecretSubkeyPacket(pubKey.publicPk, encAlgorithm, null, null, bOut.toByteArray());
+ }
+ }
+ }
+ catch (PGPException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new PGPException("Exception encrypting key", e);
+ }
+ }
+
+ /**
+ * Construct a PGPSecretKey using the passed in private/public key pair and binding it to the passed in id
+ * using a generated certification of certificationLevel.The secret key checksum is calculated using the original
+ * non-digest based checksum.
+ *
+ * @param certificationLevel the type of certification to be added.
+ * @param keyPair the public/private keys to use.
+ * @param id the id to bind to the key.
+ * @param hashedPcks the hashed packets to be added to the certification.
+ * @param unhashedPcks the unhashed packets to be added to the certification.
+ * @param certificationSignerBuilder the builder for generating the certification.
+ * @param keyEncryptor an encryptor for the key if required (null otherwise).
+ * @throws PGPException if there is an issue creating the secret key packet or the certification.
+ */
+ public PGPSecretKey(
+ int certificationLevel,
+ PGPKeyPair keyPair,
+ String id,
+ PGPSignatureSubpacketVector hashedPcks,
+ PGPSignatureSubpacketVector unhashedPcks,
+ PGPContentSignerBuilder certificationSignerBuilder,
+ PBESecretKeyEncryptor keyEncryptor)
+ throws PGPException
+ {
+ this(certificationLevel, keyPair, id, null, hashedPcks, unhashedPcks, certificationSignerBuilder, keyEncryptor);
+ }
+
+ /**
+ * Construct a PGPSecretKey using the passed in private/public key pair and binding it to the passed in id
+ * using a generated certification of certificationLevel.
+ *
+ * @param certificationLevel the type of certification to be added.
+ * @param keyPair the public/private keys to use.
+ * @param id the id to bind to the key.
+ * @param checksumCalculator a calculator for the private key checksum.
+ * @param hashedPcks the hashed packets to be added to the certification.
+ * @param unhashedPcks the unhashed packets to be added to the certification.
+ * @param certificationSignerBuilder the builder for generating the certification.
+ * @param keyEncryptor an encryptor for the key if required (null otherwise).
+ * @throws PGPException if there is an issue creating the secret key packet or the certification.
+ */
+ public PGPSecretKey(
+ int certificationLevel,
+ PGPKeyPair keyPair,
+ String id,
+ PGPDigestCalculator checksumCalculator,
+ PGPSignatureSubpacketVector hashedPcks,
+ PGPSignatureSubpacketVector unhashedPcks,
+ PGPContentSignerBuilder certificationSignerBuilder,
+ PBESecretKeyEncryptor keyEncryptor)
+ throws PGPException
+ {
+ this(keyPair.getPrivateKey(), certifiedPublicKey(certificationLevel, keyPair, id, hashedPcks, unhashedPcks, certificationSignerBuilder), checksumCalculator, true, keyEncryptor);
+ }
+
+ private static PGPPublicKey certifiedPublicKey(
+ int certificationLevel,
+ PGPKeyPair keyPair,
+ String id,
+ PGPSignatureSubpacketVector hashedPcks,
+ PGPSignatureSubpacketVector unhashedPcks,
+ PGPContentSignerBuilder certificationSignerBuilder)
+ throws PGPException
+ {
+ PGPSignatureGenerator sGen;
+
+ try
+ {
+ sGen = new PGPSignatureGenerator(certificationSignerBuilder);
+ }
+ catch (Exception e)
+ {
+ throw new PGPException("creating signature generator: " + e, e);
+ }
+
+ //
+ // generate the certification
+ //
+ sGen.init(certificationLevel, keyPair.getPrivateKey());
+
+ sGen.setHashedSubpackets(hashedPcks);
+ sGen.setUnhashedSubpackets(unhashedPcks);
+
+ try
+ {
+ PGPSignature certification = sGen.generateCertification(id, keyPair.getPublicKey());
+
+ return PGPPublicKey.addCertification(keyPair.getPublicKey(), id, certification);
+ }
+ catch (Exception e)
+ {
+ throw new PGPException("exception doing certification: " + e, e);
+ }
+ }
+
+ /**
+ * Return true if this key has an algorithm type that makes it suitable to use for signing.
+ * <p>
+ * Note: with version 4 keys KeyFlags subpackets should also be considered when present for
+ * determining the preferred use of the key.
+ *
+ * @return true if this key algorithm is suitable for use with signing.
+ */
+ public boolean isSigningKey()
+ {
+ int algorithm = pub.getAlgorithm();
+
+ return ((algorithm == PGPPublicKey.RSA_GENERAL) || (algorithm == PGPPublicKey.RSA_SIGN)
+ || (algorithm == PGPPublicKey.DSA) || (algorithm == PGPPublicKey.ECDSA) || (algorithm == PGPPublicKey.ELGAMAL_GENERAL));
+ }
+
+ /**
+ * Return true if this is a master key.
+ * @return true if a master key.
+ */
+ public boolean isMasterKey()
+ {
+ return pub.isMasterKey();
+ }
+
+ /**
+ * Detect if the Secret Key's Private Key is empty or not
+ *
+ * @return boolean whether or not the private key is empty
+ */
+ public boolean isPrivateKeyEmpty()
+ {
+ byte[] secKeyData = secret.getSecretKeyData();
+
+ return (secKeyData == null || secKeyData.length < 1);
+ }
+
+ /**
+ * return the algorithm the key is encrypted with.
+ *
+ * @return the algorithm used to encrypt the secret key.
+ */
+ public int getKeyEncryptionAlgorithm()
+ {
+ return secret.getEncAlgorithm();
+ }
+
+ /**
+ * Return the keyID of the public key associated with this key.
+ *
+ * @return the keyID associated with this key.
+ */
+ public long getKeyID()
+ {
+ return pub.getKeyID();
+ }
+
+ /**
+ * Return the public key associated with this key.
+ *
+ * @return the public key for this key.
+ */
+ public PGPPublicKey getPublicKey()
+ {
+ return pub;
+ }
+
+ /**
+ * Return any userIDs associated with the key.
+ *
+ * @return an iterator of Strings.
+ */
+ public Iterator getUserIDs()
+ {
+ return pub.getUserIDs();
+ }
+
+ /**
+ * Return any user attribute vectors associated with the key.
+ *
+ * @return an iterator of PGPUserAttributeSubpacketVector.
+ */
+ public Iterator getUserAttributes()
+ {
+ return pub.getUserAttributes();
+ }
+
+ private byte[] extractKeyData(
+ PBESecretKeyDecryptor decryptorFactory)
+ throws PGPException
+ {
+ byte[] encData = secret.getSecretKeyData();
+ byte[] data = null;
+
+ if (secret.getEncAlgorithm() != SymmetricKeyAlgorithmTags.NULL)
+ {
+ try
+ {
+ if (secret.getPublicKeyPacket().getVersion() == 4)
+ {
+ byte[] key = decryptorFactory.makeKeyFromPassPhrase(secret.getEncAlgorithm(), secret.getS2K());
+
+ data = decryptorFactory.recoverKeyData(secret.getEncAlgorithm(), key, secret.getIV(), encData, 0, encData.length);
+
+ boolean useSHA1 = secret.getS2KUsage() == SecretKeyPacket.USAGE_SHA1;
+ byte[] check = checksum(useSHA1 ? decryptorFactory.getChecksumCalculator(HashAlgorithmTags.SHA1) : null, data, (useSHA1) ? data.length - 20 : data.length - 2);
+
+ for (int i = 0; i != check.length; i++)
+ {
+ if (check[i] != data[data.length - check.length + i])
+ {
+ throw new PGPException("checksum mismatch at " + i + " of " + check.length);
+ }
+ }
+ }
+ else // version 2 or 3, RSA only.
+ {
+ byte[] key = decryptorFactory.makeKeyFromPassPhrase(secret.getEncAlgorithm(), secret.getS2K());
+
+ data = new byte[encData.length];
+
+ byte[] iv = new byte[secret.getIV().length];
+
+ System.arraycopy(secret.getIV(), 0, iv, 0, iv.length);
+
+ //
+ // read in the four numbers
+ //
+ int pos = 0;
+
+ for (int i = 0; i != 4; i++)
+ {
+ int encLen = (((encData[pos] << 8) | (encData[pos + 1] & 0xff)) + 7) / 8;
+
+ data[pos] = encData[pos];
+ data[pos + 1] = encData[pos + 1];
+
+ byte[] tmp = decryptorFactory.recoverKeyData(secret.getEncAlgorithm(), key, iv, encData, pos + 2, encLen);
+ System.arraycopy(tmp, 0, data, pos + 2, tmp.length);
+ pos += 2 + encLen;
+
+ if (i != 3)
+ {
+ System.arraycopy(encData, pos - iv.length, iv, 0, iv.length);
+ }
+ }
+
+ //
+ // verify and copy checksum
+ //
+
+ data[pos] = encData[pos];
+ data[pos + 1] = encData[pos + 1];
+
+ int cs = ((encData[pos] << 8) & 0xff00) | (encData[pos + 1] & 0xff);
+ int calcCs = 0;
+ for (int j = 0; j < data.length - 2; j++)
+ {
+ calcCs += data[j] & 0xff;
+ }
+
+ calcCs &= 0xffff;
+ if (calcCs != cs)
+ {
+ throw new PGPException("checksum mismatch: passphrase wrong, expected "
+ + Integer.toHexString(cs)
+ + " found " + Integer.toHexString(calcCs));
+ }
+ }
+ }
+ catch (PGPException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new PGPException("Exception decrypting key", e);
+ }
+ }
+ else
+ {
+ data = encData;
+ }
+
+ return data;
+ }
+
+ /**
+ * Extract a PGPPrivate key from the SecretKey's encrypted contents.
+ *
+ * @param decryptorFactory factory to use to generate a decryptor for the passed in secretKey.
+ * @return PGPPrivateKey the unencrypted private key.
+ * @throws PGPException on failure.
+ */
+ public PGPPrivateKey extractPrivateKey(
+ PBESecretKeyDecryptor decryptorFactory)
+ throws PGPException
+ {
+ if (isPrivateKeyEmpty())
+ {
+ return null;
+ }
+
+ PublicKeyPacket pubPk = secret.getPublicKeyPacket();
+
+ try
+ {
+ byte[] data = extractKeyData(decryptorFactory);
+ BCPGInputStream in = new BCPGInputStream(new ByteArrayInputStream(data));
+
+
+ switch (pubPk.getAlgorithm())
+ {
+ case PGPPublicKey.RSA_ENCRYPT:
+ case PGPPublicKey.RSA_GENERAL:
+ case PGPPublicKey.RSA_SIGN:
+ RSASecretBCPGKey rsaPriv = new RSASecretBCPGKey(in);
+
+ return new PGPPrivateKey(this.getKeyID(), pubPk, rsaPriv);
+ case PGPPublicKey.DSA:
+ DSASecretBCPGKey dsaPriv = new DSASecretBCPGKey(in);
+
+ return new PGPPrivateKey(this.getKeyID(), pubPk, dsaPriv);
+ case PGPPublicKey.ELGAMAL_ENCRYPT:
+ case PGPPublicKey.ELGAMAL_GENERAL:
+ ElGamalSecretBCPGKey elPriv = new ElGamalSecretBCPGKey(in);
+
+ return new PGPPrivateKey(this.getKeyID(), pubPk, elPriv);
+ case PGPPublicKey.ECDH:
+ case PGPPublicKey.ECDSA:
+ ECSecretBCPGKey ecPriv = new ECSecretBCPGKey(in);
+
+ return new PGPPrivateKey(this.getKeyID(), pubPk, ecPriv);
+ default:
+ throw new PGPException("unknown public key algorithm encountered");
+ }
+ }
+ catch (PGPException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new PGPException("Exception constructing key", e);
+ }
+ }
+
+ private static byte[] checksum(PGPDigestCalculator digCalc, byte[] bytes, int length)
+ throws PGPException
+ {
+ if (digCalc != null)
+ {
+ OutputStream dOut = digCalc.getOutputStream();
+
+ try
+ {
+ dOut.write(bytes, 0, length);
+
+ dOut.close();
+ }
+ catch (Exception e)
+ {
+ throw new PGPException("checksum digest calculation failed: " + e.getMessage(), e);
+ }
+ return digCalc.getDigest();
+ }
+ else
+ {
+ int checksum = 0;
+
+ for (int i = 0; i != length; i++)
+ {
+ checksum += bytes[i] & 0xff;
+ }
+
+ byte[] check = new byte[2];
+
+ check[0] = (byte)(checksum >> 8);
+ check[1] = (byte)checksum;
+
+ return check;
+ }
+ }
+
+ public byte[] getEncoded()
+ throws IOException
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ this.encode(bOut);
+
+ return bOut.toByteArray();
+ }
+
+ public void encode(
+ OutputStream outStream)
+ throws IOException
+ {
+ BCPGOutputStream out;
+
+ if (outStream instanceof BCPGOutputStream)
+ {
+ out = (BCPGOutputStream)outStream;
+ }
+ else
+ {
+ out = new BCPGOutputStream(outStream);
+ }
+
+ out.writePacket(secret);
+ if (pub.trustPk != null)
+ {
+ out.writePacket(pub.trustPk);
+ }
+
+ if (pub.subSigs == null) // is not a sub key
+ {
+ for (int i = 0; i != pub.keySigs.size(); i++)
+ {
+ ((PGPSignature)pub.keySigs.get(i)).encode(out);
+ }
+
+ for (int i = 0; i != pub.ids.size(); i++)
+ {
+ if (pub.ids.get(i) instanceof UserIDPacket)
+ {
+ UserIDPacket id = (UserIDPacket)pub.ids.get(i);
+
+ out.writePacket(id);
+ }
+ else
+ {
+ PGPUserAttributeSubpacketVector v = (PGPUserAttributeSubpacketVector)pub.ids.get(i);
+
+ out.writePacket(new UserAttributePacket(v.toSubpacketArray()));
+ }
+
+ if (pub.idTrusts.get(i) != null)
+ {
+ out.writePacket((ContainedPacket)pub.idTrusts.get(i));
+ }
+
+ List sigs = (ArrayList)pub.idSigs.get(i);
+
+ for (int j = 0; j != sigs.size(); j++)
+ {
+ ((PGPSignature)sigs.get(j)).encode(out);
+ }
+ }
+ }
+ else
+ {
+ for (int j = 0; j != pub.subSigs.size(); j++)
+ {
+ ((PGPSignature)pub.subSigs.get(j)).encode(out);
+ }
+ }
+ }
+
+ /**
+ * Return a copy of the passed in secret key, encrypted using a new
+ * password and the passed in algorithm.
+ *
+ * @param key the PGPSecretKey to be copied.
+ * @param oldKeyDecryptor the current decryptor based on the current password for key.
+ * @param newKeyEncryptor a new encryptor based on a new password for encrypting the secret key material.
+ */
+ public static PGPSecretKey copyWithNewPassword(
+ PGPSecretKey key,
+ PBESecretKeyDecryptor oldKeyDecryptor,
+ PBESecretKeyEncryptor newKeyEncryptor)
+ throws PGPException
+ {
+ if (key.isPrivateKeyEmpty())
+ {
+ throw new PGPException("no private key in this SecretKey - public key present only.");
+ }
+
+ byte[] rawKeyData = key.extractKeyData(oldKeyDecryptor);
+ int s2kUsage = key.secret.getS2KUsage();
+ byte[] iv = null;
+ S2K s2k = null;
+ byte[] keyData;
+ int newEncAlgorithm = SymmetricKeyAlgorithmTags.NULL;
+
+ if (newKeyEncryptor == null || newKeyEncryptor.getAlgorithm() == SymmetricKeyAlgorithmTags.NULL)
+ {
+ s2kUsage = SecretKeyPacket.USAGE_NONE;
+ if (key.secret.getS2KUsage() == SecretKeyPacket.USAGE_SHA1) // SHA-1 hash, need to rewrite checksum
+ {
+ keyData = new byte[rawKeyData.length - 18];
+
+ System.arraycopy(rawKeyData, 0, keyData, 0, keyData.length - 2);
+
+ byte[] check = checksum(null, keyData, keyData.length - 2);
+
+ keyData[keyData.length - 2] = check[0];
+ keyData[keyData.length - 1] = check[1];
+ }
+ else
+ {
+ keyData = rawKeyData;
+ }
+ }
+ else
+ {
+ if (key.secret.getPublicKeyPacket().getVersion() < 4)
+ {
+ // Version 2 or 3 - RSA Keys only
+
+ byte[] encKey = newKeyEncryptor.getKey();
+ keyData = new byte[rawKeyData.length];
+
+ if (newKeyEncryptor.getHashAlgorithm() != HashAlgorithmTags.MD5)
+ {
+ throw new PGPException("MD5 Digest Calculator required for version 3 key encryptor.");
+ }
+
+ //
+ // process 4 numbers
+ //
+ int pos = 0;
+ for (int i = 0; i != 4; i++)
+ {
+ int encLen = (((rawKeyData[pos] << 8) | (rawKeyData[pos + 1] & 0xff)) + 7) / 8;
+
+ keyData[pos] = rawKeyData[pos];
+ keyData[pos + 1] = rawKeyData[pos + 1];
+
+ byte[] tmp;
+ if (i == 0)
+ {
+ tmp = newKeyEncryptor.encryptKeyData(encKey, rawKeyData, pos + 2, encLen);
+ iv = newKeyEncryptor.getCipherIV();
+
+ }
+ else
+ {
+ byte[] tmpIv = new byte[iv.length];
+
+ System.arraycopy(keyData, pos - iv.length, tmpIv, 0, tmpIv.length);
+ tmp = newKeyEncryptor.encryptKeyData(encKey, tmpIv, rawKeyData, pos + 2, encLen);
+ }
+
+ System.arraycopy(tmp, 0, keyData, pos + 2, tmp.length);
+ pos += 2 + encLen;
+ }
+
+ //
+ // copy in checksum.
+ //
+ keyData[pos] = rawKeyData[pos];
+ keyData[pos + 1] = rawKeyData[pos + 1];
+
+ s2k = newKeyEncryptor.getS2K();
+ newEncAlgorithm = newKeyEncryptor.getAlgorithm();
+ }
+ else
+ {
+ keyData = newKeyEncryptor.encryptKeyData(rawKeyData, 0, rawKeyData.length);
+
+ iv = newKeyEncryptor.getCipherIV();
+
+ s2k = newKeyEncryptor.getS2K();
+
+ newEncAlgorithm = newKeyEncryptor.getAlgorithm();
+ }
+ }
+
+ SecretKeyPacket secret;
+ if (key.secret instanceof SecretSubkeyPacket)
+ {
+ secret = new SecretSubkeyPacket(key.secret.getPublicKeyPacket(),
+ newEncAlgorithm, s2kUsage, s2k, iv, keyData);
+ }
+ else
+ {
+ secret = new SecretKeyPacket(key.secret.getPublicKeyPacket(),
+ newEncAlgorithm, s2kUsage, s2k, iv, keyData);
+ }
+
+ return new PGPSecretKey(secret, key.pub);
+ }
+
+ /**
+ * Replace the passed the public key on the passed in secret key.
+ *
+ * @param secretKey secret key to change
+ * @param publicKey new public key.
+ * @return a new secret key.
+ * @throws IllegalArgumentException if keyIDs do not match.
+ */
+ public static PGPSecretKey replacePublicKey(PGPSecretKey secretKey, PGPPublicKey publicKey)
+ {
+ if (publicKey.getKeyID() != secretKey.getKeyID())
+ {
+ throw new IllegalArgumentException("keyIDs do not match");
+ }
+
+ return new PGPSecretKey(secretKey.secret, publicKey);
+ }
+
+ /**
+ * Parse a secret key from one of the GPG S expression keys associating it with the passed in public key.
+ *
+ * @return a secret key object.
+ */
+ public static PGPSecretKey parseSecretKeyFromSExpr(InputStream inputStream, PBEProtectionRemoverFactory keyProtectionRemoverFactory, PGPPublicKey pubKey)
+ throws IOException, PGPException
+ {
+ SXprUtils.skipOpenParenthesis(inputStream);
+
+ String type;
+
+ type = SXprUtils.readString(inputStream, inputStream.read());
+ if (type.equals("protected-private-key"))
+ {
+ SXprUtils.skipOpenParenthesis(inputStream);
+
+ String curveName;
+
+ String keyType = SXprUtils.readString(inputStream, inputStream.read());
+ if (keyType.equals("ecc"))
+ {
+ SXprUtils.skipOpenParenthesis(inputStream);
+
+ String curveID = SXprUtils.readString(inputStream, inputStream.read());
+ curveName = SXprUtils.readString(inputStream, inputStream.read());
+
+ SXprUtils.skipCloseParenthesis(inputStream);
+ }
+ else
+ {
+ throw new PGPException("no curve details found");
+ }
+
+ byte[] qVal;
+
+ SXprUtils.skipOpenParenthesis(inputStream);
+
+ type = SXprUtils.readString(inputStream, inputStream.read());
+ if (type.equals("q"))
+ {
+ qVal = SXprUtils.readBytes(inputStream, inputStream.read());
+ }
+ else
+ {
+ throw new PGPException("no q value found");
+ }
+
+ SXprUtils.skipCloseParenthesis(inputStream);
+
+ byte[] dValue = getDValue(inputStream, keyProtectionRemoverFactory, curveName);
+ // TODO: check SHA-1 hash.
+
+ return new PGPSecretKey(new SecretKeyPacket(pubKey.getPublicKeyPacket(), SymmetricKeyAlgorithmTags.NULL, null, null, new ECSecretBCPGKey(new BigInteger(1, dValue)).getEncoded()), pubKey);
+ }
+
+ throw new PGPException("unknown key type found");
+ }
+
+ /**
+ * Parse a secret key from one of the GPG S expression keys.
+ *
+ * @return a secret key object.
+ */
+ public static PGPSecretKey parseSecretKeyFromSExpr(InputStream inputStream, PBEProtectionRemoverFactory keyProtectionRemoverFactory, KeyFingerPrintCalculator fingerPrintCalculator)
+ throws IOException, PGPException
+ {
+ SXprUtils.skipOpenParenthesis(inputStream);
+
+ String type;
+
+ type = SXprUtils.readString(inputStream, inputStream.read());
+ if (type.equals("protected-private-key"))
+ {
+ SXprUtils.skipOpenParenthesis(inputStream);
+
+ String curveName;
+
+ String keyType = SXprUtils.readString(inputStream, inputStream.read());
+ if (keyType.equals("ecc"))
+ {
+ SXprUtils.skipOpenParenthesis(inputStream);
+
+ String curveID = SXprUtils.readString(inputStream, inputStream.read());
+ curveName = SXprUtils.readString(inputStream, inputStream.read());
+
+ if (curveName.startsWith("NIST "))
+ {
+ curveName = curveName.substring("NIST ".length());
+ }
+
+ SXprUtils.skipCloseParenthesis(inputStream);
+ }
+ else
+ {
+ throw new PGPException("no curve details found");
+ }
+
+ byte[] qVal;
+
+ SXprUtils.skipOpenParenthesis(inputStream);
+
+ type = SXprUtils.readString(inputStream, inputStream.read());
+ if (type.equals("q"))
+ {
+ qVal = SXprUtils.readBytes(inputStream, inputStream.read());
+ }
+ else
+ {
+ throw new PGPException("no q value found");
+ }
+
+ PublicKeyPacket pubPacket = new PublicKeyPacket(PublicKeyAlgorithmTags.ECDSA, new Date(), new ECDSAPublicBCPGKey(ECNamedCurveTable.getOID(curveName), new BigInteger(1, qVal)));
+
+ SXprUtils.skipCloseParenthesis(inputStream);
+
+ byte[] dValue = getDValue(inputStream, keyProtectionRemoverFactory, curveName);
+ // TODO: check SHA-1 hash.
+
+ return new PGPSecretKey(new SecretKeyPacket(pubPacket, SymmetricKeyAlgorithmTags.NULL, null, null, new ECSecretBCPGKey(new BigInteger(1, dValue)).getEncoded()), new PGPPublicKey(pubPacket, fingerPrintCalculator));
+ }
+
+ throw new PGPException("unknown key type found");
+ }
+
+ private static byte[] getDValue(InputStream inputStream, PBEProtectionRemoverFactory keyProtectionRemoverFactory, String curveName)
+ throws IOException, PGPException
+ {
+ String type;
+ SXprUtils.skipOpenParenthesis(inputStream);
+
+ String protection;
+ S2K s2k;
+ byte[] iv;
+ byte[] secKeyData;
+
+ type = SXprUtils.readString(inputStream, inputStream.read());
+ if (type.equals("protected"))
+ {
+ protection = SXprUtils.readString(inputStream, inputStream.read());
+
+ SXprUtils.skipOpenParenthesis(inputStream);
+
+ s2k = SXprUtils.parseS2K(inputStream);
+
+ iv = SXprUtils.readBytes(inputStream, inputStream.read());
+
+ SXprUtils.skipCloseParenthesis(inputStream);
+
+ secKeyData = SXprUtils.readBytes(inputStream, inputStream.read());
+ }
+ else
+ {
+ throw new PGPException("protected block not found");
+ }
+
+ PBESecretKeyDecryptor keyDecryptor = keyProtectionRemoverFactory.createDecryptor(protection);
+
+ // TODO: recognise other algorithms
+ byte[] key = keyDecryptor.makeKeyFromPassPhrase(SymmetricKeyAlgorithmTags.AES_128, s2k);
+
+ byte[] data = keyDecryptor.recoverKeyData(SymmetricKeyAlgorithmTags.AES_128, key, iv, secKeyData, 0, secKeyData.length);
+
+ //
+ // parse the secret key S-expr
+ //
+ InputStream keyIn = new ByteArrayInputStream(data);
+
+ SXprUtils.skipOpenParenthesis(keyIn);
+ SXprUtils.skipOpenParenthesis(keyIn);
+ SXprUtils.skipOpenParenthesis(keyIn);
+ String name = SXprUtils.readString(keyIn, keyIn.read());
+ return SXprUtils.readBytes(keyIn, keyIn.read());
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPSecretKeyRing.java b/pg/src/main/java/org/spongycastle/openpgp/PGPSecretKeyRing.java
new file mode 100644
index 00000000..4391bc9b
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPSecretKeyRing.java
@@ -0,0 +1,401 @@
+package org.spongycastle.openpgp;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.spongycastle.bcpg.BCPGInputStream;
+import org.spongycastle.bcpg.PacketTags;
+import org.spongycastle.bcpg.PublicSubkeyPacket;
+import org.spongycastle.bcpg.SecretKeyPacket;
+import org.spongycastle.bcpg.SecretSubkeyPacket;
+import org.spongycastle.bcpg.TrustPacket;
+import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator;
+import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
+import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor;
+
+/**
+ * Class to hold a single master secret key and its subkeys.
+ * <p>
+ * Often PGP keyring files consist of multiple master keys, if you are trying to process
+ * or construct one of these you should use the {@link PGPSecretKeyRingCollection} class.
+ */
+public class PGPSecretKeyRing
+ extends PGPKeyRing
+{
+ List keys;
+ List extraPubKeys;
+
+ PGPSecretKeyRing(List keys)
+ {
+ this(keys, new ArrayList());
+ }
+
+ private PGPSecretKeyRing(List keys, List extraPubKeys)
+ {
+ this.keys = keys;
+ this.extraPubKeys = extraPubKeys;
+ }
+
+ public PGPSecretKeyRing(
+ byte[] encoding,
+ KeyFingerPrintCalculator fingerPrintCalculator)
+ throws IOException, PGPException
+ {
+ this(new ByteArrayInputStream(encoding), fingerPrintCalculator);
+ }
+
+ public PGPSecretKeyRing(
+ InputStream in,
+ KeyFingerPrintCalculator fingerPrintCalculator)
+ throws IOException, PGPException
+ {
+ this.keys = new ArrayList();
+ this.extraPubKeys = new ArrayList();
+
+ BCPGInputStream pIn = wrap(in);
+
+ int initialTag = pIn.nextPacketTag();
+ if (initialTag != PacketTags.SECRET_KEY && initialTag != PacketTags.SECRET_SUBKEY)
+ {
+ throw new IOException(
+ "secret key ring doesn't start with secret key tag: " +
+ "tag 0x" + Integer.toHexString(initialTag));
+ }
+
+ SecretKeyPacket secret = (SecretKeyPacket)pIn.readPacket();
+
+ //
+ // ignore GPG comment packets if found.
+ //
+ while (pIn.nextPacketTag() == PacketTags.EXPERIMENTAL_2)
+ {
+ pIn.readPacket();
+ }
+
+ TrustPacket trust = readOptionalTrustPacket(pIn);
+
+ // revocation and direct signatures
+ List keySigs = readSignaturesAndTrust(pIn);
+
+ List ids = new ArrayList();
+ List idTrusts = new ArrayList();
+ List idSigs = new ArrayList();
+ readUserIDs(pIn, ids, idTrusts, idSigs);
+
+ keys.add(new PGPSecretKey(secret, new PGPPublicKey(secret.getPublicKeyPacket(), trust, keySigs, ids, idTrusts, idSigs, fingerPrintCalculator)));
+
+
+ // Read subkeys
+ while (pIn.nextPacketTag() == PacketTags.SECRET_SUBKEY
+ || pIn.nextPacketTag() == PacketTags.PUBLIC_SUBKEY)
+ {
+ if (pIn.nextPacketTag() == PacketTags.SECRET_SUBKEY)
+ {
+ SecretSubkeyPacket sub = (SecretSubkeyPacket)pIn.readPacket();
+
+ //
+ // ignore GPG comment packets if found.
+ //
+ while (pIn.nextPacketTag() == PacketTags.EXPERIMENTAL_2)
+ {
+ pIn.readPacket();
+ }
+
+ TrustPacket subTrust = readOptionalTrustPacket(pIn);
+ List sigList = readSignaturesAndTrust(pIn);
+
+ keys.add(new PGPSecretKey(sub, new PGPPublicKey(sub.getPublicKeyPacket(), subTrust, sigList, fingerPrintCalculator)));
+ }
+ else
+ {
+ PublicSubkeyPacket sub = (PublicSubkeyPacket)pIn.readPacket();
+
+ TrustPacket subTrust = readOptionalTrustPacket(pIn);
+ List sigList = readSignaturesAndTrust(pIn);
+
+ extraPubKeys.add(new PGPPublicKey(sub, subTrust, sigList, fingerPrintCalculator));
+ }
+ }
+ }
+
+ /**
+ * Return the public key for the master key.
+ *
+ * @return PGPPublicKey
+ */
+ public PGPPublicKey getPublicKey()
+ {
+ return ((PGPSecretKey)keys.get(0)).getPublicKey();
+ }
+
+ /**
+ * Return the public key referred to by the passed in keyID if it
+ * is present.
+ *
+ * @param keyID
+ * @return PGPPublicKey
+ */
+ public PGPPublicKey getPublicKey(
+ long keyID)
+ {
+ PGPSecretKey key = getSecretKey(keyID);
+ if (key != null)
+ {
+ return key.getPublicKey();
+ }
+
+ for (int i = 0; i != extraPubKeys.size(); i++)
+ {
+ PGPPublicKey k = (PGPPublicKey)keys.get(i);
+
+ if (keyID == k.getKeyID())
+ {
+ return k;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return an iterator containing all the public keys.
+ *
+ * @return Iterator
+ */
+ public Iterator getPublicKeys()
+ {
+ List pubKeys = new ArrayList();
+
+ for (Iterator it = getSecretKeys(); it.hasNext();)
+ {
+ pubKeys.add(((PGPSecretKey)it.next()).getPublicKey());
+ }
+
+ pubKeys.addAll(extraPubKeys);
+
+ return Collections.unmodifiableList(pubKeys).iterator();
+ }
+
+ /**
+ * Return the master private key.
+ *
+ * @return PGPSecretKey
+ */
+ public PGPSecretKey getSecretKey()
+ {
+ return ((PGPSecretKey)keys.get(0));
+ }
+
+ /**
+ * Return an iterator containing all the secret keys.
+ *
+ * @return Iterator
+ */
+ public Iterator getSecretKeys()
+ {
+ return Collections.unmodifiableList(keys).iterator();
+ }
+
+ public PGPSecretKey getSecretKey(
+ long keyId)
+ {
+ for (int i = 0; i != keys.size(); i++)
+ {
+ PGPSecretKey k = (PGPSecretKey)keys.get(i);
+
+ if (keyId == k.getKeyID())
+ {
+ return k;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return an iterator of the public keys in the secret key ring that
+ * have no matching private key. At the moment only personal certificate data
+ * appears in this fashion.
+ *
+ * @return iterator of unattached, or extra, public keys.
+ */
+ public Iterator getExtraPublicKeys()
+ {
+ return extraPubKeys.iterator();
+ }
+
+ public byte[] getEncoded()
+ throws IOException
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ this.encode(bOut);
+
+ return bOut.toByteArray();
+ }
+
+ public void encode(
+ OutputStream outStream)
+ throws IOException
+ {
+ for (int i = 0; i != keys.size(); i++)
+ {
+ PGPSecretKey k = (PGPSecretKey)keys.get(i);
+
+ k.encode(outStream);
+ }
+ for (int i = 0; i != extraPubKeys.size(); i++)
+ {
+ PGPPublicKey k = (PGPPublicKey)extraPubKeys.get(i);
+
+ k.encode(outStream);
+ }
+ }
+
+ /**
+ * Replace the public key set on the secret ring with the corresponding key off the public ring.
+ *
+ * @param secretRing secret ring to be changed.
+ * @param publicRing public ring containing the new public key set.
+ */
+ public static PGPSecretKeyRing replacePublicKeys(PGPSecretKeyRing secretRing, PGPPublicKeyRing publicRing)
+ {
+ List newList = new ArrayList(secretRing.keys.size());
+
+ for (Iterator it = secretRing.keys.iterator(); it.hasNext();)
+ {
+ PGPSecretKey sk = (PGPSecretKey)it.next();
+ PGPPublicKey pk = publicRing.getPublicKey(sk.getKeyID());
+
+ newList.add(PGPSecretKey.replacePublicKey(sk, pk));
+ }
+
+ return new PGPSecretKeyRing(newList);
+ }
+
+ /**
+ * Return a copy of the passed in secret key ring, with the private keys (where present) associated with the master key and sub keys
+ * are encrypted using a new password and the passed in algorithm.
+ *
+ * @param ring the PGPSecretKeyRing to be copied.
+ * @param oldKeyDecryptor the current decryptor based on the current password for key.
+ * @param newKeyEncryptor a new encryptor based on a new password for encrypting the secret key material.
+ * @return the updated key ring.
+ */
+ public static PGPSecretKeyRing copyWithNewPassword(
+ PGPSecretKeyRing ring,
+ PBESecretKeyDecryptor oldKeyDecryptor,
+ PBESecretKeyEncryptor newKeyEncryptor)
+ throws PGPException
+ {
+ List newKeys = new ArrayList(ring.keys.size());
+
+ for (Iterator keys = ring.getSecretKeys(); keys.hasNext();)
+ {
+ PGPSecretKey key = (PGPSecretKey)keys.next();
+
+ if (key.isPrivateKeyEmpty())
+ {
+ newKeys.add(key);
+ }
+ else
+ {
+ newKeys.add(PGPSecretKey.copyWithNewPassword(key, oldKeyDecryptor, newKeyEncryptor));
+ }
+ }
+
+ return new PGPSecretKeyRing(newKeys, ring.extraPubKeys);
+ }
+
+ /**
+ * Returns a new key ring with the secret key passed in either added or
+ * replacing an existing one with the same key ID.
+ *
+ * @param secRing the secret key ring to be modified.
+ * @param secKey the secret key to be added.
+ * @return a new secret key ring.
+ */
+ public static PGPSecretKeyRing insertSecretKey(
+ PGPSecretKeyRing secRing,
+ PGPSecretKey secKey)
+ {
+ List keys = new ArrayList(secRing.keys);
+ boolean found = false;
+ boolean masterFound = false;
+
+ for (int i = 0; i != keys.size();i++)
+ {
+ PGPSecretKey key = (PGPSecretKey)keys.get(i);
+
+ if (key.getKeyID() == secKey.getKeyID())
+ {
+ found = true;
+ keys.set(i, secKey);
+ }
+ if (key.isMasterKey())
+ {
+ masterFound = true;
+ }
+ }
+
+ if (!found)
+ {
+ if (secKey.isMasterKey())
+ {
+ if (masterFound)
+ {
+ throw new IllegalArgumentException("cannot add a master key to a ring that already has one");
+ }
+
+ keys.add(0, secKey);
+ }
+ else
+ {
+ keys.add(secKey);
+ }
+ }
+
+ return new PGPSecretKeyRing(keys, secRing.extraPubKeys);
+ }
+
+ /**
+ * Returns a new key ring with the secret key passed in removed from the
+ * key ring.
+ *
+ * @param secRing the secret key ring to be modified.
+ * @param secKey the secret key to be removed.
+ * @return a new secret key ring, or null if secKey is not found.
+ */
+ public static PGPSecretKeyRing removeSecretKey(
+ PGPSecretKeyRing secRing,
+ PGPSecretKey secKey)
+ {
+ List keys = new ArrayList(secRing.keys);
+ boolean found = false;
+
+ for (int i = 0; i < keys.size();i++)
+ {
+ PGPSecretKey key = (PGPSecretKey)keys.get(i);
+
+ if (key.getKeyID() == secKey.getKeyID())
+ {
+ found = true;
+ keys.remove(i);
+ }
+ }
+
+ if (!found)
+ {
+ return null;
+ }
+
+ return new PGPSecretKeyRing(keys, secRing.extraPubKeys);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPSecretKeyRingCollection.java b/pg/src/main/java/org/spongycastle/openpgp/PGPSecretKeyRingCollection.java
new file mode 100644
index 00000000..61469242
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPSecretKeyRingCollection.java
@@ -0,0 +1,389 @@
+package org.spongycastle.openpgp;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.spongycastle.bcpg.BCPGOutputStream;
+import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator;
+import org.spongycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
+import org.spongycastle.util.Strings;
+
+/**
+ * Often a PGP key ring file is made up of a succession of master/sub-key key rings.
+ * If you want to read an entire secret key file in one hit this is the class for you.
+ */
+public class PGPSecretKeyRingCollection
+{
+ private Map secretRings = new HashMap();
+ private List order = new ArrayList();
+
+ private PGPSecretKeyRingCollection(
+ Map secretRings,
+ List order)
+ {
+ this.secretRings = secretRings;
+ this.order = order;
+ }
+
+ public PGPSecretKeyRingCollection(
+ byte[] encoding,
+ KeyFingerPrintCalculator fingerPrintCalculator)
+ throws IOException, PGPException
+ {
+ this(new ByteArrayInputStream(encoding), fingerPrintCalculator);
+ }
+
+ /**
+ * @deprecated use JcePGPSecretKeyRingCollection or BcPGPSecretKeyRingCollection.
+ */
+ public PGPSecretKeyRingCollection(byte[] encoding)
+ throws IOException, PGPException
+ {
+ this(encoding, new BcKeyFingerprintCalculator());
+ }
+
+ /**
+ * @deprecated use JcePGPSecretKeyRingCollection or BcPGPSecretKeyRingCollection.
+ */
+ public PGPSecretKeyRingCollection(InputStream in)
+ throws IOException, PGPException
+ {
+ this(in, new BcKeyFingerprintCalculator());
+ }
+
+ /**
+ * Build a PGPSecretKeyRingCollection from the passed in input stream.
+ *
+ * @param in input stream containing data
+ * @throws IOException if a problem parsinh the base stream occurs
+ * @throws PGPException if an object is encountered which isn't a PGPSecretKeyRing
+ */
+ public PGPSecretKeyRingCollection(
+ InputStream in,
+ KeyFingerPrintCalculator fingerPrintCalculator)
+ throws IOException, PGPException
+ {
+ PGPObjectFactory pgpFact = new PGPObjectFactory(in, fingerPrintCalculator);
+ Object obj;
+
+ while ((obj = pgpFact.nextObject()) != null)
+ {
+ if (!(obj instanceof PGPSecretKeyRing))
+ {
+ throw new PGPException(obj.getClass().getName() + " found where PGPSecretKeyRing expected");
+ }
+
+ PGPSecretKeyRing pgpSecret = (PGPSecretKeyRing)obj;
+ Long key = new Long(pgpSecret.getPublicKey().getKeyID());
+
+ secretRings.put(key, pgpSecret);
+ order.add(key);
+ }
+ }
+
+ public PGPSecretKeyRingCollection(
+ Collection collection)
+ throws IOException, PGPException
+ {
+ Iterator it = collection.iterator();
+
+ while (it.hasNext())
+ {
+ PGPSecretKeyRing pgpSecret = (PGPSecretKeyRing)it.next();
+ Long key = new Long(pgpSecret.getPublicKey().getKeyID());
+
+ secretRings.put(key, pgpSecret);
+ order.add(key);
+ }
+ }
+
+ /**
+ * Return the number of rings in this collection.
+ *
+ * @return size of the collection
+ */
+ public int size()
+ {
+ return order.size();
+ }
+
+ /**
+ * return the secret key rings making up this collection.
+ */
+ public Iterator getKeyRings()
+ {
+ return secretRings.values().iterator();
+ }
+
+ /**
+ * Return an iterator of the key rings associated with the passed in userID.
+ *
+ * @param userID the user ID to be matched.
+ * @return an iterator (possibly empty) of key rings which matched.
+ * @throws PGPException
+ */
+ public Iterator getKeyRings(
+ String userID)
+ throws PGPException
+ {
+ return getKeyRings(userID, false, false);
+ }
+
+ /**
+ * Return an iterator of the key rings associated with the passed in userID.
+ * <p>
+ *
+ * @param userID the user ID to be matched.
+ * @param matchPartial if true userID need only be a substring of an actual ID string to match.
+ * @return an iterator (possibly empty) of key rings which matched.
+ * @throws PGPException
+ */
+ public Iterator getKeyRings(
+ String userID,
+ boolean matchPartial)
+ throws PGPException
+ {
+ return getKeyRings(userID, matchPartial, false);
+ }
+
+ /**
+ * Return an iterator of the key rings associated with the passed in userID.
+ * <p>
+ *
+ * @param userID the user ID to be matched.
+ * @param matchPartial if true userID need only be a substring of an actual ID string to match.
+ * @param ignoreCase if true case is ignored in user ID comparisons.
+ * @return an iterator (possibly empty) of key rings which matched.
+ * @throws PGPException
+ */
+ public Iterator getKeyRings(
+ String userID,
+ boolean matchPartial,
+ boolean ignoreCase)
+ throws PGPException
+ {
+ Iterator it = this.getKeyRings();
+ List rings = new ArrayList();
+
+ if (ignoreCase)
+ {
+ userID = Strings.toLowerCase(userID);
+ }
+
+ while (it.hasNext())
+ {
+ PGPSecretKeyRing secRing = (PGPSecretKeyRing)it.next();
+ Iterator uIt = secRing.getSecretKey().getUserIDs();
+
+ while (uIt.hasNext())
+ {
+ String next = (String)uIt.next();
+ if (ignoreCase)
+ {
+ next = Strings.toLowerCase(next);
+ }
+
+ if (matchPartial)
+ {
+ if (next.indexOf(userID) > -1)
+ {
+ rings.add(secRing);
+ }
+ }
+ else
+ {
+ if (next.equals(userID))
+ {
+ rings.add(secRing);
+ }
+ }
+ }
+ }
+
+ return rings.iterator();
+ }
+
+ /**
+ * Return the PGP secret key associated with the given key id.
+ *
+ * @param keyID
+ * @return the secret key
+ * @throws PGPException
+ */
+ public PGPSecretKey getSecretKey(
+ long keyID)
+ throws PGPException
+ {
+ Iterator it = this.getKeyRings();
+
+ while (it.hasNext())
+ {
+ PGPSecretKeyRing secRing = (PGPSecretKeyRing)it.next();
+ PGPSecretKey sec = secRing.getSecretKey(keyID);
+
+ if (sec != null)
+ {
+ return sec;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the secret key ring which contains the key referred to by keyID.
+ *
+ * @param keyID
+ * @return the secret key ring
+ * @throws PGPException
+ */
+ public PGPSecretKeyRing getSecretKeyRing(
+ long keyID)
+ throws PGPException
+ {
+ Long id = new Long(keyID);
+
+ if (secretRings.containsKey(id))
+ {
+ return (PGPSecretKeyRing)secretRings.get(id);
+ }
+
+ Iterator it = this.getKeyRings();
+
+ while (it.hasNext())
+ {
+ PGPSecretKeyRing secretRing = (PGPSecretKeyRing)it.next();
+ PGPSecretKey secret = secretRing.getSecretKey(keyID);
+
+ if (secret != null)
+ {
+ return secretRing;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return true if a key matching the passed in key ID is present, false otherwise.
+ *
+ * @param keyID key ID to look for.
+ * @return true if keyID present, false otherwise.
+ */
+ public boolean contains(long keyID)
+ throws PGPException
+ {
+ return getSecretKey(keyID) != null;
+ }
+
+ public byte[] getEncoded()
+ throws IOException
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ this.encode(bOut);
+
+ return bOut.toByteArray();
+ }
+
+ public void encode(
+ OutputStream outStream)
+ throws IOException
+ {
+ BCPGOutputStream out;
+
+ if (outStream instanceof BCPGOutputStream)
+ {
+ out = (BCPGOutputStream)outStream;
+ }
+ else
+ {
+ out = new BCPGOutputStream(outStream);
+ }
+
+ Iterator it = order.iterator();
+ while (it.hasNext())
+ {
+ PGPSecretKeyRing sr = (PGPSecretKeyRing)secretRings.get(it.next());
+
+ sr.encode(out);
+ }
+ }
+
+ /**
+ * Return a new collection object containing the contents of the passed in collection and
+ * the passed in secret key ring.
+ *
+ * @param ringCollection the collection the ring to be added to.
+ * @param secretKeyRing the key ring to be added.
+ * @return a new collection merging the current one with the passed in ring.
+ * @exception IllegalArgumentException if the keyID for the passed in ring is already present.
+ */
+ public static PGPSecretKeyRingCollection addSecretKeyRing(
+ PGPSecretKeyRingCollection ringCollection,
+ PGPSecretKeyRing secretKeyRing)
+ {
+ Long key = new Long(secretKeyRing.getPublicKey().getKeyID());
+
+ if (ringCollection.secretRings.containsKey(key))
+ {
+ throw new IllegalArgumentException("Collection already contains a key with a keyID for the passed in ring.");
+ }
+
+ Map newSecretRings = new HashMap(ringCollection.secretRings);
+ List newOrder = new ArrayList(ringCollection.order);
+
+ newSecretRings.put(key, secretKeyRing);
+ newOrder.add(key);
+
+ return new PGPSecretKeyRingCollection(newSecretRings, newOrder);
+ }
+
+ /**
+ * Return a new collection object containing the contents of this collection with
+ * the passed in secret key ring removed.
+ *
+ * @param ringCollection the collection the ring to be removed from.
+ * @param secretKeyRing the key ring to be removed.
+ * @return a new collection merging the current one with the passed in ring.
+ * @exception IllegalArgumentException if the keyID for the passed in ring is not present.
+ */
+ public static PGPSecretKeyRingCollection removeSecretKeyRing(
+ PGPSecretKeyRingCollection ringCollection,
+ PGPSecretKeyRing secretKeyRing)
+ {
+ Long key = new Long(secretKeyRing.getPublicKey().getKeyID());
+
+ if (!ringCollection.secretRings.containsKey(key))
+ {
+ throw new IllegalArgumentException("Collection does not contain a key with a keyID for the passed in ring.");
+ }
+
+ Map newSecretRings = new HashMap(ringCollection.secretRings);
+ List newOrder = new ArrayList(ringCollection.order);
+
+ newSecretRings.remove(key);
+
+ for (int i = 0; i < newOrder.size(); i++)
+ {
+ Long r = (Long)newOrder.get(i);
+
+ if (r.longValue() == key.longValue())
+ {
+ newOrder.remove(i);
+ break;
+ }
+ }
+
+ return new PGPSecretKeyRingCollection(newSecretRings, newOrder);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPSignature.java b/pg/src/main/java/org/spongycastle/openpgp/PGPSignature.java
new file mode 100644
index 00000000..b58a667e
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPSignature.java
@@ -0,0 +1,559 @@
+package org.spongycastle.openpgp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Date;
+
+import org.spongycastle.asn1.ASN1EncodableVector;
+import org.spongycastle.asn1.ASN1Integer;
+import org.spongycastle.asn1.DERSequence;
+import org.spongycastle.bcpg.BCPGInputStream;
+import org.spongycastle.bcpg.BCPGOutputStream;
+import org.spongycastle.bcpg.MPInteger;
+import org.spongycastle.bcpg.SignaturePacket;
+import org.spongycastle.bcpg.SignatureSubpacket;
+import org.spongycastle.bcpg.TrustPacket;
+import org.spongycastle.bcpg.UserAttributeSubpacket;
+import org.spongycastle.openpgp.operator.PGPContentVerifier;
+import org.spongycastle.openpgp.operator.PGPContentVerifierBuilder;
+import org.spongycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
+import org.spongycastle.util.BigIntegers;
+import org.spongycastle.util.Strings;
+
+/**
+ *A PGP signature object.
+ */
+public class PGPSignature
+{
+ public static final int BINARY_DOCUMENT = 0x00;
+ public static final int CANONICAL_TEXT_DOCUMENT = 0x01;
+ public static final int STAND_ALONE = 0x02;
+
+ public static final int DEFAULT_CERTIFICATION = 0x10;
+ public static final int NO_CERTIFICATION = 0x11;
+ public static final int CASUAL_CERTIFICATION = 0x12;
+ public static final int POSITIVE_CERTIFICATION = 0x13;
+
+ public static final int SUBKEY_BINDING = 0x18;
+ public static final int PRIMARYKEY_BINDING = 0x19;
+ public static final int DIRECT_KEY = 0x1f;
+ public static final int KEY_REVOCATION = 0x20;
+ public static final int SUBKEY_REVOCATION = 0x28;
+ public static final int CERTIFICATION_REVOCATION = 0x30;
+ public static final int TIMESTAMP = 0x40;
+
+ private SignaturePacket sigPck;
+ private int signatureType;
+ private TrustPacket trustPck;
+ private PGPContentVerifier verifier;
+ private byte lastb;
+ private OutputStream sigOut;
+
+ PGPSignature(
+ BCPGInputStream pIn)
+ throws IOException, PGPException
+ {
+ this((SignaturePacket)pIn.readPacket());
+ }
+
+ PGPSignature(
+ SignaturePacket sigPacket)
+ throws PGPException
+ {
+ sigPck = sigPacket;
+ signatureType = sigPck.getSignatureType();
+ trustPck = null;
+ }
+
+ PGPSignature(
+ SignaturePacket sigPacket,
+ TrustPacket trustPacket)
+ throws PGPException
+ {
+ this(sigPacket);
+
+ this.trustPck = trustPacket;
+ }
+
+ /**
+ * Return the OpenPGP version number for this signature.
+ *
+ * @return signature version number.
+ */
+ public int getVersion()
+ {
+ return sigPck.getVersion();
+ }
+
+ /**
+ * Return the key algorithm associated with this signature.
+ * @return signature key algorithm.
+ */
+ public int getKeyAlgorithm()
+ {
+ return sigPck.getKeyAlgorithm();
+ }
+
+ /**
+ * Return the hash algorithm associated with this signature.
+ * @return signature hash algorithm.
+ */
+ public int getHashAlgorithm()
+ {
+ return sigPck.getHashAlgorithm();
+ }
+
+ public void init(PGPContentVerifierBuilderProvider verifierBuilderProvider, PGPPublicKey pubKey)
+ throws PGPException
+ {
+ PGPContentVerifierBuilder verifierBuilder = verifierBuilderProvider.get(sigPck.getKeyAlgorithm(), sigPck.getHashAlgorithm());
+
+ verifier = verifierBuilder.build(pubKey);
+
+ lastb = 0;
+ sigOut = verifier.getOutputStream();
+ }
+
+ public void update(
+ byte b)
+ {
+ if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
+ {
+ if (b == '\r')
+ {
+ byteUpdate((byte)'\r');
+ byteUpdate((byte)'\n');
+ }
+ else if (b == '\n')
+ {
+ if (lastb != '\r')
+ {
+ byteUpdate((byte)'\r');
+ byteUpdate((byte)'\n');
+ }
+ }
+ else
+ {
+ byteUpdate(b);
+ }
+
+ lastb = b;
+ }
+ else
+ {
+ byteUpdate(b);
+ }
+ }
+
+ public void update(
+ byte[] bytes)
+ {
+ this.update(bytes, 0, bytes.length);
+ }
+
+ public void update(
+ byte[] bytes,
+ int off,
+ int length)
+ {
+ if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
+ {
+ int finish = off + length;
+
+ for (int i = off; i != finish; i++)
+ {
+ this.update(bytes[i]);
+ }
+ }
+ else
+ {
+ blockUpdate(bytes, off, length);
+ }
+ }
+
+ private void byteUpdate(byte b)
+ {
+ try
+ {
+ sigOut.write(b);
+ }
+ catch (IOException e)
+ {
+ throw new PGPRuntimeOperationException(e.getMessage(), e);
+ }
+ }
+
+ private void blockUpdate(byte[] block, int off, int len)
+ {
+ try
+ {
+ sigOut.write(block, off, len);
+ }
+ catch (IOException e)
+ {
+ throw new PGPRuntimeOperationException(e.getMessage(), e);
+ }
+ }
+
+ public boolean verify()
+ throws PGPException
+ {
+ try
+ {
+ sigOut.write(this.getSignatureTrailer());
+
+ sigOut.close();
+ }
+ catch (IOException e)
+ {
+ throw new PGPException(e.getMessage(), e);
+ }
+
+ return verifier.verify(this.getSignature());
+ }
+
+
+ private void updateWithIdData(int header, byte[] idBytes)
+ {
+ this.update((byte)header);
+ this.update((byte)(idBytes.length >> 24));
+ this.update((byte)(idBytes.length >> 16));
+ this.update((byte)(idBytes.length >> 8));
+ this.update((byte)(idBytes.length));
+ this.update(idBytes);
+ }
+
+ private void updateWithPublicKey(PGPPublicKey key)
+ throws PGPException
+ {
+ byte[] keyBytes = getEncodedPublicKey(key);
+
+ this.update((byte)0x99);
+ this.update((byte)(keyBytes.length >> 8));
+ this.update((byte)(keyBytes.length));
+ this.update(keyBytes);
+ }
+
+ /**
+ * Verify the signature as certifying the passed in public key as associated
+ * with the passed in user attributes.
+ *
+ * @param userAttributes user attributes the key was stored under
+ * @param key the key to be verified.
+ * @return true if the signature matches, false otherwise.
+ * @throws PGPException
+ */
+ public boolean verifyCertification(
+ PGPUserAttributeSubpacketVector userAttributes,
+ PGPPublicKey key)
+ throws PGPException
+ {
+ if (verifier == null)
+ {
+ throw new PGPException("PGPSignature not initialised - call init().");
+ }
+
+ updateWithPublicKey(key);
+
+ //
+ // hash in the userAttributes
+ //
+ try
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ UserAttributeSubpacket[] packets = userAttributes.toSubpacketArray();
+ for (int i = 0; i != packets.length; i++)
+ {
+ packets[i].encode(bOut);
+ }
+ updateWithIdData(0xd1, bOut.toByteArray());
+ }
+ catch (IOException e)
+ {
+ throw new PGPException("cannot encode subpacket array", e);
+ }
+
+ addTrailer();
+
+ return verifier.verify(this.getSignature());
+ }
+
+ /**
+ * Verify the signature as certifying the passed in public key as associated
+ * with the passed in id.
+ *
+ * @param id id the key was stored under
+ * @param key the key to be verified.
+ * @return true if the signature matches, false otherwise.
+ * @throws PGPException
+ */
+ public boolean verifyCertification(
+ String id,
+ PGPPublicKey key)
+ throws PGPException
+ {
+ if (verifier == null)
+ {
+ throw new PGPException("PGPSignature not initialised - call init().");
+ }
+
+ updateWithPublicKey(key);
+
+ //
+ // hash in the id
+ //
+ updateWithIdData(0xb4, Strings.toUTF8ByteArray(id));
+
+ addTrailer();
+
+ return verifier.verify(this.getSignature());
+ }
+
+ /**
+ * Verify the signature as certifying the passed in public key as associated
+ * with the passed in rawID.
+ *
+ * @param rawID id the key was stored under in its raw byte form.
+ * @param key the key to be verified.
+ * @return true if the signature matches, false otherwise.
+ * @throws PGPException
+ */
+ public boolean verifyCertification(
+ byte[] rawID,
+ PGPPublicKey key)
+ throws PGPException
+ {
+ if (verifier == null)
+ {
+ throw new PGPException("PGPSignature not initialised - call init().");
+ }
+
+ updateWithPublicKey(key);
+
+ //
+ // hash in the rawID
+ //
+ updateWithIdData(0xb4, rawID);
+
+ addTrailer();
+
+ return verifier.verify(this.getSignature());
+ }
+
+ /**
+ * Verify a certification for the passed in key against the passed in
+ * master key.
+ *
+ * @param masterKey the key we are verifying against.
+ * @param pubKey the key we are verifying.
+ * @return true if the certification is valid, false otherwise.
+ * @throws PGPException
+ */
+ public boolean verifyCertification(
+ PGPPublicKey masterKey,
+ PGPPublicKey pubKey)
+ throws PGPException
+ {
+ if (verifier == null)
+ {
+ throw new PGPException("PGPSignature not initialised - call init().");
+ }
+
+ updateWithPublicKey(masterKey);
+ updateWithPublicKey(pubKey);
+
+ addTrailer();
+
+ return verifier.verify(this.getSignature());
+ }
+
+ private void addTrailer()
+ {
+ try
+ {
+ sigOut.write(sigPck.getSignatureTrailer());
+
+ sigOut.close();
+ }
+ catch (IOException e)
+ {
+ throw new PGPRuntimeOperationException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Verify a key certification, such as a revocation, for the passed in key.
+ *
+ * @param pubKey the key we are checking.
+ * @return true if the certification is valid, false otherwise.
+ * @throws PGPException
+ */
+ public boolean verifyCertification(
+ PGPPublicKey pubKey)
+ throws PGPException
+ {
+ if (verifier == null)
+ {
+ throw new PGPException("PGPSignature not initialised - call init().");
+ }
+
+ if (this.getSignatureType() != KEY_REVOCATION
+ && this.getSignatureType() != SUBKEY_REVOCATION
+ && this.getSignatureType() != DIRECT_KEY)
+ {
+ throw new PGPException("signature is not a key signature");
+ }
+
+ updateWithPublicKey(pubKey);
+
+ addTrailer();
+
+ return verifier.verify(this.getSignature());
+ }
+
+ public int getSignatureType()
+ {
+ return sigPck.getSignatureType();
+ }
+
+ /**
+ * Return the id of the key that created the signature.
+ * @return keyID of the signatures corresponding key.
+ */
+ public long getKeyID()
+ {
+ return sigPck.getKeyID();
+ }
+
+ /**
+ * Return the creation time of the signature.
+ *
+ * @return the signature creation time.
+ */
+ public Date getCreationTime()
+ {
+ return new Date(sigPck.getCreationTime());
+ }
+
+ public byte[] getSignatureTrailer()
+ {
+ return sigPck.getSignatureTrailer();
+ }
+
+ /**
+ * Return true if the signature has either hashed or unhashed subpackets.
+ *
+ * @return true if either hashed or unhashed subpackets are present, false otherwise.
+ */
+ public boolean hasSubpackets()
+ {
+ return sigPck.getHashedSubPackets() != null || sigPck.getUnhashedSubPackets() != null;
+ }
+
+ public PGPSignatureSubpacketVector getHashedSubPackets()
+ {
+ return createSubpacketVector(sigPck.getHashedSubPackets());
+ }
+
+ public PGPSignatureSubpacketVector getUnhashedSubPackets()
+ {
+ return createSubpacketVector(sigPck.getUnhashedSubPackets());
+ }
+
+ private PGPSignatureSubpacketVector createSubpacketVector(SignatureSubpacket[] pcks)
+ {
+ if (pcks != null)
+ {
+ return new PGPSignatureSubpacketVector(pcks);
+ }
+
+ return null;
+ }
+
+ public byte[] getSignature()
+ throws PGPException
+ {
+ MPInteger[] sigValues = sigPck.getSignature();
+ byte[] signature;
+
+ if (sigValues != null)
+ {
+ if (sigValues.length == 1) // an RSA signature
+ {
+ signature = BigIntegers.asUnsignedByteArray(sigValues[0].getValue());
+ }
+ else
+ {
+ try
+ {
+ ASN1EncodableVector v = new ASN1EncodableVector();
+ v.add(new ASN1Integer(sigValues[0].getValue()));
+ v.add(new ASN1Integer(sigValues[1].getValue()));
+
+ signature = new DERSequence(v).getEncoded();
+ }
+ catch (IOException e)
+ {
+ throw new PGPException("exception encoding DSA sig.", e);
+ }
+ }
+ }
+ else
+ {
+ signature = sigPck.getSignatureBytes();
+ }
+
+ return signature;
+ }
+
+ public byte[] getEncoded()
+ throws IOException
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ this.encode(bOut);
+
+ return bOut.toByteArray();
+ }
+
+ public void encode(
+ OutputStream outStream)
+ throws IOException
+ {
+ BCPGOutputStream out;
+
+ if (outStream instanceof BCPGOutputStream)
+ {
+ out = (BCPGOutputStream)outStream;
+ }
+ else
+ {
+ out = new BCPGOutputStream(outStream);
+ }
+
+ out.writePacket(sigPck);
+ if (trustPck != null)
+ {
+ out.writePacket(trustPck);
+ }
+ }
+
+ private byte[] getEncodedPublicKey(
+ PGPPublicKey pubKey)
+ throws PGPException
+ {
+ byte[] keyBytes;
+
+ try
+ {
+ keyBytes = pubKey.publicPk.getEncodedContents();
+ }
+ catch (IOException e)
+ {
+ throw new PGPException("exception preparing key.", e);
+ }
+
+ return keyBytes;
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPSignatureGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/PGPSignatureGenerator.java
new file mode 100644
index 00000000..d9308c1c
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPSignatureGenerator.java
@@ -0,0 +1,443 @@
+package org.spongycastle.openpgp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.util.Date;
+
+import org.spongycastle.bcpg.MPInteger;
+import org.spongycastle.bcpg.OnePassSignaturePacket;
+import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
+import org.spongycastle.bcpg.SignaturePacket;
+import org.spongycastle.bcpg.SignatureSubpacket;
+import org.spongycastle.bcpg.SignatureSubpacketTags;
+import org.spongycastle.bcpg.UserAttributeSubpacket;
+import org.spongycastle.bcpg.sig.IssuerKeyID;
+import org.spongycastle.bcpg.sig.SignatureCreationTime;
+import org.spongycastle.openpgp.operator.PGPContentSigner;
+import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
+import org.spongycastle.util.Strings;
+
+/**
+ * Generator for PGP Signatures.
+ */
+public class PGPSignatureGenerator
+{
+ private SignatureSubpacket[] unhashed = new SignatureSubpacket[0];
+ private SignatureSubpacket[] hashed = new SignatureSubpacket[0];
+ private OutputStream sigOut;
+ private PGPContentSignerBuilder contentSignerBuilder;
+ private PGPContentSigner contentSigner;
+ private int sigType;
+ private byte lastb;
+ private int providedKeyAlgorithm = -1;
+
+ /**
+ * Create a signature generator built on the passed in contentSignerBuilder.
+ *
+ * @param contentSignerBuilder builder to produce PGPContentSigner objects for generating signatures.
+ */
+ public PGPSignatureGenerator(
+ PGPContentSignerBuilder contentSignerBuilder)
+ {
+ this.contentSignerBuilder = contentSignerBuilder;
+ }
+
+ /**
+ * Initialise the generator for signing.
+ *
+ * @param signatureType
+ * @param key
+ * @throws PGPException
+ */
+ public void init(
+ int signatureType,
+ PGPPrivateKey key)
+ throws PGPException
+ {
+ contentSigner = contentSignerBuilder.build(signatureType, key);
+ sigOut = contentSigner.getOutputStream();
+ sigType = contentSigner.getType();
+ lastb = 0;
+
+ if (providedKeyAlgorithm >= 0 && providedKeyAlgorithm != contentSigner.getKeyAlgorithm())
+ {
+ throw new PGPException("key algorithm mismatch");
+ }
+ }
+
+ public void update(
+ byte b)
+ {
+ if (sigType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
+ {
+ if (b == '\r')
+ {
+ byteUpdate((byte)'\r');
+ byteUpdate((byte)'\n');
+ }
+ else if (b == '\n')
+ {
+ if (lastb != '\r')
+ {
+ byteUpdate((byte)'\r');
+ byteUpdate((byte)'\n');
+ }
+ }
+ else
+ {
+ byteUpdate(b);
+ }
+
+ lastb = b;
+ }
+ else
+ {
+ byteUpdate(b);
+ }
+ }
+
+ public void update(
+ byte[] b)
+ {
+ this.update(b, 0, b.length);
+ }
+
+ public void update(
+ byte[] b,
+ int off,
+ int len)
+ {
+ if (sigType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
+ {
+ int finish = off + len;
+
+ for (int i = off; i != finish; i++)
+ {
+ this.update(b[i]);
+ }
+ }
+ else
+ {
+ blockUpdate(b, off, len);
+ }
+ }
+
+ private void byteUpdate(byte b)
+ {
+ try
+ {
+ sigOut.write(b);
+ }
+ catch (IOException e)
+ {
+ throw new PGPRuntimeOperationException(e.getMessage(), e);
+ }
+ }
+
+ private void blockUpdate(byte[] block, int off, int len)
+ {
+ try
+ {
+ sigOut.write(block, off, len);
+ }
+ catch (IOException e)
+ {
+ throw new PGPRuntimeOperationException(e.getMessage(), e);
+ }
+ }
+
+ public void setHashedSubpackets(
+ PGPSignatureSubpacketVector hashedPcks)
+ {
+ if (hashedPcks == null)
+ {
+ hashed = new SignatureSubpacket[0];
+ return;
+ }
+
+ hashed = hashedPcks.toSubpacketArray();
+ }
+
+ public void setUnhashedSubpackets(
+ PGPSignatureSubpacketVector unhashedPcks)
+ {
+ if (unhashedPcks == null)
+ {
+ unhashed = new SignatureSubpacket[0];
+ return;
+ }
+
+ unhashed = unhashedPcks.toSubpacketArray();
+ }
+
+ /**
+ * Return the one pass header associated with the current signature.
+ *
+ * @param isNested
+ * @return PGPOnePassSignature
+ * @throws PGPException
+ */
+ public PGPOnePassSignature generateOnePassVersion(
+ boolean isNested)
+ throws PGPException
+ {
+ return new PGPOnePassSignature(new OnePassSignaturePacket(sigType, contentSigner.getHashAlgorithm(), contentSigner.getKeyAlgorithm(), contentSigner.getKeyID(), isNested));
+ }
+
+ /**
+ * Return a signature object containing the current signature state.
+ *
+ * @return PGPSignature
+ * @throws PGPException
+ */
+ public PGPSignature generate()
+ throws PGPException
+ {
+ MPInteger[] sigValues;
+ int version = 4;
+ ByteArrayOutputStream sOut = new ByteArrayOutputStream();
+ SignatureSubpacket[] hPkts, unhPkts;
+
+ if (!packetPresent(hashed, SignatureSubpacketTags.CREATION_TIME))
+ {
+ hPkts = insertSubpacket(hashed, new SignatureCreationTime(false, new Date()));
+ }
+ else
+ {
+ hPkts = hashed;
+ }
+
+ if (!packetPresent(hashed, SignatureSubpacketTags.ISSUER_KEY_ID) && !packetPresent(unhashed, SignatureSubpacketTags.ISSUER_KEY_ID))
+ {
+ unhPkts = insertSubpacket(unhashed, new IssuerKeyID(false, contentSigner.getKeyID()));
+ }
+ else
+ {
+ unhPkts = unhashed;
+ }
+
+ try
+ {
+ sOut.write((byte)version);
+ sOut.write((byte)sigType);
+ sOut.write((byte)contentSigner.getKeyAlgorithm());
+ sOut.write((byte)contentSigner.getHashAlgorithm());
+
+ ByteArrayOutputStream hOut = new ByteArrayOutputStream();
+
+ for (int i = 0; i != hPkts.length; i++)
+ {
+ hPkts[i].encode(hOut);
+ }
+
+ byte[] data = hOut.toByteArray();
+
+ sOut.write((byte)(data.length >> 8));
+ sOut.write((byte)data.length);
+ sOut.write(data);
+ }
+ catch (IOException e)
+ {
+ throw new PGPException("exception encoding hashed data.", e);
+ }
+
+ byte[] hData = sOut.toByteArray();
+
+ sOut.write((byte)version);
+ sOut.write((byte)0xff);
+ sOut.write((byte)(hData.length >> 24));
+ sOut.write((byte)(hData.length >> 16));
+ sOut.write((byte)(hData.length >> 8));
+ sOut.write((byte)(hData.length));
+
+ byte[] trailer = sOut.toByteArray();
+
+ blockUpdate(trailer, 0, trailer.length);
+
+ if (contentSigner.getKeyAlgorithm() == PublicKeyAlgorithmTags.RSA_SIGN
+ || contentSigner.getKeyAlgorithm() == PublicKeyAlgorithmTags.RSA_GENERAL) // an RSA signature
+ {
+ sigValues = new MPInteger[1];
+ sigValues[0] = new MPInteger(new BigInteger(1, contentSigner.getSignature()));
+ }
+ else
+ {
+ sigValues = PGPUtil.dsaSigToMpi(contentSigner.getSignature());
+ }
+
+ byte[] digest = contentSigner.getDigest();
+ byte[] fingerPrint = new byte[2];
+
+ fingerPrint[0] = digest[0];
+ fingerPrint[1] = digest[1];
+
+ return new PGPSignature(new SignaturePacket(sigType, contentSigner.getKeyID(), contentSigner.getKeyAlgorithm(), contentSigner.getHashAlgorithm(), hPkts, unhPkts, fingerPrint, sigValues));
+ }
+
+ /**
+ * Generate a certification for the passed in id and key.
+ *
+ * @param id the id we are certifying against the public key.
+ * @param pubKey the key we are certifying against the id.
+ * @return the certification.
+ * @throws PGPException
+ */
+ public PGPSignature generateCertification(
+ String id,
+ PGPPublicKey pubKey)
+ throws PGPException
+ {
+ updateWithPublicKey(pubKey);
+
+ //
+ // hash in the id
+ //
+ updateWithIdData(0xb4, Strings.toUTF8ByteArray(id));
+
+ return this.generate();
+ }
+
+ /**
+ * Generate a certification for the passed in userAttributes
+ * @param userAttributes the id we are certifying against the public key.
+ * @param pubKey the key we are certifying against the id.
+ * @return the certification.
+ * @throws PGPException
+ */
+ public PGPSignature generateCertification(
+ PGPUserAttributeSubpacketVector userAttributes,
+ PGPPublicKey pubKey)
+ throws PGPException
+ {
+ updateWithPublicKey(pubKey);
+
+ //
+ // hash in the attributes
+ //
+ try
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ UserAttributeSubpacket[] packets = userAttributes.toSubpacketArray();
+ for (int i = 0; i != packets.length; i++)
+ {
+ packets[i].encode(bOut);
+ }
+ updateWithIdData(0xd1, bOut.toByteArray());
+ }
+ catch (IOException e)
+ {
+ throw new PGPException("cannot encode subpacket array", e);
+ }
+
+ return this.generate();
+ }
+
+ /**
+ * Generate a certification for the passed in key against the passed in
+ * master key.
+ *
+ * @param masterKey the key we are certifying against.
+ * @param pubKey the key we are certifying.
+ * @return the certification.
+ * @throws PGPException
+ */
+ public PGPSignature generateCertification(
+ PGPPublicKey masterKey,
+ PGPPublicKey pubKey)
+ throws PGPException
+ {
+ updateWithPublicKey(masterKey);
+ updateWithPublicKey(pubKey);
+
+ return this.generate();
+ }
+
+ /**
+ * Generate a certification, such as a revocation, for the passed in key.
+ *
+ * @param pubKey the key we are certifying.
+ * @return the certification.
+ * @throws PGPException
+ */
+ public PGPSignature generateCertification(
+ PGPPublicKey pubKey)
+ throws PGPException
+ {
+ if ((sigType == PGPSignature.SUBKEY_REVOCATION || sigType == PGPSignature.SUBKEY_BINDING) && !pubKey.isMasterKey())
+ {
+ throw new IllegalArgumentException("certifications involving subkey requires public key of revoking key as well.");
+ }
+
+ updateWithPublicKey(pubKey);
+
+ return this.generate();
+ }
+
+ private byte[] getEncodedPublicKey(
+ PGPPublicKey pubKey)
+ throws PGPException
+ {
+ byte[] keyBytes;
+
+ try
+ {
+ keyBytes = pubKey.publicPk.getEncodedContents();
+ }
+ catch (IOException e)
+ {
+ throw new PGPException("exception preparing key.", e);
+ }
+
+ return keyBytes;
+ }
+
+ private boolean packetPresent(
+ SignatureSubpacket[] packets,
+ int type)
+ {
+ for (int i = 0; i != packets.length; i++)
+ {
+ if (packets[i].getType() == type)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private SignatureSubpacket[] insertSubpacket(
+ SignatureSubpacket[] packets,
+ SignatureSubpacket subpacket)
+ {
+ SignatureSubpacket[] tmp = new SignatureSubpacket[packets.length + 1];
+
+ tmp[0] = subpacket;
+ System.arraycopy(packets, 0, tmp, 1, packets.length);
+
+ return tmp;
+ }
+
+ private void updateWithIdData(int header, byte[] idBytes)
+ {
+ this.update((byte)header);
+ this.update((byte)(idBytes.length >> 24));
+ this.update((byte)(idBytes.length >> 16));
+ this.update((byte)(idBytes.length >> 8));
+ this.update((byte)(idBytes.length));
+ this.update(idBytes);
+ }
+
+ private void updateWithPublicKey(PGPPublicKey key)
+ throws PGPException
+ {
+ byte[] keyBytes = getEncodedPublicKey(key);
+
+ this.update((byte)0x99);
+ this.update((byte)(keyBytes.length >> 8));
+ this.update((byte)(keyBytes.length));
+ this.update(keyBytes);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPSignatureList.java b/pg/src/main/java/org/spongycastle/openpgp/PGPSignatureList.java
new file mode 100644
index 00000000..a7f6b8cf
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPSignatureList.java
@@ -0,0 +1,40 @@
+package org.spongycastle.openpgp;
+
+/**
+ * A list of PGP signatures - normally in the signature block after literal data.
+ */
+public class PGPSignatureList
+{
+ PGPSignature[] sigs;
+
+ public PGPSignatureList(
+ PGPSignature[] sigs)
+ {
+ this.sigs = new PGPSignature[sigs.length];
+
+ System.arraycopy(sigs, 0, this.sigs, 0, sigs.length);
+ }
+
+ public PGPSignatureList(
+ PGPSignature sig)
+ {
+ this.sigs = new PGPSignature[1];
+ this.sigs[0] = sig;
+ }
+
+ public PGPSignature get(
+ int index)
+ {
+ return sigs[index];
+ }
+
+ public int size()
+ {
+ return sigs.length;
+ }
+
+ public boolean isEmpty()
+ {
+ return (sigs.length == 0);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPSignatureSubpacketGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/PGPSignatureSubpacketGenerator.java
new file mode 100644
index 00000000..80718cfe
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPSignatureSubpacketGenerator.java
@@ -0,0 +1,207 @@
+package org.spongycastle.openpgp;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.spongycastle.bcpg.SignatureSubpacket;
+import org.spongycastle.bcpg.SignatureSubpacketTags;
+import org.spongycastle.bcpg.sig.EmbeddedSignature;
+import org.spongycastle.bcpg.sig.Exportable;
+import org.spongycastle.bcpg.sig.Features;
+import org.spongycastle.bcpg.sig.IssuerKeyID;
+import org.spongycastle.bcpg.sig.KeyExpirationTime;
+import org.spongycastle.bcpg.sig.KeyFlags;
+import org.spongycastle.bcpg.sig.NotationData;
+import org.spongycastle.bcpg.sig.PreferredAlgorithms;
+import org.spongycastle.bcpg.sig.PrimaryUserID;
+import org.spongycastle.bcpg.sig.Revocable;
+import org.spongycastle.bcpg.sig.RevocationKey;
+import org.spongycastle.bcpg.sig.RevocationKeyTags;
+import org.spongycastle.bcpg.sig.RevocationReason;
+import org.spongycastle.bcpg.sig.SignatureCreationTime;
+import org.spongycastle.bcpg.sig.SignatureExpirationTime;
+import org.spongycastle.bcpg.sig.SignerUserID;
+import org.spongycastle.bcpg.sig.TrustSignature;
+
+/**
+ * Generator for signature subpackets.
+ */
+public class PGPSignatureSubpacketGenerator
+{
+ List list = new ArrayList();
+
+ public PGPSignatureSubpacketGenerator()
+ {
+ }
+
+ public void setRevocable(boolean isCritical, boolean isRevocable)
+ {
+ list.add(new Revocable(isCritical, isRevocable));
+ }
+
+ public void setExportable(boolean isCritical, boolean isExportable)
+ {
+ list.add(new Exportable(isCritical, isExportable));
+ }
+
+ public void setFeature(boolean isCritical, byte feature)
+ {
+ list.add(new Features(isCritical, feature));
+ }
+
+ /**
+ * Add a TrustSignature packet to the signature. The values for depth and trust are
+ * largely installation dependent but there are some guidelines in RFC 4880 -
+ * 5.2.3.13.
+ *
+ * @param isCritical true if the packet is critical.
+ * @param depth depth level.
+ * @param trustAmount trust amount.
+ */
+ public void setTrust(boolean isCritical, int depth, int trustAmount)
+ {
+ list.add(new TrustSignature(isCritical, depth, trustAmount));
+ }
+
+ /**
+ * Set the number of seconds a key is valid for after the time of its creation. A
+ * value of zero means the key never expires.
+ *
+ * @param isCritical true if should be treated as critical, false otherwise.
+ * @param seconds
+ */
+ public void setKeyExpirationTime(boolean isCritical, long seconds)
+ {
+ list.add(new KeyExpirationTime(isCritical, seconds));
+ }
+
+ /**
+ * Set the number of seconds a signature is valid for after the time of its creation.
+ * A value of zero means the signature never expires.
+ *
+ * @param isCritical true if should be treated as critical, false otherwise.
+ * @param seconds
+ */
+ public void setSignatureExpirationTime(boolean isCritical, long seconds)
+ {
+ list.add(new SignatureExpirationTime(isCritical, seconds));
+ }
+
+ /**
+ * Set the creation time for the signature.
+ * <p>
+ * Note: this overrides the generation of a creation time when the signature is
+ * generated.
+ */
+ public void setSignatureCreationTime(boolean isCritical, Date date)
+ {
+ list.add(new SignatureCreationTime(isCritical, date));
+ }
+
+ public void setPreferredHashAlgorithms(boolean isCritical, int[] algorithms)
+ {
+ list.add(new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_HASH_ALGS, isCritical,
+ algorithms));
+ }
+
+ public void setPreferredSymmetricAlgorithms(boolean isCritical, int[] algorithms)
+ {
+ list.add(new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_SYM_ALGS, isCritical,
+ algorithms));
+ }
+
+ public void setPreferredCompressionAlgorithms(boolean isCritical, int[] algorithms)
+ {
+ list.add(new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_COMP_ALGS, isCritical,
+ algorithms));
+ }
+
+ public void setKeyFlags(boolean isCritical, int flags)
+ {
+ list.add(new KeyFlags(isCritical, flags));
+ }
+
+ public void setSignerUserID(boolean isCritical, String userID)
+ {
+ if (userID == null)
+ {
+ throw new IllegalArgumentException("attempt to set null SignerUserID");
+ }
+
+ list.add(new SignerUserID(isCritical, userID));
+ }
+
+ public void setSignerUserID(boolean isCritical, byte[] rawUserID)
+ {
+ if (rawUserID == null)
+ {
+ throw new IllegalArgumentException("attempt to set null SignerUserID");
+ }
+
+ list.add(new SignerUserID(isCritical, rawUserID));
+ }
+
+ public void setEmbeddedSignature(boolean isCritical, PGPSignature pgpSignature)
+ throws IOException
+ {
+ byte[] sig = pgpSignature.getEncoded();
+ byte[] data;
+
+ if (sig.length - 1 > 256)
+ {
+ data = new byte[sig.length - 3];
+ }
+ else
+ {
+ data = new byte[sig.length - 2];
+ }
+
+ System.arraycopy(sig, sig.length - data.length, data, 0, data.length);
+
+ list.add(new EmbeddedSignature(isCritical, data));
+ }
+
+ public void setPrimaryUserID(boolean isCritical, boolean isPrimaryUserID)
+ {
+ list.add(new PrimaryUserID(isCritical, isPrimaryUserID));
+ }
+
+ public void setNotationData(boolean isCritical, boolean isHumanReadable, String notationName,
+ String notationValue)
+ {
+ list.add(new NotationData(isCritical, isHumanReadable, notationName, notationValue));
+ }
+
+ /**
+ * Sets revocation reason sub packet
+ */
+ public void setRevocationReason(boolean isCritical, byte reason, String description)
+ {
+ list.add(new RevocationReason(isCritical, reason, description));
+ }
+
+ /**
+ * Sets revocation key sub packet
+ */
+ public void setRevocationKey(boolean isCritical, int keyAlgorithm, byte[] fingerprint)
+ {
+ list.add(new RevocationKey(isCritical, RevocationKeyTags.CLASS_DEFAULT, keyAlgorithm,
+ fingerprint));
+ }
+
+ /**
+ * Sets issuer key sub packe
+ */
+ public void setIssuerKeyID(boolean isCritical, long keyID)
+ {
+ list.add(new IssuerKeyID(isCritical, keyID));
+ }
+
+ public PGPSignatureSubpacketVector generate()
+ {
+ return new PGPSignatureSubpacketVector(
+ (SignatureSubpacket[])list.toArray(new SignatureSubpacket[list.size()]));
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPSignatureSubpacketVector.java b/pg/src/main/java/org/spongycastle/openpgp/PGPSignatureSubpacketVector.java
new file mode 100644
index 00000000..3b48ccce
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPSignatureSubpacketVector.java
@@ -0,0 +1,308 @@
+package org.spongycastle.openpgp;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.spongycastle.bcpg.SignaturePacket;
+import org.spongycastle.bcpg.SignatureSubpacket;
+import org.spongycastle.bcpg.SignatureSubpacketTags;
+import org.spongycastle.bcpg.sig.Features;
+import org.spongycastle.bcpg.sig.IssuerKeyID;
+import org.spongycastle.bcpg.sig.KeyExpirationTime;
+import org.spongycastle.bcpg.sig.KeyFlags;
+import org.spongycastle.bcpg.sig.NotationData;
+import org.spongycastle.bcpg.sig.PreferredAlgorithms;
+import org.spongycastle.bcpg.sig.PrimaryUserID;
+import org.spongycastle.bcpg.sig.SignatureCreationTime;
+import org.spongycastle.bcpg.sig.SignatureExpirationTime;
+import org.spongycastle.bcpg.sig.SignerUserID;
+
+/**
+ * Container for a list of signature subpackets.
+ */
+public class PGPSignatureSubpacketVector
+{
+ SignatureSubpacket[] packets;
+
+ PGPSignatureSubpacketVector(
+ SignatureSubpacket[] packets)
+ {
+ this.packets = packets;
+ }
+
+ public SignatureSubpacket getSubpacket(
+ int type)
+ {
+ for (int i = 0; i != packets.length; i++)
+ {
+ if (packets[i].getType() == type)
+ {
+ return packets[i];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return true if a particular subpacket type exists.
+ *
+ * @param type type to look for.
+ * @return true if present, false otherwise.
+ */
+ public boolean hasSubpacket(
+ int type)
+ {
+ return getSubpacket(type) != null;
+ }
+
+ /**
+ * Return all signature subpackets of the passed in type.
+ * @param type subpacket type code
+ * @return an array of zero or more matching subpackets.
+ */
+ public SignatureSubpacket[] getSubpackets(
+ int type)
+ {
+ List list = new ArrayList();
+
+ for (int i = 0; i != packets.length; i++)
+ {
+ if (packets[i].getType() == type)
+ {
+ list.add(packets[i]);
+ }
+ }
+
+ return (SignatureSubpacket[])list.toArray(new SignatureSubpacket[]{});
+ }
+
+ public PGPSignatureList getEmbeddedSignatures()
+ throws PGPException
+ {
+ SignatureSubpacket[] sigs = getSubpackets(SignatureSubpacketTags.EMBEDDED_SIGNATURE);
+ ArrayList l = new ArrayList();
+
+ for (int i = 0; i < sigs.length; i++)
+ {
+ try
+ {
+ l.add(new PGPSignature(SignaturePacket.fromByteArray(sigs[i].getData())));
+ }
+ catch (IOException e)
+ {
+ throw new PGPException("Unable to parse signature packet: " + e.getMessage(), e);
+ }
+ }
+
+ return new PGPSignatureList((PGPSignature[])l.toArray(new PGPSignature[l.size()]));
+ }
+
+ public NotationData[] getNotationDataOccurrences()
+ {
+ SignatureSubpacket[] notations = getSubpackets(SignatureSubpacketTags.NOTATION_DATA);
+ NotationData[] vals = new NotationData[notations.length];
+ for (int i = 0; i < notations.length; i++)
+ {
+ vals[i] = (NotationData)notations[i];
+ }
+
+ return vals;
+ }
+
+ /**
+ * @deprecated use getNotationDataOccurrences()
+ */
+ public NotationData[] getNotationDataOccurences()
+ {
+ return getNotationDataOccurrences();
+ }
+
+ public long getIssuerKeyID()
+ {
+ SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.ISSUER_KEY_ID);
+
+ if (p == null)
+ {
+ return 0;
+ }
+
+ return ((IssuerKeyID)p).getKeyID();
+ }
+
+ public Date getSignatureCreationTime()
+ {
+ SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.CREATION_TIME);
+
+ if (p == null)
+ {
+ return null;
+ }
+
+ return ((SignatureCreationTime)p).getTime();
+ }
+
+ /**
+ * Return the number of seconds a signature is valid for after its creation date. A value of zero means
+ * the signature never expires.
+ *
+ * @return seconds a signature is valid for.
+ */
+ public long getSignatureExpirationTime()
+ {
+ SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.EXPIRE_TIME);
+
+ if (p == null)
+ {
+ return 0;
+ }
+
+ return ((SignatureExpirationTime)p).getTime();
+ }
+
+ /**
+ * Return the number of seconds a key is valid for after its creation date. A value of zero means
+ * the key never expires.
+ *
+ * @return seconds a key is valid for.
+ */
+ public long getKeyExpirationTime()
+ {
+ SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.KEY_EXPIRE_TIME);
+
+ if (p == null)
+ {
+ return 0;
+ }
+
+ return ((KeyExpirationTime)p).getTime();
+ }
+
+ public int[] getPreferredHashAlgorithms()
+ {
+ SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.PREFERRED_HASH_ALGS);
+
+ if (p == null)
+ {
+ return null;
+ }
+
+ return ((PreferredAlgorithms)p).getPreferences();
+ }
+
+ public int[] getPreferredSymmetricAlgorithms()
+ {
+ SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.PREFERRED_SYM_ALGS);
+
+ if (p == null)
+ {
+ return null;
+ }
+
+ return ((PreferredAlgorithms)p).getPreferences();
+ }
+
+ public int[] getPreferredCompressionAlgorithms()
+ {
+ SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.PREFERRED_COMP_ALGS);
+
+ if (p == null)
+ {
+ return null;
+ }
+
+ return ((PreferredAlgorithms)p).getPreferences();
+ }
+
+ public int getKeyFlags()
+ {
+ SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.KEY_FLAGS);
+
+ if (p == null)
+ {
+ return 0;
+ }
+
+ return ((KeyFlags)p).getFlags();
+ }
+
+ public String getSignerUserID()
+ {
+ SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.SIGNER_USER_ID);
+
+ if (p == null)
+ {
+ return null;
+ }
+
+ return ((SignerUserID)p).getID();
+ }
+
+ public boolean isPrimaryUserID()
+ {
+ PrimaryUserID primaryId = (PrimaryUserID)this.getSubpacket(SignatureSubpacketTags.PRIMARY_USER_ID);
+
+ if (primaryId != null)
+ {
+ return primaryId.isPrimaryUserID();
+ }
+
+ return false;
+ }
+
+ public int[] getCriticalTags()
+ {
+ int count = 0;
+
+ for (int i = 0; i != packets.length; i++)
+ {
+ if (packets[i].isCritical())
+ {
+ count++;
+ }
+ }
+
+ int[] list = new int[count];
+
+ count = 0;
+
+ for (int i = 0; i != packets.length; i++)
+ {
+ if (packets[i].isCritical())
+ {
+ list[count++] = packets[i].getType();
+ }
+ }
+
+ return list;
+ }
+
+ public Features getFeatures()
+ {
+ SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.FEATURES);
+
+ if (p == null)
+ {
+ return null;
+ }
+
+ return new Features(p.isCritical(), p.getData());
+ }
+
+ /**
+ * Return the number of packets this vector contains.
+ *
+ * @return size of the packet vector.
+ */
+ public int size()
+ {
+ return packets.length;
+ }
+
+ SignatureSubpacket[] toSubpacketArray()
+ {
+ return packets;
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPUserAttributeSubpacketVector.java b/pg/src/main/java/org/spongycastle/openpgp/PGPUserAttributeSubpacketVector.java
new file mode 100644
index 00000000..26d6c736
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPUserAttributeSubpacketVector.java
@@ -0,0 +1,93 @@
+package org.spongycastle.openpgp;
+
+import org.spongycastle.bcpg.UserAttributeSubpacket;
+import org.spongycastle.bcpg.UserAttributeSubpacketTags;
+import org.spongycastle.bcpg.attr.ImageAttribute;
+
+/**
+ * Container for a list of user attribute subpackets.
+ */
+public class PGPUserAttributeSubpacketVector
+{
+ UserAttributeSubpacket[] packets;
+
+ PGPUserAttributeSubpacketVector(
+ UserAttributeSubpacket[] packets)
+ {
+ this.packets = packets;
+ }
+
+ public UserAttributeSubpacket getSubpacket(
+ int type)
+ {
+ for (int i = 0; i != packets.length; i++)
+ {
+ if (packets[i].getType() == type)
+ {
+ return packets[i];
+ }
+ }
+
+ return null;
+ }
+
+ public ImageAttribute getImageAttribute()
+ {
+ UserAttributeSubpacket p = this.getSubpacket(UserAttributeSubpacketTags.IMAGE_ATTRIBUTE);
+
+ if (p == null)
+ {
+ return null;
+ }
+
+ return (ImageAttribute)p;
+ }
+
+ UserAttributeSubpacket[] toSubpacketArray()
+ {
+ return packets;
+ }
+
+ public boolean equals(
+ Object o)
+ {
+ if (o == this)
+ {
+ return true;
+ }
+
+ if (o instanceof PGPUserAttributeSubpacketVector)
+ {
+ PGPUserAttributeSubpacketVector other = (PGPUserAttributeSubpacketVector)o;
+
+ if (other.packets.length != packets.length)
+ {
+ return false;
+ }
+
+ for (int i = 0; i != packets.length; i++)
+ {
+ if (!other.packets[i].equals(packets[i]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public int hashCode()
+ {
+ int code = 0;
+
+ for (int i = 0; i != packets.length; i++)
+ {
+ code ^= packets[i].hashCode();
+ }
+
+ return code;
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPUserAttributeSubpacketVectorGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/PGPUserAttributeSubpacketVectorGenerator.java
new file mode 100644
index 00000000..66af84b3
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPUserAttributeSubpacketVectorGenerator.java
@@ -0,0 +1,27 @@
+package org.spongycastle.openpgp;
+
+import org.spongycastle.bcpg.UserAttributeSubpacket;
+import org.spongycastle.bcpg.attr.ImageAttribute;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PGPUserAttributeSubpacketVectorGenerator
+{
+ private List list = new ArrayList();
+
+ public void setImageAttribute(int imageType, byte[] imageData)
+ {
+ if (imageData == null)
+ {
+ throw new IllegalArgumentException("attempt to set null image");
+ }
+
+ list.add(new ImageAttribute(imageType, imageData));
+ }
+
+ public PGPUserAttributeSubpacketVector generate()
+ {
+ return new PGPUserAttributeSubpacketVector((UserAttributeSubpacket[])list.toArray(new UserAttributeSubpacket[list.size()]));
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPUtil.java b/pg/src/main/java/org/spongycastle/openpgp/PGPUtil.java
new file mode 100644
index 00000000..5f2c2619
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPUtil.java
@@ -0,0 +1,403 @@
+package org.spongycastle.openpgp;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.security.Signature;
+import java.util.Date;
+
+import org.spongycastle.asn1.ASN1InputStream;
+import org.spongycastle.asn1.ASN1Integer;
+import org.spongycastle.asn1.ASN1Sequence;
+import org.spongycastle.bcpg.ArmoredInputStream;
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.bcpg.MPInteger;
+import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.util.encoders.Base64;
+
+/**
+ * PGP utilities.
+ */
+public class PGPUtil
+ implements HashAlgorithmTags
+{
+ private static String defProvider = "SC";
+
+ /**
+ * Return the JCA/JCE provider that will be used by factory classes in situations where a
+ * provider must be determined on the fly.
+ *
+ * @return the name of the default provider.
+ */
+ public static String getDefaultProvider()
+ {
+ // TODO: This is unused?
+ return defProvider;
+ }
+
+ /**
+ * Set the provider to be used by the package when it is necessary to find one on the fly.
+ *
+ * @param provider the name of the JCA/JCE provider to use by default.
+ */
+ public static void setDefaultProvider(
+ String provider)
+ {
+ defProvider = provider;
+ }
+
+ static MPInteger[] dsaSigToMpi(
+ byte[] encoding)
+ throws PGPException
+ {
+ ASN1InputStream aIn = new ASN1InputStream(encoding);
+
+ ASN1Integer i1;
+ ASN1Integer i2;
+
+ try
+ {
+ ASN1Sequence s = (ASN1Sequence)aIn.readObject();
+
+ i1 = (ASN1Integer)s.getObjectAt(0);
+ i2 = (ASN1Integer)s.getObjectAt(1);
+ }
+ catch (IOException e)
+ {
+ throw new PGPException("exception encoding signature", e);
+ }
+
+ MPInteger[] values = new MPInteger[2];
+
+ values[0] = new MPInteger(i1.getValue());
+ values[1] = new MPInteger(i2.getValue());
+
+ return values;
+ }
+
+ /**
+ * Translates a PGP {@link HashAlgorithmTags hash algorithm tag} to a JCA {@link MessageDigest}
+ * algorithm name
+ *
+ * @param hashAlgorithm the hash algorithm identifier.
+ * @return the corresponding JCA algorithm name.
+ * @throws PGPException if the hash algorithm is unknown.
+ */
+ static String getDigestName(
+ int hashAlgorithm)
+ throws PGPException
+ {
+ switch (hashAlgorithm)
+ {
+ case HashAlgorithmTags.SHA1:
+ return "SHA1";
+ case HashAlgorithmTags.MD2:
+ return "MD2";
+ case HashAlgorithmTags.MD5:
+ return "MD5";
+ case HashAlgorithmTags.RIPEMD160:
+ return "RIPEMD160";
+ case HashAlgorithmTags.SHA256:
+ return "SHA256";
+ case HashAlgorithmTags.SHA384:
+ return "SHA384";
+ case HashAlgorithmTags.SHA512:
+ return "SHA512";
+ case HashAlgorithmTags.SHA224:
+ return "SHA224";
+ default:
+ throw new PGPException("unknown hash algorithm tag in getDigestName: " + hashAlgorithm);
+ }
+ }
+
+ /**
+ * Translates a PGP {@link PublicKeyAlgorithmTags public key algorithm tag} and a
+ * {@link HashAlgorithmTags hash algorithm tag} to a JCA {@link Signature} algorithm name.
+ *
+ * @param keyAlgorithm they public key algorithm identifier.
+ * @param hashAlgorithm the hash algorithm identifier.
+ * @return the corresponding JCA algorithm name.
+ * @throws PGPException if the public key or hash algorithm is unknown.
+ */
+ static String getSignatureName(
+ int keyAlgorithm,
+ int hashAlgorithm)
+ throws PGPException
+ {
+ String encAlg;
+
+ switch (keyAlgorithm)
+ {
+ case PublicKeyAlgorithmTags.RSA_GENERAL:
+ case PublicKeyAlgorithmTags.RSA_SIGN:
+ encAlg = "RSA";
+ break;
+ case PublicKeyAlgorithmTags.DSA:
+ encAlg = "DSA";
+ break;
+ case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: // in some malformed cases.
+ case PublicKeyAlgorithmTags.ELGAMAL_GENERAL:
+ encAlg = "ElGamal";
+ break;
+ default:
+ throw new PGPException("unknown algorithm tag in signature:" + keyAlgorithm);
+ }
+
+ return getDigestName(hashAlgorithm) + "with" + encAlg;
+ }
+
+ /**
+ * Generates a random key for a {@link SymmetricKeyAlgorithmTags symmetric encryption algorithm}
+ * .
+ *
+ * @param algorithm the symmetric key algorithm identifier.
+ * @param random a source of random data.
+ * @return a key of the length required by the specified encryption algorithm.
+ * @throws PGPException if the encryption algorithm is unknown.
+ */
+ public static byte[] makeRandomKey(
+ int algorithm,
+ SecureRandom random)
+ throws PGPException
+ {
+ int keySize = 0;
+
+ switch (algorithm)
+ {
+ case SymmetricKeyAlgorithmTags.TRIPLE_DES:
+ keySize = 192;
+ break;
+ case SymmetricKeyAlgorithmTags.IDEA:
+ keySize = 128;
+ break;
+ case SymmetricKeyAlgorithmTags.CAST5:
+ keySize = 128;
+ break;
+ case SymmetricKeyAlgorithmTags.BLOWFISH:
+ keySize = 128;
+ break;
+ case SymmetricKeyAlgorithmTags.SAFER:
+ keySize = 128;
+ break;
+ case SymmetricKeyAlgorithmTags.DES:
+ keySize = 64;
+ break;
+ case SymmetricKeyAlgorithmTags.AES_128:
+ keySize = 128;
+ break;
+ case SymmetricKeyAlgorithmTags.AES_192:
+ keySize = 192;
+ break;
+ case SymmetricKeyAlgorithmTags.AES_256:
+ keySize = 256;
+ break;
+ case SymmetricKeyAlgorithmTags.CAMELLIA_128:
+ keySize = 128;
+ break;
+ case SymmetricKeyAlgorithmTags.CAMELLIA_192:
+ keySize = 192;
+ break;
+ case SymmetricKeyAlgorithmTags.CAMELLIA_256:
+ keySize = 256;
+ break;
+ case SymmetricKeyAlgorithmTags.TWOFISH:
+ keySize = 256;
+ break;
+ default:
+ throw new PGPException("unknown symmetric algorithm: " + algorithm);
+ }
+
+ byte[] keyBytes = new byte[(keySize + 7) / 8];
+
+ random.nextBytes(keyBytes);
+
+ return keyBytes;
+ }
+
+ /**
+ * Write out the contents of the provided file as a literal data packet.
+ *
+ * @param out the stream to write the literal data to.
+ * @param fileType the {@link PGPLiteralData} type to use for the file data.
+ * @param file the file to write the contents of.
+ *
+ * @throws IOException if an error occurs reading the file or writing to the output stream.
+ */
+ public static void writeFileToLiteralData(
+ OutputStream out,
+ char fileType,
+ File file)
+ throws IOException
+ {
+ PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
+ OutputStream pOut = lData.open(out, fileType, file);
+ pipeFileContents(file, pOut, 4096);
+ }
+
+ /**
+ * Write out the contents of the provided file as a literal data packet in partial packet
+ * format.
+ *
+ * @param out the stream to write the literal data to.
+ * @param fileType the {@link PGPLiteralData} type to use for the file data.
+ * @param file the file to write the contents of.
+ * @param buffer buffer to be used to chunk the file into partial packets.
+ * @see {@link PGPLiteralDataGenerator#open(OutputStream, char, String, Date, byte[])}.
+ *
+ * @throws IOException if an error occurs reading the file or writing to the output stream.
+ */
+ public static void writeFileToLiteralData(
+ OutputStream out,
+ char fileType,
+ File file,
+ byte[] buffer)
+ throws IOException
+ {
+ PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
+ OutputStream pOut = lData.open(out, fileType, file.getName(), new Date(file.lastModified()), buffer);
+ pipeFileContents(file, pOut, buffer.length);
+ }
+
+ private static void pipeFileContents(File file, OutputStream pOut, int bufSize) throws IOException
+ {
+ FileInputStream in = new FileInputStream(file);
+ byte[] buf = new byte[bufSize];
+
+ int len;
+ while ((len = in.read(buf)) > 0)
+ {
+ pOut.write(buf, 0, len);
+ }
+
+ pOut.close();
+ in.close();
+ }
+
+ private static final int READ_AHEAD = 60;
+
+ private static boolean isPossiblyBase64(
+ int ch)
+ {
+ return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')
+ || (ch >= '0' && ch <= '9') || (ch == '+') || (ch == '/')
+ || (ch == '\r') || (ch == '\n');
+ }
+
+ /**
+ * Obtains a stream that can be used to read PGP data from the provided stream.
+ * <p/>
+ * If the initial bytes of the underlying stream are binary PGP encodings, then the stream will
+ * be returned directly, otherwise an {@link ArmoredInputStream} is used to wrap the provided
+ * stream and remove ASCII-Armored encoding.
+ *
+ * @param in the stream to be checked and possibly wrapped.
+ * @return a stream that will return PGP binary encoded data.
+ * @throws IOException if an error occurs reading the stream, or initalising the
+ * {@link ArmoredInputStream}.
+ */
+ public static InputStream getDecoderStream(
+ InputStream in)
+ throws IOException
+ {
+ if (!in.markSupported())
+ {
+ in = new BufferedInputStreamExt(in);
+ }
+
+ in.mark(READ_AHEAD);
+
+ int ch = in.read();
+
+
+ if ((ch & 0x80) != 0)
+ {
+ in.reset();
+
+ return in;
+ }
+ else
+ {
+ if (!isPossiblyBase64(ch))
+ {
+ in.reset();
+
+ return new ArmoredInputStream(in);
+ }
+
+ byte[] buf = new byte[READ_AHEAD];
+ int count = 1;
+ int index = 1;
+
+ buf[0] = (byte)ch;
+ while (count != READ_AHEAD && (ch = in.read()) >= 0)
+ {
+ if (!isPossiblyBase64(ch))
+ {
+ in.reset();
+
+ return new ArmoredInputStream(in);
+ }
+
+ if (ch != '\n' && ch != '\r')
+ {
+ buf[index++] = (byte)ch;
+ }
+
+ count++;
+ }
+
+ in.reset();
+
+ //
+ // nothing but new lines, little else, assume regular armoring
+ //
+ if (count < 4)
+ {
+ return new ArmoredInputStream(in);
+ }
+
+ //
+ // test our non-blank data
+ //
+ byte[] firstBlock = new byte[8];
+
+ System.arraycopy(buf, 0, firstBlock, 0, firstBlock.length);
+
+ byte[] decoded = Base64.decode(firstBlock);
+
+ //
+ // it's a base64 PGP block.
+ //
+ if ((decoded[0] & 0x80) != 0)
+ {
+ return new ArmoredInputStream(in, false);
+ }
+
+ return new ArmoredInputStream(in);
+ }
+ }
+
+ static class BufferedInputStreamExt extends BufferedInputStream
+ {
+ BufferedInputStreamExt(InputStream input)
+ {
+ super(input);
+ }
+
+ public synchronized int available() throws IOException
+ {
+ int result = super.available();
+ if (result < 0)
+ {
+ result = Integer.MAX_VALUE;
+ }
+ return result;
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/PGPV3SignatureGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/PGPV3SignatureGenerator.java
new file mode 100644
index 00000000..cb6cb921
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/PGPV3SignatureGenerator.java
@@ -0,0 +1,201 @@
+package org.spongycastle.openpgp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.util.Date;
+
+import org.spongycastle.bcpg.MPInteger;
+import org.spongycastle.bcpg.OnePassSignaturePacket;
+import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
+import org.spongycastle.bcpg.SignaturePacket;
+import org.spongycastle.openpgp.operator.PGPContentSigner;
+import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
+
+/**
+ * Generator for old style PGP V3 Signatures.
+ */
+public class PGPV3SignatureGenerator
+{
+ private byte lastb;
+ private OutputStream sigOut;
+ private PGPContentSignerBuilder contentSignerBuilder;
+ private PGPContentSigner contentSigner;
+ private int sigType;
+ private int providedKeyAlgorithm = -1;
+
+ /**
+ * Create a signature generator built on the passed in contentSignerBuilder.
+ *
+ * @param contentSignerBuilder builder to produce PGPContentSigner objects for generating signatures.
+ */
+ public PGPV3SignatureGenerator(
+ PGPContentSignerBuilder contentSignerBuilder)
+ {
+ this.contentSignerBuilder = contentSignerBuilder;
+ }
+
+ /**
+ * Initialise the generator for signing.
+ *
+ * @param signatureType
+ * @param key
+ * @throws PGPException
+ */
+ public void init(
+ int signatureType,
+ PGPPrivateKey key)
+ throws PGPException
+ {
+ contentSigner = contentSignerBuilder.build(signatureType, key);
+ sigOut = contentSigner.getOutputStream();
+ sigType = contentSigner.getType();
+ lastb = 0;
+
+ if (providedKeyAlgorithm >= 0 && providedKeyAlgorithm != contentSigner.getKeyAlgorithm())
+ {
+ throw new PGPException("key algorithm mismatch");
+ }
+ }
+
+ public void update(
+ byte b)
+ {
+ if (sigType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
+ {
+ if (b == '\r')
+ {
+ byteUpdate((byte)'\r');
+ byteUpdate((byte)'\n');
+ }
+ else if (b == '\n')
+ {
+ if (lastb != '\r')
+ {
+ byteUpdate((byte)'\r');
+ byteUpdate((byte)'\n');
+ }
+ }
+ else
+ {
+ byteUpdate(b);
+ }
+
+ lastb = b;
+ }
+ else
+ {
+ byteUpdate(b);
+ }
+ }
+
+ public void update(
+ byte[] b)
+ {
+ this.update(b, 0, b.length);
+ }
+
+ public void update(
+ byte[] b,
+ int off,
+ int len)
+ {
+ if (sigType == PGPSignature.CANONICAL_TEXT_DOCUMENT)
+ {
+ int finish = off + len;
+
+ for (int i = off; i != finish; i++)
+ {
+ this.update(b[i]);
+ }
+ }
+ else
+ {
+ blockUpdate(b, off, len);
+ }
+ }
+
+ private void byteUpdate(byte b)
+ {
+ try
+ {
+ sigOut.write(b);
+ }
+ catch (IOException e)
+ {
+ throw new PGPRuntimeOperationException("unable to update signature: " + e.getMessage(), e);
+ }
+ }
+
+ private void blockUpdate(byte[] block, int off, int len)
+ {
+ try
+ {
+ sigOut.write(block, off, len);
+ }
+ catch (IOException e)
+ {
+ throw new PGPRuntimeOperationException("unable to update signature: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Return the one pass header associated with the current signature.
+ *
+ * @param isNested
+ * @return PGPOnePassSignature
+ * @throws PGPException
+ */
+ public PGPOnePassSignature generateOnePassVersion(
+ boolean isNested)
+ throws PGPException
+ {
+ return new PGPOnePassSignature(new OnePassSignaturePacket(sigType, contentSigner.getHashAlgorithm(), contentSigner.getKeyAlgorithm(), contentSigner.getKeyID(), isNested));
+ }
+
+ /**
+ * Return a V3 signature object containing the current signature state.
+ *
+ * @return PGPSignature
+ * @throws PGPException
+ */
+ public PGPSignature generate()
+ throws PGPException
+ {
+ long creationTime = new Date().getTime() / 1000;
+
+ ByteArrayOutputStream sOut = new ByteArrayOutputStream();
+
+ sOut.write(sigType);
+ sOut.write((byte)(creationTime >> 24));
+ sOut.write((byte)(creationTime >> 16));
+ sOut.write((byte)(creationTime >> 8));
+ sOut.write((byte)creationTime);
+
+ byte[] hData = sOut.toByteArray();
+
+ blockUpdate(hData, 0, hData.length);
+
+ MPInteger[] sigValues;
+ if (contentSigner.getKeyAlgorithm() == PublicKeyAlgorithmTags.RSA_SIGN
+ || contentSigner.getKeyAlgorithm() == PublicKeyAlgorithmTags.RSA_GENERAL)
+ // an RSA signature
+ {
+ sigValues = new MPInteger[1];
+ sigValues[0] = new MPInteger(new BigInteger(1, contentSigner.getSignature()));
+ }
+ else
+ {
+ sigValues = PGPUtil.dsaSigToMpi(contentSigner.getSignature());
+ }
+
+ byte[] digest = contentSigner.getDigest();
+ byte[] fingerPrint = new byte[2];
+
+ fingerPrint[0] = digest[0];
+ fingerPrint[1] = digest[1];
+
+ return new PGPSignature(new SignaturePacket(3, contentSigner.getType(), contentSigner.getKeyID(), contentSigner.getKeyAlgorithm(), contentSigner.getHashAlgorithm(), creationTime * 1000, fingerPrint, sigValues));
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/SXprUtils.java b/pg/src/main/java/org/spongycastle/openpgp/SXprUtils.java
new file mode 100644
index 00000000..0da315ec
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/SXprUtils.java
@@ -0,0 +1,101 @@
+package org.spongycastle.openpgp;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.bcpg.S2K;
+import org.spongycastle.util.io.Streams;
+
+/**
+ * Utility functions for looking a S-expression keys. This class will move when it finds a better home!
+ * <p>
+ * Format documented here:
+ * http://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=agent/keyformat.txt;h=42c4b1f06faf1bbe71ffadc2fee0fad6bec91a97;hb=refs/heads/master
+ * </p>
+ */
+class SXprUtils
+{
+ private static int readLength(InputStream in, int ch)
+ throws IOException
+ {
+ int len = ch - '0';
+
+ while ((ch = in.read()) >= 0 && ch != ':')
+ {
+ len = len * 10 + ch - '0';
+ }
+
+ return len;
+ }
+
+ static String readString(InputStream in, int ch)
+ throws IOException
+ {
+ int len = readLength(in, ch);
+
+ char[] chars = new char[len];
+
+ for (int i = 0; i != chars.length; i++)
+ {
+ chars[i] = (char)in.read();
+ }
+
+ return new String(chars);
+ }
+
+ static byte[] readBytes(InputStream in, int ch)
+ throws IOException
+ {
+ int len = readLength(in, ch);
+
+ byte[] data = new byte[len];
+
+ Streams.readFully(in, data);
+
+ return data;
+ }
+
+ static S2K parseS2K(InputStream in)
+ throws IOException
+ {
+ skipOpenParenthesis(in);
+
+ String alg = readString(in, in.read());
+ byte[] iv = readBytes(in, in.read());
+ final long iterationCount = Long.parseLong(readString(in, in.read()));
+
+ skipCloseParenthesis(in);
+
+ // we have to return the actual iteration count provided.
+ S2K s2k = new S2K(HashAlgorithmTags.SHA1, iv, (int)iterationCount)
+ {
+ public long getIterationCount()
+ {
+ return iterationCount;
+ }
+ };
+
+ return s2k;
+ }
+
+ static void skipOpenParenthesis(InputStream in)
+ throws IOException
+ {
+ int ch = in.read();
+ if (ch != '(')
+ {
+ throw new IOException("unknown character encountered");
+ }
+ }
+
+ static void skipCloseParenthesis(InputStream in)
+ throws IOException
+ {
+ int ch = in.read();
+ if (ch != ')')
+ {
+ throw new IOException("unknown character encountered");
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/StreamGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/StreamGenerator.java
new file mode 100644
index 00000000..d65339f3
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/StreamGenerator.java
@@ -0,0 +1,16 @@
+package org.spongycastle.openpgp;
+
+import java.io.IOException;
+
+/**
+ * Callback interface for generators that produce a stream to be informed when the stream has been
+ * closed by the client.
+ */
+interface StreamGenerator
+{
+ /**
+ * Signal that the stream has been closed.
+ */
+ void close()
+ throws IOException;
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/WrappedGeneratorStream.java b/pg/src/main/java/org/spongycastle/openpgp/WrappedGeneratorStream.java
new file mode 100644
index 00000000..f5360d51
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/WrappedGeneratorStream.java
@@ -0,0 +1,46 @@
+package org.spongycastle.openpgp;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+class WrappedGeneratorStream
+ extends OutputStream
+{
+ private final OutputStream _out;
+ private final StreamGenerator _sGen;
+
+ public WrappedGeneratorStream(OutputStream out, StreamGenerator sGen)
+ {
+ _out = out;
+ _sGen = sGen;
+ }
+ public void write(byte[] bytes)
+ throws IOException
+ {
+ _out.write(bytes);
+ }
+
+ public void write(byte[] bytes, int offset, int length)
+ throws IOException
+ {
+ _out.write(bytes, offset, length);
+ }
+
+ public void write(int b)
+ throws IOException
+ {
+ _out.write(b);
+ }
+
+ public void flush()
+ throws IOException
+ {
+ _out.flush();
+ }
+
+ public void close()
+ throws IOException
+ {
+ _sGen.close();
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/bc/BcPGPObjectFactory.java b/pg/src/main/java/org/spongycastle/openpgp/bc/BcPGPObjectFactory.java
new file mode 100644
index 00000000..d11c71b3
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/bc/BcPGPObjectFactory.java
@@ -0,0 +1,35 @@
+package org.spongycastle.openpgp.bc;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+import org.spongycastle.openpgp.PGPObjectFactory;
+import org.spongycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
+
+/**
+ * {@link PGPObjectFactory} that uses the Bouncy Castle lightweight API to implement cryptographic
+ * primitives.
+ */
+public class BcPGPObjectFactory
+ extends PGPObjectFactory
+{
+ /**
+ * Construct an object factory to read PGP objects from encoded data.
+ *
+ * @param encoded the PGP encoded data.
+ */
+ public BcPGPObjectFactory(byte[] encoded)
+ {
+ this(new ByteArrayInputStream(encoded));
+ }
+
+ /**
+ * Construct an object factory to read PGP objects from a stream.
+ *
+ * @param in the stream containing PGP encoded objects.
+ */
+ public BcPGPObjectFactory(InputStream in)
+ {
+ super(in, new BcKeyFingerprintCalculator());
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/bc/BcPGPPublicKeyRing.java b/pg/src/main/java/org/spongycastle/openpgp/bc/BcPGPPublicKeyRing.java
new file mode 100644
index 00000000..cb7f3839
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/bc/BcPGPPublicKeyRing.java
@@ -0,0 +1,26 @@
+package org.spongycastle.openpgp.bc;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator;
+import org.spongycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
+
+public class BcPGPPublicKeyRing
+ extends PGPPublicKeyRing
+{
+ private static KeyFingerPrintCalculator fingerPrintCalculator = new BcKeyFingerprintCalculator();
+
+ public BcPGPPublicKeyRing(byte[] encoding)
+ throws IOException
+ {
+ super(encoding, fingerPrintCalculator);
+ }
+
+ public BcPGPPublicKeyRing(InputStream in)
+ throws IOException
+ {
+ super(in, fingerPrintCalculator);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/bc/BcPGPPublicKeyRingCollection.java b/pg/src/main/java/org/spongycastle/openpgp/bc/BcPGPPublicKeyRingCollection.java
new file mode 100644
index 00000000..e769f0ad
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/bc/BcPGPPublicKeyRingCollection.java
@@ -0,0 +1,32 @@
+package org.spongycastle.openpgp.bc;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPublicKeyRingCollection;
+import org.spongycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
+
+public class BcPGPPublicKeyRingCollection
+ extends PGPPublicKeyRingCollection
+{
+ public BcPGPPublicKeyRingCollection(byte[] encoding)
+ throws IOException, PGPException
+ {
+ this(new ByteArrayInputStream(encoding));
+ }
+
+ public BcPGPPublicKeyRingCollection(InputStream in)
+ throws IOException, PGPException
+ {
+ super(in, new BcKeyFingerprintCalculator());
+ }
+
+ public BcPGPPublicKeyRingCollection(Collection collection)
+ throws IOException, PGPException
+ {
+ super(collection);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/bc/BcPGPSecretKeyRing.java b/pg/src/main/java/org/spongycastle/openpgp/bc/BcPGPSecretKeyRing.java
new file mode 100644
index 00000000..2286b78c
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/bc/BcPGPSecretKeyRing.java
@@ -0,0 +1,27 @@
+package org.spongycastle.openpgp.bc;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator;
+import org.spongycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
+
+public class BcPGPSecretKeyRing
+ extends PGPSecretKeyRing
+{
+ private static KeyFingerPrintCalculator fingerPrintCalculator = new BcKeyFingerprintCalculator();
+
+ public BcPGPSecretKeyRing(byte[] encoding)
+ throws IOException, PGPException
+ {
+ super(encoding, fingerPrintCalculator);
+ }
+
+ public BcPGPSecretKeyRing(InputStream in)
+ throws IOException, PGPException
+ {
+ super(in, fingerPrintCalculator);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/bc/BcPGPSecretKeyRingCollection.java b/pg/src/main/java/org/spongycastle/openpgp/bc/BcPGPSecretKeyRingCollection.java
new file mode 100644
index 00000000..64688e92
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/bc/BcPGPSecretKeyRingCollection.java
@@ -0,0 +1,32 @@
+package org.spongycastle.openpgp.bc;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPSecretKeyRingCollection;
+import org.spongycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
+
+public class BcPGPSecretKeyRingCollection
+ extends PGPSecretKeyRingCollection
+{
+ public BcPGPSecretKeyRingCollection(byte[] encoding)
+ throws IOException, PGPException
+ {
+ this(new ByteArrayInputStream(encoding));
+ }
+
+ public BcPGPSecretKeyRingCollection(InputStream in)
+ throws IOException, PGPException
+ {
+ super(in, new BcKeyFingerprintCalculator());
+ }
+
+ public BcPGPSecretKeyRingCollection(Collection collection)
+ throws IOException, PGPException
+ {
+ super(collection);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/examples/ByteArrayHandler.java b/pg/src/main/java/org/spongycastle/openpgp/examples/ByteArrayHandler.java
new file mode 100644
index 00000000..52b2327d
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/examples/ByteArrayHandler.java
@@ -0,0 +1,206 @@
+package org.spongycastle.openpgp.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.util.Date;
+
+import org.spongycastle.bcpg.ArmoredOutputStream;
+import org.spongycastle.bcpg.CompressionAlgorithmTags;
+import org.spongycastle.jce.provider.BouncyCastleProvider;
+import org.spongycastle.openpgp.PGPCompressedData;
+import org.spongycastle.openpgp.PGPCompressedDataGenerator;
+import org.spongycastle.openpgp.PGPEncryptedDataGenerator;
+import org.spongycastle.openpgp.PGPEncryptedDataList;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPLiteralData;
+import org.spongycastle.openpgp.PGPLiteralDataGenerator;
+import org.spongycastle.openpgp.PGPPBEEncryptedData;
+import org.spongycastle.openpgp.PGPUtil;
+import org.spongycastle.openpgp.jcajce.JcaPGPObjectFactory;
+import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
+import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
+import org.spongycastle.util.io.Streams;
+
+/**
+ * Simple routine to encrypt and decrypt using a passphrase.
+ * This service routine provides the basic PGP services between
+ * byte arrays.
+ *
+ * Note: this code plays no attention to -CONSOLE in the file name
+ * the specification of "_CONSOLE" in the filename.
+ * It also expects that a single pass phrase will have been used.
+ *
+ */
+public class ByteArrayHandler
+{
+ /**
+ * decrypt the passed in message stream
+ *
+ * @param encrypted The message to be decrypted.
+ * @param passPhrase Pass phrase (key)
+ *
+ * @return Clear text as a byte array. I18N considerations are
+ * not handled by this routine
+ * @exception IOException
+ * @exception PGPException
+ * @exception NoSuchProviderException
+ */
+ public static byte[] decrypt(
+ byte[] encrypted,
+ char[] passPhrase)
+ throws IOException, PGPException, NoSuchProviderException
+ {
+ InputStream in = new ByteArrayInputStream(encrypted);
+
+ in = PGPUtil.getDecoderStream(in);
+
+ JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in);
+ PGPEncryptedDataList enc;
+ Object o = pgpF.nextObject();
+
+ //
+ // the first object might be a PGP marker packet.
+ //
+ if (o instanceof PGPEncryptedDataList)
+ {
+ enc = (PGPEncryptedDataList)o;
+ }
+ else
+ {
+ enc = (PGPEncryptedDataList)pgpF.nextObject();
+ }
+
+ PGPPBEEncryptedData pbe = (PGPPBEEncryptedData)enc.get(0);
+
+ InputStream clear = pbe.getDataStream(new JcePBEDataDecryptorFactoryBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider("SC").build()).setProvider("SC").build(passPhrase));
+
+ JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(clear);
+
+ PGPCompressedData cData = (PGPCompressedData)pgpFact.nextObject();
+
+ pgpFact = new JcaPGPObjectFactory(cData.getDataStream());
+
+ PGPLiteralData ld = (PGPLiteralData)pgpFact.nextObject();
+
+ return Streams.readAll(ld.getInputStream());
+ }
+
+ /**
+ * Simple PGP encryptor between byte[].
+ *
+ * @param clearData The test to be encrypted
+ * @param passPhrase The pass phrase (key). This method assumes that the
+ * key is a simple pass phrase, and does not yet support
+ * RSA or more sophisiticated keying.
+ * @param fileName File name. This is used in the Literal Data Packet (tag 11)
+ * which is really inly important if the data is to be
+ * related to a file to be recovered later. Because this
+ * routine does not know the source of the information, the
+ * caller can set something here for file name use that
+ * will be carried. If this routine is being used to
+ * encrypt SOAP MIME bodies, for example, use the file name from the
+ * MIME type, if applicable. Or anything else appropriate.
+ *
+ * @param armor
+ *
+ * @return encrypted data.
+ * @exception IOException
+ * @exception PGPException
+ * @exception NoSuchProviderException
+ */
+ public static byte[] encrypt(
+ byte[] clearData,
+ char[] passPhrase,
+ String fileName,
+ int algorithm,
+ boolean armor)
+ throws IOException, PGPException, NoSuchProviderException
+ {
+ if (fileName == null)
+ {
+ fileName= PGPLiteralData.CONSOLE;
+ }
+
+ byte[] compressedData = compress(clearData, fileName, CompressionAlgorithmTags.ZIP);
+
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ OutputStream out = bOut;
+ if (armor)
+ {
+ out = new ArmoredOutputStream(out);
+ }
+
+ PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(new JcePGPDataEncryptorBuilder(algorithm).setSecureRandom(new SecureRandom()).setProvider("SC"));
+ encGen.addMethod(new JcePBEKeyEncryptionMethodGenerator(passPhrase).setProvider("SC"));
+
+ OutputStream encOut = encGen.open(out, compressedData.length);
+
+ encOut.write(compressedData);
+ encOut.close();
+
+ if (armor)
+ {
+ out.close();
+ }
+
+ return bOut.toByteArray();
+ }
+
+ private static byte[] compress(byte[] clearData, String fileName, int algorithm) throws IOException
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(algorithm);
+ OutputStream cos = comData.open(bOut); // open it with the final destination
+
+ PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
+
+ // we want to generate compressed data. This might be a user option later,
+ // in which case we would pass in bOut.
+ OutputStream pOut = lData.open(cos, // the compressed output stream
+ PGPLiteralData.BINARY,
+ fileName, // "filename" to store
+ clearData.length, // length of clear data
+ new Date() // current time
+ );
+
+ pOut.write(clearData);
+ pOut.close();
+
+ comData.close();
+
+ return bOut.toByteArray();
+ }
+
+ public static void main(String[] args) throws Exception
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ String passPhrase = "Dick Beck";
+ char[] passArray = passPhrase.toCharArray();
+
+ byte[] original = "Hello world".getBytes();
+ System.out.println("Starting PGP test");
+ byte[] encrypted = encrypt(original, passArray, "iway", PGPEncryptedDataGenerator.CAST5, true);
+
+ System.out.println("\nencrypted data = '"+new String(encrypted)+"'");
+ byte[] decrypted= decrypt(encrypted,passArray);
+
+ System.out.println("\ndecrypted data = '"+new String(decrypted)+"'");
+
+ encrypted = encrypt(original, passArray, "iway", PGPEncryptedDataGenerator.AES_256, false);
+
+ System.out.println("\nencrypted data = '"+new String(org.spongycastle.util.encoders.Hex.encode(encrypted))+"'");
+ decrypted= decrypt(encrypted, passArray);
+
+ System.out.println("\ndecrypted data = '"+new String(decrypted)+"'");
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java b/pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java
new file mode 100644
index 00000000..62f53539
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java
@@ -0,0 +1,391 @@
+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;
+
+/**
+ * A simple utility class that creates clear signed files and verifies them.
+ * <p>
+ * To sign a file: ClearSignedFileProcessor -s fileName secretKey passPhrase.<br>
+ * <p>
+ * 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);
+ }
+ }
+
+ 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 = System.getProperty("line.separator");
+ 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]");
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/examples/DSAElGamalKeyRingGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/examples/DSAElGamalKeyRingGenerator.java
new file mode 100644
index 00000000..e67e6f22
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/examples/DSAElGamalKeyRingGenerator.java
@@ -0,0 +1,139 @@
+package org.spongycastle.openpgp.examples;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchProviderException;
+import java.security.Security;
+import java.security.SignatureException;
+import java.util.Date;
+
+import org.spongycastle.bcpg.ArmoredOutputStream;
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.jce.provider.BouncyCastleProvider;
+import org.spongycastle.jce.spec.ElGamalParameterSpec;
+import org.spongycastle.openpgp.PGPEncryptedData;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPKeyPair;
+import org.spongycastle.openpgp.PGPKeyRingGenerator;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPSignature;
+import org.spongycastle.openpgp.operator.PGPDigestCalculator;
+import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
+import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
+
+/**
+ * A simple utility class that generates a public/secret keyring containing a DSA signing
+ * key and an El Gamal key for encryption.
+ * <p>
+ * usage: DSAElGamalKeyRingGenerator [-a] identity passPhrase
+ * <p>
+ * Where identity is the name to be associated with the public key. The keys are placed
+ * in the files pub.[asc|bpg] and secret.[asc|bpg].
+ * <p>
+ * <b>Note</b>: this example encrypts the secret key using AES_256, many PGP products still
+ * do not support this, if you are having problems importing keys try changing the algorithm
+ * id to PGPEncryptedData.CAST5. CAST5 is more widely supported.
+ */
+public class DSAElGamalKeyRingGenerator
+{
+ private static void exportKeyPair(
+ OutputStream secretOut,
+ OutputStream publicOut,
+ KeyPair dsaKp,
+ KeyPair elgKp,
+ String identity,
+ char[] passPhrase,
+ boolean armor)
+ throws IOException, InvalidKeyException, NoSuchProviderException, SignatureException, PGPException
+ {
+ if (armor)
+ {
+ secretOut = new ArmoredOutputStream(secretOut);
+ }
+
+ PGPKeyPair dsaKeyPair = new JcaPGPKeyPair(PGPPublicKey.DSA, dsaKp, new Date());
+ PGPKeyPair elgKeyPair = new JcaPGPKeyPair(PGPPublicKey.ELGAMAL_ENCRYPT, elgKp, new Date());
+ PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1);
+ PGPKeyRingGenerator keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, dsaKeyPair,
+ identity, sha1Calc, null, null, new JcaPGPContentSignerBuilder(dsaKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1), new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256, sha1Calc).setProvider("SC").build(passPhrase));
+
+ keyRingGen.addSubKey(elgKeyPair);
+
+ keyRingGen.generateSecretKeyRing().encode(secretOut);
+
+ secretOut.close();
+
+ if (armor)
+ {
+ publicOut = new ArmoredOutputStream(publicOut);
+ }
+
+ keyRingGen.generatePublicKeyRing().encode(publicOut);
+
+ publicOut.close();
+ }
+
+ public static void main(
+ String[] args)
+ throws Exception
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ if (args.length < 2)
+ {
+ System.out.println("DSAElGamalKeyRingGenerator [-a] identity passPhrase");
+ System.exit(0);
+ }
+
+ KeyPairGenerator dsaKpg = KeyPairGenerator.getInstance("DSA", "SC");
+
+ dsaKpg.initialize(1024);
+
+ //
+ // this takes a while as the key generator has to generate some DSA params
+ // before it generates the key.
+ //
+ KeyPair dsaKp = dsaKpg.generateKeyPair();
+
+ KeyPairGenerator elgKpg = KeyPairGenerator.getInstance("ELGAMAL", "SC");
+ BigInteger g = new BigInteger("153d5d6172adb43045b68ae8e1de1070b6137005686d29d3d73a7749199681ee5b212c9b96bfdcfa5b20cd5e3fd2044895d609cf9b410b7a0f12ca1cb9a428cc", 16);
+ BigInteger p = new BigInteger("9494fec095f3b85ee286542b3836fc81a5dd0a0349b4c239dd38744d488cf8e31db8bcb7d33b41abb9e5a33cca9144b1cef332c94bf0573bf047a3aca98cdf3b", 16);
+
+ ElGamalParameterSpec elParams = new ElGamalParameterSpec(p, g);
+
+ elgKpg.initialize(elParams);
+
+ //
+ // this is quicker because we are using pregenerated parameters.
+ //
+ KeyPair elgKp = elgKpg.generateKeyPair();
+
+ if (args[0].equals("-a"))
+ {
+ if (args.length < 3)
+ {
+ System.out.println("DSAElGamalKeyRingGenerator [-a] identity passPhrase");
+ System.exit(0);
+ }
+
+ FileOutputStream out1 = new FileOutputStream("secret.asc");
+ FileOutputStream out2 = new FileOutputStream("pub.asc");
+
+ exportKeyPair(out1, out2, dsaKp, elgKp, args[1], args[2].toCharArray(), true);
+ }
+ else
+ {
+ FileOutputStream out1 = new FileOutputStream("secret.bpg");
+ FileOutputStream out2 = new FileOutputStream("pub.bpg");
+
+ exportKeyPair(out1, out2, dsaKp, elgKp, args[0], args[1].toCharArray(), false);
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/examples/DetachedSignatureProcessor.java b/pg/src/main/java/org/spongycastle/openpgp/examples/DetachedSignatureProcessor.java
new file mode 100644
index 00000000..fc9fa531
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/examples/DetachedSignatureProcessor.java
@@ -0,0 +1,199 @@
+package org.spongycastle.openpgp.examples;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+import java.security.Security;
+
+import org.spongycastle.bcpg.ArmoredOutputStream;
+import org.spongycastle.bcpg.BCPGOutputStream;
+import org.spongycastle.jce.provider.BouncyCastleProvider;
+import org.spongycastle.openpgp.PGPCompressedData;
+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.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;
+
+/**
+ * A simple utility class that creates seperate signatures for files and verifies them.
+ * <p>
+ * To sign a file: DetachedSignatureProcessor -s [-a] fileName secretKey passPhrase.<br>
+ * If -a is specified the output file will be "ascii-armored".
+ * <p>
+ * To decrypt: DetachedSignatureProcessor -v fileName signatureFile publicKeyFile.
+ * <p>
+ * Note: this example will silently overwrite files.
+ * It also expects that a single pass phrase
+ * will have been used.
+ */
+public class DetachedSignatureProcessor
+{
+ private static void verifySignature(
+ String fileName,
+ String inputFileName,
+ String keyFileName)
+ throws GeneralSecurityException, IOException, PGPException
+ {
+ InputStream in = new BufferedInputStream(new FileInputStream(inputFileName));
+ InputStream keyIn = new BufferedInputStream(new FileInputStream(keyFileName));
+
+ verifySignature(fileName, in, keyIn);
+
+ keyIn.close();
+ in.close();
+ }
+
+ /*
+ * verify the signature in in against the file fileName.
+ */
+ private static void verifySignature(
+ String fileName,
+ InputStream in,
+ InputStream keyIn)
+ throws GeneralSecurityException, IOException, PGPException
+ {
+ in = PGPUtil.getDecoderStream(in);
+
+ JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(in);
+ PGPSignatureList p3;
+
+ Object o = pgpFact.nextObject();
+ if (o instanceof PGPCompressedData)
+ {
+ PGPCompressedData c1 = (PGPCompressedData)o;
+
+ pgpFact = new JcaPGPObjectFactory(c1.getDataStream());
+
+ p3 = (PGPSignatureList)pgpFact.nextObject();
+ }
+ else
+ {
+ p3 = (PGPSignatureList)o;
+ }
+
+ PGPPublicKeyRingCollection pgpPubRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(keyIn), new JcaKeyFingerprintCalculator());
+
+
+ InputStream dIn = new BufferedInputStream(new FileInputStream(fileName));
+
+ PGPSignature sig = p3.get(0);
+ PGPPublicKey key = pgpPubRingCollection.getPublicKey(sig.getKeyID());
+
+ sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider("SC"), key);
+
+ int ch;
+ while ((ch = dIn.read()) >= 0)
+ {
+ sig.update((byte)ch);
+ }
+
+ dIn.close();
+
+ if (sig.verify())
+ {
+ System.out.println("signature verified.");
+ }
+ else
+ {
+ System.out.println("signature verification failed.");
+ }
+ }
+
+ private static void createSignature(
+ String inputFileName,
+ String keyFileName,
+ String outputFileName,
+ char[] pass,
+ boolean armor)
+ throws GeneralSecurityException, IOException, PGPException
+ {
+ InputStream keyIn = new BufferedInputStream(new FileInputStream(keyFileName));
+ OutputStream out = new BufferedOutputStream(new FileOutputStream(outputFileName));
+
+ createSignature(inputFileName, keyIn, out, pass, armor);
+
+ out.close();
+ keyIn.close();
+ }
+
+ private static void createSignature(
+ String fileName,
+ InputStream keyIn,
+ OutputStream out,
+ char[] pass,
+ boolean armor)
+ throws GeneralSecurityException, IOException, PGPException
+ {
+ if (armor)
+ {
+ out = new ArmoredOutputStream(out);
+ }
+
+ PGPSecretKey pgpSec = PGPExampleUtil.readSecretKey(keyIn);
+ PGPPrivateKey pgpPrivKey = pgpSec.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("SC").build(pass));
+ PGPSignatureGenerator sGen = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(pgpSec.getPublicKey().getAlgorithm(), PGPUtil.SHA1).setProvider("SC"));
+
+ sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
+
+ BCPGOutputStream bOut = new BCPGOutputStream(out);
+
+ InputStream fIn = new BufferedInputStream(new FileInputStream(fileName));
+
+ int ch;
+ while ((ch = fIn.read()) >= 0)
+ {
+ sGen.update((byte)ch);
+ }
+
+ fIn.close();
+
+ sGen.generate().encode(bOut);
+
+ if (armor)
+ {
+ out.close();
+ }
+ }
+
+ public static void main(
+ String[] args)
+ throws Exception
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ if (args[0].equals("-s"))
+ {
+ if (args[1].equals("-a"))
+ {
+ createSignature(args[2], args[3], args[2] + ".asc", args[4].toCharArray(), true);
+ }
+ else
+ {
+ createSignature(args[1], args[2], args[1] + ".bpg", args[3].toCharArray(), false);
+ }
+ }
+ else if (args[0].equals("-v"))
+ {
+ verifySignature(args[1], args[2], args[3]);
+ }
+ else
+ {
+ System.err.println("usage: DetachedSignatureProcessor [-s [-a] file keyfile passPhrase]|[-v file sigFile keyFile]");
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/examples/DirectKeySignature.java b/pg/src/main/java/org/spongycastle/openpgp/examples/DirectKeySignature.java
new file mode 100644
index 00000000..00f14a23
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/examples/DirectKeySignature.java
@@ -0,0 +1,115 @@
+package org.spongycastle.openpgp.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.security.Security;
+import java.util.Iterator;
+
+import org.spongycastle.bcpg.ArmoredOutputStream;
+import org.spongycastle.bcpg.sig.NotationData;
+import org.spongycastle.jce.provider.BouncyCastleProvider;
+import org.spongycastle.openpgp.PGPPrivateKey;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.spongycastle.openpgp.PGPSignature;
+import org.spongycastle.openpgp.PGPSignatureGenerator;
+import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
+import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
+import org.spongycastle.openpgp.PGPUtil;
+import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
+
+/**
+ * A simple utility class that directly signs a public key and writes the signed key to "SignedKey.asc" in
+ * the current working directory.
+ * <p>
+ * To sign a key: DirectKeySignature secretKeyFile secretKeyPass publicKeyFile(key to be signed) NotationName NotationValue.<br/>
+ * </p><p>
+ * To display a NotationData packet from a publicKey previously signed: DirectKeySignature signedPublicKeyFile.<br/>
+ * </p><p>
+ * <b>Note</b>: this example will silently overwrite files, nor does it pay any attention to
+ * the specification of "_CONSOLE" in the filename. It also expects that a single pass phrase
+ * will have been used.
+ * </p>
+ */
+public class DirectKeySignature
+{
+ public static void main(
+ String[] args)
+ throws Exception
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ if (args.length == 1)
+ {
+ PGPPublicKeyRing ring = new PGPPublicKeyRing(PGPUtil.getDecoderStream(new FileInputStream(args[0])), new JcaKeyFingerprintCalculator());
+ PGPPublicKey key = ring.getPublicKey();
+
+ // iterate through all direct key signautures and look for NotationData subpackets
+ Iterator iter = key.getSignaturesOfType(PGPSignature.DIRECT_KEY);
+ while(iter.hasNext())
+ {
+ PGPSignature sig = (PGPSignature)iter.next();
+
+ System.out.println("Signature date is: " + sig.getHashedSubPackets().getSignatureCreationTime());
+
+ NotationData[] data = sig.getHashedSubPackets().getNotationDataOccurences();//.getSubpacket(SignatureSubpacketTags.NOTATION_DATA);
+
+ for (int i = 0; i < data.length; i++)
+ {
+ System.out.println("Found Notaion named '"+data[i].getNotationName()+"' with content '"+data[i].getNotationValue()+"'.");
+ }
+ }
+ }
+ else if (args.length == 5)
+ {
+ // gather command line arguments
+ PGPSecretKeyRing secRing = new PGPSecretKeyRing(PGPUtil.getDecoderStream(new FileInputStream(args[0])), new JcaKeyFingerprintCalculator());
+ String secretKeyPass = args[1];
+ PGPPublicKeyRing ring = new PGPPublicKeyRing(PGPUtil.getDecoderStream(new FileInputStream(args[2])), new JcaKeyFingerprintCalculator());
+ String notationName = args[3];
+ String notationValue = args[4];
+
+ // create the signed keyRing
+ PGPPublicKeyRing sRing = new PGPPublicKeyRing(new ByteArrayInputStream(signPublicKey(secRing.getSecretKey(), secretKeyPass, ring.getPublicKey(), notationName, notationValue)), new JcaKeyFingerprintCalculator());
+ ring = sRing;
+
+ // write the created keyRing to file
+ ArmoredOutputStream out = new ArmoredOutputStream(new FileOutputStream("SignedKey.asc"));
+ sRing.encode(out);
+ out.flush();
+ out.close();
+ }
+ else
+ {
+ System.err.println("usage: DirectKeySignature secretKeyFile secretKeyPass publicKeyFile(key to be signed) NotationName NotationValue");
+ System.err.println("or: DirectKeySignature signedPublicKeyFile");
+
+ }
+ }
+
+ private static byte[] signPublicKey(PGPSecretKey secretKey, String secretKeyPass, PGPPublicKey keyToBeSigned, String notationName, String notationValue) throws Exception
+ {
+ PGPPrivateKey pgpPrivKey = secretKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("SC").build(secretKeyPass.toCharArray()));
+
+ PGPSignatureGenerator sGen = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(secretKey.getPublicKey().getAlgorithm(), PGPUtil.SHA1).setProvider("SC"));
+
+ sGen.init(PGPSignature.DIRECT_KEY, pgpPrivKey);
+
+ PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
+
+ boolean isHumanReadable = true;
+
+ spGen.setNotationData(true, isHumanReadable, notationName, notationValue);
+
+ PGPSignatureSubpacketVector packetVector = spGen.generate();
+
+ sGen.setHashedSubpackets(packetVector);
+
+ return PGPPublicKey.addCertification(keyToBeSigned, sGen.generate()).getEncoded();
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/examples/KeyBasedFileProcessor.java b/pg/src/main/java/org/spongycastle/openpgp/examples/KeyBasedFileProcessor.java
new file mode 100644
index 00000000..cd893eaa
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/examples/KeyBasedFileProcessor.java
@@ -0,0 +1,280 @@
+package org.spongycastle.openpgp.examples;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.util.Iterator;
+
+import org.spongycastle.bcpg.ArmoredOutputStream;
+import org.spongycastle.bcpg.CompressionAlgorithmTags;
+import org.spongycastle.jce.provider.BouncyCastleProvider;
+import org.spongycastle.openpgp.PGPCompressedData;
+import org.spongycastle.openpgp.PGPEncryptedData;
+import org.spongycastle.openpgp.PGPEncryptedDataGenerator;
+import org.spongycastle.openpgp.PGPEncryptedDataList;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPLiteralData;
+import org.spongycastle.openpgp.PGPOnePassSignatureList;
+import org.spongycastle.openpgp.PGPPrivateKey;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPPublicKeyEncryptedData;
+import org.spongycastle.openpgp.PGPSecretKeyRingCollection;
+import org.spongycastle.openpgp.PGPUtil;
+import org.spongycastle.openpgp.jcajce.JcaPGPObjectFactory;
+import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
+import org.spongycastle.util.io.Streams;
+
+/**
+ * A simple utility class that encrypts/decrypts public key based
+ * encryption files.
+ * <p>
+ * To encrypt a file: KeyBasedFileProcessor -e [-a|-ai] fileName publicKeyFile.<br>
+ * If -a is specified the output file will be "ascii-armored".
+ * If -i is specified the output file will be have integrity checking added.
+ * <p>
+ * To decrypt: KeyBasedFileProcessor -d fileName secretKeyFile passPhrase.
+ * <p>
+ * Note 1: this example will silently overwrite files, nor does it pay any attention to
+ * the specification of "_CONSOLE" in the filename. It also expects that a single pass phrase
+ * will have been used.
+ * <p>
+ * Note 2: if an empty file name has been specified in the literal data object contained in the
+ * encrypted packet a file with the name filename.out will be generated in the current working directory.
+ */
+public class KeyBasedFileProcessor
+{
+ private static void decryptFile(
+ String inputFileName,
+ String keyFileName,
+ char[] passwd,
+ String defaultFileName)
+ throws IOException, NoSuchProviderException
+ {
+ InputStream in = new BufferedInputStream(new FileInputStream(inputFileName));
+ InputStream keyIn = new BufferedInputStream(new FileInputStream(keyFileName));
+ decryptFile(in, keyIn, passwd, defaultFileName);
+ keyIn.close();
+ in.close();
+ }
+
+ /**
+ * decrypt the passed in message stream
+ */
+ private static void decryptFile(
+ InputStream in,
+ InputStream keyIn,
+ char[] passwd,
+ String defaultFileName)
+ throws IOException, NoSuchProviderException
+ {
+ in = PGPUtil.getDecoderStream(in);
+
+ try
+ {
+ JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in);
+ PGPEncryptedDataList enc;
+
+ Object o = pgpF.nextObject();
+ //
+ // the first object might be a PGP marker packet.
+ //
+ if (o instanceof PGPEncryptedDataList)
+ {
+ enc = (PGPEncryptedDataList)o;
+ }
+ else
+ {
+ enc = (PGPEncryptedDataList)pgpF.nextObject();
+ }
+
+ //
+ // find the secret key
+ //
+ Iterator it = enc.getEncryptedDataObjects();
+ PGPPrivateKey sKey = null;
+ PGPPublicKeyEncryptedData pbe = null;
+ PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(
+ PGPUtil.getDecoderStream(keyIn), new JcaKeyFingerprintCalculator());
+
+ while (sKey == null && it.hasNext())
+ {
+ pbe = (PGPPublicKeyEncryptedData)it.next();
+
+ sKey = PGPExampleUtil.findSecretKey(pgpSec, pbe.getKeyID(), passwd);
+ }
+
+ if (sKey == null)
+ {
+ throw new IllegalArgumentException("secret key for message not found.");
+ }
+
+ InputStream clear = pbe.getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider("SC").build(sKey));
+
+ JcaPGPObjectFactory plainFact = new JcaPGPObjectFactory(clear);
+
+ Object message = plainFact.nextObject();
+
+ if (message instanceof PGPCompressedData)
+ {
+ PGPCompressedData cData = (PGPCompressedData)message;
+ JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(cData.getDataStream());
+
+ message = pgpFact.nextObject();
+ }
+
+ if (message instanceof PGPLiteralData)
+ {
+ PGPLiteralData ld = (PGPLiteralData)message;
+
+ String outFileName = ld.getFileName();
+ if (outFileName.length() == 0)
+ {
+ outFileName = defaultFileName;
+ }
+
+ InputStream unc = ld.getInputStream();
+ OutputStream fOut = new BufferedOutputStream(new FileOutputStream(outFileName));
+
+ Streams.pipeAll(unc, fOut);
+
+ fOut.close();
+ }
+ else if (message instanceof PGPOnePassSignatureList)
+ {
+ throw new PGPException("encrypted message contains a signed message - not literal data.");
+ }
+ else
+ {
+ throw new PGPException("message is not a simple encrypted file - type unknown.");
+ }
+
+ if (pbe.isIntegrityProtected())
+ {
+ if (!pbe.verify())
+ {
+ System.err.println("message failed integrity check");
+ }
+ else
+ {
+ System.err.println("message integrity check passed");
+ }
+ }
+ else
+ {
+ System.err.println("no message integrity check");
+ }
+ }
+ catch (PGPException e)
+ {
+ System.err.println(e);
+ if (e.getUnderlyingException() != null)
+ {
+ e.getUnderlyingException().printStackTrace();
+ }
+ }
+ }
+
+ private static void encryptFile(
+ String outputFileName,
+ String inputFileName,
+ String encKeyFileName,
+ boolean armor,
+ boolean withIntegrityCheck)
+ throws IOException, NoSuchProviderException, PGPException
+ {
+ OutputStream out = new BufferedOutputStream(new FileOutputStream(outputFileName));
+ PGPPublicKey encKey = PGPExampleUtil.readPublicKey(encKeyFileName);
+ encryptFile(out, inputFileName, encKey, armor, withIntegrityCheck);
+ out.close();
+ }
+
+ private static void encryptFile(
+ OutputStream out,
+ String fileName,
+ PGPPublicKey encKey,
+ boolean armor,
+ boolean withIntegrityCheck)
+ throws IOException, NoSuchProviderException
+ {
+ if (armor)
+ {
+ out = new ArmoredOutputStream(out);
+ }
+
+ try
+ {
+ byte[] bytes = PGPExampleUtil.compressFile(fileName, CompressionAlgorithmTags.ZIP);
+
+ PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(
+ new JcePGPDataEncryptorBuilder(PGPEncryptedData.CAST5).setWithIntegrityPacket(withIntegrityCheck).setSecureRandom(new SecureRandom()).setProvider("SC"));
+
+ encGen.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(encKey).setProvider("SC"));
+
+ OutputStream cOut = encGen.open(out, bytes.length);
+
+ cOut.write(bytes);
+ cOut.close();
+
+ if (armor)
+ {
+ out.close();
+ }
+ }
+ catch (PGPException e)
+ {
+ System.err.println(e);
+ if (e.getUnderlyingException() != null)
+ {
+ e.getUnderlyingException().printStackTrace();
+ }
+ }
+ }
+
+ public static void main(
+ String[] args)
+ throws Exception
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ if (args.length == 0)
+ {
+ System.err.println("usage: KeyBasedFileProcessor -e|-d [-a|ai] file [secretKeyFile passPhrase|pubKeyFile]");
+ return;
+ }
+
+ if (args[0].equals("-e"))
+ {
+ if (args[1].equals("-a") || args[1].equals("-ai") || args[1].equals("-ia"))
+ {
+ encryptFile(args[2] + ".asc", args[2], args[3], true, (args[1].indexOf('i') > 0));
+ }
+ else if (args[1].equals("-i"))
+ {
+ encryptFile(args[2] + ".bpg", args[2], args[3], false, true);
+ }
+ else
+ {
+ encryptFile(args[1] + ".bpg", args[1], args[2], false, false);
+ }
+ }
+ else if (args[0].equals("-d"))
+ {
+ decryptFile(args[1], args[2], args[3].toCharArray(), new File(args[1]).getName() + ".out");
+ }
+ else
+ {
+ System.err.println("usage: KeyBasedFileProcessor -d|-e [-a|ai] file [secretKeyFile passPhrase|pubKeyFile]");
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/examples/KeyBasedLargeFileProcessor.java b/pg/src/main/java/org/spongycastle/openpgp/examples/KeyBasedLargeFileProcessor.java
new file mode 100644
index 00000000..cd39d05a
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/examples/KeyBasedLargeFileProcessor.java
@@ -0,0 +1,284 @@
+package org.spongycastle.openpgp.examples;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.util.Iterator;
+
+import org.spongycastle.bcpg.ArmoredOutputStream;
+import org.spongycastle.jce.provider.BouncyCastleProvider;
+import org.spongycastle.openpgp.PGPCompressedData;
+import org.spongycastle.openpgp.PGPCompressedDataGenerator;
+import org.spongycastle.openpgp.PGPEncryptedData;
+import org.spongycastle.openpgp.PGPEncryptedDataGenerator;
+import org.spongycastle.openpgp.PGPEncryptedDataList;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPLiteralData;
+import org.spongycastle.openpgp.PGPOnePassSignatureList;
+import org.spongycastle.openpgp.PGPPrivateKey;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPPublicKeyEncryptedData;
+import org.spongycastle.openpgp.PGPSecretKeyRingCollection;
+import org.spongycastle.openpgp.PGPUtil;
+import org.spongycastle.openpgp.jcajce.JcaPGPObjectFactory;
+import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
+import org.spongycastle.util.io.Streams;
+
+/**
+ * A simple utility class that encrypts/decrypts public key based
+ * encryption large files.
+ * <p>
+ * To encrypt a file: KeyBasedLargeFileProcessor -e [-a|-ai] fileName publicKeyFile.<br>
+ * If -a is specified the output file will be "ascii-armored".
+ * If -i is specified the output file will be have integrity checking added.
+ * <p>
+ * To decrypt: KeyBasedLargeFileProcessor -d fileName secretKeyFile passPhrase.
+ * <p>
+ * Note 1: this example will silently overwrite files, nor does it pay any attention to
+ * the specification of "_CONSOLE" in the filename. It also expects that a single pass phrase
+ * will have been used.
+ * <p>
+ * Note 2: this example generates partial packets to encode the file, the output it generates
+ * will not be readable by older PGP products or products that don't support partial packet
+ * encoding.
+ * <p>
+ * Note 3: if an empty file name has been specified in the literal data object contained in the
+ * encrypted packet a file with the name filename.out will be generated in the current working directory.
+ */
+public class KeyBasedLargeFileProcessor
+{
+ private static void decryptFile(
+ String inputFileName,
+ String keyFileName,
+ char[] passwd,
+ String defaultFileName)
+ throws IOException, NoSuchProviderException
+ {
+ InputStream in = new BufferedInputStream(new FileInputStream(inputFileName));
+ InputStream keyIn = new BufferedInputStream(new FileInputStream(keyFileName));
+ decryptFile(in, keyIn, passwd, defaultFileName);
+ keyIn.close();
+ in.close();
+ }
+
+ /**
+ * decrypt the passed in message stream
+ */
+ private static void decryptFile(
+ InputStream in,
+ InputStream keyIn,
+ char[] passwd,
+ String defaultFileName)
+ throws IOException, NoSuchProviderException
+ {
+ in = PGPUtil.getDecoderStream(in);
+
+ try
+ {
+ JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in);
+ PGPEncryptedDataList enc;
+
+ Object o = pgpF.nextObject();
+ //
+ // the first object might be a PGP marker packet.
+ //
+ if (o instanceof PGPEncryptedDataList)
+ {
+ enc = (PGPEncryptedDataList)o;
+ }
+ else
+ {
+ enc = (PGPEncryptedDataList)pgpF.nextObject();
+ }
+
+ //
+ // find the secret key
+ //
+ Iterator it = enc.getEncryptedDataObjects();
+ PGPPrivateKey sKey = null;
+ PGPPublicKeyEncryptedData pbe = null;
+ PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(
+ PGPUtil.getDecoderStream(keyIn), new JcaKeyFingerprintCalculator());
+
+ while (sKey == null && it.hasNext())
+ {
+ pbe = (PGPPublicKeyEncryptedData)it.next();
+
+ sKey = PGPExampleUtil.findSecretKey(pgpSec, pbe.getKeyID(), passwd);
+ }
+
+ if (sKey == null)
+ {
+ throw new IllegalArgumentException("secret key for message not found.");
+ }
+
+ InputStream clear = pbe.getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider("SC").build(sKey));
+
+ JcaPGPObjectFactory plainFact = new JcaPGPObjectFactory(clear);
+
+ PGPCompressedData cData = (PGPCompressedData)plainFact.nextObject();
+
+ InputStream compressedStream = new BufferedInputStream(cData.getDataStream());
+ JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(compressedStream);
+
+ Object message = pgpFact.nextObject();
+
+ if (message instanceof PGPLiteralData)
+ {
+ PGPLiteralData ld = (PGPLiteralData)message;
+
+ String outFileName = ld.getFileName();
+ if (outFileName.length() == 0)
+ {
+ outFileName = defaultFileName;
+ }
+
+ InputStream unc = ld.getInputStream();
+ OutputStream fOut = new BufferedOutputStream(new FileOutputStream(outFileName));
+
+ Streams.pipeAll(unc, fOut);
+
+ fOut.close();
+ }
+ else if (message instanceof PGPOnePassSignatureList)
+ {
+ throw new PGPException("encrypted message contains a signed message - not literal data.");
+ }
+ else
+ {
+ throw new PGPException("message is not a simple encrypted file - type unknown.");
+ }
+
+ if (pbe.isIntegrityProtected())
+ {
+ if (!pbe.verify())
+ {
+ System.err.println("message failed integrity check");
+ }
+ else
+ {
+ System.err.println("message integrity check passed");
+ }
+ }
+ else
+ {
+ System.err.println("no message integrity check");
+ }
+ }
+ catch (PGPException e)
+ {
+ System.err.println(e);
+ if (e.getUnderlyingException() != null)
+ {
+ e.getUnderlyingException().printStackTrace();
+ }
+ }
+ }
+
+ private static void encryptFile(
+ String outputFileName,
+ String inputFileName,
+ String encKeyFileName,
+ boolean armor,
+ boolean withIntegrityCheck)
+ throws IOException, NoSuchProviderException, PGPException
+ {
+ OutputStream out = new BufferedOutputStream(new FileOutputStream(outputFileName));
+ PGPPublicKey encKey = PGPExampleUtil.readPublicKey(encKeyFileName);
+ encryptFile(out, inputFileName, encKey, armor, withIntegrityCheck);
+ out.close();
+ }
+
+ private static void encryptFile(
+ OutputStream out,
+ String fileName,
+ PGPPublicKey encKey,
+ boolean armor,
+ boolean withIntegrityCheck)
+ throws IOException, NoSuchProviderException
+ {
+ if (armor)
+ {
+ out = new ArmoredOutputStream(out);
+ }
+
+ try
+ {
+ PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(new JcePGPDataEncryptorBuilder(PGPEncryptedData.CAST5).setWithIntegrityPacket(withIntegrityCheck).setSecureRandom(new SecureRandom()).setProvider("SC"));
+
+ cPk.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(encKey).setProvider("SC"));
+
+ OutputStream cOut = cPk.open(out, new byte[1 << 16]);
+
+ PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(
+ PGPCompressedData.ZIP);
+
+ PGPUtil.writeFileToLiteralData(comData.open(cOut), PGPLiteralData.BINARY, new File(fileName), new byte[1 << 16]);
+
+ comData.close();
+
+ cOut.close();
+
+ if (armor)
+ {
+ out.close();
+ }
+ }
+ catch (PGPException e)
+ {
+ System.err.println(e);
+ if (e.getUnderlyingException() != null)
+ {
+ e.getUnderlyingException().printStackTrace();
+ }
+ }
+ }
+
+ public static void main(
+ String[] args)
+ throws Exception
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ if (args.length == 0)
+ {
+ System.err.println("usage: KeyBasedLargeFileProcessor -e|-d [-a|ai] file [secretKeyFile passPhrase|pubKeyFile]");
+ return;
+ }
+
+ if (args[0].equals("-e"))
+ {
+ if (args[1].equals("-a") || args[1].equals("-ai") || args[1].equals("-ia"))
+ {
+ encryptFile(args[2] + ".asc", args[2], args[3], true, (args[1].indexOf('i') > 0));
+ }
+ else if (args[1].equals("-i"))
+ {
+ encryptFile(args[2] + ".bpg", args[2], args[3], false, true);
+ }
+ else
+ {
+ encryptFile(args[1] + ".bpg", args[1], args[2], false, false);
+ }
+ }
+ else if (args[0].equals("-d"))
+ {
+ decryptFile(args[1], args[2], args[3].toCharArray(), new File(args[1]).getName() + ".out");
+ }
+ else
+ {
+ System.err.println("usage: KeyBasedLargeFileProcessor -d|-e [-a|ai] file [secretKeyFile passPhrase|pubKeyFile]");
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/examples/PBEFileProcessor.java b/pg/src/main/java/org/spongycastle/openpgp/examples/PBEFileProcessor.java
new file mode 100644
index 00000000..5e9fc83c
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/examples/PBEFileProcessor.java
@@ -0,0 +1,214 @@
+package org.spongycastle.openpgp.examples;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+import java.security.Security;
+
+import org.spongycastle.bcpg.ArmoredOutputStream;
+import org.spongycastle.bcpg.CompressionAlgorithmTags;
+import org.spongycastle.jce.provider.BouncyCastleProvider;
+import org.spongycastle.openpgp.PGPCompressedData;
+import org.spongycastle.openpgp.PGPEncryptedData;
+import org.spongycastle.openpgp.PGPEncryptedDataGenerator;
+import org.spongycastle.openpgp.PGPEncryptedDataList;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPLiteralData;
+import org.spongycastle.openpgp.PGPPBEEncryptedData;
+import org.spongycastle.openpgp.PGPUtil;
+import org.spongycastle.openpgp.jcajce.JcaPGPObjectFactory;
+import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
+import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
+import org.spongycastle.util.io.Streams;
+
+/**
+ * A simple utility class that encrypts/decrypts password based
+ * encryption files.
+ * <p>
+ * To encrypt a file: PBEFileProcessor -e [-ai] fileName passPhrase.<br>
+ * If -a is specified the output file will be "ascii-armored".<br>
+ * If -i is specified the output file will be "integrity protected".
+ * <p>
+ * To decrypt: PBEFileProcessor -d fileName passPhrase.
+ * <p>
+ * Note: this example will silently overwrite files, nor does it pay any attention to
+ * the specification of "_CONSOLE" in the filename. It also expects that a single pass phrase
+ * will have been used.
+ */
+public class PBEFileProcessor
+{
+ private static void decryptFile(String inputFileName, char[] passPhrase)
+ throws IOException, NoSuchProviderException, PGPException
+ {
+ InputStream in = new BufferedInputStream(new FileInputStream(inputFileName));
+ decryptFile(in, passPhrase);
+ in.close();
+ }
+
+ /*
+ * decrypt the passed in message stream
+ */
+ private static void decryptFile(
+ InputStream in,
+ char[] passPhrase)
+ throws IOException, NoSuchProviderException, PGPException
+ {
+ in = PGPUtil.getDecoderStream(in);
+
+ JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in);
+ PGPEncryptedDataList enc;
+ Object o = pgpF.nextObject();
+
+ //
+ // the first object might be a PGP marker packet.
+ //
+ if (o instanceof PGPEncryptedDataList)
+ {
+ enc = (PGPEncryptedDataList)o;
+ }
+ else
+ {
+ enc = (PGPEncryptedDataList)pgpF.nextObject();
+ }
+
+ PGPPBEEncryptedData pbe = (PGPPBEEncryptedData)enc.get(0);
+
+ InputStream clear = pbe.getDataStream(new JcePBEDataDecryptorFactoryBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider("SC").build()).setProvider("SC").build(passPhrase));
+
+ JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(clear);
+
+ //
+ // if we're trying to read a file generated by someone other than us
+ // the data might not be compressed, so we check the return type from
+ // the factory and behave accordingly.
+ //
+ o = pgpFact.nextObject();
+ if (o instanceof PGPCompressedData)
+ {
+ PGPCompressedData cData = (PGPCompressedData)o;
+
+ pgpFact = new JcaPGPObjectFactory(cData.getDataStream());
+
+ o = pgpFact.nextObject();
+ }
+
+ PGPLiteralData ld = (PGPLiteralData)o;
+ InputStream unc = ld.getInputStream();
+
+ OutputStream fOut = new BufferedOutputStream(new FileOutputStream(ld.getFileName()));
+
+ Streams.pipeAll(unc, fOut);
+
+ fOut.close();
+
+ if (pbe.isIntegrityProtected())
+ {
+ if (!pbe.verify())
+ {
+ System.err.println("message failed integrity check");
+ }
+ else
+ {
+ System.err.println("message integrity check passed");
+ }
+ }
+ else
+ {
+ System.err.println("no message integrity check");
+ }
+ }
+
+ private static void encryptFile(
+ String outputFileName,
+ String inputFileName,
+ char[] passPhrase,
+ boolean armor,
+ boolean withIntegrityCheck)
+ throws IOException, NoSuchProviderException
+ {
+ OutputStream out = new BufferedOutputStream(new FileOutputStream(outputFileName));
+ encryptFile(out, inputFileName, passPhrase, armor, withIntegrityCheck);
+ out.close();
+ }
+
+ private static void encryptFile(
+ OutputStream out,
+ String fileName,
+ char[] passPhrase,
+ boolean armor,
+ boolean withIntegrityCheck)
+ throws IOException, NoSuchProviderException
+ {
+ if (armor)
+ {
+ out = new ArmoredOutputStream(out);
+ }
+
+ try
+ {
+ byte[] compressedData = PGPExampleUtil.compressFile(fileName, CompressionAlgorithmTags.ZIP);
+
+ PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(new JcePGPDataEncryptorBuilder(PGPEncryptedData.CAST5)
+ .setWithIntegrityPacket(withIntegrityCheck).setSecureRandom(new SecureRandom()).setProvider("SC"));
+
+ encGen.addMethod(new JcePBEKeyEncryptionMethodGenerator(passPhrase).setProvider("SC"));
+
+ OutputStream encOut = encGen.open(out, compressedData.length);
+
+ encOut.write(compressedData);
+ encOut.close();
+
+ if (armor)
+ {
+ out.close();
+ }
+ }
+ catch (PGPException e)
+ {
+ System.err.println(e);
+ if (e.getUnderlyingException() != null)
+ {
+ e.getUnderlyingException().printStackTrace();
+ }
+ }
+ }
+
+ public static void main(
+ String[] args)
+ throws Exception
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ if (args[0].equals("-e"))
+ {
+ if (args[1].equals("-a") || args[1].equals("-ai") || args[1].equals("-ia"))
+ {
+ encryptFile(args[2] + ".asc", args[2], args[3].toCharArray(), true, (args[1].indexOf('i') > 0));
+ }
+ else if (args[1].equals("-i"))
+ {
+ encryptFile(args[2] + ".bpg", args[2], args[3].toCharArray(), false, true);
+ }
+ else
+ {
+ encryptFile(args[1] + ".bpg", args[1], args[2].toCharArray(), false, false);
+ }
+ }
+ else if (args[0].equals("-d"))
+ {
+ decryptFile(args[1], args[2].toCharArray());
+ }
+ else
+ {
+ System.err.println("usage: PBEFileProcessor -e [-ai]|-d file passPhrase");
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/examples/PGPExampleUtil.java b/pg/src/main/java/org/spongycastle/openpgp/examples/PGPExampleUtil.java
new file mode 100644
index 00000000..c83ee189
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/examples/PGPExampleUtil.java
@@ -0,0 +1,155 @@
+package org.spongycastle.openpgp.examples;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.NoSuchProviderException;
+import java.util.Iterator;
+
+import org.spongycastle.openpgp.PGPCompressedDataGenerator;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPLiteralData;
+import org.spongycastle.openpgp.PGPPrivateKey;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.spongycastle.openpgp.PGPPublicKeyRingCollection;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.spongycastle.openpgp.PGPSecretKeyRingCollection;
+import org.spongycastle.openpgp.PGPUtil;
+import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
+
+class PGPExampleUtil
+{
+ static byte[] compressFile(String fileName, int algorithm) throws IOException
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(algorithm);
+ PGPUtil.writeFileToLiteralData(comData.open(bOut), PGPLiteralData.BINARY,
+ new File(fileName));
+ comData.close();
+ return bOut.toByteArray();
+ }
+
+ /**
+ * Search a secret key ring collection for a secret key corresponding to keyID if it
+ * exists.
+ *
+ * @param pgpSec a secret key ring collection.
+ * @param keyID keyID we want.
+ * @param pass passphrase to decrypt secret key with.
+ * @return the private key.
+ * @throws PGPException
+ * @throws NoSuchProviderException
+ */
+ static PGPPrivateKey findSecretKey(PGPSecretKeyRingCollection pgpSec, long keyID, char[] pass)
+ throws PGPException, NoSuchProviderException
+ {
+ PGPSecretKey pgpSecKey = pgpSec.getSecretKey(keyID);
+
+ if (pgpSecKey == null)
+ {
+ return null;
+ }
+
+ return pgpSecKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("SC").build(pass));
+ }
+
+ static PGPPublicKey readPublicKey(String fileName) throws IOException, PGPException
+ {
+ InputStream keyIn = new BufferedInputStream(new FileInputStream(fileName));
+ PGPPublicKey pubKey = readPublicKey(keyIn);
+ keyIn.close();
+ return pubKey;
+ }
+
+ /**
+ * A simple routine that opens a key ring file and loads the first available key
+ * suitable for encryption.
+ *
+ * @param input data stream containing the public key data
+ * @return the first public key found.
+ * @throws IOException
+ * @throws PGPException
+ */
+ static PGPPublicKey readPublicKey(InputStream input) throws IOException, PGPException
+ {
+ PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(
+ PGPUtil.getDecoderStream(input), new JcaKeyFingerprintCalculator());
+
+ //
+ // we just loop through the collection till we find a key suitable for encryption, in the real
+ // world you would probably want to be a bit smarter about this.
+ //
+
+ Iterator keyRingIter = pgpPub.getKeyRings();
+ while (keyRingIter.hasNext())
+ {
+ PGPPublicKeyRing keyRing = (PGPPublicKeyRing)keyRingIter.next();
+
+ Iterator keyIter = keyRing.getPublicKeys();
+ while (keyIter.hasNext())
+ {
+ PGPPublicKey key = (PGPPublicKey)keyIter.next();
+
+ if (key.isEncryptionKey())
+ {
+ return key;
+ }
+ }
+ }
+
+ throw new IllegalArgumentException("Can't find encryption key in key ring.");
+ }
+
+ static PGPSecretKey readSecretKey(String fileName) throws IOException, PGPException
+ {
+ InputStream keyIn = new BufferedInputStream(new FileInputStream(fileName));
+ PGPSecretKey secKey = readSecretKey(keyIn);
+ keyIn.close();
+ return secKey;
+ }
+
+ /**
+ * A simple routine that opens a key ring file and loads the first available key
+ * suitable for signature generation.
+ *
+ * @param input stream to read the secret key ring collection from.
+ * @return a secret key.
+ * @throws IOException on a problem with using the input stream.
+ * @throws PGPException if there is an issue parsing the input stream.
+ */
+ static PGPSecretKey readSecretKey(InputStream input) throws IOException, PGPException
+ {
+ PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(
+ PGPUtil.getDecoderStream(input), new JcaKeyFingerprintCalculator());
+
+ //
+ // we just loop through the collection till we find a key suitable for encryption, in the real
+ // world you would probably want to be a bit smarter about this.
+ //
+
+ Iterator keyRingIter = pgpSec.getKeyRings();
+ while (keyRingIter.hasNext())
+ {
+ PGPSecretKeyRing keyRing = (PGPSecretKeyRing)keyRingIter.next();
+
+ Iterator keyIter = keyRing.getSecretKeys();
+ while (keyIter.hasNext())
+ {
+ PGPSecretKey key = (PGPSecretKey)keyIter.next();
+
+ if (key.isSigningKey())
+ {
+ return key;
+ }
+ }
+ }
+
+ throw new IllegalArgumentException("Can't find signing key in key ring.");
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/examples/PubringDump.java b/pg/src/main/java/org/spongycastle/openpgp/examples/PubringDump.java
new file mode 100644
index 00000000..de565b96
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/examples/PubringDump.java
@@ -0,0 +1,100 @@
+package org.spongycastle.openpgp.examples;
+
+import java.io.FileInputStream;
+import java.security.Security;
+import java.util.Iterator;
+
+import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
+import org.spongycastle.jce.provider.BouncyCastleProvider;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.spongycastle.openpgp.PGPPublicKeyRingCollection;
+import org.spongycastle.openpgp.PGPUtil;
+import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+import org.spongycastle.util.encoders.Hex;
+
+/**
+ * Basic class which just lists the contents of the public key file passed
+ * as an argument. If the file contains more than one "key ring" they are
+ * listed in the order found.
+ */
+public class PubringDump
+{
+ public static String getAlgorithm(
+ int algId)
+ {
+ switch (algId)
+ {
+ case PublicKeyAlgorithmTags.RSA_GENERAL:
+ return "RSA_GENERAL";
+ case PublicKeyAlgorithmTags.RSA_ENCRYPT:
+ return "RSA_ENCRYPT";
+ case PublicKeyAlgorithmTags.RSA_SIGN:
+ return "RSA_SIGN";
+ case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT:
+ return "ELGAMAL_ENCRYPT";
+ case PublicKeyAlgorithmTags.DSA:
+ return "DSA";
+ case PublicKeyAlgorithmTags.EC:
+ return "EC";
+ case PublicKeyAlgorithmTags.ECDSA:
+ return "ECDSA";
+ case PublicKeyAlgorithmTags.ELGAMAL_GENERAL:
+ return "ELGAMAL_GENERAL";
+ case PublicKeyAlgorithmTags.DIFFIE_HELLMAN:
+ return "DIFFIE_HELLMAN";
+ }
+
+ return "unknown";
+ }
+
+ public static void main(String[] args)
+ throws Exception
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ PGPUtil.setDefaultProvider("SC");
+
+ //
+ // Read the public key rings
+ //
+ PGPPublicKeyRingCollection pubRings = new PGPPublicKeyRingCollection(
+ PGPUtil.getDecoderStream(new FileInputStream(args[0])), new JcaKeyFingerprintCalculator());
+
+ Iterator rIt = pubRings.getKeyRings();
+
+ while (rIt.hasNext())
+ {
+ PGPPublicKeyRing pgpPub = (PGPPublicKeyRing)rIt.next();
+
+ try
+ {
+ pgpPub.getPublicKey();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ continue;
+ }
+
+ Iterator it = pgpPub.getPublicKeys();
+ boolean first = true;
+ while (it.hasNext())
+ {
+ PGPPublicKey pgpKey = (PGPPublicKey)it.next();
+
+ if (first)
+ {
+ System.out.println("Key ID: " + Long.toHexString(pgpKey.getKeyID()));
+ first = false;
+ }
+ else
+ {
+ System.out.println("Key ID: " + Long.toHexString(pgpKey.getKeyID()) + " (subkey)");
+ }
+ System.out.println(" Algorithm: " + getAlgorithm(pgpKey.getAlgorithm()));
+ System.out.println(" Fingerprint: " + new String(Hex.encode(pgpKey.getFingerprint())));
+ }
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/examples/RSAKeyPairGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/examples/RSAKeyPairGenerator.java
new file mode 100644
index 00000000..de653453
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/examples/RSAKeyPairGenerator.java
@@ -0,0 +1,112 @@
+package org.spongycastle.openpgp.examples;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchProviderException;
+import java.security.Security;
+import java.security.SignatureException;
+import java.util.Date;
+
+import org.spongycastle.bcpg.ArmoredOutputStream;
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.jce.provider.BouncyCastleProvider;
+import org.spongycastle.openpgp.PGPEncryptedData;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPKeyPair;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.spongycastle.openpgp.PGPSignature;
+import org.spongycastle.openpgp.operator.PGPDigestCalculator;
+import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
+import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
+
+/**
+ * A simple utility class that generates a RSA PGPPublicKey/PGPSecretKey pair.
+ * <p>
+ * usage: RSAKeyPairGenerator [-a] identity passPhrase
+ * <p>
+ * Where identity is the name to be associated with the public key. The keys are placed
+ * in the files pub.[asc|bpg] and secret.[asc|bpg].
+ */
+public class RSAKeyPairGenerator
+{
+ private static void exportKeyPair(
+ OutputStream secretOut,
+ OutputStream publicOut,
+ KeyPair pair,
+ String identity,
+ char[] passPhrase,
+ boolean armor)
+ throws IOException, InvalidKeyException, NoSuchProviderException, SignatureException, PGPException
+ {
+ if (armor)
+ {
+ secretOut = new ArmoredOutputStream(secretOut);
+ }
+
+ PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1);
+ PGPKeyPair keyPair = new JcaPGPKeyPair(PGPPublicKey.RSA_GENERAL, pair, new Date());
+ PGPSecretKey secretKey = new PGPSecretKey(PGPSignature.DEFAULT_CERTIFICATION, keyPair, identity, sha1Calc, null, null, new JcaPGPContentSignerBuilder(keyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1), new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.CAST5, sha1Calc).setProvider("SC").build(passPhrase));
+
+ secretKey.encode(secretOut);
+
+ secretOut.close();
+
+ if (armor)
+ {
+ publicOut = new ArmoredOutputStream(publicOut);
+ }
+
+ PGPPublicKey key = secretKey.getPublicKey();
+
+ key.encode(publicOut);
+
+ publicOut.close();
+ }
+
+ public static void main(
+ String[] args)
+ throws Exception
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "SC");
+
+ kpg.initialize(1024);
+
+ KeyPair kp = kpg.generateKeyPair();
+
+ if (args.length < 2)
+ {
+ System.out.println("RSAKeyPairGenerator [-a] identity passPhrase");
+ System.exit(0);
+ }
+
+ if (args[0].equals("-a"))
+ {
+ if (args.length < 3)
+ {
+ System.out.println("RSAKeyPairGenerator [-a] identity passPhrase");
+ System.exit(0);
+ }
+
+ FileOutputStream out1 = new FileOutputStream("secret.asc");
+ FileOutputStream out2 = new FileOutputStream("pub.asc");
+
+ exportKeyPair(out1, out2, kp, args[1], args[2].toCharArray(), true);
+ }
+ else
+ {
+ FileOutputStream out1 = new FileOutputStream("secret.bpg");
+ FileOutputStream out2 = new FileOutputStream("pub.bpg");
+
+ exportKeyPair(out1, out2, kp, args[0], args[1].toCharArray(), false);
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/examples/SignedFileProcessor.java b/pg/src/main/java/org/spongycastle/openpgp/examples/SignedFileProcessor.java
new file mode 100644
index 00000000..4cd79133
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/examples/SignedFileProcessor.java
@@ -0,0 +1,216 @@
+package org.spongycastle.openpgp.examples;
+
+import java.io.File;
+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.ArmoredOutputStream;
+import org.spongycastle.bcpg.BCPGOutputStream;
+import org.spongycastle.jce.provider.BouncyCastleProvider;
+import org.spongycastle.openpgp.PGPCompressedData;
+import org.spongycastle.openpgp.PGPCompressedDataGenerator;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPLiteralData;
+import org.spongycastle.openpgp.PGPLiteralDataGenerator;
+import org.spongycastle.openpgp.PGPOnePassSignature;
+import org.spongycastle.openpgp.PGPOnePassSignatureList;
+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;
+
+/**
+ * A simple utility class that signs and verifies files.
+ * <p>
+ * To sign a file: SignedFileProcessor -s [-a] fileName secretKey passPhrase.<br>
+ * If -a is specified the output file will be "ascii-armored".
+ * <p>
+ * To decrypt: SignedFileProcessor -v fileName publicKeyFile.
+ * <p>
+ * <b>Note</b>: this example will silently overwrite files, nor does it pay any attention to
+ * the specification of "_CONSOLE" in the filename. It also expects that a single pass phrase
+ * will have been used.
+ * <p>
+ * <b>Note</b>: the example also makes use of PGP compression. If you are having difficulty getting it
+ * to interoperate with other PGP programs try removing the use of compression first.
+ */
+public class SignedFileProcessor
+{
+ /*
+ * verify the passed in file as being correctly signed.
+ */
+ private static void verifyFile(
+ InputStream in,
+ InputStream keyIn)
+ throws Exception
+ {
+ in = PGPUtil.getDecoderStream(in);
+
+ JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(in);
+
+ PGPCompressedData c1 = (PGPCompressedData)pgpFact.nextObject();
+
+ pgpFact = new JcaPGPObjectFactory(c1.getDataStream());
+
+ PGPOnePassSignatureList p1 = (PGPOnePassSignatureList)pgpFact.nextObject();
+
+ PGPOnePassSignature ops = p1.get(0);
+
+ PGPLiteralData p2 = (PGPLiteralData)pgpFact.nextObject();
+
+ InputStream dIn = p2.getInputStream();
+ int ch;
+ PGPPublicKeyRingCollection pgpRing = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(keyIn), new JcaKeyFingerprintCalculator());
+
+ PGPPublicKey key = pgpRing.getPublicKey(ops.getKeyID());
+ FileOutputStream out = new FileOutputStream(p2.getFileName());
+
+ ops.init(new JcaPGPContentVerifierBuilderProvider().setProvider("SC"), key);
+
+ while ((ch = dIn.read()) >= 0)
+ {
+ ops.update((byte)ch);
+ out.write(ch);
+ }
+
+ out.close();
+
+ PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject();
+
+ if (ops.verify(p3.get(0)))
+ {
+ System.out.println("signature verified.");
+ }
+ else
+ {
+ System.out.println("signature verification failed.");
+ }
+ }
+
+ /**
+ * Generate an encapsulated signed file.
+ *
+ * @param fileName
+ * @param keyIn
+ * @param out
+ * @param pass
+ * @param armor
+ * @throws IOException
+ * @throws NoSuchAlgorithmException
+ * @throws NoSuchProviderException
+ * @throws PGPException
+ * @throws SignatureException
+ */
+ private static void signFile(
+ String fileName,
+ InputStream keyIn,
+ OutputStream out,
+ char[] pass,
+ boolean armor)
+ throws IOException, NoSuchAlgorithmException, NoSuchProviderException, PGPException, SignatureException
+ {
+ if (armor)
+ {
+ out = new ArmoredOutputStream(out);
+ }
+
+ PGPSecretKey pgpSec = PGPExampleUtil.readSecretKey(keyIn);
+ PGPPrivateKey pgpPrivKey = pgpSec.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("SC").build(pass));
+ PGPSignatureGenerator sGen = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(pgpSec.getPublicKey().getAlgorithm(), PGPUtil.SHA1).setProvider("SC"));
+
+ sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
+
+ Iterator it = pgpSec.getPublicKey().getUserIDs();
+ if (it.hasNext())
+ {
+ PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
+
+ spGen.setSignerUserID(false, (String)it.next());
+ sGen.setHashedSubpackets(spGen.generate());
+ }
+
+ PGPCompressedDataGenerator cGen = new PGPCompressedDataGenerator(
+ PGPCompressedData.ZLIB);
+
+ BCPGOutputStream bOut = new BCPGOutputStream(cGen.open(out));
+
+ sGen.generateOnePassVersion(false).encode(bOut);
+
+ File file = new File(fileName);
+ PGPLiteralDataGenerator lGen = new PGPLiteralDataGenerator();
+ OutputStream lOut = lGen.open(bOut, PGPLiteralData.BINARY, file);
+ FileInputStream fIn = new FileInputStream(file);
+ int ch;
+
+ while ((ch = fIn.read()) >= 0)
+ {
+ lOut.write(ch);
+ sGen.update((byte)ch);
+ }
+
+ lGen.close();
+
+ sGen.generate().encode(bOut);
+
+ cGen.close();
+
+ if (armor)
+ {
+ out.close();
+ }
+ }
+
+ public static void main(
+ String[] args)
+ throws Exception
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ if (args[0].equals("-s"))
+ {
+ if (args[1].equals("-a"))
+ {
+ FileInputStream keyIn = new FileInputStream(args[3]);
+ FileOutputStream out = new FileOutputStream(args[2] + ".asc");
+
+ signFile(args[2], keyIn, out, args[4].toCharArray(), true);
+ }
+ else
+ {
+ FileInputStream keyIn = new FileInputStream(args[2]);
+ FileOutputStream out = new FileOutputStream(args[1] + ".bpg");
+
+ signFile(args[1], keyIn, out, args[3].toCharArray(), false);
+ }
+ }
+ else if (args[0].equals("-v"))
+ {
+ FileInputStream in = new FileInputStream(args[1]);
+ FileInputStream keyIn = new FileInputStream(args[2]);
+
+ verifyFile(in, keyIn);
+ }
+ else
+ {
+ System.err.println("usage: SignedFileProcessor -v|-s [-a] file keyfile [passPhrase]");
+ }
+ }
+} \ No newline at end of file
diff --git a/pg/src/main/java/org/spongycastle/openpgp/jcajce/JcaPGPObjectFactory.java b/pg/src/main/java/org/spongycastle/openpgp/jcajce/JcaPGPObjectFactory.java
new file mode 100644
index 00000000..b310de39
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/jcajce/JcaPGPObjectFactory.java
@@ -0,0 +1,35 @@
+package org.spongycastle.openpgp.jcajce;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+import org.spongycastle.openpgp.PGPObjectFactory;
+import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+
+/**
+ * {@link PGPObjectFactory} that uses the sources cryptographic primitives from the JCA API.
+ */
+public class JcaPGPObjectFactory
+ extends PGPObjectFactory
+{
+ /**
+ * Construct an object factory to read PGP objects from encoded data.
+ *
+ * @param encoded the PGP encoded data.
+ */
+ public JcaPGPObjectFactory(byte[] encoded)
+ {
+ this(new ByteArrayInputStream(encoded));
+ }
+
+ /**
+ * Construct an object factory to read PGP objects from a stream.
+ *
+ * @param in the stream containing PGP encoded objects.
+ */
+ public JcaPGPObjectFactory(InputStream in)
+ {
+ // FIXME: Convert this to builder style so we can set provider?
+ super(in, new JcaKeyFingerprintCalculator());
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/jcajce/JcaPGPPublicKeyRing.java b/pg/src/main/java/org/spongycastle/openpgp/jcajce/JcaPGPPublicKeyRing.java
new file mode 100644
index 00000000..218e7f2f
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/jcajce/JcaPGPPublicKeyRing.java
@@ -0,0 +1,26 @@
+package org.spongycastle.openpgp.jcajce;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator;
+import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+
+public class JcaPGPPublicKeyRing
+ extends PGPPublicKeyRing
+{
+ private static KeyFingerPrintCalculator fingerPrintCalculator = new JcaKeyFingerprintCalculator();
+
+ public JcaPGPPublicKeyRing(byte[] encoding)
+ throws IOException
+ {
+ super(encoding, fingerPrintCalculator);
+ }
+
+ public JcaPGPPublicKeyRing(InputStream in)
+ throws IOException
+ {
+ super(in, fingerPrintCalculator);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/jcajce/JcaPGPPublicKeyRingCollection.java b/pg/src/main/java/org/spongycastle/openpgp/jcajce/JcaPGPPublicKeyRingCollection.java
new file mode 100644
index 00000000..e23e18e6
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/jcajce/JcaPGPPublicKeyRingCollection.java
@@ -0,0 +1,32 @@
+package org.spongycastle.openpgp.jcajce;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPublicKeyRingCollection;
+import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+
+public class JcaPGPPublicKeyRingCollection
+ extends PGPPublicKeyRingCollection
+{
+ public JcaPGPPublicKeyRingCollection(byte[] encoding)
+ throws IOException, PGPException
+ {
+ this(new ByteArrayInputStream(encoding));
+ }
+
+ public JcaPGPPublicKeyRingCollection(InputStream in)
+ throws IOException, PGPException
+ {
+ super(in, new JcaKeyFingerprintCalculator());
+ }
+
+ public JcaPGPPublicKeyRingCollection(Collection collection)
+ throws IOException, PGPException
+ {
+ super(collection);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/jcajce/JcaPGPSecretKeyRing.java b/pg/src/main/java/org/spongycastle/openpgp/jcajce/JcaPGPSecretKeyRing.java
new file mode 100644
index 00000000..9e1395a6
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/jcajce/JcaPGPSecretKeyRing.java
@@ -0,0 +1,27 @@
+package org.spongycastle.openpgp.jcajce;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator;
+import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+
+public class JcaPGPSecretKeyRing
+ extends PGPSecretKeyRing
+{
+ private static KeyFingerPrintCalculator fingerPrintCalculator = new JcaKeyFingerprintCalculator();
+
+ public JcaPGPSecretKeyRing(byte[] encoding)
+ throws IOException, PGPException
+ {
+ super(encoding, fingerPrintCalculator);
+ }
+
+ public JcaPGPSecretKeyRing(InputStream in)
+ throws IOException, PGPException
+ {
+ super(in, fingerPrintCalculator);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/jcajce/JcaPGPSecretKeyRingCollection.java b/pg/src/main/java/org/spongycastle/openpgp/jcajce/JcaPGPSecretKeyRingCollection.java
new file mode 100644
index 00000000..a105ac08
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/jcajce/JcaPGPSecretKeyRingCollection.java
@@ -0,0 +1,32 @@
+package org.spongycastle.openpgp.jcajce;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPSecretKeyRingCollection;
+import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+
+public class JcaPGPSecretKeyRingCollection
+ extends PGPSecretKeyRingCollection
+{
+ public JcaPGPSecretKeyRingCollection(byte[] encoding)
+ throws IOException, PGPException
+ {
+ this(new ByteArrayInputStream(encoding));
+ }
+
+ public JcaPGPSecretKeyRingCollection(InputStream in)
+ throws IOException, PGPException
+ {
+ super(in, new JcaKeyFingerprintCalculator());
+ }
+
+ public JcaPGPSecretKeyRingCollection(Collection collection)
+ throws IOException, PGPException
+ {
+ super(collection);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/KeyFingerPrintCalculator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/KeyFingerPrintCalculator.java
new file mode 100644
index 00000000..a5a8a269
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/KeyFingerPrintCalculator.java
@@ -0,0 +1,10 @@
+package org.spongycastle.openpgp.operator;
+
+import org.spongycastle.bcpg.PublicKeyPacket;
+import org.spongycastle.openpgp.PGPException;
+
+public interface KeyFingerPrintCalculator
+{
+ byte[] calculateFingerprint(PublicKeyPacket publicPk)
+ throws PGPException;
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PBEDataDecryptorFactory.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PBEDataDecryptorFactory.java
new file mode 100644
index 00000000..5660468e
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PBEDataDecryptorFactory.java
@@ -0,0 +1,57 @@
+package org.spongycastle.openpgp.operator;
+
+import org.spongycastle.bcpg.S2K;
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.openpgp.PGPException;
+
+/**
+ * A factory for performing PBE decryption operations.
+ */
+public abstract class PBEDataDecryptorFactory
+ implements PGPDataDecryptorFactory
+{
+ private char[] passPhrase;
+ private PGPDigestCalculatorProvider calculatorProvider;
+
+ /**
+ * Construct a PBE data decryptor factory.
+ *
+ * @param passPhrase the pass phrase to generate decryption keys with.
+ * @param calculatorProvider the digest to use in key generation.
+ */
+ protected PBEDataDecryptorFactory(char[] passPhrase, PGPDigestCalculatorProvider calculatorProvider)
+ {
+ this.passPhrase = passPhrase;
+ this.calculatorProvider = calculatorProvider;
+ }
+
+ /**
+ * Generates an encryption key using the pass phrase and digest calculator configured for this
+ * factory.
+ *
+ * @param keyAlgorithm the {@link SymmetricKeyAlgorithmTags encryption algorithm} to generate a
+ * key for.
+ * @param s2k the string-to-key specification to use to generate the key.
+ * @return the key bytes for the encryption algorithm, generated using the pass phrase of this
+ * factory.
+ * @throws PGPException if an error occurs generating the key.
+ */
+ public byte[] makeKeyFromPassPhrase(int keyAlgorithm, S2K s2k)
+ throws PGPException
+ {
+ return PGPUtil.makeKeyFromPassPhrase(calculatorProvider, keyAlgorithm, s2k, passPhrase);
+ }
+
+ /**
+ * Decrypts session data from an encrypted data packet.
+ *
+ * @param keyAlgorithm the {@link SymmetricKeyAlgorithmTags encryption algorithm} used to
+ * encrypt the session data.
+ * @param key the key bytes for the encryption algorithm.
+ * @param seckKeyData the encrypted session data to decrypt.
+ * @return the decrypted session data.
+ * @throws PGPException if an error occurs decrypting the session data.
+ */
+ public abstract byte[] recoverSessionData(int keyAlgorithm, byte[] key, byte[] seckKeyData)
+ throws PGPException;
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PBEKeyEncryptionMethodGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PBEKeyEncryptionMethodGenerator.java
new file mode 100644
index 00000000..55a3e64b
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PBEKeyEncryptionMethodGenerator.java
@@ -0,0 +1,134 @@
+package org.spongycastle.openpgp.operator;
+
+import java.security.SecureRandom;
+
+import org.spongycastle.bcpg.ContainedPacket;
+import org.spongycastle.bcpg.S2K;
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.bcpg.SymmetricKeyEncSessionPacket;
+import org.spongycastle.openpgp.PGPException;
+
+/**
+ * PGP style PBE encryption method.
+ * <p/>
+ * A pass phrase is used to generate an encryption key using the PGP {@link S2K string-to-key}
+ * method. This class always uses the {@link S2K#SALTED_AND_ITERATED salted and iterated form of the
+ * S2K algorithm}.
+ * <p/>
+ * Note that the iteration count provided to this method is a single byte as described by the
+ * {@link S2K} algorithm, and the actual iteration count ranges exponentially from
+ * <code>0x01<code> == 1088 to <code>0xFF</code> == 65,011,712.
+ */
+public abstract class PBEKeyEncryptionMethodGenerator
+ extends PGPKeyEncryptionMethodGenerator
+{
+ private char[] passPhrase;
+ private PGPDigestCalculator s2kDigestCalculator;
+ private S2K s2k;
+ private SecureRandom random;
+ private int s2kCount;
+
+ /**
+ * Construct a PBE key generator using the default iteration count (<code>0x60</code> == 65536
+ * iterations).
+ *
+ * @param passPhrase the pass phrase to encrypt with.
+ * @param s2kDigestCalculator a digest calculator to use in the string-to-key function.
+ */
+ protected PBEKeyEncryptionMethodGenerator(
+ char[] passPhrase,
+ PGPDigestCalculator s2kDigestCalculator)
+ {
+ this(passPhrase, s2kDigestCalculator, 0x60);
+ }
+
+ /**
+ * Construct a PBE key generator using a specific iteration level.
+ *
+ * @param passPhrase the pass phrase to encrypt with.
+ * @param s2kDigestCalculator a digest calculator to use in the string-to-key function.
+ * @param s2kCount a single byte {@link S2K} iteration count specifier, which is translated to
+ * an actual iteration count by the S2K class.
+ */
+ protected PBEKeyEncryptionMethodGenerator(
+ char[] passPhrase,
+ PGPDigestCalculator s2kDigestCalculator,
+ int s2kCount)
+ {
+ this.passPhrase = passPhrase;
+ this.s2kDigestCalculator = s2kDigestCalculator;
+
+ if (s2kCount < 0 || s2kCount > 0xff)
+ {
+ throw new IllegalArgumentException("s2kCount value outside of range 0 to 255.");
+ }
+
+ this.s2kCount = s2kCount;
+ }
+
+ /**
+ * Sets a user defined source of randomness.
+ * <p/>
+ * If no SecureRandom is configured, a default SecureRandom will be used.
+ *
+ * @return the current generator.
+ */
+ public PBEKeyEncryptionMethodGenerator setSecureRandom(SecureRandom random)
+ {
+ this.random = random;
+
+ return this;
+ }
+
+ /**
+ * Generate a key for a symmetric encryption algorithm using the PBE configuration in this
+ * method.
+ *
+ * @param encAlgorithm the {@link SymmetricKeyAlgorithmTags encryption algorithm} to generate
+ * the key for.
+ * @return the bytes of the generated key.
+ * @throws PGPException if an error occurs performing the string-to-key generation.
+ */
+ public byte[] getKey(int encAlgorithm)
+ throws PGPException
+ {
+ if (s2k == null)
+ {
+ byte[] iv = new byte[8];
+
+ if (random == null)
+ {
+ random = new SecureRandom();
+ }
+
+ random.nextBytes(iv);
+
+ s2k = new S2K(s2kDigestCalculator.getAlgorithm(), iv, s2kCount);
+ }
+
+ return PGPUtil.makeKeyFromPassPhrase(s2kDigestCalculator, encAlgorithm, s2k, passPhrase);
+ }
+
+ public ContainedPacket generate(int encAlgorithm, byte[] sessionInfo)
+ throws PGPException
+ {
+ byte[] key = getKey(encAlgorithm);
+
+ if (sessionInfo == null)
+ {
+ return new SymmetricKeyEncSessionPacket(encAlgorithm, s2k, null);
+ }
+
+ //
+ // the passed in session info has the an RSA/ElGamal checksum added to it, for PBE this is not included.
+ //
+ byte[] nSessionInfo = new byte[sessionInfo.length - 2];
+
+ System.arraycopy(sessionInfo, 0, nSessionInfo, 0, nSessionInfo.length);
+
+ return new SymmetricKeyEncSessionPacket(encAlgorithm, s2k, encryptSessionInfo(encAlgorithm, key, nSessionInfo));
+ }
+
+ abstract protected byte[] encryptSessionInfo(int encAlgorithm, byte[] key, byte[] sessionInfo)
+ throws PGPException;
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PBEProtectionRemoverFactory.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PBEProtectionRemoverFactory.java
new file mode 100644
index 00000000..99166f6f
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PBEProtectionRemoverFactory.java
@@ -0,0 +1,9 @@
+package org.spongycastle.openpgp.operator;
+
+import org.spongycastle.openpgp.PGPException;
+
+public interface PBEProtectionRemoverFactory
+{
+ PBESecretKeyDecryptor createDecryptor(String protection)
+ throws PGPException;
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PBESecretKeyDecryptor.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PBESecretKeyDecryptor.java
new file mode 100644
index 00000000..2f75702f
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PBESecretKeyDecryptor.java
@@ -0,0 +1,31 @@
+package org.spongycastle.openpgp.operator;
+
+import org.spongycastle.bcpg.S2K;
+import org.spongycastle.openpgp.PGPException;
+
+public abstract class PBESecretKeyDecryptor
+{
+ private char[] passPhrase;
+ private PGPDigestCalculatorProvider calculatorProvider;
+
+ protected PBESecretKeyDecryptor(char[] passPhrase, PGPDigestCalculatorProvider calculatorProvider)
+ {
+ this.passPhrase = passPhrase;
+ this.calculatorProvider = calculatorProvider;
+ }
+
+ public PGPDigestCalculator getChecksumCalculator(int hashAlgorithm)
+ throws PGPException
+ {
+ return calculatorProvider.get(hashAlgorithm);
+ }
+
+ public byte[] makeKeyFromPassPhrase(int keyAlgorithm, S2K s2k)
+ throws PGPException
+ {
+ return PGPUtil.makeKeyFromPassPhrase(calculatorProvider, keyAlgorithm, s2k, passPhrase);
+ }
+
+ public abstract byte[] recoverKeyData(int encAlgorithm, byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen)
+ throws PGPException;
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PBESecretKeyEncryptor.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PBESecretKeyEncryptor.java
new file mode 100644
index 00000000..2bd1bf8f
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PBESecretKeyEncryptor.java
@@ -0,0 +1,104 @@
+package org.spongycastle.openpgp.operator;
+
+import java.security.SecureRandom;
+
+import org.spongycastle.bcpg.S2K;
+import org.spongycastle.openpgp.PGPException;
+
+public abstract class PBESecretKeyEncryptor
+{
+ protected int encAlgorithm;
+ protected char[] passPhrase;
+ protected PGPDigestCalculator s2kDigestCalculator;
+ protected int s2kCount;
+ protected S2K s2k;
+
+ protected SecureRandom random;
+
+ protected PBESecretKeyEncryptor(int encAlgorithm, PGPDigestCalculator s2kDigestCalculator, SecureRandom random, char[] passPhrase)
+ {
+ this(encAlgorithm, s2kDigestCalculator, 0x60, random, passPhrase);
+ }
+
+ protected PBESecretKeyEncryptor(int encAlgorithm, PGPDigestCalculator s2kDigestCalculator, int s2kCount, SecureRandom random, char[] passPhrase)
+ {
+ this.encAlgorithm = encAlgorithm;
+ this.passPhrase = passPhrase;
+ this.random = random;
+ this.s2kDigestCalculator = s2kDigestCalculator;
+
+ if (s2kCount < 0 || s2kCount > 0xff)
+ {
+ throw new IllegalArgumentException("s2kCount value outside of range 0 to 255.");
+ }
+
+ this.s2kCount = s2kCount;
+ }
+
+ public int getAlgorithm()
+ {
+ return encAlgorithm;
+ }
+
+ public int getHashAlgorithm()
+ {
+ if (s2kDigestCalculator != null)
+ {
+ return s2kDigestCalculator.getAlgorithm();
+ }
+
+ return -1;
+ }
+
+ public byte[] getKey()
+ throws PGPException
+ {
+ return PGPUtil.makeKeyFromPassPhrase(s2kDigestCalculator, encAlgorithm, s2k, passPhrase);
+ }
+
+ public S2K getS2K()
+ {
+ return s2k;
+ }
+
+ /**
+ * Key encryption method invoked for V4 keys and greater.
+ *
+ * @param keyData raw key data
+ * @param keyOff offset into rawe key data
+ * @param keyLen length of key data to use.
+ * @return an encryption of the passed in keyData.
+ * @throws PGPException on error in the underlying encryption process.
+ */
+ public byte[] encryptKeyData(byte[] keyData, int keyOff, int keyLen)
+ throws PGPException
+ {
+ if (s2k == null)
+ {
+ byte[] iv = new byte[8];
+
+ random.nextBytes(iv);
+
+ s2k = new S2K(s2kDigestCalculator.getAlgorithm(), iv, s2kCount);
+ }
+
+ return encryptKeyData(getKey(), keyData, keyOff, keyLen);
+ }
+
+ public abstract byte[] encryptKeyData(byte[] key, byte[] keyData, int keyOff, int keyLen)
+ throws PGPException;
+
+ /**
+ * Encrypt the passed in keyData using the key and the iv provided.
+ * <p>
+ * This method is only used for processing version 3 keys.
+ * </p>
+ */
+ public byte[] encryptKeyData(byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen)
+ throws PGPException
+ {
+ throw new PGPException("encryption of version 3 keys not supported.");
+ }
+
+ public abstract byte[] getCipherIV();
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentSigner.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentSigner.java
new file mode 100644
index 00000000..7a3891e6
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentSigner.java
@@ -0,0 +1,20 @@
+package org.spongycastle.openpgp.operator;
+
+import java.io.OutputStream;
+
+public interface PGPContentSigner
+{
+ public OutputStream getOutputStream();
+
+ byte[] getSignature();
+
+ byte[] getDigest();
+
+ int getType();
+
+ int getHashAlgorithm();
+
+ int getKeyAlgorithm();
+
+ long getKeyID();
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentSignerBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentSignerBuilder.java
new file mode 100644
index 00000000..44f8c8dd
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentSignerBuilder.java
@@ -0,0 +1,10 @@
+package org.spongycastle.openpgp.operator;
+
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPrivateKey;
+
+public interface PGPContentSignerBuilder
+{
+ public PGPContentSigner build(final int signatureType, final PGPPrivateKey privateKey)
+ throws PGPException;
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifier.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifier.java
new file mode 100644
index 00000000..7ed1aea6
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifier.java
@@ -0,0 +1,20 @@
+package org.spongycastle.openpgp.operator;
+
+import java.io.OutputStream;
+
+public interface PGPContentVerifier
+{
+ public OutputStream getOutputStream();
+
+ int getHashAlgorithm();
+
+ int getKeyAlgorithm();
+
+ long getKeyID();
+
+ /**
+ * @param expected expected value of the signature on the data.
+ * @return true if the signature verifies, false otherwise
+ */
+ boolean verify(byte[] expected);
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifierBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifierBuilder.java
new file mode 100644
index 00000000..9e8e4a94
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifierBuilder.java
@@ -0,0 +1,10 @@
+package org.spongycastle.openpgp.operator;
+
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPublicKey;
+
+public interface PGPContentVerifierBuilder
+{
+ public PGPContentVerifier build(final PGPPublicKey publicKey)
+ throws PGPException;
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifierBuilderProvider.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifierBuilderProvider.java
new file mode 100644
index 00000000..44c138bf
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifierBuilderProvider.java
@@ -0,0 +1,9 @@
+package org.spongycastle.openpgp.operator;
+
+import org.spongycastle.openpgp.PGPException;
+
+public interface PGPContentVerifierBuilderProvider
+{
+ public PGPContentVerifierBuilder get(int keyAlgorithm, int hashAlgorithm)
+ throws PGPException;
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptor.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptor.java
new file mode 100644
index 00000000..ceeca672
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptor.java
@@ -0,0 +1,30 @@
+package org.spongycastle.openpgp.operator;
+
+import java.io.InputStream;
+
+/**
+ * A decryptor that wraps a stream of PGP encrypted data to decrypt, and optionally integrity check,
+ * the data.
+ */
+public interface PGPDataDecryptor
+{
+ /**
+ * Wraps an encrypted data stream with a stream that will return the decrypted data.
+ *
+ * @param in the encrypted data.
+ * @return a decrypting stream.
+ */
+ InputStream getInputStream(InputStream in);
+
+ /**
+ * Obtains the block size of the encryption algorithm used in this decryptor.
+ *
+ * @return the block size of the cipher in bytes.
+ */
+ int getBlockSize();
+
+ /**
+ * Obtains the digest calculator used to verify the integrity check.
+ */
+ PGPDigestCalculator getIntegrityCalculator();
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptorFactory.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptorFactory.java
new file mode 100644
index 00000000..7223d039
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptorFactory.java
@@ -0,0 +1,25 @@
+package org.spongycastle.openpgp.operator;
+
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.openpgp.PGPException;
+
+/**
+ * Base interface of factories for {@link PGPDataDecryptor}.
+ */
+public interface PGPDataDecryptorFactory
+{
+ /**
+ * Constructs a data decryptor.
+ *
+ * @param withIntegrityPacket <code>true</code> if the packet to be decrypted has integrity
+ * checking enabled.
+ * @param encAlgorithm the identifier of the {@link SymmetricKeyAlgorithmTags encryption
+ * algorithm} to decrypt with.
+ * @param key the bytes of the key for the cipher.
+ * @return a data decryptor that can decrypt (and verify) streams of encrypted data.
+ * @throws PGPException if an error occurs initialising the decryption and integrity checking
+ * functions.
+ */
+ public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
+ throws PGPException;
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptorProvider.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptorProvider.java
new file mode 100644
index 00000000..9e87a565
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptorProvider.java
@@ -0,0 +1,5 @@
+package org.spongycastle.openpgp.operator;
+
+public interface PGPDataDecryptorProvider
+{
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataEncryptor.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataEncryptor.java
new file mode 100644
index 00000000..9811a6de
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataEncryptor.java
@@ -0,0 +1,39 @@
+package org.spongycastle.openpgp.operator;
+
+import java.io.OutputStream;
+
+/**
+ * A data encryptor, combining a cipher instance and an optional integrity check calculator.
+ * <p/>
+ * {@link PGPDataEncryptor} instances are generally not constructed directly, but obtained from a
+ * {@link PGPDataEncryptorBuilder}.
+ */
+public interface PGPDataEncryptor
+{
+ /**
+ * Constructs an encrypting output stream that encrypts data using the underlying cipher of this
+ * encryptor.
+ * <p/>
+ * The cipher instance in this encryptor is used for all output streams obtained from this
+ * method, so it should only be invoked once.
+ *
+ * @param out the stream to wrap and write encrypted data to.
+ * @return a cipher output stream appropriate to the type of this data encryptor.
+ */
+ OutputStream getOutputStream(OutputStream out);
+
+ /**
+ * Obtains the integrity check calculator configured for this encryptor instance.
+ *
+ * @return the integrity check calculator, or <code>null</code> if no integrity checking was
+ * configured.
+ */
+ PGPDigestCalculator getIntegrityCalculator();
+
+ /**
+ * Gets the block size of the underlying cipher used by this encryptor.
+ *
+ * @return the block size in bytes.
+ */
+ int getBlockSize();
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataEncryptorBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataEncryptorBuilder.java
new file mode 100644
index 00000000..a889b3be
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataEncryptorBuilder.java
@@ -0,0 +1,36 @@
+package org.spongycastle.openpgp.operator;
+
+import java.security.SecureRandom;
+
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.openpgp.PGPException;
+
+/**
+ * A builder for {@link PGPDataEncryptor} instances, which can be used to encrypt data objects.
+ */
+public interface PGPDataEncryptorBuilder
+{
+ /**
+ * The encryption algorithm used by data encryptors created by this builder.
+ *
+ * @return one of the {@link SymmetricKeyAlgorithmTags symmetric encryption algorithms}.
+ */
+ int getAlgorithm();
+
+ /**
+ * Builds a data encryptor using the algorithm configured for this builder.
+ *
+ * @param keyBytes the bytes of the key to use for the cipher.
+ * @return a data encryptor with an initialised cipher.
+ * @throws PGPException if an error occurs initialising the configured encryption.
+ */
+ PGPDataEncryptor build(byte[] keyBytes)
+ throws PGPException;
+
+ /**
+ * Gets the SecureRandom instance used by this builder. <br/>
+ * If a SecureRandom has not been explicitly configured, a default {@link SecureRandom} is
+ * constructed and retained by the this builder.
+ */
+ SecureRandom getSecureRandom();
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDigestCalculator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDigestCalculator.java
new file mode 100644
index 00000000..a4b926b0
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDigestCalculator.java
@@ -0,0 +1,40 @@
+package org.spongycastle.openpgp.operator;
+
+import java.io.OutputStream;
+
+import org.spongycastle.bcpg.HashAlgorithmTags;
+
+/**
+ * A digest calculator, which consumes a stream of data and computes a digest value over it.
+ */
+public interface PGPDigestCalculator
+{
+ /**
+ * Return the {@link HashAlgorithmTags algorithm number} representing the digest implemented by
+ * this calculator.
+ *
+ * @return the hash algorithm number
+ */
+ int getAlgorithm();
+
+ /**
+ * Returns a stream that will accept data for the purpose of calculating a digest. Use
+ * org.spongycastle.util.io.TeeOutputStream if you want to accumulate the data on the fly as
+ * well.
+ *
+ * @return an OutputStream that data to be digested can be written to.
+ */
+ OutputStream getOutputStream();
+
+ /**
+ * Return the digest calculated on what has been written to the calculator's output stream.
+ *
+ * @return a digest.
+ */
+ byte[] getDigest();
+
+ /**
+ * Reset the underlying digest calculator
+ */
+ void reset();
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDigestCalculatorProvider.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDigestCalculatorProvider.java
new file mode 100644
index 00000000..8ad35b13
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDigestCalculatorProvider.java
@@ -0,0 +1,21 @@
+package org.spongycastle.openpgp.operator;
+
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.openpgp.PGPException;
+
+/**
+ * A factory for digest algorithms.
+ */
+public interface PGPDigestCalculatorProvider
+{
+ /**
+ * Construct a new instance of a cryptographic digest.
+ *
+ * @param algorithm the identifier of the {@link HashAlgorithmTags digest algorithm} to
+ * instantiate.
+ * @return a digest calculator for the specified algorithm.
+ * @throws PGPException if an error occurs constructing the specified digest.
+ */
+ PGPDigestCalculator get(int algorithm)
+ throws PGPException;
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPKeyEncryptionMethodGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPKeyEncryptionMethodGenerator.java
new file mode 100644
index 00000000..d0a3c5b6
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPKeyEncryptionMethodGenerator.java
@@ -0,0 +1,23 @@
+package org.spongycastle.openpgp.operator;
+
+import org.spongycastle.bcpg.ContainedPacket;
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.openpgp.PGPEncryptedDataGenerator;
+import org.spongycastle.openpgp.PGPException;
+
+/**
+ * An encryption method that can be applied to encrypt data in a {@link PGPEncryptedDataGenerator}.
+ */
+public abstract class PGPKeyEncryptionMethodGenerator
+{
+ /**
+ * Generates a packet encoding the details of this encryption method.
+ *
+ * @param encAlgorithm the {@link SymmetricKeyAlgorithmTags encryption algorithm} being used
+ * @param sessionInfo session data generated by the encrypted data generator.
+ * @return a packet encoding the provided information and the configuration of this instance.
+ * @throws PGPException if an error occurs constructing the packet.
+ */
+ public abstract ContainedPacket generate(int encAlgorithm, byte[] sessionInfo)
+ throws PGPException;
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPPad.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPPad.java
new file mode 100644
index 00000000..d3ef360c
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPPad.java
@@ -0,0 +1,50 @@
+package org.spongycastle.openpgp.operator;
+
+import org.spongycastle.openpgp.PGPException;
+
+/**
+ * Utility class that provides padding addition and removal for PGP session keys.
+ */
+public class PGPPad
+{
+ private PGPPad()
+ {
+
+ }
+
+ public static byte[] padSessionData(byte[] sessionInfo)
+ {
+ byte[] result = new byte[40];
+
+ System.arraycopy(sessionInfo, 0, result, 0, sessionInfo.length);
+
+ byte padValue = (byte)(result.length - sessionInfo.length);
+
+ for (int i = sessionInfo.length; i != result.length; i++)
+ {
+ result[i] = padValue;
+ }
+
+ return result;
+ }
+
+ public static byte[] unpadSessionData(byte[] encoded)
+ throws PGPException
+ {
+ byte padValue = encoded[encoded.length - 1];
+
+ for (int i = encoded.length - padValue; i != encoded.length; i++)
+ {
+ if (encoded[i] != padValue)
+ {
+ throw new PGPException("bad padding found in session data");
+ }
+ }
+
+ byte[] taggedKey = new byte[encoded.length - padValue];
+
+ System.arraycopy(encoded, 0, taggedKey, 0, taggedKey.length);
+
+ return taggedKey;
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PGPUtil.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPUtil.java
new file mode 100644
index 00000000..c318c673
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PGPUtil.java
@@ -0,0 +1,229 @@
+package org.spongycastle.openpgp.operator;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.bcpg.S2K;
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.util.Strings;
+
+/**
+ * Basic utility class
+ */
+class PGPUtil
+ implements HashAlgorithmTags
+{
+ static byte[] makeKeyFromPassPhrase(
+ PGPDigestCalculator digestCalculator,
+ int algorithm,
+ S2K s2k,
+ char[] passPhrase)
+ throws PGPException
+ {
+ // TODO: Never used
+ String algName = null;
+ int keySize = 0;
+
+ switch (algorithm)
+ {
+ case SymmetricKeyAlgorithmTags.TRIPLE_DES:
+ keySize = 192;
+ algName = "DES_EDE";
+ break;
+ case SymmetricKeyAlgorithmTags.IDEA:
+ keySize = 128;
+ algName = "IDEA";
+ break;
+ case SymmetricKeyAlgorithmTags.CAST5:
+ keySize = 128;
+ algName = "CAST5";
+ break;
+ case SymmetricKeyAlgorithmTags.BLOWFISH:
+ keySize = 128;
+ algName = "Blowfish";
+ break;
+ case SymmetricKeyAlgorithmTags.SAFER:
+ keySize = 128;
+ algName = "SAFER";
+ break;
+ case SymmetricKeyAlgorithmTags.DES:
+ keySize = 64;
+ algName = "DES";
+ break;
+ case SymmetricKeyAlgorithmTags.AES_128:
+ keySize = 128;
+ algName = "AES";
+ break;
+ case SymmetricKeyAlgorithmTags.AES_192:
+ keySize = 192;
+ algName = "AES";
+ break;
+ case SymmetricKeyAlgorithmTags.AES_256:
+ keySize = 256;
+ algName = "AES";
+ break;
+ case SymmetricKeyAlgorithmTags.TWOFISH:
+ keySize = 256;
+ algName = "Twofish";
+ break;
+ case SymmetricKeyAlgorithmTags.CAMELLIA_128:
+ keySize = 128;
+ algName = "Camellia";
+ break;
+ case SymmetricKeyAlgorithmTags.CAMELLIA_192:
+ keySize = 192;
+ algName = "Camellia";
+ break;
+ case SymmetricKeyAlgorithmTags.CAMELLIA_256:
+ keySize = 256;
+ algName = "Camellia";
+ break;
+ default:
+ throw new PGPException("unknown symmetric algorithm: " + algorithm);
+ }
+
+ byte[] pBytes = Strings.toUTF8ByteArray(passPhrase);
+ byte[] keyBytes = new byte[(keySize + 7) / 8];
+
+ int generatedBytes = 0;
+ int loopCount = 0;
+
+ if (s2k != null)
+ {
+ if (s2k.getHashAlgorithm() != digestCalculator.getAlgorithm())
+ {
+ throw new PGPException("s2k/digestCalculator mismatch");
+ }
+ }
+ else
+ {
+ if (digestCalculator.getAlgorithm() != HashAlgorithmTags.MD5)
+ {
+ throw new PGPException("digestCalculator not for MD5");
+ }
+ }
+
+ OutputStream dOut = digestCalculator.getOutputStream();
+
+ try
+ {
+ while (generatedBytes < keyBytes.length)
+ {
+ if (s2k != null)
+ {
+ for (int i = 0; i != loopCount; i++)
+ {
+ dOut.write(0);
+ }
+
+ byte[] iv = s2k.getIV();
+
+ switch (s2k.getType())
+ {
+ case S2K.SIMPLE:
+ dOut.write(pBytes);
+ break;
+ case S2K.SALTED:
+ dOut.write(iv);
+ dOut.write(pBytes);
+ break;
+ case S2K.SALTED_AND_ITERATED:
+ long count = s2k.getIterationCount();
+ dOut.write(iv);
+ dOut.write(pBytes);
+
+ count -= iv.length + pBytes.length;
+
+ while (count > 0)
+ {
+ if (count < iv.length)
+ {
+ dOut.write(iv, 0, (int)count);
+ break;
+ }
+ else
+ {
+ dOut.write(iv);
+ count -= iv.length;
+ }
+
+ if (count < pBytes.length)
+ {
+ dOut.write(pBytes, 0, (int)count);
+ count = 0;
+ }
+ else
+ {
+ dOut.write(pBytes);
+ count -= pBytes.length;
+ }
+ }
+ break;
+ default:
+ throw new PGPException("unknown S2K type: " + s2k.getType());
+ }
+ }
+ else
+ {
+ for (int i = 0; i != loopCount; i++)
+ {
+ dOut.write((byte)0);
+ }
+
+ dOut.write(pBytes);
+ }
+
+ dOut.close();
+
+ byte[] dig = digestCalculator.getDigest();
+
+ if (dig.length > (keyBytes.length - generatedBytes))
+ {
+ System.arraycopy(dig, 0, keyBytes, generatedBytes, keyBytes.length - generatedBytes);
+ }
+ else
+ {
+ System.arraycopy(dig, 0, keyBytes, generatedBytes, dig.length);
+ }
+
+ generatedBytes += dig.length;
+
+ loopCount++;
+ }
+ }
+ catch (IOException e)
+ {
+ throw new PGPException("exception calculating digest: " + e.getMessage(), e);
+ }
+
+ for (int i = 0; i != pBytes.length; i++)
+ {
+ pBytes[i] = 0;
+ }
+
+ return keyBytes;
+ }
+
+ public static byte[] makeKeyFromPassPhrase(
+ PGPDigestCalculatorProvider digCalcProvider,
+ int algorithm,
+ S2K s2k,
+ char[] passPhrase)
+ throws PGPException
+ {
+ PGPDigestCalculator digestCalculator;
+
+ if (s2k != null)
+ {
+ digestCalculator = digCalcProvider.get(s2k.getHashAlgorithm());
+ }
+ else
+ {
+ digestCalculator = digCalcProvider.get(HashAlgorithmTags.MD5);
+ }
+
+ return makeKeyFromPassPhrase(digestCalculator, algorithm, s2k, passPhrase);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PublicKeyDataDecryptorFactory.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PublicKeyDataDecryptorFactory.java
new file mode 100644
index 00000000..35d2a01d
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PublicKeyDataDecryptorFactory.java
@@ -0,0 +1,10 @@
+package org.spongycastle.openpgp.operator;
+
+import org.spongycastle.openpgp.PGPException;
+
+public interface PublicKeyDataDecryptorFactory
+ extends PGPDataDecryptorFactory
+{
+ public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData)
+ throws PGPException;
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/PublicKeyKeyEncryptionMethodGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/PublicKeyKeyEncryptionMethodGenerator.java
new file mode 100644
index 00000000..8030b946
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/PublicKeyKeyEncryptionMethodGenerator.java
@@ -0,0 +1,100 @@
+package org.spongycastle.openpgp.operator;
+
+import java.io.IOException;
+import java.math.BigInteger;
+
+import org.spongycastle.bcpg.ContainedPacket;
+import org.spongycastle.bcpg.MPInteger;
+import org.spongycastle.bcpg.PublicKeyEncSessionPacket;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPublicKey;
+
+public abstract class PublicKeyKeyEncryptionMethodGenerator
+ extends PGPKeyEncryptionMethodGenerator
+{
+ private PGPPublicKey pubKey;
+
+ protected PublicKeyKeyEncryptionMethodGenerator(
+ PGPPublicKey pubKey)
+ {
+ this.pubKey = pubKey;
+
+ switch (pubKey.getAlgorithm())
+ {
+ case PGPPublicKey.RSA_ENCRYPT:
+ case PGPPublicKey.RSA_GENERAL:
+ break;
+ case PGPPublicKey.ELGAMAL_ENCRYPT:
+ case PGPPublicKey.ELGAMAL_GENERAL:
+ break;
+ case PGPPublicKey.ECDH:
+ break;
+ case PGPPublicKey.DSA:
+ throw new IllegalArgumentException("Can't use DSA for encryption.");
+ case PGPPublicKey.ECDSA:
+ throw new IllegalArgumentException("Can't use ECDSA for encryption.");
+ default:
+ throw new IllegalArgumentException("unknown asymmetric algorithm: " + pubKey.getAlgorithm());
+ }
+ }
+
+ public byte[][] processSessionInfo(
+ byte[] encryptedSessionInfo)
+ throws PGPException
+ {
+ byte[][] data;
+
+ switch (pubKey.getAlgorithm())
+ {
+ case PGPPublicKey.RSA_ENCRYPT:
+ case PGPPublicKey.RSA_GENERAL:
+ data = new byte[1][];
+
+ data[0] = convertToEncodedMPI(encryptedSessionInfo);
+ break;
+ case PGPPublicKey.ELGAMAL_ENCRYPT:
+ case PGPPublicKey.ELGAMAL_GENERAL:
+ byte[] b1 = new byte[encryptedSessionInfo.length / 2];
+ byte[] b2 = new byte[encryptedSessionInfo.length / 2];
+
+ System.arraycopy(encryptedSessionInfo, 0, b1, 0, b1.length);
+ System.arraycopy(encryptedSessionInfo, b1.length, b2, 0, b2.length);
+
+ data = new byte[2][];
+ data[0] = convertToEncodedMPI(b1);
+ data[1] = convertToEncodedMPI(b2);
+ break;
+ case PGPPublicKey.ECDH:
+ data = new byte[1][];
+
+ data[0] = encryptedSessionInfo;
+ break;
+ default:
+ throw new PGPException("unknown asymmetric algorithm: " + pubKey.getAlgorithm());
+ }
+
+ return data;
+ }
+
+ private byte[] convertToEncodedMPI(byte[] encryptedSessionInfo)
+ throws PGPException
+ {
+ try
+ {
+ return new MPInteger(new BigInteger(1, encryptedSessionInfo)).getEncoded();
+ }
+ catch (IOException e)
+ {
+ throw new PGPException("Invalid MPI encoding: " + e.getMessage(), e);
+ }
+ }
+
+ public ContainedPacket generate(int encAlgorithm, byte[] sessionInfo)
+ throws PGPException
+ {
+ return new PublicKeyEncSessionPacket(pubKey.getKeyID(), pubKey.getAlgorithm(), processSessionInfo(encryptSessionInfo(pubKey, sessionInfo)));
+ }
+
+ abstract protected byte[] encryptSessionInfo(PGPPublicKey pubKey, byte[] sessionInfo)
+ throws PGPException;
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/RFC6637KDFCalculator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/RFC6637KDFCalculator.java
new file mode 100644
index 00000000..bc342f68
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/RFC6637KDFCalculator.java
@@ -0,0 +1,115 @@
+package org.spongycastle.openpgp.operator;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.spongycastle.asn1.ASN1ObjectIdentifier;
+import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.math.ec.ECPoint;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.util.encoders.Hex;
+
+/**
+ * Calculator for the EC based KDF algorithm described in RFC 6637
+ */
+public class RFC6637KDFCalculator
+{
+ // "Anonymous Sender ", which is the octet sequence
+ private static final byte[] ANONYMOUS_SENDER = Hex.decode("416E6F6E796D6F75732053656E64657220202020");
+
+ private final PGPDigestCalculator digCalc;
+ private final int keyAlgorithm;
+
+ public RFC6637KDFCalculator(PGPDigestCalculator digCalc, int keyAlgorithm)
+ {
+ this.digCalc = digCalc;
+ this.keyAlgorithm = keyAlgorithm;
+ }
+
+ public byte[] createKey(ASN1ObjectIdentifier curveOID, ECPoint s, byte[] recipientFingerPrint)
+ throws PGPException
+ {
+ try
+ {
+ // RFC 6637 - Section 8
+ // curve_OID_len = (byte)len(curve_OID);
+ // Param = curve_OID_len || curve_OID || public_key_alg_ID || 03
+ // || 01 || KDF_hash_ID || KEK_alg_ID for AESKeyWrap || "Anonymous
+ // Sender " || recipient_fingerprint;
+ // Z_len = the key size for the KEK_alg_ID used with AESKeyWrap
+ // Compute Z = KDF( S, Z_len, Param );
+ ByteArrayOutputStream pOut = new ByteArrayOutputStream();
+
+ byte[] encOid = curveOID.getEncoded();
+
+ pOut.write(encOid, 1, encOid.length - 1);
+ pOut.write(PublicKeyAlgorithmTags.ECDH);
+ pOut.write(0x03);
+ pOut.write(0x01);
+ pOut.write(digCalc.getAlgorithm());
+ pOut.write(keyAlgorithm);
+ pOut.write(ANONYMOUS_SENDER);
+ pOut.write(recipientFingerPrint);
+
+ return KDF(digCalc, s, getKeyLen(keyAlgorithm), pOut.toByteArray());
+ }
+ catch (IOException e)
+ {
+ throw new PGPException("Exception performing KDF: " + e.getMessage(), e);
+ }
+ }
+
+ // RFC 6637 - Section 7
+ // Implements KDF( X, oBits, Param );
+ // Input: point X = (x,y)
+ // oBits - the desired size of output
+ // hBits - the size of output of hash function Hash
+ // Param - octets representing the parameters
+ // Assumes that oBits <= hBits
+ // Convert the point X to the octet string, see section 6:
+ // ZB' = 04 || x || y
+ // and extract the x portion from ZB'
+ // ZB = x;
+ // MB = Hash ( 00 || 00 || 00 || 01 || ZB || Param );
+ // return oBits leftmost bits of MB.
+ private static byte[] KDF(PGPDigestCalculator digCalc, ECPoint s, int keyLen, byte[] param)
+ throws IOException
+ {
+ byte[] ZB = s.getXCoord().getEncoded();
+
+ OutputStream dOut = digCalc.getOutputStream();
+
+ dOut.write(0x00);
+ dOut.write(0x00);
+ dOut.write(0x00);
+ dOut.write(0x01);
+ dOut.write(ZB);
+ dOut.write(param);
+
+ byte[] digest = digCalc.getDigest();
+
+ byte[] key = new byte[keyLen];
+
+ System.arraycopy(digest, 0, key, 0, key.length);
+
+ return key;
+ }
+
+ private static int getKeyLen(int algID)
+ throws PGPException
+ {
+ switch (algID)
+ {
+ case SymmetricKeyAlgorithmTags.AES_128:
+ return 16;
+ case SymmetricKeyAlgorithmTags.AES_192:
+ return 24;
+ case SymmetricKeyAlgorithmTags.AES_256:
+ return 32;
+ default:
+ throw new PGPException("unknown symmetric algorithm ID: " + algID);
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcImplProvider.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcImplProvider.java
new file mode 100644
index 00000000..f54870f0
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcImplProvider.java
@@ -0,0 +1,174 @@
+package org.spongycastle.openpgp.operator.bc;
+
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.crypto.AsymmetricBlockCipher;
+import org.spongycastle.crypto.BlockCipher;
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.crypto.Signer;
+import org.spongycastle.crypto.Wrapper;
+import org.spongycastle.crypto.digests.MD2Digest;
+import org.spongycastle.crypto.digests.MD5Digest;
+import org.spongycastle.crypto.digests.RIPEMD160Digest;
+import org.spongycastle.crypto.digests.SHA1Digest;
+import org.spongycastle.crypto.digests.SHA224Digest;
+import org.spongycastle.crypto.digests.SHA256Digest;
+import org.spongycastle.crypto.digests.SHA384Digest;
+import org.spongycastle.crypto.digests.SHA512Digest;
+import org.spongycastle.crypto.digests.TigerDigest;
+import org.spongycastle.crypto.encodings.PKCS1Encoding;
+import org.spongycastle.crypto.engines.AESEngine;
+import org.spongycastle.crypto.engines.AESFastEngine;
+import org.spongycastle.crypto.engines.BlowfishEngine;
+import org.spongycastle.crypto.engines.CAST5Engine;
+import org.spongycastle.crypto.engines.CamelliaEngine;
+import org.spongycastle.crypto.engines.DESEngine;
+import org.spongycastle.crypto.engines.DESedeEngine;
+import org.spongycastle.crypto.engines.ElGamalEngine;
+import org.spongycastle.crypto.engines.IDEAEngine;
+import org.spongycastle.crypto.engines.RFC3394WrapEngine;
+import org.spongycastle.crypto.engines.RSABlindedEngine;
+import org.spongycastle.crypto.engines.TwofishEngine;
+import org.spongycastle.crypto.signers.DSADigestSigner;
+import org.spongycastle.crypto.signers.DSASigner;
+import org.spongycastle.crypto.signers.ECDSASigner;
+import org.spongycastle.crypto.signers.RSADigestSigner;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPublicKey;
+
+class BcImplProvider
+{
+ static Digest createDigest(int algorithm)
+ throws PGPException
+ {
+ switch (algorithm)
+ {
+ case HashAlgorithmTags.SHA1:
+ return new SHA1Digest();
+ case HashAlgorithmTags.SHA224:
+ return new SHA224Digest();
+ case HashAlgorithmTags.SHA256:
+ return new SHA256Digest();
+ case HashAlgorithmTags.SHA384:
+ return new SHA384Digest();
+ case HashAlgorithmTags.SHA512:
+ return new SHA512Digest();
+ case HashAlgorithmTags.MD2:
+ return new MD2Digest();
+ case HashAlgorithmTags.MD5:
+ return new MD5Digest();
+ case HashAlgorithmTags.RIPEMD160:
+ return new RIPEMD160Digest();
+ case HashAlgorithmTags.TIGER_192:
+ return new TigerDigest();
+ default:
+ throw new PGPException("cannot recognise digest");
+ }
+ }
+
+ static Signer createSigner(int keyAlgorithm, int hashAlgorithm)
+ throws PGPException
+ {
+ switch(keyAlgorithm)
+ {
+ case PublicKeyAlgorithmTags.RSA_GENERAL:
+ case PublicKeyAlgorithmTags.RSA_SIGN:
+ return new RSADigestSigner(createDigest(hashAlgorithm));
+ case PublicKeyAlgorithmTags.DSA:
+ return new DSADigestSigner(new DSASigner(), createDigest(hashAlgorithm));
+ case PublicKeyAlgorithmTags.ECDSA:
+ return new DSADigestSigner(new ECDSASigner(), createDigest(hashAlgorithm));
+ default:
+ throw new PGPException("cannot recognise keyAlgorithm: " + keyAlgorithm);
+ }
+ }
+
+ static BlockCipher createBlockCipher(int encAlgorithm)
+ throws PGPException
+ {
+ BlockCipher engine;
+
+ switch (encAlgorithm)
+ {
+ case SymmetricKeyAlgorithmTags.AES_128:
+ case SymmetricKeyAlgorithmTags.AES_192:
+ case SymmetricKeyAlgorithmTags.AES_256:
+ engine = new AESEngine();
+ break;
+ case SymmetricKeyAlgorithmTags.CAMELLIA_128:
+ case SymmetricKeyAlgorithmTags.CAMELLIA_192:
+ case SymmetricKeyAlgorithmTags.CAMELLIA_256:
+ engine = new CamelliaEngine();
+ break;
+ case SymmetricKeyAlgorithmTags.BLOWFISH:
+ engine = new BlowfishEngine();
+ break;
+ case SymmetricKeyAlgorithmTags.CAST5:
+ engine = new CAST5Engine();
+ break;
+ case SymmetricKeyAlgorithmTags.DES:
+ engine = new DESEngine();
+ break;
+ case SymmetricKeyAlgorithmTags.IDEA:
+ engine = new IDEAEngine();
+ break;
+ case SymmetricKeyAlgorithmTags.TWOFISH:
+ engine = new TwofishEngine();
+ break;
+ case SymmetricKeyAlgorithmTags.TRIPLE_DES:
+ engine = new DESedeEngine();
+ break;
+ default:
+ throw new PGPException("cannot recognise cipher");
+ }
+
+ return engine;
+ }
+
+ static Wrapper createWrapper(int encAlgorithm)
+ throws PGPException
+ {
+ switch (encAlgorithm)
+ {
+ case SymmetricKeyAlgorithmTags.AES_128:
+ case SymmetricKeyAlgorithmTags.AES_192:
+ case SymmetricKeyAlgorithmTags.AES_256:
+ return new RFC3394WrapEngine(new AESFastEngine());
+ case SymmetricKeyAlgorithmTags.CAMELLIA_128:
+ case SymmetricKeyAlgorithmTags.CAMELLIA_192:
+ case SymmetricKeyAlgorithmTags.CAMELLIA_256:
+ return new RFC3394WrapEngine(new CamelliaEngine());
+ default:
+ throw new PGPException("unknown wrap algorithm: " + encAlgorithm);
+ }
+ }
+
+ static AsymmetricBlockCipher createPublicKeyCipher(int encAlgorithm)
+ throws PGPException
+ {
+ AsymmetricBlockCipher c;
+
+ switch (encAlgorithm)
+ {
+ case PGPPublicKey.RSA_ENCRYPT:
+ case PGPPublicKey.RSA_GENERAL:
+ c = new PKCS1Encoding(new RSABlindedEngine());
+ break;
+ case PGPPublicKey.ELGAMAL_ENCRYPT:
+ case PGPPublicKey.ELGAMAL_GENERAL:
+ c = new PKCS1Encoding(new ElGamalEngine());
+ break;
+ case PGPPublicKey.DSA:
+ throw new PGPException("Can't use DSA for encryption.");
+ case PGPPublicKey.ECDSA:
+ throw new PGPException("Can't use ECDSA for encryption.");
+ case PGPPublicKey.ECDH:
+ throw new PGPException("Not implemented.");
+ default:
+ throw new PGPException("unknown asymmetric algorithm: " + encAlgorithm);
+ }
+
+ return c;
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcKeyFingerprintCalculator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcKeyFingerprintCalculator.java
new file mode 100644
index 00000000..9b9b7f26
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcKeyFingerprintCalculator.java
@@ -0,0 +1,68 @@
+package org.spongycastle.openpgp.operator.bc;
+
+import java.io.IOException;
+
+import org.spongycastle.bcpg.BCPGKey;
+import org.spongycastle.bcpg.MPInteger;
+import org.spongycastle.bcpg.PublicKeyPacket;
+import org.spongycastle.bcpg.RSAPublicBCPGKey;
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.crypto.digests.MD5Digest;
+import org.spongycastle.crypto.digests.SHA1Digest;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator;
+
+public class BcKeyFingerprintCalculator
+ implements KeyFingerPrintCalculator
+{
+ public byte[] calculateFingerprint(PublicKeyPacket publicPk)
+ throws PGPException
+ {
+ BCPGKey key = publicPk.getKey();
+ Digest digest;
+
+ if (publicPk.getVersion() <= 3)
+ {
+ RSAPublicBCPGKey rK = (RSAPublicBCPGKey)key;
+
+ try
+ {
+ digest = new MD5Digest();
+
+ byte[] bytes = new MPInteger(rK.getModulus()).getEncoded();
+ digest.update(bytes, 2, bytes.length - 2);
+
+ bytes = new MPInteger(rK.getPublicExponent()).getEncoded();
+ digest.update(bytes, 2, bytes.length - 2);
+ }
+ catch (IOException e)
+ {
+ throw new PGPException("can't encode key components: " + e.getMessage(), e);
+ }
+ }
+ else
+ {
+ try
+ {
+ byte[] kBytes = publicPk.getEncodedContents();
+
+ digest = new SHA1Digest();
+
+ digest.update((byte)0x99);
+ digest.update((byte)(kBytes.length >> 8));
+ digest.update((byte)kBytes.length);
+ digest.update(kBytes, 0, kBytes.length);
+ }
+ catch (IOException e)
+ {
+ throw new PGPException("can't encode key components: " + e.getMessage(), e);
+ }
+ }
+
+ byte[] digBuf = new byte[digest.getDigestSize()];
+
+ digest.doFinal(digBuf, 0);
+
+ return digBuf;
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java
new file mode 100644
index 00000000..b9a1c189
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java
@@ -0,0 +1,68 @@
+package org.spongycastle.openpgp.operator.bc;
+
+import org.spongycastle.crypto.BlockCipher;
+import org.spongycastle.crypto.BufferedBlockCipher;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory;
+import org.spongycastle.openpgp.operator.PGPDataDecryptor;
+
+/**
+ * A {@link PBEDataDecryptorFactory} for handling PBE decryption operations using the Bouncy Castle
+ * lightweight API to implement cryptographic primitives.
+ */
+public class BcPBEDataDecryptorFactory
+ extends PBEDataDecryptorFactory
+{
+ /**
+ * Base constructor.
+ *
+ * @param pass the passphrase to use as the primary source of key material.
+ * @param calculatorProvider a digest calculator provider to provide calculators to support the key generation calculation required.
+ */
+ public BcPBEDataDecryptorFactory(char[] pass, BcPGPDigestCalculatorProvider calculatorProvider)
+ {
+ super(pass, calculatorProvider);
+ }
+
+ public byte[] recoverSessionData(int keyAlgorithm, byte[] key, byte[] secKeyData)
+ throws PGPException
+ {
+ try
+ {
+ if (secKeyData != null && secKeyData.length > 0)
+ {
+ BlockCipher engine = BcImplProvider.createBlockCipher(keyAlgorithm);
+ BufferedBlockCipher cipher = BcUtil.createSymmetricKeyWrapper(false, engine, key, new byte[engine.getBlockSize()]);
+
+ byte[] out = new byte[secKeyData.length];
+
+ int len = cipher.processBytes(secKeyData, 0, secKeyData.length, out, 0);
+
+ len += cipher.doFinal(out, len);
+
+ return out;
+ }
+ else
+ {
+ byte[] keyBytes = new byte[key.length + 1];
+
+ keyBytes[0] = (byte)keyAlgorithm;
+ System.arraycopy(key, 0, keyBytes, 1, key.length);
+
+ return keyBytes;
+ }
+ }
+ catch (Exception e)
+ {
+ throw new PGPException("Exception recovering session info", e);
+ }
+ }
+
+ public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
+ throws PGPException
+ {
+ BlockCipher engine = BcImplProvider.createBlockCipher(encAlgorithm);
+
+ return BcUtil.createDataDecryptor(withIntegrityPacket, engine, key);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBEKeyEncryptionMethodGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBEKeyEncryptionMethodGenerator.java
new file mode 100644
index 00000000..2a151f0d
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBEKeyEncryptionMethodGenerator.java
@@ -0,0 +1,95 @@
+package org.spongycastle.openpgp.operator.bc;
+
+import java.security.SecureRandom;
+
+import org.spongycastle.bcpg.S2K;
+import org.spongycastle.crypto.BlockCipher;
+import org.spongycastle.crypto.BufferedBlockCipher;
+import org.spongycastle.crypto.InvalidCipherTextException;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
+import org.spongycastle.openpgp.operator.PGPDigestCalculator;
+
+/**
+ * A BC lightweight method generator for supporting PBE based encryption operations.
+ */
+public class BcPBEKeyEncryptionMethodGenerator
+ extends PBEKeyEncryptionMethodGenerator
+{
+ /**
+ * Create a PBE encryption method generator using the provided digest and the default S2K count
+ * for key generation.
+ *
+ * @param passPhrase the passphrase to use as the primary source of key material.
+ * @param s2kDigestCalculator the digest calculator to use for key calculation.
+ */
+ public BcPBEKeyEncryptionMethodGenerator(char[] passPhrase, PGPDigestCalculator s2kDigestCalculator)
+ {
+ super(passPhrase, s2kDigestCalculator);
+ }
+
+ /**
+ * Create a PBE encryption method generator using the default SHA-1 digest and the default S2K
+ * count for key generation.
+ *
+ * @param passPhrase the passphrase to use as the primary source of key material.
+ */
+ public BcPBEKeyEncryptionMethodGenerator(char[] passPhrase)
+ {
+ this(passPhrase, new SHA1PGPDigestCalculator());
+ }
+
+ /**
+ * Create a PBE encryption method generator using the provided calculator and S2K count for key
+ * generation.
+ *
+ * @param passPhrase the passphrase to use as the primary source of key material.
+ * @param s2kDigestCalculator the digest calculator to use for key calculation.
+ * @param s2kCount the single byte {@link S2K} count to use.
+ */
+ public BcPBEKeyEncryptionMethodGenerator(char[] passPhrase, PGPDigestCalculator s2kDigestCalculator, int s2kCount)
+ {
+ super(passPhrase, s2kDigestCalculator, s2kCount);
+ }
+
+ /**
+ * Create a PBE encryption method generator using the default SHA-1 digest calculator and a S2K
+ * count other than the default for key generation.
+ *
+ * @param passPhrase the passphrase to use as the primary source of key material.
+ * @param s2kCount the single byte {@link S2K} count to use.
+ */
+ public BcPBEKeyEncryptionMethodGenerator(char[] passPhrase, int s2kCount)
+ {
+ super(passPhrase, new SHA1PGPDigestCalculator(), s2kCount);
+ }
+
+ public PBEKeyEncryptionMethodGenerator setSecureRandom(SecureRandom random)
+ {
+ super.setSecureRandom(random);
+
+ return this;
+ }
+
+ protected byte[] encryptSessionInfo(int encAlgorithm, byte[] key, byte[] sessionInfo)
+ throws PGPException
+ {
+ try
+ {
+ BlockCipher engine = BcImplProvider.createBlockCipher(encAlgorithm);
+ BufferedBlockCipher cipher = BcUtil.createSymmetricKeyWrapper(true, engine, key, new byte[engine.getBlockSize()]);
+
+ byte[] out = new byte[sessionInfo.length];
+
+ int len = cipher.processBytes(sessionInfo, 0, sessionInfo.length, out, 0);
+
+ len += cipher.doFinal(out, len);
+
+ return out;
+ }
+ catch (InvalidCipherTextException e)
+ {
+ throw new PGPException("encryption failed: " + e.getMessage(), e);
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilder.java
new file mode 100644
index 00000000..bf0a0db9
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilder.java
@@ -0,0 +1,43 @@
+package org.spongycastle.openpgp.operator.bc;
+
+import org.spongycastle.crypto.BufferedBlockCipher;
+import org.spongycastle.crypto.InvalidCipherTextException;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
+import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider;
+
+public class BcPBESecretKeyDecryptorBuilder
+{
+ private PGPDigestCalculatorProvider calculatorProvider;
+
+ public BcPBESecretKeyDecryptorBuilder(PGPDigestCalculatorProvider calculatorProvider)
+ {
+ this.calculatorProvider = calculatorProvider;
+ }
+
+ public PBESecretKeyDecryptor build(char[] passPhrase)
+ {
+ return new PBESecretKeyDecryptor(passPhrase, calculatorProvider)
+ {
+ public byte[] recoverKeyData(int encAlgorithm, byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen)
+ throws PGPException
+ {
+ try
+ {
+ BufferedBlockCipher c = BcUtil.createSymmetricKeyWrapper(false, BcImplProvider.createBlockCipher(encAlgorithm), key, iv);
+
+ byte[] out = new byte[keyLen];
+ int outLen = c.processBytes(keyData, keyOff, keyLen, out, 0);
+
+ outLen += c.doFinal(out, outLen);
+
+ return out;
+ }
+ catch (InvalidCipherTextException e)
+ {
+ throw new PGPException("decryption failed: " + e.getMessage(), e);
+ }
+ }
+ };
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBESecretKeyEncryptorBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBESecretKeyEncryptorBuilder.java
new file mode 100644
index 00000000..aea664ae
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBESecretKeyEncryptorBuilder.java
@@ -0,0 +1,142 @@
+package org.spongycastle.openpgp.operator.bc;
+
+import java.security.SecureRandom;
+
+import org.spongycastle.crypto.BlockCipher;
+import org.spongycastle.crypto.BufferedBlockCipher;
+import org.spongycastle.crypto.InvalidCipherTextException;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor;
+import org.spongycastle.openpgp.operator.PGPDigestCalculator;
+
+public class BcPBESecretKeyEncryptorBuilder
+{
+ private int encAlgorithm;
+ private PGPDigestCalculator s2kDigestCalculator;
+ private SecureRandom random;
+ private int s2kCount = 0x60;
+
+ public BcPBESecretKeyEncryptorBuilder(int encAlgorithm)
+ {
+ this(encAlgorithm, new SHA1PGPDigestCalculator());
+ }
+
+ /**
+ * Create an SecretKeyEncryptorBuilder with the S2K count different to the default of 0x60.
+ *
+ * @param encAlgorithm encryption algorithm to use.
+ * @param s2kCount iteration count to use for S2K function.
+ */
+ public BcPBESecretKeyEncryptorBuilder(int encAlgorithm, int s2kCount)
+ {
+ this(encAlgorithm, new SHA1PGPDigestCalculator(), s2kCount);
+ }
+
+ /**
+ * Create a builder which will make encryptors using the passed in digest calculator. If a MD5 calculator is
+ * passed in the builder will assume the encryptors are for use with version 3 keys.
+ *
+ * @param encAlgorithm encryption algorithm to use.
+ * @param s2kDigestCalculator digest calculator to use.
+ */
+ public BcPBESecretKeyEncryptorBuilder(int encAlgorithm, PGPDigestCalculator s2kDigestCalculator)
+ {
+ this(encAlgorithm, s2kDigestCalculator, 0x60);
+ }
+
+ /**
+ * Create an SecretKeyEncryptorBuilder with the S2k count different to the default of 0x60, and the S2K digest
+ * different from SHA-1.
+ *
+ * @param encAlgorithm encryption algorithm to use.
+ * @param s2kDigestCalculator digest calculator to use.
+ * @param s2kCount iteration count to use for S2K function.
+ */
+ public BcPBESecretKeyEncryptorBuilder(int encAlgorithm, PGPDigestCalculator s2kDigestCalculator, int s2kCount)
+ {
+ this.encAlgorithm = encAlgorithm;
+ this.s2kDigestCalculator = s2kDigestCalculator;
+
+ if (s2kCount < 0 || s2kCount > 0xff)
+ {
+ throw new IllegalArgumentException("s2KCount value outside of range 0 to 255.");
+ }
+
+ this.s2kCount = s2kCount;
+ }
+
+ /**
+ * Provide a user defined source of randomness.
+ *
+ * @param random the secure random to be used.
+ * @return the current builder.
+ */
+ public BcPBESecretKeyEncryptorBuilder setSecureRandom(SecureRandom random)
+ {
+ this.random = random;
+
+ return this;
+ }
+
+ public PBESecretKeyEncryptor build(char[] passPhrase)
+ {
+ if (this.random == null)
+ {
+ this.random = new SecureRandom();
+ }
+
+ return new PBESecretKeyEncryptor(encAlgorithm, s2kDigestCalculator, s2kCount, this.random, passPhrase)
+ {
+ private byte[] iv;
+
+ public byte[] encryptKeyData(byte[] key, byte[] keyData, int keyOff, int keyLen)
+ throws PGPException
+ {
+ return encryptKeyData(key, null, keyData, keyOff, keyLen);
+ }
+
+ public byte[] encryptKeyData(byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen)
+ throws PGPException
+ {
+ try
+ {
+ BlockCipher engine = BcImplProvider.createBlockCipher(this.encAlgorithm);
+
+ if (iv != null)
+ { // to deal with V3 key encryption
+ this.iv = iv;
+ }
+ else
+ {
+ if (this.random == null)
+ {
+ this.random = new SecureRandom();
+ }
+
+ this.iv = iv = new byte[engine.getBlockSize()];
+
+ this.random.nextBytes(iv);
+ }
+
+ BufferedBlockCipher c = BcUtil.createSymmetricKeyWrapper(true, engine, key, iv);
+
+ byte[] out = new byte[keyLen];
+ int outLen = c.processBytes(keyData, keyOff, keyLen, out, 0);
+
+ outLen += c.doFinal(out, outLen);
+
+ return out;
+ }
+ catch (InvalidCipherTextException e)
+ {
+ throw new PGPException("decryption failed: " + e.getMessage(), e);
+ }
+ }
+
+ public byte[] getCipherIV()
+ {
+ return iv;
+ }
+ };
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPContentSignerBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPContentSignerBuilder.java
new file mode 100644
index 00000000..cd98ef38
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPContentSignerBuilder.java
@@ -0,0 +1,98 @@
+package org.spongycastle.openpgp.operator.bc;
+
+import java.io.OutputStream;
+import java.security.SecureRandom;
+
+import org.spongycastle.crypto.CryptoException;
+import org.spongycastle.crypto.Signer;
+import org.spongycastle.crypto.params.ParametersWithRandom;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPrivateKey;
+import org.spongycastle.openpgp.operator.PGPContentSigner;
+import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
+import org.spongycastle.openpgp.operator.PGPDigestCalculator;
+import org.spongycastle.util.io.TeeOutputStream;
+
+public class BcPGPContentSignerBuilder
+ implements PGPContentSignerBuilder
+{
+ private BcPGPDigestCalculatorProvider digestCalculatorProvider = new BcPGPDigestCalculatorProvider();
+ private BcPGPKeyConverter keyConverter = new BcPGPKeyConverter();
+ private int hashAlgorithm;
+ private SecureRandom random;
+ private int keyAlgorithm;
+
+ public BcPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm)
+ {
+ this.keyAlgorithm = keyAlgorithm;
+ this.hashAlgorithm = hashAlgorithm;
+ }
+
+ public BcPGPContentSignerBuilder setSecureRandom(SecureRandom random)
+ {
+ this.random = random;
+
+ return this;
+ }
+
+ public PGPContentSigner build(final int signatureType, final PGPPrivateKey privateKey)
+ throws PGPException
+ {
+ final PGPDigestCalculator digestCalculator = digestCalculatorProvider.get(hashAlgorithm);
+ final Signer signer = BcImplProvider.createSigner(keyAlgorithm, hashAlgorithm);
+
+ if (random != null)
+ {
+ signer.init(true, new ParametersWithRandom(keyConverter.getPrivateKey(privateKey), random));
+ }
+ else
+ {
+ signer.init(true, keyConverter.getPrivateKey(privateKey));
+ }
+
+ return new PGPContentSigner()
+ {
+ public int getType()
+ {
+ return signatureType;
+ }
+
+ public int getHashAlgorithm()
+ {
+ return hashAlgorithm;
+ }
+
+ public int getKeyAlgorithm()
+ {
+ return keyAlgorithm;
+ }
+
+ public long getKeyID()
+ {
+ return privateKey.getKeyID();
+ }
+
+ public OutputStream getOutputStream()
+ {
+ return new TeeOutputStream(new SignerOutputStream(signer), digestCalculator.getOutputStream());
+ }
+
+ public byte[] getSignature()
+ {
+ try
+ {
+ return signer.generateSignature();
+ }
+ catch (CryptoException e)
+ { // TODO: need a specific runtime exception for PGP operators.
+ throw new IllegalStateException("unable to create signature");
+ }
+ }
+
+ public byte[] getDigest()
+ {
+ return digestCalculator.getDigest();
+ }
+ };
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPContentVerifierBuilderProvider.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPContentVerifierBuilderProvider.java
new file mode 100644
index 00000000..a2cfbf91
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPContentVerifierBuilderProvider.java
@@ -0,0 +1,75 @@
+package org.spongycastle.openpgp.operator.bc;
+
+import java.io.OutputStream;
+
+import org.spongycastle.crypto.Signer;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.operator.PGPContentVerifier;
+import org.spongycastle.openpgp.operator.PGPContentVerifierBuilder;
+import org.spongycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
+
+public class BcPGPContentVerifierBuilderProvider
+ implements PGPContentVerifierBuilderProvider
+{
+ private BcPGPKeyConverter keyConverter = new BcPGPKeyConverter();
+
+ public BcPGPContentVerifierBuilderProvider()
+ {
+ }
+
+ public PGPContentVerifierBuilder get(int keyAlgorithm, int hashAlgorithm)
+ throws PGPException
+ {
+ return new BcPGPContentVerifierBuilder(keyAlgorithm, hashAlgorithm);
+ }
+
+ private class BcPGPContentVerifierBuilder
+ implements PGPContentVerifierBuilder
+ {
+ private int hashAlgorithm;
+ private int keyAlgorithm;
+
+ public BcPGPContentVerifierBuilder(int keyAlgorithm, int hashAlgorithm)
+ {
+ this.keyAlgorithm = keyAlgorithm;
+ this.hashAlgorithm = hashAlgorithm;
+ }
+
+ public PGPContentVerifier build(final PGPPublicKey publicKey)
+ throws PGPException
+ {
+ final Signer signer = BcImplProvider.createSigner(keyAlgorithm, hashAlgorithm);
+
+ signer.init(false, keyConverter.getPublicKey(publicKey));
+
+ return new PGPContentVerifier()
+ {
+ public int getHashAlgorithm()
+ {
+ return hashAlgorithm;
+ }
+
+ public int getKeyAlgorithm()
+ {
+ return keyAlgorithm;
+ }
+
+ public long getKeyID()
+ {
+ return publicKey.getKeyID();
+ }
+
+ public boolean verify(byte[] expected)
+ {
+ return signer.verifySignature(expected);
+ }
+
+ public OutputStream getOutputStream()
+ {
+ return new SignerOutputStream(signer);
+ }
+ };
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPDataEncryptorBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPDataEncryptorBuilder.java
new file mode 100644
index 00000000..bfb2946a
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPDataEncryptorBuilder.java
@@ -0,0 +1,131 @@
+package org.spongycastle.openpgp.operator.bc;
+
+import java.io.OutputStream;
+import java.security.SecureRandom;
+
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.crypto.BlockCipher;
+import org.spongycastle.crypto.BufferedBlockCipher;
+import org.spongycastle.crypto.io.CipherOutputStream;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.operator.PGPDataEncryptor;
+import org.spongycastle.openpgp.operator.PGPDataEncryptorBuilder;
+import org.spongycastle.openpgp.operator.PGPDigestCalculator;
+
+/**
+ * {@link PGPDataEncryptorBuilder} implementation that uses the Bouncy Castle lightweight API to
+ * implement cryptographic primitives.
+ */
+public class BcPGPDataEncryptorBuilder
+ implements PGPDataEncryptorBuilder
+{
+ private SecureRandom random;
+ private boolean withIntegrityPacket;
+ private int encAlgorithm;
+
+ /**
+ * Constructs a new data encryptor builder for a specified cipher type.
+ *
+ * @param encAlgorithm one of the {@link SymmetricKeyAlgorithmTags supported symmetric cipher
+ * algorithms}. May not be {@link SymmetricKeyAlgorithmTags#NULL}.
+ */
+ public BcPGPDataEncryptorBuilder(int encAlgorithm)
+ {
+ this.encAlgorithm = encAlgorithm;
+
+ if (encAlgorithm == 0)
+ {
+ throw new IllegalArgumentException("null cipher specified");
+ }
+ }
+
+ /**
+ * Sets whether or not the resulting encrypted data will be protected using an integrity packet.
+ *
+ * @param withIntegrityPacket true if an integrity packet is to be included, false otherwise.
+ * @return the current builder.
+ */
+ public BcPGPDataEncryptorBuilder setWithIntegrityPacket(boolean withIntegrityPacket)
+ {
+ this.withIntegrityPacket = withIntegrityPacket;
+
+ return this;
+ }
+
+ /**
+ * Provide a user defined source of randomness.
+ * <p/>
+ * If no SecureRandom is configured, a default SecureRandom will be used.
+ *
+ * @param random the secure random to be used.
+ * @return the current builder.
+ */
+ public BcPGPDataEncryptorBuilder setSecureRandom(SecureRandom random)
+ {
+ this.random = random;
+
+ return this;
+ }
+
+ public int getAlgorithm()
+ {
+ return encAlgorithm;
+ }
+
+ public SecureRandom getSecureRandom()
+ {
+ if (random == null)
+ {
+ random = new SecureRandom();
+ }
+
+ return random;
+ }
+
+ public PGPDataEncryptor build(byte[] keyBytes)
+ throws PGPException
+ {
+ return new MyPGPDataEncryptor(keyBytes);
+ }
+
+ private class MyPGPDataEncryptor
+ implements PGPDataEncryptor
+ {
+ private final BufferedBlockCipher c;
+
+ MyPGPDataEncryptor(byte[] keyBytes)
+ throws PGPException
+ {
+ BlockCipher engine = BcImplProvider.createBlockCipher(encAlgorithm);
+
+ try
+ {
+ c = BcUtil.createStreamCipher(true, engine, withIntegrityPacket, keyBytes);
+ }
+ catch (IllegalArgumentException e)
+ {
+ throw new PGPException("invalid parameters: " + e.getMessage(), e);
+ }
+ }
+
+ public OutputStream getOutputStream(OutputStream out)
+ {
+ return new CipherOutputStream(out, c);
+ }
+
+ public PGPDigestCalculator getIntegrityCalculator()
+ {
+ if (withIntegrityPacket)
+ {
+ return new SHA1PGPDigestCalculator();
+ }
+
+ return null;
+ }
+
+ public int getBlockSize()
+ {
+ return c.getBlockSize();
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPDigestCalculatorProvider.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPDigestCalculatorProvider.java
new file mode 100644
index 00000000..50d5fc73
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPDigestCalculatorProvider.java
@@ -0,0 +1,82 @@
+package org.spongycastle.openpgp.operator.bc;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.operator.PGPDigestCalculator;
+import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider;
+
+public class BcPGPDigestCalculatorProvider
+ implements PGPDigestCalculatorProvider
+{
+ public PGPDigestCalculator get(final int algorithm)
+ throws PGPException
+ {
+ final Digest dig = BcImplProvider.createDigest(algorithm);
+
+ final DigestOutputStream stream = new DigestOutputStream(dig);
+
+ return new PGPDigestCalculator()
+ {
+ public int getAlgorithm()
+ {
+ return algorithm;
+ }
+
+ public OutputStream getOutputStream()
+ {
+ return stream;
+ }
+
+ public byte[] getDigest()
+ {
+ return stream.getDigest();
+ }
+
+ public void reset()
+ {
+ dig.reset();
+ }
+ };
+ }
+
+ private class DigestOutputStream
+ extends OutputStream
+ {
+ private Digest dig;
+
+ DigestOutputStream(Digest dig)
+ {
+ this.dig = dig;
+ }
+
+ public void write(byte[] bytes, int off, int len)
+ throws IOException
+ {
+ dig.update(bytes, off, len);
+ }
+
+ public void write(byte[] bytes)
+ throws IOException
+ {
+ dig.update(bytes, 0, bytes.length);
+ }
+
+ public void write(int b)
+ throws IOException
+ {
+ dig.update((byte)b);
+ }
+
+ byte[] getDigest()
+ {
+ byte[] d = new byte[dig.getDigestSize()];
+
+ dig.doFinal(d, 0);
+
+ return d;
+ }
+ }
+} \ No newline at end of file
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPKeyConverter.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPKeyConverter.java
new file mode 100644
index 00000000..40577467
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPKeyConverter.java
@@ -0,0 +1,239 @@
+package org.spongycastle.openpgp.operator.bc;
+
+import java.util.Date;
+
+import org.spongycastle.asn1.x9.ECNamedCurveTable;
+import org.spongycastle.asn1.x9.X9ECParameters;
+import org.spongycastle.bcpg.BCPGKey;
+import org.spongycastle.bcpg.DSAPublicBCPGKey;
+import org.spongycastle.bcpg.DSASecretBCPGKey;
+import org.spongycastle.bcpg.ECDHPublicBCPGKey;
+import org.spongycastle.bcpg.ECDSAPublicBCPGKey;
+import org.spongycastle.bcpg.ECPublicBCPGKey;
+import org.spongycastle.bcpg.ECSecretBCPGKey;
+import org.spongycastle.bcpg.ElGamalPublicBCPGKey;
+import org.spongycastle.bcpg.ElGamalSecretBCPGKey;
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
+import org.spongycastle.bcpg.PublicKeyPacket;
+import org.spongycastle.bcpg.RSAPublicBCPGKey;
+import org.spongycastle.bcpg.RSASecretBCPGKey;
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.crypto.ec.CustomNamedCurves;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+import org.spongycastle.crypto.params.DSAParameters;
+import org.spongycastle.crypto.params.DSAPrivateKeyParameters;
+import org.spongycastle.crypto.params.DSAPublicKeyParameters;
+import org.spongycastle.crypto.params.ECNamedDomainParameters;
+import org.spongycastle.crypto.params.ECPrivateKeyParameters;
+import org.spongycastle.crypto.params.ECPublicKeyParameters;
+import org.spongycastle.crypto.params.ElGamalParameters;
+import org.spongycastle.crypto.params.ElGamalPrivateKeyParameters;
+import org.spongycastle.crypto.params.ElGamalPublicKeyParameters;
+import org.spongycastle.crypto.params.RSAKeyParameters;
+import org.spongycastle.crypto.params.RSAPrivateCrtKeyParameters;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPrivateKey;
+import org.spongycastle.openpgp.PGPPublicKey;
+
+public class BcPGPKeyConverter
+{
+ /**
+ * Create a PGPPublicKey from the passed in JCA one.
+ * <p/>
+ * Note: the time passed in affects the value of the key's keyID, so you probably only want
+ * to do this once for a JCA key, or make sure you keep track of the time you used.
+ *
+ * @param algorithm asymmetric algorithm type representing the public key.
+ * @param pubKey actual public key to associate.
+ * @param time date of creation.
+ * @throws PGPException on key creation problem.
+ */
+ public PGPPublicKey getPGPPublicKey(int algorithm, AsymmetricKeyParameter pubKey, Date time)
+ throws PGPException
+ {
+ BCPGKey bcpgKey;
+
+ if (pubKey instanceof RSAKeyParameters)
+ {
+ RSAKeyParameters rK = (RSAKeyParameters)pubKey;
+
+ bcpgKey = new RSAPublicBCPGKey(rK.getModulus(), rK.getExponent());
+ }
+ else if (pubKey instanceof DSAPublicKeyParameters)
+ {
+ DSAPublicKeyParameters dK = (DSAPublicKeyParameters)pubKey;
+ DSAParameters dP = dK.getParameters();
+
+ bcpgKey = new DSAPublicBCPGKey(dP.getP(), dP.getQ(), dP.getG(), dK.getY());
+ }
+ else if (pubKey instanceof ElGamalPublicKeyParameters)
+ {
+ ElGamalPublicKeyParameters eK = (ElGamalPublicKeyParameters)pubKey;
+ ElGamalParameters eS = eK.getParameters();
+
+ bcpgKey = new ElGamalPublicBCPGKey(eS.getP(), eS.getG(), eK.getY());
+ }
+ else if (pubKey instanceof ECPublicKeyParameters)
+ {
+ ECPublicKeyParameters eK = (ECPublicKeyParameters)pubKey;
+
+ if (algorithm == PGPPublicKey.EC)
+ { // TODO: KDF parameters setting
+ bcpgKey = new ECDHPublicBCPGKey(((ECNamedDomainParameters)eK.getParameters()).getName(), eK.getQ(), HashAlgorithmTags.SHA256, SymmetricKeyAlgorithmTags.AES_128);
+ }
+ else
+ {
+ bcpgKey = new ECDSAPublicBCPGKey(((ECNamedDomainParameters)eK.getParameters()).getName(), eK.getQ());
+ }
+ }
+ else
+ {
+ throw new PGPException("unknown key class");
+ }
+
+ return new PGPPublicKey(new PublicKeyPacket(algorithm, time, bcpgKey), new BcKeyFingerprintCalculator());
+ }
+
+ public PGPPrivateKey getPGPPrivateKey(PGPPublicKey pubKey, AsymmetricKeyParameter privKey)
+ throws PGPException
+ {
+ BCPGKey privPk;
+
+ switch (pubKey.getAlgorithm())
+ {
+ case PGPPublicKey.RSA_ENCRYPT:
+ case PGPPublicKey.RSA_SIGN:
+ case PGPPublicKey.RSA_GENERAL:
+ RSAPrivateCrtKeyParameters rsK = (RSAPrivateCrtKeyParameters)privKey;
+
+ privPk = new RSASecretBCPGKey(rsK.getExponent(), rsK.getP(), rsK.getQ());
+ break;
+ case PGPPublicKey.DSA:
+ DSAPrivateKeyParameters dsK = (DSAPrivateKeyParameters)privKey;
+
+ privPk = new DSASecretBCPGKey(dsK.getX());
+ break;
+ case PGPPublicKey.ELGAMAL_ENCRYPT:
+ case PGPPublicKey.ELGAMAL_GENERAL:
+ ElGamalPrivateKeyParameters esK = (ElGamalPrivateKeyParameters)privKey;
+
+ privPk = new ElGamalSecretBCPGKey(esK.getX());
+ break;
+ case PGPPublicKey.ECDH:
+ case PGPPublicKey.ECDSA:
+ ECPrivateKeyParameters ecK = (ECPrivateKeyParameters)privKey;
+
+ privPk = new ECSecretBCPGKey(ecK.getD());
+ break;
+ default:
+ throw new PGPException("unknown key class");
+ }
+ return new PGPPrivateKey(pubKey.getKeyID(), pubKey.getPublicKeyPacket(), privPk);
+ }
+
+ public AsymmetricKeyParameter getPublicKey(PGPPublicKey publicKey)
+ throws PGPException
+ {
+ PublicKeyPacket publicPk = publicKey.getPublicKeyPacket();
+
+ try
+ {
+ switch (publicPk.getAlgorithm())
+ {
+ case PublicKeyAlgorithmTags.RSA_ENCRYPT:
+ case PublicKeyAlgorithmTags.RSA_GENERAL:
+ case PublicKeyAlgorithmTags.RSA_SIGN:
+ RSAPublicBCPGKey rsaK = (RSAPublicBCPGKey)publicPk.getKey();
+
+ return new RSAKeyParameters(false, rsaK.getModulus(), rsaK.getPublicExponent());
+ case PublicKeyAlgorithmTags.DSA:
+ DSAPublicBCPGKey dsaK = (DSAPublicBCPGKey)publicPk.getKey();
+
+ return new DSAPublicKeyParameters(dsaK.getY(), new DSAParameters(dsaK.getP(), dsaK.getQ(), dsaK.getG()));
+ case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT:
+ case PublicKeyAlgorithmTags.ELGAMAL_GENERAL:
+ ElGamalPublicBCPGKey elK = (ElGamalPublicBCPGKey)publicPk.getKey();
+
+ return new ElGamalPublicKeyParameters(elK.getY(), new ElGamalParameters(elK.getP(), elK.getG()));
+ case PGPPublicKey.ECDH:
+ case PGPPublicKey.ECDSA:
+ ECPublicBCPGKey ecPub = (ECPublicBCPGKey)publicPk.getKey();
+
+ X9ECParameters x9 = CustomNamedCurves.getByOID(ecPub.getCurveOID());
+ if (x9 == null)
+ {
+ x9 = ECNamedCurveTable.getByOID(ecPub.getCurveOID());
+ }
+
+ return new ECPublicKeyParameters(ecPub.getPoint(),
+ new ECNamedDomainParameters(ecPub.getCurveOID(), x9.getCurve(), x9.getG(), x9.getN(), x9.getH()));
+ default:
+ throw new PGPException("unknown public key algorithm encountered");
+ }
+ }
+ catch (PGPException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new PGPException("exception constructing public key", e);
+ }
+ }
+
+ public AsymmetricKeyParameter getPrivateKey(PGPPrivateKey privKey)
+ throws PGPException
+ {
+ PublicKeyPacket pubPk = privKey.getPublicKeyPacket();
+ BCPGKey privPk = privKey.getPrivateKeyDataPacket();
+
+ try
+ {
+ switch (pubPk.getAlgorithm())
+ {
+ case PGPPublicKey.RSA_ENCRYPT:
+ case PGPPublicKey.RSA_GENERAL:
+ case PGPPublicKey.RSA_SIGN:
+ RSAPublicBCPGKey rsaPub = (RSAPublicBCPGKey)pubPk.getKey();
+ RSASecretBCPGKey rsaPriv = (RSASecretBCPGKey)privPk;
+
+ return new RSAPrivateCrtKeyParameters(rsaPriv.getModulus(), rsaPub.getPublicExponent(), rsaPriv.getPrivateExponent(), rsaPriv.getPrimeP(), rsaPriv.getPrimeQ(), rsaPriv.getPrimeExponentP(), rsaPriv.getPrimeExponentQ(), rsaPriv.getCrtCoefficient());
+ case PGPPublicKey.DSA:
+ DSAPublicBCPGKey dsaPub = (DSAPublicBCPGKey)pubPk.getKey();
+ DSASecretBCPGKey dsaPriv = (DSASecretBCPGKey)privPk;
+
+ return new DSAPrivateKeyParameters(dsaPriv.getX(), new DSAParameters(dsaPub.getP(), dsaPub.getQ(), dsaPub.getG()));
+ case PGPPublicKey.ELGAMAL_ENCRYPT:
+ case PGPPublicKey.ELGAMAL_GENERAL:
+ ElGamalPublicBCPGKey elPub = (ElGamalPublicBCPGKey)pubPk.getKey();
+ ElGamalSecretBCPGKey elPriv = (ElGamalSecretBCPGKey)privPk;
+
+ return new ElGamalPrivateKeyParameters(elPriv.getX(), new ElGamalParameters(elPub.getP(), elPub.getG()));
+ case PGPPublicKey.ECDH:
+ case PGPPublicKey.ECDSA:
+ ECPublicBCPGKey ecPub = (ECPublicBCPGKey)pubPk.getKey();
+ ECSecretBCPGKey ecPriv = (ECSecretBCPGKey)privPk;
+
+ X9ECParameters x9 = CustomNamedCurves.getByOID(ecPub.getCurveOID());
+ if (x9 == null)
+ {
+ x9 = ECNamedCurveTable.getByOID(ecPub.getCurveOID());
+ }
+
+ return new ECPrivateKeyParameters(ecPriv.getX(),
+ new ECNamedDomainParameters(ecPub.getCurveOID(), x9.getCurve(), x9.getG(), x9.getN(), x9.getH()));
+ default:
+ throw new PGPException("unknown public key algorithm encountered");
+ }
+ }
+ catch (PGPException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new PGPException("Exception constructing key", e);
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPKeyPair.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPKeyPair.java
new file mode 100644
index 00000000..ed6a0d7e
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPKeyPair.java
@@ -0,0 +1,33 @@
+package org.spongycastle.openpgp.operator.bc;
+
+import java.util.Date;
+
+import org.spongycastle.crypto.AsymmetricCipherKeyPair;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPKeyPair;
+import org.spongycastle.openpgp.PGPPrivateKey;
+import org.spongycastle.openpgp.PGPPublicKey;
+
+public class BcPGPKeyPair
+ extends PGPKeyPair
+{
+ private static PGPPublicKey getPublicKey(int algorithm, AsymmetricKeyParameter pubKey, Date date)
+ throws PGPException
+ {
+ return new BcPGPKeyConverter().getPGPPublicKey(algorithm, pubKey, date);
+ }
+
+ private static PGPPrivateKey getPrivateKey(PGPPublicKey pub, AsymmetricKeyParameter privKey)
+ throws PGPException
+ {
+ return new BcPGPKeyConverter().getPGPPrivateKey(pub, privKey);
+ }
+
+ public BcPGPKeyPair(int algorithm, AsymmetricCipherKeyPair keyPair, Date date)
+ throws PGPException
+ {
+ this.pub = getPublicKey(algorithm, keyPair.getPublic(), date);
+ this.priv = getPrivateKey(this.pub, keyPair.getPrivate());
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java
new file mode 100644
index 00000000..4c5124bb
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java
@@ -0,0 +1,139 @@
+package org.spongycastle.openpgp.operator.bc;
+
+import org.spongycastle.asn1.nist.NISTNamedCurves;
+import org.spongycastle.asn1.x9.X9ECParameters;
+import org.spongycastle.bcpg.ECDHPublicBCPGKey;
+import org.spongycastle.bcpg.ECSecretBCPGKey;
+import org.spongycastle.crypto.AsymmetricBlockCipher;
+import org.spongycastle.crypto.BlockCipher;
+import org.spongycastle.crypto.BufferedAsymmetricBlockCipher;
+import org.spongycastle.crypto.InvalidCipherTextException;
+import org.spongycastle.crypto.Wrapper;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+import org.spongycastle.crypto.params.ElGamalPrivateKeyParameters;
+import org.spongycastle.crypto.params.KeyParameter;
+import org.spongycastle.math.ec.ECPoint;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPrivateKey;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.operator.PGPDataDecryptor;
+import org.spongycastle.openpgp.operator.PGPPad;
+import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
+import org.spongycastle.openpgp.operator.RFC6637KDFCalculator;
+
+/**
+ * A decryptor factory for handling public key decryption operations.
+ */
+public class BcPublicKeyDataDecryptorFactory
+ implements PublicKeyDataDecryptorFactory
+{
+ private BcPGPKeyConverter keyConverter = new BcPGPKeyConverter();
+ private PGPPrivateKey privKey;
+
+ public BcPublicKeyDataDecryptorFactory(PGPPrivateKey privKey)
+ {
+ this.privKey = privKey;
+ }
+
+ public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData)
+ throws PGPException
+ {
+ try
+ {
+ if (keyAlgorithm != PGPPublicKey.ECDH)
+ {
+ AsymmetricBlockCipher c = BcImplProvider.createPublicKeyCipher(keyAlgorithm);
+
+ AsymmetricKeyParameter key = keyConverter.getPrivateKey(privKey);
+
+ BufferedAsymmetricBlockCipher c1 = new BufferedAsymmetricBlockCipher(c);
+
+ c1.init(false, key);
+
+ if (keyAlgorithm == PGPPublicKey.RSA_ENCRYPT
+ || keyAlgorithm == PGPPublicKey.RSA_GENERAL)
+ {
+ byte[] bi = secKeyData[0];
+
+ c1.processBytes(bi, 2, bi.length - 2);
+ }
+ else
+ {
+ BcPGPKeyConverter converter = new BcPGPKeyConverter();
+ ElGamalPrivateKeyParameters parms = (ElGamalPrivateKeyParameters)converter.getPrivateKey(privKey);
+ int size = (parms.getParameters().getP().bitLength() + 7) / 8;
+ byte[] tmp = new byte[size];
+
+ byte[] bi = secKeyData[0]; // encoded MPI
+ if (bi.length - 2 > size) // leading Zero? Shouldn't happen but...
+ {
+ c1.processBytes(bi, 3, bi.length - 3);
+ }
+ else
+ {
+ System.arraycopy(bi, 2, tmp, tmp.length - (bi.length - 2), bi.length - 2);
+ c1.processBytes(tmp, 0, tmp.length);
+ }
+
+ bi = secKeyData[1]; // encoded MPI
+ for (int i = 0; i != tmp.length; i++)
+ {
+ tmp[i] = 0;
+ }
+
+ if (bi.length - 2 > size) // leading Zero? Shouldn't happen but...
+ {
+ c1.processBytes(bi, 3, bi.length - 3);
+ }
+ else
+ {
+ System.arraycopy(bi, 2, tmp, tmp.length - (bi.length - 2), bi.length - 2);
+ c1.processBytes(tmp, 0, tmp.length);
+ }
+ }
+
+ return c1.doFinal();
+ }
+ else
+ {
+ ECDHPublicBCPGKey ecKey = (ECDHPublicBCPGKey)privKey.getPublicKeyPacket().getKey();
+ X9ECParameters x9Params = NISTNamedCurves.getByOID(ecKey.getCurveOID());
+
+ byte[] enc = secKeyData[0];
+
+ int pLen = ((((enc[0] & 0xff) << 8) + (enc[1] & 0xff)) + 7) / 8;
+ byte[] pEnc = new byte[pLen];
+
+ System.arraycopy(enc, 2, pEnc, 0, pLen);
+
+ byte[] keyEnc = new byte[enc[pLen + 2]];
+
+ System.arraycopy(enc, 2 + pLen + 1, keyEnc, 0, keyEnc.length);
+
+ Wrapper c = BcImplProvider.createWrapper(ecKey.getSymmetricKeyAlgorithm());
+
+ ECPoint S = x9Params.getCurve().decodePoint(pEnc).multiply(((ECSecretBCPGKey)privKey.getPrivateKeyDataPacket()).getX()).normalize();
+
+ RFC6637KDFCalculator rfc6637KDFCalculator = new RFC6637KDFCalculator(new BcPGPDigestCalculatorProvider().get(ecKey.getHashAlgorithm()), ecKey.getSymmetricKeyAlgorithm());
+ KeyParameter key = new KeyParameter(rfc6637KDFCalculator.createKey(ecKey.getCurveOID(), S, new BcKeyFingerprintCalculator().calculateFingerprint(privKey.getPublicKeyPacket())));
+
+ c.init(false, key);
+
+ return PGPPad.unpadSessionData(c.unwrap(keyEnc, 0, keyEnc.length));
+ }
+ }
+ catch (InvalidCipherTextException e)
+ {
+ throw new PGPException("exception encrypting session info: " + e.getMessage(), e);
+ }
+
+ }
+
+ public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
+ throws PGPException
+ {
+ BlockCipher engine = BcImplProvider.createBlockCipher(encAlgorithm);
+
+ return BcUtil.createDataDecryptor(withIntegrityPacket, engine, key);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPublicKeyKeyEncryptionMethodGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPublicKeyKeyEncryptionMethodGenerator.java
new file mode 100644
index 00000000..72d501b0
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPublicKeyKeyEncryptionMethodGenerator.java
@@ -0,0 +1,139 @@
+package org.spongycastle.openpgp.operator.bc;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.spongycastle.asn1.nist.NISTNamedCurves;
+import org.spongycastle.asn1.x9.X9ECParameters;
+import org.spongycastle.bcpg.ECDHPublicBCPGKey;
+import org.spongycastle.bcpg.MPInteger;
+import org.spongycastle.crypto.AsymmetricBlockCipher;
+import org.spongycastle.crypto.EphemeralKeyPair;
+import org.spongycastle.crypto.InvalidCipherTextException;
+import org.spongycastle.crypto.KeyEncoder;
+import org.spongycastle.crypto.Wrapper;
+import org.spongycastle.crypto.generators.ECKeyPairGenerator;
+import org.spongycastle.crypto.generators.EphemeralKeyPairGenerator;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+import org.spongycastle.crypto.params.ECDomainParameters;
+import org.spongycastle.crypto.params.ECKeyGenerationParameters;
+import org.spongycastle.crypto.params.ECPrivateKeyParameters;
+import org.spongycastle.crypto.params.ECPublicKeyParameters;
+import org.spongycastle.crypto.params.KeyParameter;
+import org.spongycastle.crypto.params.ParametersWithRandom;
+import org.spongycastle.math.ec.ECPoint;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.operator.PGPPad;
+import org.spongycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator;
+import org.spongycastle.openpgp.operator.RFC6637KDFCalculator;
+
+/**
+ * A method generator for supporting public key based encryption operations.
+ */
+public class BcPublicKeyKeyEncryptionMethodGenerator
+ extends PublicKeyKeyEncryptionMethodGenerator
+{
+ private SecureRandom random;
+ private BcPGPKeyConverter keyConverter = new BcPGPKeyConverter();
+
+ /**
+ * Create a public key encryption method generator with the method to be based on the passed in key.
+ *
+ * @param key the public key to use for encryption.
+ */
+ public BcPublicKeyKeyEncryptionMethodGenerator(PGPPublicKey key)
+ {
+ super(key);
+ }
+
+ /**
+ * Provide a user defined source of randomness.
+ *
+ * @param random the secure random to be used.
+ * @return the current generator.
+ */
+ public BcPublicKeyKeyEncryptionMethodGenerator setSecureRandom(SecureRandom random)
+ {
+ this.random = random;
+
+ return this;
+ }
+
+ protected byte[] encryptSessionInfo(PGPPublicKey pubKey, byte[] sessionInfo)
+ throws PGPException
+ {
+ try
+ {
+ if (pubKey.getAlgorithm() != PGPPublicKey.ECDH)
+ {
+ AsymmetricBlockCipher c = BcImplProvider.createPublicKeyCipher(pubKey.getAlgorithm());
+
+ AsymmetricKeyParameter key = keyConverter.getPublicKey(pubKey);
+
+ if (random == null)
+ {
+ random = new SecureRandom();
+ }
+
+ c.init(true, new ParametersWithRandom(key, random));
+
+ return c.processBlock(sessionInfo, 0, sessionInfo.length);
+ }
+ else
+ {
+ ECDHPublicBCPGKey ecKey = (ECDHPublicBCPGKey)pubKey.getPublicKeyPacket().getKey();
+ X9ECParameters x9Params = NISTNamedCurves.getByOID(ecKey.getCurveOID());
+ ECDomainParameters ecParams = new ECDomainParameters(x9Params.getCurve(), x9Params.getG(), x9Params.getN());
+
+ // Generate the ephemeral key pair
+ ECKeyPairGenerator gen = new ECKeyPairGenerator();
+ gen.init(new ECKeyGenerationParameters(ecParams, random));
+
+ EphemeralKeyPairGenerator kGen = new EphemeralKeyPairGenerator(gen, new KeyEncoder()
+ {
+ public byte[] getEncoded(AsymmetricKeyParameter keyParameter)
+ {
+ return ((ECPublicKeyParameters)keyParameter).getQ().getEncoded(false);
+ }
+ });
+
+ EphemeralKeyPair ephKp = kGen.generate();
+
+ ECPrivateKeyParameters ephPriv = (ECPrivateKeyParameters)ephKp.getKeyPair().getPrivate();
+
+ ECPoint S = ecKey.getPoint().multiply(ephPriv.getD()).normalize();
+
+ RFC6637KDFCalculator rfc6637KDFCalculator = new RFC6637KDFCalculator(new BcPGPDigestCalculatorProvider().get(ecKey.getHashAlgorithm()), ecKey.getSymmetricKeyAlgorithm());
+
+ KeyParameter key = new KeyParameter(rfc6637KDFCalculator.createKey(ecKey.getCurveOID(), S, pubKey.getFingerprint()));
+
+ Wrapper c = BcImplProvider.createWrapper(ecKey.getSymmetricKeyAlgorithm());
+
+ c.init(true, new ParametersWithRandom(key, random));
+
+ byte[] paddedSessionData = PGPPad.padSessionData(sessionInfo);
+
+ byte[] C = c.wrap(paddedSessionData, 0, paddedSessionData.length);
+ byte[] VB = new MPInteger(new BigInteger(1, ephKp.getEncodedPublicKey())).getEncoded();
+
+ byte[] rv = new byte[VB.length + 1 + C.length];
+
+ System.arraycopy(VB, 0, rv, 0, VB.length);
+ rv[VB.length] = (byte)C.length;
+ System.arraycopy(C, 0, rv, VB.length + 1, C.length);
+
+ return rv;
+ }
+ }
+ catch (InvalidCipherTextException e)
+ {
+ throw new PGPException("exception encrypting session info: " + e.getMessage(), e);
+ }
+ catch (IOException e)
+ {
+ throw new PGPException("exception encrypting session info: " + e.getMessage(), e);
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcUtil.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcUtil.java
new file mode 100644
index 00000000..c3be8c83
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcUtil.java
@@ -0,0 +1,75 @@
+package org.spongycastle.openpgp.operator.bc;
+
+import java.io.InputStream;
+
+import org.spongycastle.crypto.BlockCipher;
+import org.spongycastle.crypto.BufferedBlockCipher;
+import org.spongycastle.crypto.io.CipherInputStream;
+import org.spongycastle.crypto.modes.CFBBlockCipher;
+import org.spongycastle.crypto.modes.OpenPGPCFBBlockCipher;
+import org.spongycastle.crypto.params.KeyParameter;
+import org.spongycastle.crypto.params.ParametersWithIV;
+import org.spongycastle.openpgp.operator.PGPDataDecryptor;
+import org.spongycastle.openpgp.operator.PGPDigestCalculator;
+
+class BcUtil
+{
+ static BufferedBlockCipher createStreamCipher(boolean forEncryption, BlockCipher engine, boolean withIntegrityPacket, byte[] key)
+ {
+ BufferedBlockCipher c;
+
+ if (withIntegrityPacket)
+ {
+ c = new BufferedBlockCipher(new CFBBlockCipher(engine, engine.getBlockSize() * 8));
+ }
+ else
+ {
+ c = new BufferedBlockCipher(new OpenPGPCFBBlockCipher(engine));
+ }
+
+ KeyParameter keyParameter = new KeyParameter(key);
+
+ if (withIntegrityPacket)
+ {
+ c.init(forEncryption, new ParametersWithIV(keyParameter, new byte[engine.getBlockSize()]));
+ }
+ else
+ {
+ c.init(forEncryption, keyParameter);
+ }
+
+ return c;
+ }
+
+ public static PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, BlockCipher engine, byte[] key)
+ {
+ final BufferedBlockCipher c = createStreamCipher(false, engine, withIntegrityPacket, key);
+
+ return new PGPDataDecryptor()
+ {
+ public InputStream getInputStream(InputStream in)
+ {
+ return new CipherInputStream(in, c);
+ }
+
+ public int getBlockSize()
+ {
+ return c.getBlockSize();
+ }
+
+ public PGPDigestCalculator getIntegrityCalculator()
+ {
+ return new SHA1PGPDigestCalculator();
+ }
+ };
+ }
+
+ public static BufferedBlockCipher createSymmetricKeyWrapper(boolean forEncryption, BlockCipher engine, byte[] key, byte[] iv)
+ {
+ BufferedBlockCipher c = new BufferedBlockCipher(new CFBBlockCipher(engine, engine.getBlockSize() * 8));
+
+ c.init(forEncryption, new ParametersWithIV(new KeyParameter(key), iv));
+
+ return c;
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/SHA1PGPDigestCalculator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/SHA1PGPDigestCalculator.java
new file mode 100644
index 00000000..15572a39
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/SHA1PGPDigestCalculator.java
@@ -0,0 +1,68 @@
+package org.spongycastle.openpgp.operator.bc;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.crypto.digests.SHA1Digest;
+import org.spongycastle.openpgp.operator.PGPDigestCalculator;
+
+class SHA1PGPDigestCalculator
+ implements PGPDigestCalculator
+{
+ private Digest digest = new SHA1Digest();
+
+ public int getAlgorithm()
+ {
+ return HashAlgorithmTags.SHA1;
+ }
+
+ public OutputStream getOutputStream()
+ {
+ return new DigestOutputStream(digest);
+ }
+
+ public byte[] getDigest()
+ {
+ byte[] d = new byte[digest.getDigestSize()];
+
+ digest.doFinal(d, 0);
+
+ return d;
+ }
+
+ public void reset()
+ {
+ digest.reset();
+ }
+
+ private class DigestOutputStream
+ extends OutputStream
+ {
+ private Digest dig;
+
+ DigestOutputStream(Digest dig)
+ {
+ this.dig = dig;
+ }
+
+ public void write(byte[] bytes, int off, int len)
+ throws IOException
+ {
+ dig.update(bytes, off, len);
+ }
+
+ public void write(byte[] bytes)
+ throws IOException
+ {
+ dig.update(bytes, 0, bytes.length);
+ }
+
+ public void write(int b)
+ throws IOException
+ {
+ dig.update((byte)b);
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/bc/SignerOutputStream.java b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/SignerOutputStream.java
new file mode 100644
index 00000000..cdc2d7e3
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/bc/SignerOutputStream.java
@@ -0,0 +1,35 @@
+package org.spongycastle.openpgp.operator.bc;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.spongycastle.crypto.Signer;
+
+class SignerOutputStream
+ extends OutputStream
+{
+ private Signer sig;
+
+ SignerOutputStream(Signer sig)
+ {
+ this.sig = sig;
+ }
+
+ public void write(byte[] bytes, int off, int len)
+ throws IOException
+ {
+ sig.update(bytes, off, len);
+ }
+
+ public void write(byte[] bytes)
+ throws IOException
+ {
+ sig.update(bytes, 0, bytes.length);
+ }
+
+ public void write(int b)
+ throws IOException
+ {
+ sig.update((byte)b);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaKeyFingerprintCalculator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaKeyFingerprintCalculator.java
new file mode 100644
index 00000000..e72955f2
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaKeyFingerprintCalculator.java
@@ -0,0 +1,74 @@
+package org.spongycastle.openpgp.operator.jcajce;
+
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import org.spongycastle.bcpg.BCPGKey;
+import org.spongycastle.bcpg.MPInteger;
+import org.spongycastle.bcpg.PublicKeyPacket;
+import org.spongycastle.bcpg.RSAPublicBCPGKey;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator;
+
+public class JcaKeyFingerprintCalculator
+ implements KeyFingerPrintCalculator
+{
+
+ // FIXME: Convert this to builder style so we can set provider?
+ public byte[] calculateFingerprint(PublicKeyPacket publicPk)
+ throws PGPException
+ {
+ BCPGKey key = publicPk.getKey();
+
+ if (publicPk.getVersion() <= 3)
+ {
+ RSAPublicBCPGKey rK = (RSAPublicBCPGKey)key;
+
+ try
+ {
+ MessageDigest digest = MessageDigest.getInstance("MD5");
+
+ byte[] bytes = new MPInteger(rK.getModulus()).getEncoded();
+ digest.update(bytes, 2, bytes.length - 2);
+
+ bytes = new MPInteger(rK.getPublicExponent()).getEncoded();
+ digest.update(bytes, 2, bytes.length - 2);
+
+ return digest.digest();
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ throw new PGPException("can't find MD5", e);
+ }
+ catch (IOException e)
+ {
+ throw new PGPException("can't encode key components: " + e.getMessage(), e);
+ }
+ }
+ else
+ {
+ try
+ {
+ byte[] kBytes = publicPk.getEncodedContents();
+
+ MessageDigest digest = MessageDigest.getInstance("SHA1");
+
+ digest.update((byte)0x99);
+ digest.update((byte)(kBytes.length >> 8));
+ digest.update((byte)kBytes.length);
+ digest.update(kBytes);
+
+ return digest.digest();
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ throw new PGPException("can't find SHA1", e);
+ }
+ catch (IOException e)
+ {
+ throw new PGPException("can't encode key components: " + e.getMessage(), e);
+ }
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPContentSignerBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPContentSignerBuilder.java
new file mode 100644
index 00000000..5cb6f007
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPContentSignerBuilder.java
@@ -0,0 +1,156 @@
+package org.spongycastle.openpgp.operator.jcajce;
+
+import java.io.OutputStream;
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.Signature;
+import java.security.SignatureException;
+
+import org.spongycastle.jcajce.util.DefaultJcaJceHelper;
+import org.spongycastle.jcajce.util.NamedJcaJceHelper;
+import org.spongycastle.jcajce.util.ProviderJcaJceHelper;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPrivateKey;
+import org.spongycastle.openpgp.operator.PGPContentSigner;
+import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
+import org.spongycastle.openpgp.operator.PGPDigestCalculator;
+import org.spongycastle.util.io.TeeOutputStream;
+
+public class JcaPGPContentSignerBuilder
+ implements PGPContentSignerBuilder
+{
+ private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+ private JcaPGPDigestCalculatorProviderBuilder digestCalculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder();
+ private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter();
+ private int hashAlgorithm;
+ private SecureRandom random;
+ private int keyAlgorithm;
+
+ public JcaPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm)
+ {
+ this.keyAlgorithm = keyAlgorithm;
+ this.hashAlgorithm = hashAlgorithm;
+ }
+
+ public JcaPGPContentSignerBuilder setSecureRandom(SecureRandom random)
+ {
+ this.random = random;
+
+ return this;
+ }
+
+ public JcaPGPContentSignerBuilder setProvider(Provider provider)
+ {
+ this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+ keyConverter.setProvider(provider);
+ digestCalculatorProviderBuilder.setProvider(provider);
+
+ return this;
+ }
+
+ public JcaPGPContentSignerBuilder setProvider(String providerName)
+ {
+ this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+ keyConverter.setProvider(providerName);
+ digestCalculatorProviderBuilder.setProvider(providerName);
+
+ return this;
+ }
+
+ public JcaPGPContentSignerBuilder setDigestProvider(Provider provider)
+ {
+ digestCalculatorProviderBuilder.setProvider(provider);
+
+ return this;
+ }
+
+ public JcaPGPContentSignerBuilder setDigestProvider(String providerName)
+ {
+ digestCalculatorProviderBuilder.setProvider(providerName);
+
+ return this;
+ }
+
+ public PGPContentSigner build(final int signatureType, PGPPrivateKey privateKey)
+ throws PGPException
+ {
+ if (privateKey instanceof JcaPGPPrivateKey)
+ {
+ return build(signatureType, privateKey.getKeyID(), ((JcaPGPPrivateKey)privateKey).getPrivateKey());
+ }
+ else
+ {
+ return build(signatureType, privateKey.getKeyID(), keyConverter.getPrivateKey(privateKey));
+ }
+ }
+
+ public PGPContentSigner build(final int signatureType, final long keyID, final PrivateKey privateKey)
+ throws PGPException
+ {
+ final PGPDigestCalculator digestCalculator = digestCalculatorProviderBuilder.build().get(hashAlgorithm);
+ final Signature signature = helper.createSignature(keyAlgorithm, hashAlgorithm);
+
+ try
+ {
+ if (random != null)
+ {
+ signature.initSign(privateKey, random);
+ }
+ else
+ {
+ signature.initSign(privateKey);
+ }
+ }
+ catch (InvalidKeyException e)
+ {
+ throw new PGPException("invalid key.", e);
+ }
+
+ return new PGPContentSigner()
+ {
+ public int getType()
+ {
+ return signatureType;
+ }
+
+ public int getHashAlgorithm()
+ {
+ return hashAlgorithm;
+ }
+
+ public int getKeyAlgorithm()
+ {
+ return keyAlgorithm;
+ }
+
+ public long getKeyID()
+ {
+ return keyID;
+ }
+
+ public OutputStream getOutputStream()
+ {
+ return new TeeOutputStream(new SignatureOutputStream(signature), digestCalculator.getOutputStream());
+ }
+
+ public byte[] getSignature()
+ {
+ try
+ {
+ return signature.sign();
+ }
+ catch (SignatureException e)
+ { // TODO: need a specific runtime exception for PGP operators.
+ throw new IllegalStateException("unable to create signature");
+ }
+ }
+
+ public byte[] getDigest()
+ {
+ return digestCalculator.getDigest();
+ }
+ };
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPContentVerifierBuilderProvider.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPContentVerifierBuilderProvider.java
new file mode 100644
index 00000000..99973776
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPContentVerifierBuilderProvider.java
@@ -0,0 +1,113 @@
+package org.spongycastle.openpgp.operator.jcajce;
+
+import java.io.OutputStream;
+import java.security.InvalidKeyException;
+import java.security.Provider;
+import java.security.Signature;
+import java.security.SignatureException;
+
+import org.spongycastle.jcajce.util.DefaultJcaJceHelper;
+import org.spongycastle.jcajce.util.NamedJcaJceHelper;
+import org.spongycastle.jcajce.util.ProviderJcaJceHelper;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPRuntimeOperationException;
+import org.spongycastle.openpgp.operator.PGPContentVerifier;
+import org.spongycastle.openpgp.operator.PGPContentVerifierBuilder;
+import org.spongycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
+
+public class JcaPGPContentVerifierBuilderProvider
+ implements PGPContentVerifierBuilderProvider
+{
+ private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+ private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter();
+
+ public JcaPGPContentVerifierBuilderProvider()
+ {
+ }
+
+ public JcaPGPContentVerifierBuilderProvider setProvider(Provider provider)
+ {
+ this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+ keyConverter.setProvider(provider);
+
+ return this;
+ }
+
+ public JcaPGPContentVerifierBuilderProvider setProvider(String providerName)
+ {
+ this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+ keyConverter.setProvider(providerName);
+
+ return this;
+ }
+
+ public PGPContentVerifierBuilder get(int keyAlgorithm, int hashAlgorithm)
+ throws PGPException
+ {
+ return new JcaPGPContentVerifierBuilder(keyAlgorithm, hashAlgorithm);
+ }
+
+ private class JcaPGPContentVerifierBuilder
+ implements PGPContentVerifierBuilder
+ {
+ private int hashAlgorithm;
+ private int keyAlgorithm;
+
+ public JcaPGPContentVerifierBuilder(int keyAlgorithm, int hashAlgorithm)
+ {
+ this.keyAlgorithm = keyAlgorithm;
+ this.hashAlgorithm = hashAlgorithm;
+ }
+
+ public PGPContentVerifier build(final PGPPublicKey publicKey)
+ throws PGPException
+ {
+ final Signature signature = helper.createSignature(keyAlgorithm, hashAlgorithm);
+
+ try
+ {
+ signature.initVerify(keyConverter.getPublicKey(publicKey));
+ }
+ catch (InvalidKeyException e)
+ {
+ throw new PGPException("invalid key.", e);
+ }
+
+ return new PGPContentVerifier()
+ {
+ public int getHashAlgorithm()
+ {
+ return hashAlgorithm;
+ }
+
+ public int getKeyAlgorithm()
+ {
+ return keyAlgorithm;
+ }
+
+ public long getKeyID()
+ {
+ return publicKey.getKeyID();
+ }
+
+ public boolean verify(byte[] expected)
+ {
+ try
+ {
+ return signature.verify(expected);
+ }
+ catch (SignatureException e)
+ {
+ throw new PGPRuntimeOperationException("unable to verify signature: " + e.getMessage(), e);
+ }
+ }
+
+ public OutputStream getOutputStream()
+ {
+ return new SignatureOutputStream(signature);
+ }
+ };
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPDigestCalculatorProviderBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPDigestCalculatorProviderBuilder.java
new file mode 100644
index 00000000..753e289c
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPDigestCalculatorProviderBuilder.java
@@ -0,0 +1,149 @@
+package org.spongycastle.openpgp.operator.jcajce;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.Provider;
+
+import org.spongycastle.jcajce.util.DefaultJcaJceHelper;
+import org.spongycastle.jcajce.util.NamedJcaJceHelper;
+import org.spongycastle.jcajce.util.ProviderJcaJceHelper;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.operator.PGPDigestCalculator;
+import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider;
+
+/**
+ * A builder for {@link PGPDigestCalculatorProvider} instances that obtain cryptographic primitives
+ * using the JCA API.
+ * <p/>
+ * By default digest calculator providers obtained from this builder will use the default JCA
+ * algorithm lookup mechanisms (i.e. specifying no provider), but a specific provider can be
+ * specified prior to building.
+ */
+public class JcaPGPDigestCalculatorProviderBuilder
+{
+ private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+
+ /**
+ * Default constructor.
+ */
+ public JcaPGPDigestCalculatorProviderBuilder()
+ {
+ }
+
+ /**
+ * Sets the provider to use to obtain cryptographic primitives.
+ *
+ * @param provider the JCA provider to use.
+ * @return the current builder.
+ */
+ public JcaPGPDigestCalculatorProviderBuilder setProvider(Provider provider)
+ {
+ this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+ return this;
+ }
+
+ /**
+ * Sets the provider to use to obtain cryptographic primitives.
+ *
+ * @param providerName the name of the JCA provider to use.
+ * @return the current builder.
+ */
+ public JcaPGPDigestCalculatorProviderBuilder setProvider(String providerName)
+ {
+ this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+ return this;
+ }
+
+ /**
+ * Constructs a new PGPDigestCalculatorProvider
+ *
+ * @return a PGPDigestCalculatorProvider that will use the JCA algorithm lookup strategy
+ * configured on this builder.
+ * @throws PGPException if an error occurs constructing the digest calculator provider.
+ */
+ public PGPDigestCalculatorProvider build()
+ throws PGPException
+ {
+ return new PGPDigestCalculatorProvider()
+ {
+ public PGPDigestCalculator get(final int algorithm)
+ throws PGPException
+ {
+ final DigestOutputStream stream;
+ final MessageDigest dig;
+
+ try
+ {
+ dig = helper.createDigest(algorithm);
+
+ stream = new DigestOutputStream(dig);
+ }
+ catch (GeneralSecurityException e)
+ {
+ throw new PGPException("exception on setup: " + e, e);
+ }
+
+ return new PGPDigestCalculator()
+ {
+ public int getAlgorithm()
+ {
+ return algorithm;
+ }
+
+ public OutputStream getOutputStream()
+ {
+ return stream;
+ }
+
+ public byte[] getDigest()
+ {
+ return stream.getDigest();
+ }
+
+ public void reset()
+ {
+ dig.reset();
+ }
+ };
+ }
+ };
+ }
+
+ private class DigestOutputStream
+ extends OutputStream
+ {
+ private MessageDigest dig;
+
+ DigestOutputStream(MessageDigest dig)
+ {
+ this.dig = dig;
+ }
+
+ public void write(byte[] bytes, int off, int len)
+ throws IOException
+ {
+ dig.update(bytes, off, len);
+ }
+
+ public void write(byte[] bytes)
+ throws IOException
+ {
+ dig.update(bytes);
+ }
+
+ public void write(int b)
+ throws IOException
+ {
+ dig.update((byte)b);
+ }
+
+ byte[] getDigest()
+ {
+ return dig.digest();
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPKeyConverter.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPKeyConverter.java
new file mode 100644
index 00000000..2f82fdfa
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPKeyConverter.java
@@ -0,0 +1,377 @@
+package org.spongycastle.openpgp.operator.jcajce;
+
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.DSAPrivateKeySpec;
+import java.security.spec.DSAPublicKeySpec;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPrivateKeySpec;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Date;
+
+import org.spongycastle.asn1.ASN1ObjectIdentifier;
+import org.spongycastle.asn1.ASN1OctetString;
+import org.spongycastle.asn1.DEROctetString;
+import org.spongycastle.asn1.nist.NISTNamedCurves;
+import org.spongycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.spongycastle.asn1.x9.ECNamedCurveTable;
+import org.spongycastle.asn1.x9.X9ECParameters;
+import org.spongycastle.asn1.x9.X9ECPoint;
+import org.spongycastle.bcpg.BCPGKey;
+import org.spongycastle.bcpg.DSAPublicBCPGKey;
+import org.spongycastle.bcpg.DSASecretBCPGKey;
+import org.spongycastle.bcpg.ECDHPublicBCPGKey;
+import org.spongycastle.bcpg.ECDSAPublicBCPGKey;
+import org.spongycastle.bcpg.ECSecretBCPGKey;
+import org.spongycastle.bcpg.ElGamalPublicBCPGKey;
+import org.spongycastle.bcpg.ElGamalSecretBCPGKey;
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
+import org.spongycastle.bcpg.PublicKeyPacket;
+import org.spongycastle.bcpg.RSAPublicBCPGKey;
+import org.spongycastle.bcpg.RSASecretBCPGKey;
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.crypto.ec.CustomNamedCurves;
+import org.spongycastle.jcajce.util.DefaultJcaJceHelper;
+import org.spongycastle.jcajce.util.NamedJcaJceHelper;
+import org.spongycastle.jcajce.util.ProviderJcaJceHelper;
+import org.spongycastle.jce.interfaces.ElGamalPrivateKey;
+import org.spongycastle.jce.interfaces.ElGamalPublicKey;
+import org.spongycastle.jce.spec.ECNamedCurveSpec;
+import org.spongycastle.jce.spec.ElGamalParameterSpec;
+import org.spongycastle.jce.spec.ElGamalPrivateKeySpec;
+import org.spongycastle.jce.spec.ElGamalPublicKeySpec;
+import org.spongycastle.openpgp.PGPAlgorithmParameters;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPKdfParameters;
+import org.spongycastle.openpgp.PGPPrivateKey;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator;
+
+public class JcaPGPKeyConverter
+{
+ private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+ private KeyFingerPrintCalculator fingerPrintCalculator = new JcaKeyFingerprintCalculator();
+
+ public JcaPGPKeyConverter setProvider(Provider provider)
+ {
+ this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+ return this;
+ }
+
+ public JcaPGPKeyConverter setProvider(String providerName)
+ {
+ this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+ return this;
+ }
+
+ public PublicKey getPublicKey(PGPPublicKey publicKey)
+ throws PGPException
+ {
+ KeyFactory fact;
+
+ PublicKeyPacket publicPk = publicKey.getPublicKeyPacket();
+
+ try
+ {
+ switch (publicPk.getAlgorithm())
+ {
+ case PublicKeyAlgorithmTags.RSA_ENCRYPT:
+ case PublicKeyAlgorithmTags.RSA_GENERAL:
+ case PublicKeyAlgorithmTags.RSA_SIGN:
+ RSAPublicBCPGKey rsaK = (RSAPublicBCPGKey)publicPk.getKey();
+ RSAPublicKeySpec rsaSpec = new RSAPublicKeySpec(rsaK.getModulus(), rsaK.getPublicExponent());
+
+ fact = helper.createKeyFactory("RSA");
+
+ return fact.generatePublic(rsaSpec);
+ case PublicKeyAlgorithmTags.DSA:
+ DSAPublicBCPGKey dsaK = (DSAPublicBCPGKey)publicPk.getKey();
+ DSAPublicKeySpec dsaSpec = new DSAPublicKeySpec(dsaK.getY(), dsaK.getP(), dsaK.getQ(), dsaK.getG());
+
+ fact = helper.createKeyFactory("DSA");
+
+ return fact.generatePublic(dsaSpec);
+ case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT:
+ case PublicKeyAlgorithmTags.ELGAMAL_GENERAL:
+ ElGamalPublicBCPGKey elK = (ElGamalPublicBCPGKey)publicPk.getKey();
+ ElGamalPublicKeySpec elSpec = new ElGamalPublicKeySpec(elK.getY(), new ElGamalParameterSpec(elK.getP(), elK.getG()));
+
+ fact = helper.createKeyFactory("ElGamal");
+
+ return fact.generatePublic(elSpec);
+ case PublicKeyAlgorithmTags.EC:
+ ECDHPublicBCPGKey ecdhK = (ECDHPublicBCPGKey)publicPk.getKey();
+ ECPublicKeySpec ecDhSpec = new ECPublicKeySpec(
+ new java.security.spec.ECPoint(ecdhK.getPoint().getAffineXCoord().toBigInteger(), ecdhK.getPoint().getAffineYCoord().toBigInteger()),
+ getX9Parameters(ecdhK.getCurveOID()));
+ fact = helper.createKeyFactory("ECDH");
+
+ return fact.generatePublic(ecDhSpec);
+ case PublicKeyAlgorithmTags.ECDSA:
+ ECDSAPublicBCPGKey ecdsaK = (ECDSAPublicBCPGKey)publicPk.getKey();
+ ECPublicKeySpec ecDsaSpec = new ECPublicKeySpec(
+ new java.security.spec.ECPoint(ecdsaK.getPoint().getAffineXCoord().toBigInteger(), ecdsaK.getPoint().getAffineYCoord().toBigInteger()),
+ getX9Parameters(ecdsaK.getCurveOID()));
+ fact = helper.createKeyFactory("ECDSA");
+
+ return fact.generatePublic(ecDsaSpec);
+ default:
+ throw new PGPException("unknown public key algorithm encountered");
+ }
+ }
+ catch (PGPException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new PGPException("exception constructing public key", e);
+ }
+ }
+
+ /**
+ * Create a PGPPublicKey from the passed in JCA one.
+ * <p/>
+ * Note: the time passed in affects the value of the key's keyID, so you probably only want
+ * to do this once for a JCA key, or make sure you keep track of the time you used.
+ *
+ * @param algorithm asymmetric algorithm type representing the public key.
+ * @param algorithmParameters additional parameters to be stored against the public key.
+ * @param pubKey actual public key to associate.
+ * @param time date of creation.
+ * @throws PGPException on key creation problem.
+ */
+ public PGPPublicKey getPGPPublicKey(int algorithm, PGPAlgorithmParameters algorithmParameters, PublicKey pubKey, Date time)
+ throws PGPException
+ {
+ BCPGKey bcpgKey;
+
+ if (pubKey instanceof RSAPublicKey)
+ {
+ RSAPublicKey rK = (RSAPublicKey)pubKey;
+
+ bcpgKey = new RSAPublicBCPGKey(rK.getModulus(), rK.getPublicExponent());
+ }
+ else if (pubKey instanceof DSAPublicKey)
+ {
+ DSAPublicKey dK = (DSAPublicKey)pubKey;
+ DSAParams dP = dK.getParams();
+
+ bcpgKey = new DSAPublicBCPGKey(dP.getP(), dP.getQ(), dP.getG(), dK.getY());
+ }
+ else if (pubKey instanceof ElGamalPublicKey)
+ {
+ ElGamalPublicKey eK = (ElGamalPublicKey)pubKey;
+ ElGamalParameterSpec eS = eK.getParameters();
+
+ bcpgKey = new ElGamalPublicBCPGKey(eS.getP(), eS.getG(), eK.getY());
+ }
+ else if (pubKey instanceof ECPublicKey)
+ {
+ SubjectPublicKeyInfo keyInfo = SubjectPublicKeyInfo.getInstance(pubKey.getEncoded());
+
+ // TODO: should probably match curve by comparison as well
+ ASN1ObjectIdentifier curveOid = ASN1ObjectIdentifier.getInstance(keyInfo.getAlgorithm().getParameters());
+
+ X9ECParameters params = NISTNamedCurves.getByOID(curveOid);
+
+ ASN1OctetString key = new DEROctetString(keyInfo.getPublicKeyData().getBytes());
+ X9ECPoint derQ = new X9ECPoint(params.getCurve(), key);
+
+ if (algorithm == PGPPublicKey.EC)
+ {
+ PGPKdfParameters kdfParams = (PGPKdfParameters)algorithmParameters;
+ if (kdfParams == null)
+ {
+ // We default to these as they are specified as mandatory in RFC 6631.
+ kdfParams = new PGPKdfParameters(HashAlgorithmTags.SHA256, SymmetricKeyAlgorithmTags.AES_128);
+ }
+ bcpgKey = new ECDHPublicBCPGKey(curveOid, derQ.getPoint(), kdfParams.getHashAlgorithm(), kdfParams.getSymmetricWrapAlgorithm());
+ }
+ else
+ {
+ bcpgKey = new ECDSAPublicBCPGKey(curveOid, derQ.getPoint());
+ }
+ }
+ else
+ {
+ throw new PGPException("unknown key class");
+ }
+
+ return new PGPPublicKey(new PublicKeyPacket(algorithm, time, bcpgKey), fingerPrintCalculator);
+ }
+
+ /**
+ * Create a PGPPublicKey from the passed in JCA one.
+ * <p/>
+ * Note: the time passed in affects the value of the key's keyID, so you probably only want
+ * to do this once for a JCA key, or make sure you keep track of the time you used.
+ *
+ * @param algorithm asymmetric algorithm type representing the public key.
+ * @param pubKey actual public key to associate.
+ * @param time date of creation.
+ * @throws PGPException on key creation problem.
+ */
+ public PGPPublicKey getPGPPublicKey(int algorithm, PublicKey pubKey, Date time)
+ throws PGPException
+ {
+ return getPGPPublicKey(algorithm, null, pubKey, time);
+ }
+
+ public PrivateKey getPrivateKey(PGPPrivateKey privKey)
+ throws PGPException
+ {
+ if (privKey instanceof JcaPGPPrivateKey)
+ {
+ return ((JcaPGPPrivateKey)privKey).getPrivateKey();
+ }
+
+ PublicKeyPacket pubPk = privKey.getPublicKeyPacket();
+ BCPGKey privPk = privKey.getPrivateKeyDataPacket();
+
+ try
+ {
+ KeyFactory fact;
+
+ switch (pubPk.getAlgorithm())
+ {
+ case PGPPublicKey.RSA_ENCRYPT:
+ case PGPPublicKey.RSA_GENERAL:
+ case PGPPublicKey.RSA_SIGN:
+ RSAPublicBCPGKey rsaPub = (RSAPublicBCPGKey)pubPk.getKey();
+ RSASecretBCPGKey rsaPriv = (RSASecretBCPGKey)privPk;
+ RSAPrivateCrtKeySpec rsaPrivSpec = new RSAPrivateCrtKeySpec(
+ rsaPriv.getModulus(),
+ rsaPub.getPublicExponent(),
+ rsaPriv.getPrivateExponent(),
+ rsaPriv.getPrimeP(),
+ rsaPriv.getPrimeQ(),
+ rsaPriv.getPrimeExponentP(),
+ rsaPriv.getPrimeExponentQ(),
+ rsaPriv.getCrtCoefficient());
+
+ fact = helper.createKeyFactory("RSA");
+
+ return fact.generatePrivate(rsaPrivSpec);
+ case PGPPublicKey.DSA:
+ DSAPublicBCPGKey dsaPub = (DSAPublicBCPGKey)pubPk.getKey();
+ DSASecretBCPGKey dsaPriv = (DSASecretBCPGKey)privPk;
+ DSAPrivateKeySpec dsaPrivSpec =
+ new DSAPrivateKeySpec(dsaPriv.getX(), dsaPub.getP(), dsaPub.getQ(), dsaPub.getG());
+
+ fact = helper.createKeyFactory("DSA");
+
+ return fact.generatePrivate(dsaPrivSpec);
+ case PublicKeyAlgorithmTags.ECDH:
+ ECDHPublicBCPGKey ecdhPub = (ECDHPublicBCPGKey)pubPk.getKey();
+ ECSecretBCPGKey ecdhK = (ECSecretBCPGKey)privPk;
+ ECPrivateKeySpec ecDhSpec = new ECPrivateKeySpec(
+ ecdhK.getX(),
+ getX9Parameters(ecdhPub.getCurveOID()));
+ fact = helper.createKeyFactory("ECDH");
+
+ return fact.generatePrivate(ecDhSpec);
+ case PublicKeyAlgorithmTags.ECDSA:
+ ECDSAPublicBCPGKey ecdsaPub = (ECDSAPublicBCPGKey)pubPk.getKey();
+ ECSecretBCPGKey ecdsaK = (ECSecretBCPGKey)privPk;
+ ECPrivateKeySpec ecDsaSpec = new ECPrivateKeySpec(
+ ecdsaK.getX(),
+ getX9Parameters(ecdsaPub.getCurveOID()));
+ fact = helper.createKeyFactory("ECDSA");
+
+ return fact.generatePrivate(ecDsaSpec);
+ case PGPPublicKey.ELGAMAL_ENCRYPT:
+ case PGPPublicKey.ELGAMAL_GENERAL:
+ ElGamalPublicBCPGKey elPub = (ElGamalPublicBCPGKey)pubPk.getKey();
+ ElGamalSecretBCPGKey elPriv = (ElGamalSecretBCPGKey)privPk;
+ ElGamalPrivateKeySpec elSpec = new ElGamalPrivateKeySpec(elPriv.getX(), new ElGamalParameterSpec(elPub.getP(), elPub.getG()));
+
+ fact = helper.createKeyFactory("ElGamal");
+
+ return fact.generatePrivate(elSpec);
+ default:
+ throw new PGPException("unknown public key algorithm encountered");
+ }
+ }
+ catch (PGPException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new PGPException("Exception constructing key", e);
+ }
+ }
+
+ /**
+ * Convert a PrivateKey into a PGPPrivateKey.
+ *
+ * @param pub the corresponding PGPPublicKey to privKey.
+ * @param privKey the private key for the key in pub.
+ * @return a PGPPrivateKey
+ * @throws PGPException
+ */
+ public PGPPrivateKey getPGPPrivateKey(PGPPublicKey pub, PrivateKey privKey)
+ throws PGPException
+ {
+ BCPGKey privPk;
+
+ switch (pub.getAlgorithm())
+ {
+ case PGPPublicKey.RSA_ENCRYPT:
+ case PGPPublicKey.RSA_SIGN:
+ case PGPPublicKey.RSA_GENERAL:
+ RSAPrivateCrtKey rsK = (RSAPrivateCrtKey)privKey;
+
+ privPk = new RSASecretBCPGKey(rsK.getPrivateExponent(), rsK.getPrimeP(), rsK.getPrimeQ());
+ break;
+ case PGPPublicKey.DSA:
+ DSAPrivateKey dsK = (DSAPrivateKey)privKey;
+
+ privPk = new DSASecretBCPGKey(dsK.getX());
+ break;
+ case PGPPublicKey.ELGAMAL_ENCRYPT:
+ case PGPPublicKey.ELGAMAL_GENERAL:
+ ElGamalPrivateKey esK = (ElGamalPrivateKey)privKey;
+
+ privPk = new ElGamalSecretBCPGKey(esK.getX());
+ break;
+ case PGPPublicKey.EC:
+ case PGPPublicKey.ECDSA:
+ ECPrivateKey ecK = (ECPrivateKey)privKey;
+
+ privPk = new ECSecretBCPGKey(ecK.getS());
+ break;
+ default:
+ throw new PGPException("unknown key class");
+ }
+
+ return new PGPPrivateKey(pub.getKeyID(), pub.getPublicKeyPacket(), privPk);
+ }
+
+ private ECParameterSpec getX9Parameters(ASN1ObjectIdentifier curveOid)
+ {
+ X9ECParameters x9 = CustomNamedCurves.getByOID(curveOid);
+ if (x9 == null)
+ {
+ x9 = ECNamedCurveTable.getByOID(curveOid);
+ }
+
+ return new ECNamedCurveSpec(curveOid.getId(), x9.getCurve(), x9.getG(), x9.getN(),
+ x9.getH(), x9.getSeed());
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPKeyPair.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPKeyPair.java
new file mode 100644
index 00000000..b32d00d7
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPKeyPair.java
@@ -0,0 +1,48 @@
+package org.spongycastle.openpgp.operator.jcajce;
+
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.util.Date;
+
+import org.spongycastle.openpgp.PGPAlgorithmParameters;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPKeyPair;
+import org.spongycastle.openpgp.PGPPrivateKey;
+import org.spongycastle.openpgp.PGPPublicKey;
+
+public class JcaPGPKeyPair
+ extends PGPKeyPair
+{
+ private static PGPPublicKey getPublicKey(int algorithm, PublicKey pubKey, Date date)
+ throws PGPException
+ {
+ return new JcaPGPKeyConverter().getPGPPublicKey(algorithm, pubKey, date);
+ }
+
+ private static PGPPublicKey getPublicKey(int algorithm, PGPAlgorithmParameters algorithmParameters, PublicKey pubKey, Date date)
+ throws PGPException
+ {
+ return new JcaPGPKeyConverter().getPGPPublicKey(algorithm, algorithmParameters, pubKey, date);
+ }
+
+ private static PGPPrivateKey getPrivateKey(PGPPublicKey pub, PrivateKey privKey)
+ throws PGPException
+ {
+ return new JcaPGPKeyConverter().getPGPPrivateKey(pub, privKey);
+ }
+
+ public JcaPGPKeyPair(int algorithm, KeyPair keyPair, Date date)
+ throws PGPException
+ {
+ this.pub = getPublicKey(algorithm, keyPair.getPublic(), date);
+ this.priv = getPrivateKey(this.pub, keyPair.getPrivate());
+ }
+
+ public JcaPGPKeyPair(int algorithm, PGPAlgorithmParameters parameters, KeyPair keyPair, Date date)
+ throws PGPException
+ {
+ this.pub = getPublicKey(algorithm, parameters, keyPair.getPublic(), date);
+ this.priv = getPrivateKey(this.pub, keyPair.getPrivate());
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPPrivateKey.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPPrivateKey.java
new file mode 100644
index 00000000..a22f4561
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPPrivateKey.java
@@ -0,0 +1,34 @@
+package org.spongycastle.openpgp.operator.jcajce;
+
+import java.security.PrivateKey;
+
+import org.spongycastle.openpgp.PGPPrivateKey;
+import org.spongycastle.openpgp.PGPPublicKey;
+
+/**
+ * A JCA PrivateKey carrier. Use this one if you're dealing with a hardware adapter.
+ */
+public class JcaPGPPrivateKey
+ extends PGPPrivateKey
+{
+ private final PrivateKey privateKey;
+
+ public JcaPGPPrivateKey(long keyID, PrivateKey privateKey)
+ {
+ super(keyID, null, null);
+
+ this.privateKey = privateKey;
+ }
+
+ public JcaPGPPrivateKey(PGPPublicKey pubKey, PrivateKey privateKey)
+ {
+ super(pubKey.getKeyID(), pubKey.getPublicKeyPacket(), null);
+
+ this.privateKey = privateKey;
+ }
+
+ public PrivateKey getPrivateKey()
+ {
+ return privateKey;
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java
new file mode 100644
index 00000000..d0e73b24
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java
@@ -0,0 +1,109 @@
+package org.spongycastle.openpgp.operator.jcajce;
+
+import java.security.Provider;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.spongycastle.jcajce.util.DefaultJcaJceHelper;
+import org.spongycastle.jcajce.util.NamedJcaJceHelper;
+import org.spongycastle.jcajce.util.ProviderJcaJceHelper;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory;
+import org.spongycastle.openpgp.operator.PGPDataDecryptor;
+import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider;
+
+/**
+ * Builder for {@link PBEDataDecryptorFactory} instances that obtain cryptographic primitives using
+ * the JCE API.
+ */
+public class JcePBEDataDecryptorFactoryBuilder
+{
+ private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+ private PGPDigestCalculatorProvider calculatorProvider;
+
+ /**
+ * Base constructor.
+ *
+ * @param calculatorProvider a digest calculator provider to provide calculators to support the key generation calculation required.
+ */
+ public JcePBEDataDecryptorFactoryBuilder(PGPDigestCalculatorProvider calculatorProvider)
+ {
+ this.calculatorProvider = calculatorProvider;
+ }
+
+ /**
+ * Set the provider object to use for creating cryptographic primitives in the resulting factory the builder produces.
+ *
+ * @param provider provider object for cryptographic primitives.
+ * @return the current builder.
+ */
+ public JcePBEDataDecryptorFactoryBuilder setProvider(Provider provider)
+ {
+ this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+ return this;
+ }
+
+ /**
+ * Set the provider name to use for creating cryptographic primitives in the resulting factory the builder produces.
+ *
+ * @param providerName the name of the provider to reference for cryptographic primitives.
+ * @return the current builder.
+ */
+ public JcePBEDataDecryptorFactoryBuilder setProvider(String providerName)
+ {
+ this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+ return this;
+ }
+
+ /**
+ * Construct a {@link PBEDataDecryptorFactory} to use to decrypt PBE encrypted data.
+ *
+ * @param passPhrase the pass phrase to use to generate keys in the resulting factory.
+ * @return a decryptor factory that can be used to generate PBE keys.
+ */
+ public PBEDataDecryptorFactory build(char[] passPhrase)
+ {
+ return new PBEDataDecryptorFactory(passPhrase, calculatorProvider)
+ {
+ public byte[] recoverSessionData(int keyAlgorithm, byte[] key, byte[] secKeyData)
+ throws PGPException
+ {
+ try
+ {
+ if (secKeyData != null && secKeyData.length > 0)
+ {
+ String cipherName = PGPUtil.getSymmetricCipherName(keyAlgorithm);
+ Cipher keyCipher = helper.createCipher(cipherName + "/CFB/NoPadding");
+
+ keyCipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, cipherName), new IvParameterSpec(new byte[keyCipher.getBlockSize()]));
+
+ return keyCipher.doFinal(secKeyData);
+ }
+ else
+ {
+ byte[] keyBytes = new byte[key.length + 1];
+
+ keyBytes[0] = (byte)keyAlgorithm;
+ System.arraycopy(key, 0, keyBytes, 1, key.length);
+
+ return keyBytes;
+ }
+ }
+ catch (Exception e)
+ {
+ throw new PGPException("Exception recovering session info", e);
+ }
+ }
+
+ public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
+ throws PGPException
+ {
+ return helper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key);
+ }
+ };
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEKeyEncryptionMethodGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEKeyEncryptionMethodGenerator.java
new file mode 100644
index 00000000..510ca578
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEKeyEncryptionMethodGenerator.java
@@ -0,0 +1,142 @@
+package org.spongycastle.openpgp.operator.jcajce;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Provider;
+import java.security.SecureRandom;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.spongycastle.bcpg.S2K;
+import org.spongycastle.jcajce.util.DefaultJcaJceHelper;
+import org.spongycastle.jcajce.util.NamedJcaJceHelper;
+import org.spongycastle.jcajce.util.ProviderJcaJceHelper;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
+import org.spongycastle.openpgp.operator.PGPDigestCalculator;
+
+/**
+ * JCE based generator for password based encryption (PBE) data protection methods.
+ */
+public class JcePBEKeyEncryptionMethodGenerator
+ extends PBEKeyEncryptionMethodGenerator
+{
+ private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+
+ /**
+ * Create a PBE encryption method generator using the provided digest and the default S2K count
+ * for key generation.
+ *
+ * @param passPhrase the passphrase to use as the primary source of key material.
+ * @param s2kDigestCalculator the digest calculator to use for key calculation.
+ */
+ public JcePBEKeyEncryptionMethodGenerator(char[] passPhrase, PGPDigestCalculator s2kDigestCalculator)
+ {
+ super(passPhrase, s2kDigestCalculator);
+ }
+
+ /**
+ * Create a PBE encryption method generator using the default SHA-1 digest and the default S2K
+ * count for key generation.
+ *
+ * @param passPhrase the passphrase to use as the primary source of key material.
+ */
+ public JcePBEKeyEncryptionMethodGenerator(char[] passPhrase)
+ {
+ this(passPhrase, new SHA1PGPDigestCalculator());
+ }
+
+ /**
+ * Create a PBE encryption method generator using the provided calculator and S2K count for key
+ * generation.
+ *
+ * @param passPhrase the passphrase to use as the primary source of key material.
+ * @param s2kDigestCalculator the digest calculator to use for key calculation.
+ * @param s2kCount the single byte {@link S2K} count to use.
+ */
+ public JcePBEKeyEncryptionMethodGenerator(char[] passPhrase, PGPDigestCalculator s2kDigestCalculator, int s2kCount)
+ {
+ super(passPhrase, s2kDigestCalculator, s2kCount);
+ }
+
+ /**
+ * Create a PBE encryption method generator using the default SHA-1 digest calculator and a S2K
+ * count other than the default for key generation.
+ *
+ * @param passPhrase the passphrase to use as the primary source of key material.
+ * @param s2kCount the single byte {@link S2K} count to use.
+ */
+ public JcePBEKeyEncryptionMethodGenerator(char[] passPhrase, int s2kCount)
+ {
+ super(passPhrase, new SHA1PGPDigestCalculator(), s2kCount);
+ }
+
+ /**
+ * Sets the JCE provider to source cryptographic primitives from.
+ *
+ * @param provider the JCE provider to use.
+ * @return the current generator.
+ */
+ public JcePBEKeyEncryptionMethodGenerator setProvider(Provider provider)
+ {
+ this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+ return this;
+ }
+
+ /**
+ * Sets the JCE provider to source cryptographic primitives from.
+ *
+ * @param providerName the name of the JCE provider to use.
+ * @return the current generator.
+ */
+ public JcePBEKeyEncryptionMethodGenerator setProvider(String providerName)
+ {
+ this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+ return this;
+ }
+
+ public PBEKeyEncryptionMethodGenerator setSecureRandom(SecureRandom random)
+ {
+ super.setSecureRandom(random);
+
+ return this;
+ }
+
+ protected byte[] encryptSessionInfo(int encAlgorithm, byte[] key, byte[] sessionInfo)
+ throws PGPException
+ {
+ try
+ {
+ String cName = PGPUtil.getSymmetricCipherName(encAlgorithm);
+ Cipher c = helper.createCipher(cName + "/CFB/NoPadding");
+ SecretKey sKey = new SecretKeySpec(key, PGPUtil.getSymmetricCipherName(encAlgorithm));
+
+ c.init(Cipher.ENCRYPT_MODE, sKey, new IvParameterSpec(new byte[c.getBlockSize()]));
+
+ return c.doFinal(sessionInfo, 0, sessionInfo.length);
+ }
+ catch (IllegalBlockSizeException e)
+ {
+ throw new PGPException("illegal block size: " + e.getMessage(), e);
+ }
+ catch (BadPaddingException e)
+ {
+ throw new PGPException("bad padding: " + e.getMessage(), e);
+ }
+ catch (InvalidAlgorithmParameterException e)
+ {
+ throw new PGPException("IV invalid: " + e.getMessage(), e);
+ }
+ catch (InvalidKeyException e)
+ {
+ throw new PGPException("key invalid: " + e.getMessage(), e);
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEProtectionRemoverFactory.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEProtectionRemoverFactory.java
new file mode 100644
index 00000000..1e8dc4b9
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEProtectionRemoverFactory.java
@@ -0,0 +1,106 @@
+package org.spongycastle.openpgp.operator.jcajce;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Provider;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.spec.IvParameterSpec;
+
+import org.spongycastle.jcajce.util.DefaultJcaJceHelper;
+import org.spongycastle.jcajce.util.NamedJcaJceHelper;
+import org.spongycastle.jcajce.util.ProviderJcaJceHelper;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.operator.PBEProtectionRemoverFactory;
+import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
+import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider;
+
+public class JcePBEProtectionRemoverFactory
+ implements PBEProtectionRemoverFactory
+{
+ private final char[] passPhrase;
+
+ private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+ private PGPDigestCalculatorProvider calculatorProvider;
+
+ private JcaPGPDigestCalculatorProviderBuilder calculatorProviderBuilder;
+
+ public JcePBEProtectionRemoverFactory(char[] passPhrase)
+ {
+ this.passPhrase = passPhrase;
+ this.calculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder();
+ }
+
+ public JcePBEProtectionRemoverFactory(char[] passPhrase, PGPDigestCalculatorProvider calculatorProvider)
+ {
+ this.passPhrase = passPhrase;
+ this.calculatorProvider = calculatorProvider;
+ }
+
+ public JcePBEProtectionRemoverFactory setProvider(Provider provider)
+ {
+ this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+ if (calculatorProviderBuilder != null)
+ {
+ calculatorProviderBuilder.setProvider(provider);
+ }
+
+ return this;
+ }
+
+ public JcePBEProtectionRemoverFactory setProvider(String providerName)
+ {
+ this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+ if (calculatorProviderBuilder != null)
+ {
+ calculatorProviderBuilder.setProvider(providerName);
+ }
+
+ return this;
+ }
+
+ public PBESecretKeyDecryptor createDecryptor(String protection)
+ throws PGPException
+ {
+ if (calculatorProvider == null)
+ {
+ calculatorProvider = calculatorProviderBuilder.build();
+ }
+
+ return new PBESecretKeyDecryptor(passPhrase, calculatorProvider)
+ {
+ public byte[] recoverKeyData(int encAlgorithm, byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen)
+ throws PGPException
+ {
+ try
+ {
+ Cipher c = helper.createCipher(PGPUtil.getSymmetricCipherName(encAlgorithm) + "/CBC/NoPadding");
+
+ c.init(Cipher.DECRYPT_MODE, PGPUtil.makeSymmetricKey(encAlgorithm, key), new IvParameterSpec(iv));
+
+ return c.doFinal(keyData, keyOff, keyLen);
+ }
+ catch (IllegalBlockSizeException e)
+ {
+ throw new PGPException("illegal block size: " + e.getMessage(), e);
+ }
+ catch (BadPaddingException e)
+ {
+ throw new PGPException("bad padding: " + e.getMessage(), e);
+ }
+ catch (InvalidAlgorithmParameterException e)
+ {
+ throw new PGPException("invalid parameter: " + e.getMessage(), e);
+ }
+ catch (InvalidKeyException e)
+ {
+ throw new PGPException("invalid key: " + e.getMessage(), e);
+ }
+ }
+ };
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilder.java
new file mode 100644
index 00000000..15ab5358
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilder.java
@@ -0,0 +1,100 @@
+package org.spongycastle.openpgp.operator.jcajce;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Provider;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.spec.IvParameterSpec;
+
+import org.spongycastle.jcajce.util.DefaultJcaJceHelper;
+import org.spongycastle.jcajce.util.NamedJcaJceHelper;
+import org.spongycastle.jcajce.util.ProviderJcaJceHelper;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
+import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider;
+
+public class JcePBESecretKeyDecryptorBuilder
+{
+ private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+ private PGPDigestCalculatorProvider calculatorProvider;
+
+ private JcaPGPDigestCalculatorProviderBuilder calculatorProviderBuilder;
+
+ public JcePBESecretKeyDecryptorBuilder()
+ {
+ this.calculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder();
+ }
+
+ public JcePBESecretKeyDecryptorBuilder(PGPDigestCalculatorProvider calculatorProvider)
+ {
+ this.calculatorProvider = calculatorProvider;
+ }
+
+ public JcePBESecretKeyDecryptorBuilder setProvider(Provider provider)
+ {
+ this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+ if (calculatorProviderBuilder != null)
+ {
+ calculatorProviderBuilder.setProvider(provider);
+ }
+
+ return this;
+ }
+
+ public JcePBESecretKeyDecryptorBuilder setProvider(String providerName)
+ {
+ this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+ if (calculatorProviderBuilder != null)
+ {
+ calculatorProviderBuilder.setProvider(providerName);
+ }
+
+ return this;
+ }
+
+ public PBESecretKeyDecryptor build(char[] passPhrase)
+ throws PGPException
+ {
+ if (calculatorProvider == null)
+ {
+ calculatorProvider = calculatorProviderBuilder.build();
+ }
+
+ return new PBESecretKeyDecryptor(passPhrase, calculatorProvider)
+ {
+ public byte[] recoverKeyData(int encAlgorithm, byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen)
+ throws PGPException
+ {
+ try
+ {
+ Cipher c = helper.createCipher(PGPUtil.getSymmetricCipherName(encAlgorithm) + "/CFB/NoPadding");
+
+ c.init(Cipher.DECRYPT_MODE, PGPUtil.makeSymmetricKey(encAlgorithm, key), new IvParameterSpec(iv));
+
+ return c.doFinal(keyData, keyOff, keyLen);
+ }
+ catch (IllegalBlockSizeException e)
+ {
+ throw new PGPException("illegal block size: " + e.getMessage(), e);
+ }
+ catch (BadPaddingException e)
+ {
+ throw new PGPException("bad padding: " + e.getMessage(), e);
+ }
+ catch (InvalidAlgorithmParameterException e)
+ {
+ throw new PGPException("invalid parameter: " + e.getMessage(), e);
+ }
+ catch (InvalidKeyException e)
+ {
+ throw new PGPException("invalid key: " + e.getMessage(), e);
+ }
+ }
+ };
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBESecretKeyEncryptorBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBESecretKeyEncryptorBuilder.java
new file mode 100644
index 00000000..4a137418
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBESecretKeyEncryptorBuilder.java
@@ -0,0 +1,180 @@
+package org.spongycastle.openpgp.operator.jcajce;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Provider;
+import java.security.SecureRandom;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.spec.IvParameterSpec;
+
+import org.spongycastle.jcajce.util.DefaultJcaJceHelper;
+import org.spongycastle.jcajce.util.NamedJcaJceHelper;
+import org.spongycastle.jcajce.util.ProviderJcaJceHelper;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor;
+import org.spongycastle.openpgp.operator.PGPDigestCalculator;
+
+public class JcePBESecretKeyEncryptorBuilder
+{
+ private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+ private int encAlgorithm;
+ private PGPDigestCalculator s2kDigestCalculator;
+ private SecureRandom random;
+ private int s2kCount = 0x60;
+
+ public JcePBESecretKeyEncryptorBuilder(int encAlgorithm)
+ {
+ this(encAlgorithm, new SHA1PGPDigestCalculator());
+ }
+
+ /**
+ * Create a SecretKeyEncryptorBuilder with the S2K count different to the default of 0x60.
+ *
+ * @param encAlgorithm encryption algorithm to use.
+ * @param s2kCount iteration count to use for S2K function.
+ */
+ public JcePBESecretKeyEncryptorBuilder(int encAlgorithm, int s2kCount)
+ {
+ this(encAlgorithm, new SHA1PGPDigestCalculator(), s2kCount);
+ }
+
+ /**
+ * Create a builder which will make encryptors using the passed in digest calculator. If a MD5 calculator is
+ * passed in the builder will assume the encryptors are for use with version 3 keys.
+ *
+ * @param encAlgorithm encryption algorithm to use.
+ * @param s2kDigestCalculator digest calculator to use.
+ */
+ public JcePBESecretKeyEncryptorBuilder(int encAlgorithm, PGPDigestCalculator s2kDigestCalculator)
+ {
+ this(encAlgorithm, s2kDigestCalculator, 0x60);
+ }
+
+ /**
+ * Create an SecretKeyEncryptorBuilder with the S2k count different to the default of 0x60, and the S2K digest
+ * different from SHA-1.
+ *
+ * @param encAlgorithm encryption algorithm to use.
+ * @param s2kDigestCalculator digest calculator to use.
+ * @param s2kCount iteration count to use for S2K function.
+ */
+ public JcePBESecretKeyEncryptorBuilder(int encAlgorithm, PGPDigestCalculator s2kDigestCalculator, int s2kCount)
+ {
+ this.encAlgorithm = encAlgorithm;
+ this.s2kDigestCalculator = s2kDigestCalculator;
+
+ if (s2kCount < 0 || s2kCount > 0xff)
+ {
+ throw new IllegalArgumentException("s2KCount value outside of range 0 to 255.");
+ }
+
+ this.s2kCount = s2kCount;
+ }
+
+ public JcePBESecretKeyEncryptorBuilder setProvider(Provider provider)
+ {
+ this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+ return this;
+ }
+
+ public JcePBESecretKeyEncryptorBuilder setProvider(String providerName)
+ {
+ this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+ return this;
+ }
+
+ /**
+ * Provide a user defined source of randomness.
+ *
+ * @param random the secure random to be used.
+ * @return the current builder.
+ */
+ public JcePBESecretKeyEncryptorBuilder setSecureRandom(SecureRandom random)
+ {
+ this.random = random;
+
+ return this;
+ }
+
+ public PBESecretKeyEncryptor build(char[] passPhrase)
+ {
+ if (random == null)
+ {
+ random = new SecureRandom();
+ }
+
+ return new PBESecretKeyEncryptor(encAlgorithm, s2kDigestCalculator, s2kCount, random, passPhrase)
+ {
+ private Cipher c;
+ private byte[] iv;
+
+ public byte[] encryptKeyData(byte[] key, byte[] keyData, int keyOff, int keyLen)
+ throws PGPException
+ {
+ try
+ {
+ c = helper.createCipher(PGPUtil.getSymmetricCipherName(this.encAlgorithm) + "/CFB/NoPadding");
+
+ c.init(Cipher.ENCRYPT_MODE, PGPUtil.makeSymmetricKey(this.encAlgorithm, key), this.random);
+
+ iv = c.getIV();
+
+ return c.doFinal(keyData, keyOff, keyLen);
+ }
+ catch (IllegalBlockSizeException e)
+ {
+ throw new PGPException("illegal block size: " + e.getMessage(), e);
+ }
+ catch (BadPaddingException e)
+ {
+ throw new PGPException("bad padding: " + e.getMessage(), e);
+ }
+ catch (InvalidKeyException e)
+ {
+ throw new PGPException("invalid key: " + e.getMessage(), e);
+ }
+ }
+
+ public byte[] encryptKeyData(byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen)
+ throws PGPException
+ {
+ try
+ {
+ c = helper.createCipher(PGPUtil.getSymmetricCipherName(this.encAlgorithm) + "/CFB/NoPadding");
+
+ c.init(Cipher.ENCRYPT_MODE, PGPUtil.makeSymmetricKey(this.encAlgorithm, key), new IvParameterSpec(iv));
+
+ this.iv = iv;
+
+ return c.doFinal(keyData, keyOff, keyLen);
+ }
+ catch (IllegalBlockSizeException e)
+ {
+ throw new PGPException("illegal block size: " + e.getMessage(), e);
+ }
+ catch (BadPaddingException e)
+ {
+ throw new PGPException("bad padding: " + e.getMessage(), e);
+ }
+ catch (InvalidKeyException e)
+ {
+ throw new PGPException("invalid key: " + e.getMessage(), e);
+ }
+ catch (InvalidAlgorithmParameterException e)
+ {
+ throw new PGPException("invalid iv: " + e.getMessage(), e);
+ }
+ }
+
+ public byte[] getCipherIV()
+ {
+ return iv;
+ }
+ };
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePGPDataEncryptorBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePGPDataEncryptorBuilder.java
new file mode 100644
index 00000000..6761e607
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePGPDataEncryptorBuilder.java
@@ -0,0 +1,175 @@
+package org.spongycastle.openpgp.operator.jcajce;
+
+import java.io.OutputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Provider;
+import java.security.SecureRandom;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.spec.IvParameterSpec;
+
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.jcajce.util.DefaultJcaJceHelper;
+import org.spongycastle.jcajce.util.NamedJcaJceHelper;
+import org.spongycastle.jcajce.util.ProviderJcaJceHelper;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.operator.PGPDataEncryptor;
+import org.spongycastle.openpgp.operator.PGPDataEncryptorBuilder;
+import org.spongycastle.openpgp.operator.PGPDigestCalculator;
+
+/**
+ * {@link PGPDataEncryptorBuilder} implementation that sources cryptographic primitives using the
+ * JCE APIs.
+ * <p/>
+ * By default, cryptographic primitives will be loaded using the default JCE load order (i.e.
+ * without specifying a provider). <br/>
+ * A specific provider can be specified using one of the {@link #setProvider(String)} methods.
+ */
+public class JcePGPDataEncryptorBuilder
+ implements PGPDataEncryptorBuilder
+{
+ private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+ private SecureRandom random;
+ private boolean withIntegrityPacket;
+ private int encAlgorithm;
+
+ /**
+ * Constructs a new data encryptor builder for a specified cipher type.
+ *
+ * @param encAlgorithm one of the {@link SymmetricKeyAlgorithmTags supported symmetric cipher
+ * algorithms}. May not be {@link SymmetricKeyAlgorithmTags#NULL}.
+ */
+ public JcePGPDataEncryptorBuilder(int encAlgorithm)
+ {
+ this.encAlgorithm = encAlgorithm;
+
+ if (encAlgorithm == 0)
+ {
+ throw new IllegalArgumentException("null cipher specified");
+ }
+ }
+
+ /**
+ * Sets whether or not the resulting encrypted data will be protected using an integrity packet.
+ *
+ * @param withIntegrityPacket true if an integrity packet is to be included, false otherwise.
+ * @return the current builder.
+ */
+ public JcePGPDataEncryptorBuilder setWithIntegrityPacket(boolean withIntegrityPacket)
+ {
+ this.withIntegrityPacket = withIntegrityPacket;
+
+ return this;
+ }
+
+ /**
+ * Sets the JCE provider to source cryptographic primitives from.
+ *
+ * @param provider the JCE provider to use.
+ * @return the current builder.
+ */
+ public JcePGPDataEncryptorBuilder setProvider(Provider provider)
+ {
+ this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+ return this;
+ }
+
+ /**
+ * Sets the JCE provider to source cryptographic primitives from.
+ *
+ * @param providerName the name of the JCE provider to use.
+ * @return the current builder.
+ */
+ public JcePGPDataEncryptorBuilder setProvider(String providerName)
+ {
+ this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+ return this;
+ }
+
+ /**
+ * Provide a user defined source of randomness.
+ * <p/>
+ * If no SecureRandom is configured, a default SecureRandom will be used.
+ *
+ * @param random the secure random to be used.
+ * @return the current builder.
+ */
+ public JcePGPDataEncryptorBuilder setSecureRandom(SecureRandom random)
+ {
+ this.random = random;
+
+ return this;
+ }
+
+ public int getAlgorithm()
+ {
+ return encAlgorithm;
+ }
+
+ public SecureRandom getSecureRandom()
+ {
+ if (random == null)
+ {
+ random = new SecureRandom();
+ }
+
+ return random;
+ }
+
+ public PGPDataEncryptor build(byte[] keyBytes)
+ throws PGPException
+ {
+ return new MyPGPDataEncryptor(keyBytes);
+ }
+
+ private class MyPGPDataEncryptor
+ implements PGPDataEncryptor
+ {
+ private final Cipher c;
+
+ MyPGPDataEncryptor(byte[] keyBytes)
+ throws PGPException
+ {
+ c = helper.createStreamCipher(encAlgorithm, withIntegrityPacket);
+
+ byte[] iv = new byte[c.getBlockSize()];
+
+ try
+ {
+ c.init(Cipher.ENCRYPT_MODE, PGPUtil.makeSymmetricKey(encAlgorithm, keyBytes), new IvParameterSpec(iv));
+ }
+ catch (InvalidKeyException e)
+ {
+ throw new PGPException("invalid key: " + e.getMessage(), e);
+ }
+ catch (InvalidAlgorithmParameterException e)
+ {
+ throw new PGPException("imvalid algorithm parameter: " + e.getMessage(), e);
+ }
+ }
+
+ public OutputStream getOutputStream(OutputStream out)
+ {
+ return new CipherOutputStream(out, c);
+ }
+
+ public PGPDigestCalculator getIntegrityCalculator()
+ {
+ if (withIntegrityPacket)
+ {
+ return new SHA1PGPDigestCalculator();
+ }
+
+ return null;
+ }
+
+ public int getBlockSize()
+ {
+ return c.getBlockSize();
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java
new file mode 100644
index 00000000..51534ba9
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java
@@ -0,0 +1,239 @@
+package org.spongycastle.openpgp.operator.jcajce;
+
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.Provider;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.spongycastle.asn1.nist.NISTNamedCurves;
+import org.spongycastle.asn1.x9.X9ECParameters;
+import org.spongycastle.bcpg.BCPGKey;
+import org.spongycastle.bcpg.ECDHPublicBCPGKey;
+import org.spongycastle.bcpg.ECSecretBCPGKey;
+import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
+import org.spongycastle.bcpg.PublicKeyPacket;
+import org.spongycastle.jcajce.util.DefaultJcaJceHelper;
+import org.spongycastle.jcajce.util.NamedJcaJceHelper;
+import org.spongycastle.jcajce.util.ProviderJcaJceHelper;
+import org.spongycastle.jce.interfaces.ElGamalKey;
+import org.spongycastle.math.ec.ECPoint;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPrivateKey;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.operator.PGPDataDecryptor;
+import org.spongycastle.openpgp.operator.PGPPad;
+import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
+import org.spongycastle.openpgp.operator.RFC6637KDFCalculator;
+
+public class JcePublicKeyDataDecryptorFactoryBuilder
+{
+ private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+ private OperatorHelper contentHelper = new OperatorHelper(new DefaultJcaJceHelper());
+ private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter();
+ private JcaPGPDigestCalculatorProviderBuilder digestCalculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder();
+ private JcaKeyFingerprintCalculator fingerprintCalculator = new JcaKeyFingerprintCalculator();
+
+ public JcePublicKeyDataDecryptorFactoryBuilder()
+ {
+ }
+
+ /**
+ * Set the provider object to use for creating cryptographic primitives in the resulting factory the builder produces.
+ *
+ * @param provider provider object for cryptographic primitives.
+ * @return the current builder.
+ */
+ public JcePublicKeyDataDecryptorFactoryBuilder setProvider(Provider provider)
+ {
+ this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+ keyConverter.setProvider(provider);
+ this.contentHelper = helper;
+
+ return this;
+ }
+
+ /**
+ * Set the provider name to use for creating cryptographic primitives in the resulting factory the builder produces.
+ *
+ * @param providerName the name of the provider to reference for cryptographic primitives.
+ * @return the current builder.
+ */
+ public JcePublicKeyDataDecryptorFactoryBuilder setProvider(String providerName)
+ {
+ this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+ keyConverter.setProvider(providerName);
+ this.contentHelper = helper;
+
+ return this;
+ }
+
+ public JcePublicKeyDataDecryptorFactoryBuilder setContentProvider(Provider provider)
+ {
+ this.contentHelper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+ return this;
+ }
+
+ public JcePublicKeyDataDecryptorFactoryBuilder setContentProvider(String providerName)
+ {
+ this.contentHelper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+ return this;
+ }
+
+ public PublicKeyDataDecryptorFactory build(final PrivateKey privKey)
+ {
+ return new PublicKeyDataDecryptorFactory()
+ {
+ public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData)
+ throws PGPException
+ {
+ if (keyAlgorithm == PublicKeyAlgorithmTags.ECDH)
+ {
+ throw new PGPException("ECDH requires use of PGPPrivateKey for decryption");
+ }
+ return decryptSessionData(keyAlgorithm, privKey, secKeyData);
+ }
+
+ public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
+ throws PGPException
+ {
+ return contentHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key);
+ }
+ };
+ }
+
+ public PublicKeyDataDecryptorFactory build(final PGPPrivateKey privKey)
+ {
+ return new PublicKeyDataDecryptorFactory()
+ {
+ public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData)
+ throws PGPException
+ {
+ if (keyAlgorithm == PublicKeyAlgorithmTags.ECDH)
+ {
+ return decryptSessionData(privKey.getPrivateKeyDataPacket(), privKey.getPublicKeyPacket(), secKeyData);
+ }
+
+ return decryptSessionData(keyAlgorithm, keyConverter.getPrivateKey(privKey), secKeyData);
+ }
+
+ public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
+ throws PGPException
+ {
+ return contentHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key);
+ }
+ };
+ }
+
+ private byte[] decryptSessionData(BCPGKey privateKeyPacket, PublicKeyPacket pubKeyData, byte[][] secKeyData)
+ throws PGPException
+ {
+ ECDHPublicBCPGKey ecKey = (ECDHPublicBCPGKey)pubKeyData.getKey();
+ X9ECParameters x9Params = NISTNamedCurves.getByOID(ecKey.getCurveOID());
+
+ byte[] enc = secKeyData[0];
+
+ int pLen = ((((enc[0] & 0xff) << 8) + (enc[1] & 0xff)) + 7) / 8;
+ byte[] pEnc = new byte[pLen];
+
+ System.arraycopy(enc, 2, pEnc, 0, pLen);
+
+ byte[] keyEnc = new byte[enc[pLen + 2]];
+
+ System.arraycopy(enc, 2 + pLen + 1, keyEnc, 0, keyEnc.length);
+
+ Cipher c = helper.createKeyWrapper(ecKey.getSymmetricKeyAlgorithm());
+
+ ECPoint S = x9Params.getCurve().decodePoint(pEnc).multiply(((ECSecretBCPGKey)privateKeyPacket).getX()).normalize();
+
+ RFC6637KDFCalculator rfc6637KDFCalculator = new RFC6637KDFCalculator(digestCalculatorProviderBuilder.build().get(ecKey.getHashAlgorithm()), ecKey.getSymmetricKeyAlgorithm());
+ Key key = new SecretKeySpec(rfc6637KDFCalculator.createKey(ecKey.getCurveOID(), S, fingerprintCalculator.calculateFingerprint(pubKeyData)), "AESWrap");
+
+ try
+ {
+ c.init(Cipher.UNWRAP_MODE, key);
+
+ Key paddedSessionKey = c.unwrap(keyEnc, "Session", Cipher.SECRET_KEY);
+
+ return PGPPad.unpadSessionData(paddedSessionKey.getEncoded());
+ }
+ catch (InvalidKeyException e)
+ {
+ throw new PGPException("error setting asymmetric cipher", e);
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ throw new PGPException("error setting asymmetric cipher", e);
+ }
+ }
+
+ private byte[] decryptSessionData(int keyAlgorithm, PrivateKey privKey, byte[][] secKeyData)
+ throws PGPException
+ {
+ Cipher c1 = helper.createPublicKeyCipher(keyAlgorithm);
+
+ try
+ {
+ c1.init(Cipher.DECRYPT_MODE, privKey);
+ }
+ catch (InvalidKeyException e)
+ {
+ throw new PGPException("error setting asymmetric cipher", e);
+ }
+
+ if (keyAlgorithm == PGPPublicKey.RSA_ENCRYPT
+ || keyAlgorithm == PGPPublicKey.RSA_GENERAL)
+ {
+ byte[] bi = secKeyData[0]; // encoded MPI
+
+ c1.update(bi, 2, bi.length - 2);
+ }
+ else
+ {
+ ElGamalKey k = (ElGamalKey)privKey;
+ int size = (k.getParameters().getP().bitLength() + 7) / 8;
+ byte[] tmp = new byte[size];
+
+ byte[] bi = secKeyData[0]; // encoded MPI
+ if (bi.length - 2 > size) // leading Zero? Shouldn't happen but...
+ {
+ c1.update(bi, 3, bi.length - 3);
+ }
+ else
+ {
+ System.arraycopy(bi, 2, tmp, tmp.length - (bi.length - 2), bi.length - 2);
+ c1.update(tmp);
+ }
+
+ bi = secKeyData[1]; // encoded MPI
+ for (int i = 0; i != tmp.length; i++)
+ {
+ tmp[i] = 0;
+ }
+
+ if (bi.length - 2 > size) // leading Zero? Shouldn't happen but...
+ {
+ c1.update(bi, 3, bi.length - 3);
+ }
+ else
+ {
+ System.arraycopy(bi, 2, tmp, tmp.length - (bi.length - 2), bi.length - 2);
+ c1.update(tmp);
+ }
+ }
+
+ try
+ {
+ return c1.doFinal();
+ }
+ catch (Exception e)
+ {
+ throw new PGPException("exception decrypting session data", e);
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePublicKeyKeyEncryptionMethodGenerator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePublicKeyKeyEncryptionMethodGenerator.java
new file mode 100644
index 00000000..bd7f4999
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePublicKeyKeyEncryptionMethodGenerator.java
@@ -0,0 +1,166 @@
+package org.spongycastle.openpgp.operator.jcajce;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.Provider;
+import java.security.SecureRandom;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.spongycastle.asn1.nist.NISTNamedCurves;
+import org.spongycastle.asn1.x9.X9ECParameters;
+import org.spongycastle.bcpg.ECDHPublicBCPGKey;
+import org.spongycastle.bcpg.MPInteger;
+import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
+import org.spongycastle.crypto.EphemeralKeyPair;
+import org.spongycastle.crypto.KeyEncoder;
+import org.spongycastle.crypto.generators.ECKeyPairGenerator;
+import org.spongycastle.crypto.generators.EphemeralKeyPairGenerator;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+import org.spongycastle.crypto.params.ECDomainParameters;
+import org.spongycastle.crypto.params.ECKeyGenerationParameters;
+import org.spongycastle.crypto.params.ECPrivateKeyParameters;
+import org.spongycastle.crypto.params.ECPublicKeyParameters;
+import org.spongycastle.jcajce.util.DefaultJcaJceHelper;
+import org.spongycastle.jcajce.util.NamedJcaJceHelper;
+import org.spongycastle.jcajce.util.ProviderJcaJceHelper;
+import org.spongycastle.math.ec.ECPoint;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.operator.PGPPad;
+import org.spongycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator;
+import org.spongycastle.openpgp.operator.RFC6637KDFCalculator;
+
+public class JcePublicKeyKeyEncryptionMethodGenerator
+ extends PublicKeyKeyEncryptionMethodGenerator
+{
+ private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+ private SecureRandom random;
+ private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter();
+ private JcaPGPDigestCalculatorProviderBuilder digestCalculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder();
+
+ /**
+ * Create a public key encryption method generator with the method to be based on the passed in key.
+ *
+ * @param key the public key to use for encryption.
+ */
+ public JcePublicKeyKeyEncryptionMethodGenerator(PGPPublicKey key)
+ {
+ super(key);
+ }
+
+ public JcePublicKeyKeyEncryptionMethodGenerator setProvider(Provider provider)
+ {
+ this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+ keyConverter.setProvider(provider);
+
+ return this;
+ }
+
+ public JcePublicKeyKeyEncryptionMethodGenerator setProvider(String providerName)
+ {
+ this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+ keyConverter.setProvider(providerName);
+
+ return this;
+ }
+
+ /**
+ * Provide a user defined source of randomness.
+ *
+ * @param random the secure random to be used.
+ * @return the current generator.
+ */
+ public JcePublicKeyKeyEncryptionMethodGenerator setSecureRandom(SecureRandom random)
+ {
+ this.random = random;
+
+ return this;
+ }
+
+ protected byte[] encryptSessionInfo(PGPPublicKey pubKey, byte[] sessionInfo)
+ throws PGPException
+ {
+ try
+ {
+ if (pubKey.getAlgorithm() == PublicKeyAlgorithmTags.ECDH)
+ {
+ ECDHPublicBCPGKey ecKey = (ECDHPublicBCPGKey)pubKey.getPublicKeyPacket().getKey();
+ X9ECParameters x9Params = NISTNamedCurves.getByOID(ecKey.getCurveOID());
+ ECDomainParameters ecParams = new ECDomainParameters(x9Params.getCurve(), x9Params.getG(), x9Params.getN());
+
+ // Generate the ephemeral key pair
+ ECKeyPairGenerator gen = new ECKeyPairGenerator();
+ gen.init(new ECKeyGenerationParameters(ecParams, random));
+
+ EphemeralKeyPairGenerator kGen = new EphemeralKeyPairGenerator(gen, new KeyEncoder()
+ {
+ public byte[] getEncoded(AsymmetricKeyParameter keyParameter)
+ {
+ return ((ECPublicKeyParameters)keyParameter).getQ().getEncoded(false);
+ }
+ });
+
+ EphemeralKeyPair ephKp = kGen.generate();
+
+ ECPrivateKeyParameters ephPriv = (ECPrivateKeyParameters)ephKp.getKeyPair().getPrivate();
+
+ ECPoint S = ecKey.getPoint().multiply(ephPriv.getD()).normalize();
+
+ RFC6637KDFCalculator rfc6637KDFCalculator = new RFC6637KDFCalculator(digestCalculatorProviderBuilder.build().get(ecKey.getHashAlgorithm()), ecKey.getSymmetricKeyAlgorithm());
+
+ Key key = new SecretKeySpec(rfc6637KDFCalculator.createKey(ecKey.getCurveOID(), S, pubKey.getFingerprint()), "AESWrap");
+
+ Cipher c = helper.createKeyWrapper(ecKey.getSymmetricKeyAlgorithm());
+
+ c.init(Cipher.WRAP_MODE, key, random);
+
+ byte[] paddedSessionData = PGPPad.padSessionData(sessionInfo);
+
+ byte[] C = c.wrap(new SecretKeySpec(paddedSessionData, PGPUtil.getSymmetricCipherName(sessionInfo[0])));
+ byte[] VB = new MPInteger(new BigInteger(1, ephKp.getEncodedPublicKey())).getEncoded();
+
+ byte[] rv = new byte[VB.length + 1 + C.length];
+
+ System.arraycopy(VB, 0, rv, 0, VB.length);
+ rv[VB.length] = (byte)C.length;
+ System.arraycopy(C, 0, rv, VB.length + 1, C.length);
+
+ return rv;
+ }
+ else
+ {
+ Cipher c = helper.createPublicKeyCipher(pubKey.getAlgorithm());
+
+ Key key = keyConverter.getPublicKey(pubKey);
+
+ c.init(Cipher.ENCRYPT_MODE, key, random);
+
+ return c.doFinal(sessionInfo);
+ }
+ }
+ catch (IllegalBlockSizeException e)
+ {
+ throw new PGPException("illegal block size: " + e.getMessage(), e);
+ }
+ catch (BadPaddingException e)
+ {
+ throw new PGPException("bad padding: " + e.getMessage(), e);
+ }
+ catch (InvalidKeyException e)
+ {
+ throw new PGPException("key invalid: " + e.getMessage(), e);
+ }
+ catch (IOException e)
+ {
+ throw new PGPException("unable to encode MPI: " + e.getMessage(), e);
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/OperatorHelper.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/OperatorHelper.java
new file mode 100644
index 00000000..38e07cd0
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/OperatorHelper.java
@@ -0,0 +1,200 @@
+package org.spongycastle.openpgp.operator.jcajce;
+
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
+import java.security.Signature;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.jcajce.util.JcaJceHelper;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.operator.PGPDataDecryptor;
+import org.spongycastle.openpgp.operator.PGPDigestCalculator;
+
+class OperatorHelper
+{
+ private JcaJceHelper helper;
+
+ OperatorHelper(JcaJceHelper helper)
+ {
+ this.helper = helper;
+ }
+
+ MessageDigest createDigest(int algorithm)
+ throws GeneralSecurityException, PGPException
+ {
+ MessageDigest dig;
+
+ dig = helper.createDigest(PGPUtil.getDigestName(algorithm));
+
+ return dig;
+ }
+
+ KeyFactory createKeyFactory(String algorithm)
+ throws GeneralSecurityException, PGPException
+ {
+ return helper.createKeyFactory(algorithm);
+ }
+
+ PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
+ throws PGPException
+ {
+ try
+ {
+ SecretKey secretKey = new SecretKeySpec(key, PGPUtil.getSymmetricCipherName(encAlgorithm));
+
+ final Cipher c = createStreamCipher(encAlgorithm, withIntegrityPacket);
+
+ byte[] iv = new byte[c.getBlockSize()];
+
+ c.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
+
+ return new PGPDataDecryptor()
+ {
+ public InputStream getInputStream(InputStream in)
+ {
+ return new CipherInputStream(in, c);
+ }
+
+ public int getBlockSize()
+ {
+ return c.getBlockSize();
+ }
+
+ public PGPDigestCalculator getIntegrityCalculator()
+ {
+ return new SHA1PGPDigestCalculator();
+ }
+ };
+ }
+ catch (PGPException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new PGPException("Exception creating cipher", e);
+ }
+ }
+
+ Cipher createStreamCipher(int encAlgorithm, boolean withIntegrityPacket)
+ throws PGPException
+ {
+ String mode = (withIntegrityPacket)
+ ? "CFB"
+ : "OpenPGPCFB";
+
+ String cName = PGPUtil.getSymmetricCipherName(encAlgorithm)
+ + "/" + mode + "/NoPadding";
+
+ return createCipher(cName);
+ }
+
+ Cipher createCipher(String cipherName)
+ throws PGPException
+ {
+ try
+ {
+ return helper.createCipher(cipherName);
+ }
+ catch (GeneralSecurityException e)
+ {
+ throw new PGPException("cannot create cipher: " + e.getMessage(), e);
+ }
+ }
+
+ Cipher createPublicKeyCipher(int encAlgorithm)
+ throws PGPException
+ {
+ switch (encAlgorithm)
+ {
+ case PGPPublicKey.RSA_ENCRYPT:
+ case PGPPublicKey.RSA_GENERAL:
+ return createCipher("RSA/ECB/PKCS1Padding");
+ case PGPPublicKey.ELGAMAL_ENCRYPT:
+ case PGPPublicKey.ELGAMAL_GENERAL:
+ return createCipher("ElGamal/ECB/PKCS1Padding");
+ case PGPPublicKey.DSA:
+ throw new PGPException("Can't use DSA for encryption.");
+ case PGPPublicKey.ECDSA:
+ throw new PGPException("Can't use ECDSA for encryption.");
+ default:
+ throw new PGPException("unknown asymmetric algorithm: " + encAlgorithm);
+ }
+ }
+
+ Cipher createKeyWrapper(int encAlgorithm)
+ throws PGPException
+ {
+ try
+ {
+ switch (encAlgorithm)
+ {
+ case SymmetricKeyAlgorithmTags.AES_128:
+ case SymmetricKeyAlgorithmTags.AES_192:
+ case SymmetricKeyAlgorithmTags.AES_256:
+ return helper.createCipher("AESWrap");
+ case SymmetricKeyAlgorithmTags.CAMELLIA_128:
+ case SymmetricKeyAlgorithmTags.CAMELLIA_192:
+ case SymmetricKeyAlgorithmTags.CAMELLIA_256:
+ return helper.createCipher("CamelliaWrap");
+ default:
+ throw new PGPException("unknown wrap algorithm: " + encAlgorithm);
+ }
+ }
+ catch (GeneralSecurityException e)
+ {
+ throw new PGPException("cannot create cipher: " + e.getMessage(), e);
+ }
+ }
+
+ private Signature createSignature(String cipherName)
+ throws PGPException
+ {
+ try
+ {
+ return helper.createSignature(cipherName);
+ }
+ catch (GeneralSecurityException e)
+ {
+ throw new PGPException("cannot create signature: " + e.getMessage(), e);
+ }
+ }
+
+ public Signature createSignature(int keyAlgorithm, int hashAlgorithm)
+ throws PGPException
+ {
+ String encAlg;
+
+ switch (keyAlgorithm)
+ {
+ case PublicKeyAlgorithmTags.RSA_GENERAL:
+ case PublicKeyAlgorithmTags.RSA_SIGN:
+ encAlg = "RSA";
+ break;
+ case PublicKeyAlgorithmTags.DSA:
+ encAlg = "DSA";
+ break;
+ case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: // in some malformed cases.
+ case PublicKeyAlgorithmTags.ELGAMAL_GENERAL:
+ encAlg = "ElGamal";
+ break;
+ case PublicKeyAlgorithmTags.ECDSA:
+ encAlg = "ECDSA";
+ break;
+ default:
+ throw new PGPException("unknown algorithm tag in signature:" + keyAlgorithm);
+ }
+
+ return createSignature(PGPUtil.getDigestName(hashAlgorithm) + "with" + encAlg);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/PGPUtil.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/PGPUtil.java
new file mode 100644
index 00000000..5edbdb82
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/PGPUtil.java
@@ -0,0 +1,124 @@
+package org.spongycastle.openpgp.operator.jcajce;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.openpgp.PGPException;
+
+/**
+ * Basic utility class
+ */
+class PGPUtil
+{
+ static String getDigestName(
+ int hashAlgorithm)
+ throws PGPException
+ {
+ switch (hashAlgorithm)
+ {
+ case HashAlgorithmTags.SHA1:
+ return "SHA1";
+ case HashAlgorithmTags.MD2:
+ return "MD2";
+ case HashAlgorithmTags.MD5:
+ return "MD5";
+ case HashAlgorithmTags.RIPEMD160:
+ return "RIPEMD160";
+ case HashAlgorithmTags.SHA256:
+ return "SHA256";
+ case HashAlgorithmTags.SHA384:
+ return "SHA384";
+ case HashAlgorithmTags.SHA512:
+ return "SHA512";
+ case HashAlgorithmTags.SHA224:
+ return "SHA224";
+ case HashAlgorithmTags.TIGER_192:
+ return "TIGER";
+ default:
+ throw new PGPException("unknown hash algorithm tag in getDigestName: " + hashAlgorithm);
+ }
+ }
+
+ static String getSignatureName(
+ int keyAlgorithm,
+ int hashAlgorithm)
+ throws PGPException
+ {
+ String encAlg;
+
+ switch (keyAlgorithm)
+ {
+ case PublicKeyAlgorithmTags.RSA_GENERAL:
+ case PublicKeyAlgorithmTags.RSA_SIGN:
+ encAlg = "RSA";
+ break;
+ case PublicKeyAlgorithmTags.DSA:
+ encAlg = "DSA";
+ break;
+ case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: // in some malformed cases.
+ case PublicKeyAlgorithmTags.ELGAMAL_GENERAL:
+ encAlg = "ElGamal";
+ break;
+ default:
+ throw new PGPException("unknown algorithm tag in signature:" + keyAlgorithm);
+ }
+
+ return getDigestName(hashAlgorithm) + "with" + encAlg;
+ }
+
+ static String getSymmetricCipherName(
+ int algorithm)
+ {
+ switch (algorithm)
+ {
+ case SymmetricKeyAlgorithmTags.NULL:
+ return null;
+ case SymmetricKeyAlgorithmTags.TRIPLE_DES:
+ return "DESEDE";
+ case SymmetricKeyAlgorithmTags.IDEA:
+ return "IDEA";
+ case SymmetricKeyAlgorithmTags.CAST5:
+ return "CAST5";
+ case SymmetricKeyAlgorithmTags.BLOWFISH:
+ return "Blowfish";
+ case SymmetricKeyAlgorithmTags.SAFER:
+ return "SAFER";
+ case SymmetricKeyAlgorithmTags.DES:
+ return "DES";
+ case SymmetricKeyAlgorithmTags.AES_128:
+ return "AES";
+ case SymmetricKeyAlgorithmTags.AES_192:
+ return "AES";
+ case SymmetricKeyAlgorithmTags.AES_256:
+ return "AES";
+ case SymmetricKeyAlgorithmTags.CAMELLIA_128:
+ return "Camellia";
+ case SymmetricKeyAlgorithmTags.CAMELLIA_192:
+ return "Camellia";
+ case SymmetricKeyAlgorithmTags.CAMELLIA_256:
+ return "Camellia";
+ case SymmetricKeyAlgorithmTags.TWOFISH:
+ return "Twofish";
+ default:
+ throw new IllegalArgumentException("unknown symmetric algorithm: " + algorithm);
+ }
+ }
+
+ public static SecretKey makeSymmetricKey(
+ int algorithm,
+ byte[] keyBytes)
+ throws PGPException
+ {
+ String algName = getSymmetricCipherName(algorithm);
+
+ if (algName == null)
+ {
+ throw new PGPException("unknown symmetric algorithm: " + algorithm);
+ }
+
+ return new SecretKeySpec(keyBytes, algName);
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/SHA1PGPDigestCalculator.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/SHA1PGPDigestCalculator.java
new file mode 100644
index 00000000..424770e6
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/SHA1PGPDigestCalculator.java
@@ -0,0 +1,81 @@
+package org.spongycastle.openpgp.operator.jcajce;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.openpgp.operator.PGPDigestCalculator;
+
+class SHA1PGPDigestCalculator
+ implements PGPDigestCalculator
+{
+ private MessageDigest digest;
+
+ SHA1PGPDigestCalculator()
+ {
+ try
+ {
+ digest = MessageDigest.getInstance("SHA1");
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ throw new IllegalStateException("cannot find SHA-1: " + e.getMessage());
+ }
+ }
+
+ public int getAlgorithm()
+ {
+ return HashAlgorithmTags.SHA1;
+ }
+
+ public OutputStream getOutputStream()
+ {
+ return new DigestOutputStream(digest);
+ }
+
+ public byte[] getDigest()
+ {
+ return digest.digest();
+ }
+
+ public void reset()
+ {
+ digest.reset();
+ }
+
+ private class DigestOutputStream
+ extends OutputStream
+ {
+ private MessageDigest dig;
+
+ DigestOutputStream(MessageDigest dig)
+ {
+ this.dig = dig;
+ }
+
+ public void write(byte[] bytes, int off, int len)
+ throws IOException
+ {
+ dig.update(bytes, off, len);
+ }
+
+ public void write(byte[] bytes)
+ throws IOException
+ {
+ dig.update(bytes);
+ }
+
+ public void write(int b)
+ throws IOException
+ {
+ dig.update((byte)b);
+ }
+
+ byte[] getDigest()
+ {
+ return dig.digest();
+ }
+ }
+}
diff --git a/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/SignatureOutputStream.java b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/SignatureOutputStream.java
new file mode 100644
index 00000000..808de300
--- /dev/null
+++ b/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/SignatureOutputStream.java
@@ -0,0 +1,56 @@
+package org.spongycastle.openpgp.operator.jcajce;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.Signature;
+import java.security.SignatureException;
+
+class SignatureOutputStream
+ extends OutputStream
+{
+ private Signature sig;
+
+ SignatureOutputStream(Signature sig)
+ {
+ this.sig = sig;
+ }
+
+ public void write(byte[] bytes, int off, int len)
+ throws IOException
+ {
+ try
+ {
+ sig.update(bytes, off, len);
+ }
+ catch (SignatureException e)
+ {
+ throw new IOException("signature update caused exception: " + e.getMessage());
+ }
+ }
+
+ public void write(byte[] bytes)
+ throws IOException
+ {
+ try
+ {
+ sig.update(bytes);
+ }
+ catch (SignatureException e)
+ {
+ throw new IOException("signature update caused exception: " + e.getMessage());
+ }
+ }
+
+ public void write(int b)
+ throws IOException
+ {
+ try
+ {
+ sig.update((byte)b);
+ }
+ catch (SignatureException e)
+ {
+ throw new IOException("signature update caused exception: " + e.getMessage());
+ }
+ }
+}