diff --git a/Crypto/src/openpgp/IStreamGenerator.cs b/Crypto/src/openpgp/IStreamGenerator.cs
new file mode 100644
index 000000000..379213a66
--- /dev/null
+++ b/Crypto/src/openpgp/IStreamGenerator.cs
@@ -0,0 +1,7 @@
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ public interface IStreamGenerator
+ {
+ void Close();
+ }
+}
diff --git a/Crypto/src/openpgp/PGPKeyRing.cs b/Crypto/src/openpgp/PGPKeyRing.cs
new file mode 100644
index 000000000..6426f3f25
--- /dev/null
+++ b/Crypto/src/openpgp/PGPKeyRing.cs
@@ -0,0 +1,79 @@
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ public abstract class PgpKeyRing
+ : PgpObject
+ {
+ internal PgpKeyRing()
+ {
+ }
+
+ internal static TrustPacket ReadOptionalTrustPacket(
+ BcpgInputStream bcpgInput)
+ {
+ return (bcpgInput.NextPacketTag() == PacketTag.Trust)
+ ? (TrustPacket) bcpgInput.ReadPacket()
+ : null;
+ }
+
+ internal static IList ReadSignaturesAndTrust(
+ BcpgInputStream bcpgInput)
+ {
+ try
+ {
+ IList sigList = Platform.CreateArrayList();
+
+ while (bcpgInput.NextPacketTag() == PacketTag.Signature)
+ {
+ SignaturePacket signaturePacket = (SignaturePacket) bcpgInput.ReadPacket();
+ TrustPacket trustPacket = ReadOptionalTrustPacket(bcpgInput);
+
+ sigList.Add(new PgpSignature(signaturePacket, trustPacket));
+ }
+
+ return sigList;
+ }
+ catch (PgpException e)
+ {
+ throw new IOException("can't create signature object: " + e.Message, e);
+ }
+ }
+
+ internal static void ReadUserIDs(
+ BcpgInputStream bcpgInput,
+ out IList ids,
+ out IList idTrusts,
+ out IList idSigs)
+ {
+ ids = Platform.CreateArrayList();
+ idTrusts = Platform.CreateArrayList();
+ idSigs = Platform.CreateArrayList();
+
+ while (bcpgInput.NextPacketTag() == PacketTag.UserId
+ || bcpgInput.NextPacketTag() == PacketTag.UserAttribute)
+ {
+ Packet obj = bcpgInput.ReadPacket();
+ if (obj is UserIdPacket)
+ {
+ UserIdPacket id = (UserIdPacket)obj;
+ ids.Add(id.GetId());
+ }
+ else
+ {
+ UserAttributePacket user = (UserAttributePacket) obj;
+ ids.Add(new PgpUserAttributeSubpacketVector(user.GetSubpackets()));
+ }
+
+ idTrusts.Add(
+ ReadOptionalTrustPacket(bcpgInput));
+
+ idSigs.Add(
+ ReadSignaturesAndTrust(bcpgInput));
+ }
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PGPObject.cs b/Crypto/src/openpgp/PGPObject.cs
new file mode 100644
index 000000000..d38276cb6
--- /dev/null
+++ b/Crypto/src/openpgp/PGPObject.cs
@@ -0,0 +1,9 @@
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ public abstract class PgpObject
+ {
+ internal PgpObject()
+ {
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PGPUserAttributeSubpacketVectorGenerator.cs b/Crypto/src/openpgp/PGPUserAttributeSubpacketVectorGenerator.cs
new file mode 100644
index 000000000..9d56c8bc3
--- /dev/null
+++ b/Crypto/src/openpgp/PGPUserAttributeSubpacketVectorGenerator.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Bcpg.Attr;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ public class PgpUserAttributeSubpacketVectorGenerator
+ {
+ private IList list = Platform.CreateArrayList();
+
+ public virtual void SetImageAttribute(
+ ImageAttrib.Format imageType,
+ byte[] imageData)
+ {
+ if (imageData == null)
+ throw new ArgumentException("attempt to set null image", "imageData");
+
+ list.Add(new ImageAttrib(imageType, imageData));
+ }
+
+ public virtual PgpUserAttributeSubpacketVector Generate()
+ {
+ UserAttributeSubpacket[] a = new UserAttributeSubpacket[list.Count];
+ for (int i = 0; i < list.Count; ++i)
+ {
+ a[i] = (UserAttributeSubpacket)list[i];
+ }
+ return new PgpUserAttributeSubpacketVector(a);
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpCompressedData.cs b/Crypto/src/openpgp/PgpCompressedData.cs
new file mode 100644
index 000000000..e64a17c9c
--- /dev/null
+++ b/Crypto/src/openpgp/PgpCompressedData.cs
@@ -0,0 +1,50 @@
+using System.IO;
+
+using Org.BouncyCastle.Apache.Bzip2;
+using Org.BouncyCastle.Utilities.Zlib;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>Compressed data objects</remarks>
+ public class PgpCompressedData
+ : PgpObject
+ {
+ private readonly CompressedDataPacket data;
+
+ public PgpCompressedData(
+ BcpgInputStream bcpgInput)
+ {
+ data = (CompressedDataPacket) bcpgInput.ReadPacket();
+ }
+
+ /// <summary>The algorithm used for compression</summary>
+ public CompressionAlgorithmTag Algorithm
+ {
+ get { return data.Algorithm; }
+ }
+
+ /// <summary>Get the raw input stream contained in the object.</summary>
+ public Stream GetInputStream()
+ {
+ return data.GetInputStream();
+ }
+
+ /// <summary>Return an uncompressed input stream which allows reading of the compressed data.</summary>
+ public Stream GetDataStream()
+ {
+ switch (Algorithm)
+ {
+ case CompressionAlgorithmTag.Uncompressed:
+ return GetInputStream();
+ case CompressionAlgorithmTag.Zip:
+ return new ZInputStream(GetInputStream(), true);
+ case CompressionAlgorithmTag.ZLib:
+ return new ZInputStream(GetInputStream());
+ case CompressionAlgorithmTag.BZip2:
+ return new CBZip2InputStream(GetInputStream());
+ default:
+ throw new PgpException("can't recognise compression algorithm: " + Algorithm);
+ }
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpCompressedDataGenerator.cs b/Crypto/src/openpgp/PgpCompressedDataGenerator.cs
new file mode 100644
index 000000000..c758ecb05
--- /dev/null
+++ b/Crypto/src/openpgp/PgpCompressedDataGenerator.cs
@@ -0,0 +1,203 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Apache.Bzip2;
+using Org.BouncyCastle.Utilities.Zlib;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>Class for producing compressed data packets.</remarks>
+ public class PgpCompressedDataGenerator
+ : IStreamGenerator
+ {
+ private readonly CompressionAlgorithmTag algorithm;
+ private readonly int compression;
+
+ private Stream dOut;
+ private BcpgOutputStream pkOut;
+
+ public PgpCompressedDataGenerator(
+ CompressionAlgorithmTag algorithm)
+ : this(algorithm, JZlib.Z_DEFAULT_COMPRESSION)
+ {
+ }
+
+ public PgpCompressedDataGenerator(
+ CompressionAlgorithmTag algorithm,
+ int compression)
+ {
+ switch (algorithm)
+ {
+ case CompressionAlgorithmTag.Uncompressed:
+ case CompressionAlgorithmTag.Zip:
+ case CompressionAlgorithmTag.ZLib:
+ case CompressionAlgorithmTag.BZip2:
+ break;
+ default:
+ throw new ArgumentException("unknown compression algorithm", "algorithm");
+ }
+
+ if (compression != JZlib.Z_DEFAULT_COMPRESSION)
+ {
+ if ((compression < JZlib.Z_NO_COMPRESSION) || (compression > JZlib.Z_BEST_COMPRESSION))
+ {
+ throw new ArgumentException("unknown compression level: " + compression);
+ }
+ }
+
+ this.algorithm = algorithm;
+ this.compression = compression;
+ }
+
+ /// <summary>
+ /// <p>
+ /// Return an output stream which will save the data being written to
+ /// the compressed object.
+ /// </p>
+ /// <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 Stream parameter <c>outStr</c>.
+ /// </p>
+ /// </summary>
+ /// <param name="outStr">Stream to be used for output.</param>
+ /// <returns>A Stream for output of the compressed data.</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="InvalidOperationException"></exception>
+ /// <exception cref="IOException"></exception>
+ public Stream Open(
+ Stream outStr)
+ {
+ if (dOut != null)
+ throw new InvalidOperationException("generator already in open state");
+ if (outStr == null)
+ throw new ArgumentNullException("outStr");
+
+ this.pkOut = new BcpgOutputStream(outStr, PacketTag.CompressedData);
+
+ doOpen();
+
+ return new WrappedGeneratorStream(this, dOut);
+ }
+
+ /// <summary>
+ /// <p>
+ /// Return an output stream which will compress the data as it is written to it.
+ /// The stream will be written out in chunks according to the size of the passed in buffer.
+ /// </p>
+ /// <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 Stream parameter <c>outStr</c>.
+ /// </p>
+ /// <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>
+ /// </summary>
+ /// <param name="outStr">Stream to be used for output.</param>
+ /// <param name="buffer">The buffer to use.</param>
+ /// <returns>A Stream for output of the compressed data.</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="InvalidOperationException"></exception>
+ /// <exception cref="IOException"></exception>
+ /// <exception cref="PgpException"></exception>
+ public Stream Open(
+ Stream outStr,
+ byte[] buffer)
+ {
+ if (dOut != null)
+ throw new InvalidOperationException("generator already in open state");
+ if (outStr == null)
+ throw new ArgumentNullException("outStr");
+ if (buffer == null)
+ throw new ArgumentNullException("buffer");
+
+ this.pkOut = new BcpgOutputStream(outStr, PacketTag.CompressedData, buffer);
+
+ doOpen();
+
+ return new WrappedGeneratorStream(this, dOut);
+ }
+
+ private void doOpen()
+ {
+ pkOut.WriteByte((byte) algorithm);
+
+ switch (algorithm)
+ {
+ case CompressionAlgorithmTag.Uncompressed:
+ dOut = pkOut;
+ break;
+ case CompressionAlgorithmTag.Zip:
+ dOut = new SafeZOutputStream(pkOut, compression, true);
+ break;
+ case CompressionAlgorithmTag.ZLib:
+ dOut = new SafeZOutputStream(pkOut, compression, false);
+ break;
+ case CompressionAlgorithmTag.BZip2:
+ dOut = new SafeCBZip2OutputStream(pkOut);
+ break;
+ default:
+ // Constructor should guard against this possibility
+ throw new InvalidOperationException();
+ }
+ }
+
+ /// <summary>Close the compressed object.</summary>summary>
+ public void Close()
+ {
+ if (dOut != null)
+ {
+ if (dOut != pkOut)
+ {
+ dOut.Dispose();
+ dOut.Flush();
+ }
+
+ dOut = null;
+
+ pkOut.Finish();
+ pkOut.Flush();
+ pkOut = null;
+ }
+ }
+
+ private class SafeCBZip2OutputStream : CBZip2OutputStream
+ {
+ public SafeCBZip2OutputStream(Stream output)
+ : base(output)
+ {
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ Finish();
+ }
+ }
+ }
+
+ private class SafeZOutputStream : ZOutputStream
+ {
+ public SafeZOutputStream(Stream output, int level, bool nowrap)
+ : base(output, level, nowrap)
+ {
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ Finish();
+ End();
+ }
+ }
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpDataValidationException.cs b/Crypto/src/openpgp/PgpDataValidationException.cs
new file mode 100644
index 000000000..74674da59
--- /dev/null
+++ b/Crypto/src/openpgp/PgpDataValidationException.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>
+ /// Thrown if the IV at the start of a data stream indicates the wrong key is being used.
+ /// </remarks>
+ public class PgpDataValidationException
+ : PgpException
+ {
+ public PgpDataValidationException() : base() {}
+ public PgpDataValidationException(string message) : base(message) {}
+ public PgpDataValidationException(string message, Exception exception) : base(message, exception) {}
+ }
+}
diff --git a/Crypto/src/openpgp/PgpEncryptedData.cs b/Crypto/src/openpgp/PgpEncryptedData.cs
new file mode 100644
index 000000000..0d237b56c
--- /dev/null
+++ b/Crypto/src/openpgp/PgpEncryptedData.cs
@@ -0,0 +1,151 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.IO;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ public abstract class PgpEncryptedData
+ {
+ internal class TruncatedStream
+ : BaseInputStream
+ {
+ private const int LookAheadSize = 22;
+ private const int LookAheadBufSize = 512;
+ private const int LookAheadBufLimit = LookAheadBufSize - LookAheadSize;
+
+ private readonly Stream inStr;
+ private readonly byte[] lookAhead = new byte[LookAheadBufSize];
+ private int bufStart, bufEnd;
+
+ internal TruncatedStream(
+ Stream inStr)
+ {
+ int numRead = Streams.ReadFully(inStr, lookAhead, 0, lookAhead.Length);
+
+ if (numRead < LookAheadSize)
+ throw new EndOfStreamException();
+
+ this.inStr = inStr;
+ this.bufStart = 0;
+ this.bufEnd = numRead - LookAheadSize;
+ }
+
+ private int FillBuffer()
+ {
+ if (bufEnd < LookAheadBufLimit)
+ return 0;
+
+ Debug.Assert(bufStart == LookAheadBufLimit);
+ Debug.Assert(bufEnd == LookAheadBufLimit);
+
+ Array.Copy(lookAhead, LookAheadBufLimit, lookAhead, 0, LookAheadSize);
+ bufEnd = Streams.ReadFully(inStr, lookAhead, LookAheadSize, LookAheadBufLimit);
+ bufStart = 0;
+ return bufEnd;
+ }
+
+ public override int ReadByte()
+ {
+ if (bufStart < bufEnd)
+ return lookAhead[bufStart++];
+
+ if (FillBuffer() < 1)
+ return -1;
+
+ return lookAhead[bufStart++];
+ }
+
+ public override int Read(byte[] buf, int off, int len)
+ {
+ int avail = bufEnd - bufStart;
+
+ int pos = off;
+ while (len > avail)
+ {
+ Array.Copy(lookAhead, bufStart, buf, pos, avail);
+
+ bufStart += avail;
+ pos += avail;
+ len -= avail;
+
+ if ((avail = FillBuffer()) < 1)
+ return pos - off;
+ }
+
+ Array.Copy(lookAhead, bufStart, buf, pos, len);
+ bufStart += len;
+
+ return pos + len - off;;
+ }
+
+ internal byte[] GetLookAhead()
+ {
+ byte[] temp = new byte[LookAheadSize];
+ Array.Copy(lookAhead, bufStart, temp, 0, LookAheadSize);
+ return temp;
+ }
+ }
+
+ internal InputStreamPacket encData;
+ internal Stream encStream;
+ internal TruncatedStream truncStream;
+
+ internal PgpEncryptedData(
+ InputStreamPacket encData)
+ {
+ this.encData = encData;
+ }
+
+ /// <summary>Return the raw input stream for the data stream.</summary>
+ public virtual Stream GetInputStream()
+ {
+ return encData.GetInputStream();
+ }
+
+ /// <summary>Return true if the message is integrity protected.</summary>
+ /// <returns>True, if there is a modification detection code namespace associated
+ /// with this stream.</returns>
+ public bool IsIntegrityProtected()
+ {
+ return encData is SymmetricEncIntegrityPacket;
+ }
+
+ /// <summary>Note: This can only be called after the message has been read.</summary>
+ /// <returns>True, if the message verifies, false otherwise</returns>
+ public bool Verify()
+ {
+ if (!IsIntegrityProtected())
+ throw new PgpException("data not integrity protected.");
+
+ DigestStream dIn = (DigestStream) encStream;
+
+ //
+ // make sure we are at the end.
+ //
+ while (encStream.ReadByte() >= 0)
+ {
+ // do nothing
+ }
+
+ //
+ // process the MDC packet
+ //
+ byte[] lookAhead = truncStream.GetLookAhead();
+
+ IDigest hash = dIn.ReadDigest();
+ hash.BlockUpdate(lookAhead, 0, 2);
+ byte[] digest = DigestUtilities.DoFinal(hash);
+
+ byte[] streamDigest = new byte[digest.Length];
+ Array.Copy(lookAhead, 2, streamDigest, 0, streamDigest.Length);
+
+ return Arrays.ConstantTimeAreEqual(digest, streamDigest);
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpEncryptedDataGenerator.cs b/Crypto/src/openpgp/PgpEncryptedDataGenerator.cs
new file mode 100644
index 000000000..f46f99d37
--- /dev/null
+++ b/Crypto/src/openpgp/PgpEncryptedDataGenerator.cs
@@ -0,0 +1,506 @@
+using System;
+using System.Collections;
+using System.Diagnostics;
+using System.IO;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.IO;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>Generator for encrypted objects.</remarks>
+ public class PgpEncryptedDataGenerator
+ : IStreamGenerator
+ {
+ private BcpgOutputStream pOut;
+ private CipherStream cOut;
+ private IBufferedCipher c;
+ private bool withIntegrityPacket;
+ private bool oldFormat;
+ private DigestStream digestOut;
+
+ private abstract class EncMethod
+ : ContainedPacket
+ {
+ protected byte[] sessionInfo;
+ protected SymmetricKeyAlgorithmTag encAlgorithm;
+ protected KeyParameter key;
+
+ public abstract void AddSessionInfo(byte[] si, SecureRandom random);
+ }
+
+ private class PbeMethod
+ : EncMethod
+ {
+ private S2k s2k;
+
+ internal PbeMethod(
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ S2k s2k,
+ KeyParameter key)
+ {
+ this.encAlgorithm = encAlgorithm;
+ this.s2k = s2k;
+ this.key = key;
+ }
+
+ public KeyParameter GetKey()
+ {
+ return key;
+ }
+
+ public override void AddSessionInfo(
+ byte[] si,
+ SecureRandom random)
+ {
+ string cName = PgpUtilities.GetSymmetricCipherName(encAlgorithm);
+ IBufferedCipher c = CipherUtilities.GetCipher(cName + "/CFB/NoPadding");
+
+ byte[] iv = new byte[c.GetBlockSize()];
+ c.Init(true, new ParametersWithRandom(new ParametersWithIV(key, iv), random));
+
+ this.sessionInfo = c.DoFinal(si, 0, si.Length - 2);
+ }
+
+ public override void Encode(BcpgOutputStream pOut)
+ {
+ SymmetricKeyEncSessionPacket pk = new SymmetricKeyEncSessionPacket(
+ encAlgorithm, s2k, sessionInfo);
+
+ pOut.WritePacket(pk);
+ }
+ }
+
+ private class PubMethod
+ : EncMethod
+ {
+ internal PgpPublicKey pubKey;
+ internal BigInteger[] data;
+
+ internal PubMethod(
+ PgpPublicKey pubKey)
+ {
+ this.pubKey = pubKey;
+ }
+
+ public override void AddSessionInfo(
+ byte[] si,
+ SecureRandom random)
+ {
+ IBufferedCipher c;
+
+ switch (pubKey.Algorithm)
+ {
+ case PublicKeyAlgorithmTag.RsaEncrypt:
+ case PublicKeyAlgorithmTag.RsaGeneral:
+ c = CipherUtilities.GetCipher("RSA//PKCS1Padding");
+ break;
+ case PublicKeyAlgorithmTag.ElGamalEncrypt:
+ case PublicKeyAlgorithmTag.ElGamalGeneral:
+ c = CipherUtilities.GetCipher("ElGamal/ECB/PKCS1Padding");
+ break;
+ case PublicKeyAlgorithmTag.Dsa:
+ throw new PgpException("Can't use DSA for encryption.");
+ case PublicKeyAlgorithmTag.ECDsa:
+ throw new PgpException("Can't use ECDSA for encryption.");
+ default:
+ throw new PgpException("unknown asymmetric algorithm: " + pubKey.Algorithm);
+ }
+
+ AsymmetricKeyParameter akp = pubKey.GetKey();
+
+ c.Init(true, new ParametersWithRandom(akp, random));
+
+ byte[] encKey = c.DoFinal(si);
+
+ switch (pubKey.Algorithm)
+ {
+ case PublicKeyAlgorithmTag.RsaEncrypt:
+ case PublicKeyAlgorithmTag.RsaGeneral:
+ data = new BigInteger[]{ new BigInteger(1, encKey) };
+ break;
+ case PublicKeyAlgorithmTag.ElGamalEncrypt:
+ case PublicKeyAlgorithmTag.ElGamalGeneral:
+ int halfLength = encKey.Length / 2;
+ data = new BigInteger[]
+ {
+ new BigInteger(1, encKey, 0, halfLength),
+ new BigInteger(1, encKey, halfLength, halfLength)
+ };
+ break;
+ default:
+ throw new PgpException("unknown asymmetric algorithm: " + encAlgorithm);
+ }
+ }
+
+ public override void Encode(BcpgOutputStream pOut)
+ {
+ PublicKeyEncSessionPacket pk = new PublicKeyEncSessionPacket(
+ pubKey.KeyId, pubKey.Algorithm, data);
+
+ pOut.WritePacket(pk);
+ }
+ }
+
+ private readonly IList methods = Platform.CreateArrayList();
+ private readonly SymmetricKeyAlgorithmTag defAlgorithm;
+ private readonly SecureRandom rand;
+
+ public PgpEncryptedDataGenerator(
+ SymmetricKeyAlgorithmTag encAlgorithm)
+ {
+ this.defAlgorithm = encAlgorithm;
+ this.rand = new SecureRandom();
+ }
+
+ public PgpEncryptedDataGenerator(
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ bool withIntegrityPacket)
+ {
+ this.defAlgorithm = encAlgorithm;
+ this.withIntegrityPacket = withIntegrityPacket;
+ this.rand = new SecureRandom();
+ }
+
+ /// <summary>Existing SecureRandom constructor.</summary>
+ /// <param name="encAlgorithm">The symmetric algorithm to use.</param>
+ /// <param name="rand">Source of randomness.</param>
+ public PgpEncryptedDataGenerator(
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ SecureRandom rand)
+ {
+ this.defAlgorithm = encAlgorithm;
+ this.rand = rand;
+ }
+
+ /// <summary>Creates a cipher stream which will have an integrity packet associated with it.</summary>
+ public PgpEncryptedDataGenerator(
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ bool withIntegrityPacket,
+ SecureRandom rand)
+ {
+ this.defAlgorithm = encAlgorithm;
+ this.rand = rand;
+ this.withIntegrityPacket = withIntegrityPacket;
+ }
+
+ /// <summary>Base constructor.</summary>
+ /// <param name="encAlgorithm">The symmetric algorithm to use.</param>
+ /// <param name="rand">Source of randomness.</param>
+ /// <param name="oldFormat">PGP 2.6.x compatibility required.</param>
+ public PgpEncryptedDataGenerator(
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ SecureRandom rand,
+ bool oldFormat)
+ {
+ this.defAlgorithm = encAlgorithm;
+ this.rand = rand;
+ this.oldFormat = oldFormat;
+ }
+
+ /// <summary>
+ /// Add a PBE encryption method to the encrypted object using the default algorithm (S2K_SHA1).
+ /// </summary>
+ public void AddMethod(
+ char[] passPhrase)
+ {
+ AddMethod(passPhrase, HashAlgorithmTag.Sha1);
+ }
+
+ /// <summary>Add a PBE encryption method to the encrypted object.</summary>
+ public void AddMethod(
+ char[] passPhrase,
+ HashAlgorithmTag s2kDigest)
+ {
+ byte[] iv = new byte[8];
+ rand.NextBytes(iv);
+
+ S2k s2k = new S2k(s2kDigest, iv, 0x60);
+
+ methods.Add(new PbeMethod(defAlgorithm, s2k, PgpUtilities.MakeKeyFromPassPhrase(defAlgorithm, s2k, passPhrase)));
+ }
+
+ /// <summary>Add a public key encrypted session key to the encrypted object.</summary>
+ public void AddMethod(
+ PgpPublicKey key)
+ {
+ if (!key.IsEncryptionKey)
+ {
+ throw new ArgumentException("passed in key not an encryption key!");
+ }
+
+ methods.Add(new PubMethod(key));
+ }
+
+ private void AddCheckSum(
+ byte[] sessionInfo)
+ {
+ Debug.Assert(sessionInfo != null);
+ Debug.Assert(sessionInfo.Length >= 3);
+
+ int check = 0;
+
+ for (int i = 1; i < sessionInfo.Length - 2; i++)
+ {
+ check += sessionInfo[i];
+ }
+
+ sessionInfo[sessionInfo.Length - 2] = (byte)(check >> 8);
+ sessionInfo[sessionInfo.Length - 1] = (byte)(check);
+ }
+
+ private byte[] CreateSessionInfo(
+ SymmetricKeyAlgorithmTag algorithm,
+ KeyParameter key)
+ {
+ byte[] keyBytes = key.GetKey();
+ byte[] sessionInfo = new byte[keyBytes.Length + 3];
+ sessionInfo[0] = (byte) algorithm;
+ keyBytes.CopyTo(sessionInfo, 1);
+ AddCheckSum(sessionInfo);
+ return sessionInfo;
+ }
+
+ /// <summary>
+ /// <p>
+ /// If buffer is non null stream assumed to be partial, otherwise the length will be used
+ /// to output a fixed length packet.
+ /// </p>
+ /// <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 Stream parameter <c>outStr</c>.
+ /// </p>
+ /// </summary>
+ private Stream Open(
+ Stream outStr,
+ long length,
+ byte[] buffer)
+ {
+ if (cOut != null)
+ throw new InvalidOperationException("generator already in open state");
+ if (methods.Count == 0)
+ throw new InvalidOperationException("No encryption methods specified");
+ if (outStr == null)
+ throw new ArgumentNullException("outStr");
+
+ pOut = new BcpgOutputStream(outStr);
+
+ KeyParameter key;
+
+ if (methods.Count == 1)
+ {
+ if (methods[0] is PbeMethod)
+ {
+ PbeMethod m = (PbeMethod)methods[0];
+
+ key = m.GetKey();
+ }
+ else
+ {
+ key = PgpUtilities.MakeRandomKey(defAlgorithm, rand);
+
+ byte[] sessionInfo = CreateSessionInfo(defAlgorithm, key);
+ PubMethod m = (PubMethod)methods[0];
+
+ try
+ {
+ m.AddSessionInfo(sessionInfo, rand);
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("exception encrypting session key", e);
+ }
+ }
+
+ pOut.WritePacket((ContainedPacket)methods[0]);
+ }
+ else // multiple methods
+ {
+ key = PgpUtilities.MakeRandomKey(defAlgorithm, rand);
+ byte[] sessionInfo = CreateSessionInfo(defAlgorithm, key);
+
+ for (int i = 0; i != methods.Count; i++)
+ {
+ EncMethod m = (EncMethod)methods[i];
+
+ try
+ {
+ m.AddSessionInfo(sessionInfo, rand);
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("exception encrypting session key", e);
+ }
+
+ pOut.WritePacket(m);
+ }
+ }
+
+ string cName = PgpUtilities.GetSymmetricCipherName(defAlgorithm);
+ if (cName == null)
+ {
+ throw new PgpException("null cipher specified");
+ }
+
+ try
+ {
+ if (withIntegrityPacket)
+ {
+ cName += "/CFB/NoPadding";
+ }
+ else
+ {
+ cName += "/OpenPGPCFB/NoPadding";
+ }
+
+ c = CipherUtilities.GetCipher(cName);
+
+ // TODO Confirm the IV should be all zero bytes (not inLineIv - see below)
+ byte[] iv = new byte[c.GetBlockSize()];
+ c.Init(true, new ParametersWithRandom(new ParametersWithIV(key, iv), rand));
+
+ if (buffer == null)
+ {
+ //
+ // we have to Add block size + 2 for the Generated IV and + 1 + 22 if integrity protected
+ //
+ if (withIntegrityPacket)
+ {
+ pOut = new BcpgOutputStream(outStr, PacketTag.SymmetricEncryptedIntegrityProtected, length + c.GetBlockSize() + 2 + 1 + 22);
+ pOut.WriteByte(1); // version number
+ }
+ else
+ {
+ pOut = new BcpgOutputStream(outStr, PacketTag.SymmetricKeyEncrypted, length + c.GetBlockSize() + 2, oldFormat);
+ }
+ }
+ else
+ {
+ if (withIntegrityPacket)
+ {
+ pOut = new BcpgOutputStream(outStr, PacketTag.SymmetricEncryptedIntegrityProtected, buffer);
+ pOut.WriteByte(1); // version number
+ }
+ else
+ {
+ pOut = new BcpgOutputStream(outStr, PacketTag.SymmetricKeyEncrypted, buffer);
+ }
+ }
+
+ int blockSize = c.GetBlockSize();
+ byte[] inLineIv = new byte[blockSize + 2];
+ rand.NextBytes(inLineIv, 0, blockSize);
+ Array.Copy(inLineIv, inLineIv.Length - 4, inLineIv, inLineIv.Length - 2, 2);
+
+ Stream myOut = cOut = new CipherStream(pOut, null, c);
+
+ if (withIntegrityPacket)
+ {
+ string digestName = PgpUtilities.GetDigestName(HashAlgorithmTag.Sha1);
+ IDigest digest = DigestUtilities.GetDigest(digestName);
+ myOut = digestOut = new DigestStream(myOut, null, digest);
+ }
+
+ myOut.Write(inLineIv, 0, inLineIv.Length);
+
+ return new WrappedGeneratorStream(this, myOut);
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("Exception creating cipher", e);
+ }
+ }
+
+ /// <summary>
+ /// <p>
+ /// Return an output stream which will encrypt the data as it is written to it.
+ /// </p>
+ /// <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 Stream parameter <c>outStr</c>.
+ /// </p>
+ /// </summary>
+ public Stream Open(
+ Stream outStr,
+ long length)
+ {
+ return Open(outStr, length, null);
+ }
+
+ /// <summary>
+ /// <p>
+ /// Return an output stream which will encrypt the data as it is written to it.
+ /// The stream will be written out in chunks according to the size of the passed in buffer.
+ /// </p>
+ /// <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 Stream parameter <c>outStr</c>.
+ /// </p>
+ /// <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>
+ /// </summary>
+ public Stream Open(
+ Stream outStr,
+ byte[] buffer)
+ {
+ return Open(outStr, 0, buffer);
+ }
+
+ /// <summary>
+ /// <p>
+ /// Close off the encrypted object - this is equivalent to calling Close() on the stream
+ /// returned by the Open() method.
+ /// </p>
+ /// <p>
+ /// <b>Note</b>: This does not close the underlying output stream, only the stream on top of
+ /// it created by the Open() method.
+ /// </p>
+ /// </summary>
+ public void Close()
+ {
+ if (cOut != null)
+ {
+ // TODO Should this all be under the try/catch block?
+ if (digestOut != null)
+ {
+ //
+ // hand code a mod detection packet
+ //
+ BcpgOutputStream bOut = new BcpgOutputStream(
+ digestOut, PacketTag.ModificationDetectionCode, 20);
+
+ bOut.Flush();
+ digestOut.Flush();
+
+ // TODO
+ byte[] dig = DigestUtilities.DoFinal(digestOut.WriteDigest());
+ cOut.Write(dig, 0, dig.Length);
+ }
+
+ cOut.Flush();
+
+ try
+ {
+ pOut.Write(c.DoFinal());
+ pOut.Finish();
+ }
+ catch (Exception e)
+ {
+ throw new IOException(e.Message, e);
+ }
+
+ cOut = null;
+ pOut = null;
+ }
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpEncryptedDataList.cs b/Crypto/src/openpgp/PgpEncryptedDataList.cs
new file mode 100644
index 000000000..8dded7c05
--- /dev/null
+++ b/Crypto/src/openpgp/PgpEncryptedDataList.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Collections;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>A holder for a list of PGP encryption method packets.</remarks>
+ public class PgpEncryptedDataList
+ : PgpObject
+ {
+ private IList list = Platform.CreateArrayList();
+ private InputStreamPacket data;
+
+ public PgpEncryptedDataList(
+ BcpgInputStream bcpgInput)
+ {
+ while (bcpgInput.NextPacketTag() == PacketTag.PublicKeyEncryptedSession
+ || bcpgInput.NextPacketTag() == PacketTag.SymmetricKeyEncryptedSessionKey)
+ {
+ list.Add(bcpgInput.ReadPacket());
+ }
+
+ data = (InputStreamPacket)bcpgInput.ReadPacket();
+
+ for (int i = 0; i != list.Count; i++)
+ {
+ if (list[i] is SymmetricKeyEncSessionPacket)
+ {
+ list[i] = new PgpPbeEncryptedData((SymmetricKeyEncSessionPacket) list[i], data);
+ }
+ else
+ {
+ list[i] = new PgpPublicKeyEncryptedData((PublicKeyEncSessionPacket) list[i], data);
+ }
+ }
+ }
+
+ public PgpEncryptedData this[int index]
+ {
+ get { return (PgpEncryptedData) list[index]; }
+ }
+
+ [Obsolete("Use 'object[index]' syntax instead")]
+ public object Get(int index)
+ {
+ return this[index];
+ }
+
+ [Obsolete("Use 'Count' property instead")]
+ public int Size
+ {
+ get { return list.Count; }
+ }
+
+ public int Count
+ {
+ get { return list.Count; }
+ }
+
+ public bool IsEmpty
+ {
+ get { return list.Count == 0; }
+ }
+
+ public IEnumerable GetEncryptedDataObjects()
+ {
+ return new EnumerableProxy(list);
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpException.cs b/Crypto/src/openpgp/PgpException.cs
new file mode 100644
index 000000000..3048116fa
--- /dev/null
+++ b/Crypto/src/openpgp/PgpException.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>Generic exception class for PGP encoding/decoding problems.</remarks>
+ public class PgpException
+ : Exception
+ {
+ public PgpException() : base() {}
+ public PgpException(string message) : base(message) {}
+ public PgpException(string message, Exception exception) : base(message, exception) {}
+
+ [Obsolete("Use InnerException property")]
+ public Exception UnderlyingException
+ {
+ get { return InnerException; }
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpExperimental.cs b/Crypto/src/openpgp/PgpExperimental.cs
new file mode 100644
index 000000000..8518335a1
--- /dev/null
+++ b/Crypto/src/openpgp/PgpExperimental.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ public class PgpExperimental
+ : PgpObject
+ {
+ private readonly ExperimentalPacket p;
+
+ public PgpExperimental(
+ BcpgInputStream bcpgIn)
+ {
+ p = (ExperimentalPacket) bcpgIn.ReadPacket();
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpKeyFlags.cs b/Crypto/src/openpgp/PgpKeyFlags.cs
new file mode 100644
index 000000000..ea1800606
--- /dev/null
+++ b/Crypto/src/openpgp/PgpKeyFlags.cs
@@ -0,0 +1,13 @@
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>Key flag values for the KeyFlags subpacket.</remarks>
+ public abstract class PgpKeyFlags
+ {
+ public const int CanCertify = 0x01; // This key may be used to certify other keys.
+ public const int CanSign = 0x02; // This key may be used to sign data.
+ public const int CanEncryptCommunications = 0x04; // This key may be used to encrypt communications.
+ public const int CanEncryptStorage = 0x08; // This key may be used to encrypt storage.
+ public const int MaybeSplit = 0x10; // The private component of this key may have been split by a secret-sharing mechanism.
+ public const int MaybeShared = 0x80; // The private component of this key may be in the possession of more than one person.
+ }
+}
diff --git a/Crypto/src/openpgp/PgpKeyPair.cs b/Crypto/src/openpgp/PgpKeyPair.cs
new file mode 100644
index 000000000..6efb03a42
--- /dev/null
+++ b/Crypto/src/openpgp/PgpKeyPair.cs
@@ -0,0 +1,67 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>
+ /// General class to handle JCA key pairs and convert them into OpenPGP ones.
+ /// <p>
+ /// A word for the unwary, the KeyId for an 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 KeyIs will not be the
+ /// same as for previous generations of the key, so ideally you only want to do
+ /// this once.
+ /// </p>
+ /// </remarks>
+ public class PgpKeyPair
+ {
+ private readonly PgpPublicKey pub;
+ private readonly PgpPrivateKey priv;
+
+ public PgpKeyPair(
+ PublicKeyAlgorithmTag algorithm,
+ AsymmetricCipherKeyPair keyPair,
+ DateTime time)
+ : this(algorithm, keyPair.Public, keyPair.Private, time)
+ {
+ }
+
+ public PgpKeyPair(
+ PublicKeyAlgorithmTag algorithm,
+ AsymmetricKeyParameter pubKey,
+ AsymmetricKeyParameter privKey,
+ DateTime time)
+ {
+ this.pub = new PgpPublicKey(algorithm, pubKey, time);
+ this.priv = new PgpPrivateKey(privKey, pub.KeyId);
+ }
+
+ /// <summary>Create a key pair from a PgpPrivateKey and a PgpPublicKey.</summary>
+ /// <param name="pub">The public key.</param>
+ /// <param name="priv">The private key.</param>
+ public PgpKeyPair(
+ PgpPublicKey pub,
+ PgpPrivateKey priv)
+ {
+ this.pub = pub;
+ this.priv = priv;
+ }
+
+ /// <summary>The keyId associated with this key pair.</summary>
+ public long KeyId
+ {
+ get { return pub.KeyId; }
+ }
+
+ public PgpPublicKey PublicKey
+ {
+ get { return pub; }
+ }
+
+ public PgpPrivateKey PrivateKey
+ {
+ get { return priv; }
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpKeyRingGenerator.cs b/Crypto/src/openpgp/PgpKeyRingGenerator.cs
new file mode 100644
index 000000000..e85fc2eef
--- /dev/null
+++ b/Crypto/src/openpgp/PgpKeyRingGenerator.cs
@@ -0,0 +1,167 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>
+ /// Generator for a PGP master and subkey ring.
+ /// This class will generate both the secret and public key rings
+ /// </remarks>
+ public class PgpKeyRingGenerator
+ {
+ private IList keys = Platform.CreateArrayList();
+ private string id;
+ private SymmetricKeyAlgorithmTag encAlgorithm;
+ private int certificationLevel;
+ private char[] passPhrase;
+ private bool useSha1;
+ private PgpKeyPair masterKey;
+ private PgpSignatureSubpacketVector hashedPacketVector;
+ private PgpSignatureSubpacketVector unhashedPacketVector;
+ private SecureRandom rand;
+
+ /// <summary>
+ /// Create a new key ring generator using old style checksumming. It is recommended to use
+ /// SHA1 checksumming where possible.
+ /// </summary>
+ /// <param name="certificationLevel">The certification level for keys on this ring.</param>
+ /// <param name="masterKey">The master key pair.</param>
+ /// <param name="id">The id to be associated with the ring.</param>
+ /// <param name="encAlgorithm">The algorithm to be used to protect secret keys.</param>
+ /// <param name="passPhrase">The passPhrase to be used to protect secret keys.</param>
+ /// <param name="hashedPackets">Packets to be included in the certification hash.</param>
+ /// <param name="unhashedPackets">Packets to be attached unhashed to the certification.</param>
+ /// <param name="rand">input secured random.</param>
+ public PgpKeyRingGenerator(
+ int certificationLevel,
+ PgpKeyPair masterKey,
+ string id,
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ char[] passPhrase,
+ PgpSignatureSubpacketVector hashedPackets,
+ PgpSignatureSubpacketVector unhashedPackets,
+ SecureRandom rand)
+ : this(certificationLevel, masterKey, id, encAlgorithm, passPhrase, false, hashedPackets, unhashedPackets, rand)
+ {
+ }
+
+ /// <summary>
+ /// Create a new key ring generator.
+ /// </summary>
+ /// <param name="certificationLevel">The certification level for keys on this ring.</param>
+ /// <param name="masterKey">The master key pair.</param>
+ /// <param name="id">The id to be associated with the ring.</param>
+ /// <param name="encAlgorithm">The algorithm to be used to protect secret keys.</param>
+ /// <param name="passPhrase">The passPhrase to be used to protect secret keys.</param>
+ /// <param name="useSha1">Checksum the secret keys with SHA1 rather than the older 16 bit checksum.</param>
+ /// <param name="hashedPackets">Packets to be included in the certification hash.</param>
+ /// <param name="unhashedPackets">Packets to be attached unhashed to the certification.</param>
+ /// <param name="rand">input secured random.</param>
+ public PgpKeyRingGenerator(
+ int certificationLevel,
+ PgpKeyPair masterKey,
+ string id,
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ char[] passPhrase,
+ bool useSha1,
+ PgpSignatureSubpacketVector hashedPackets,
+ PgpSignatureSubpacketVector unhashedPackets,
+ SecureRandom rand)
+ {
+ this.certificationLevel = certificationLevel;
+ this.masterKey = masterKey;
+ this.id = id;
+ this.encAlgorithm = encAlgorithm;
+ this.passPhrase = passPhrase;
+ this.useSha1 = useSha1;
+ this.hashedPacketVector = hashedPackets;
+ this.unhashedPacketVector = unhashedPackets;
+ this.rand = rand;
+
+ keys.Add(new PgpSecretKey(certificationLevel, masterKey, id, encAlgorithm, passPhrase, useSha1, hashedPackets, unhashedPackets, rand));
+ }
+
+ /// <summary>Add a subkey to the key ring to be generated with default certification.</summary>
+ public void AddSubKey(
+ PgpKeyPair keyPair)
+ {
+ AddSubKey(keyPair, this.hashedPacketVector, this.unhashedPacketVector);
+ }
+
+ /// <summary>
+ /// Add a subkey with specific hashed and unhashed packets associated with it and
+ /// default certification.
+ /// </summary>
+ /// <param name="keyPair">Public/private key pair.</param>
+ /// <param name="hashedPackets">Hashed packet values to be included in certification.</param>
+ /// <param name="unhashedPackets">Unhashed packets values to be included in certification.</param>
+ /// <exception cref="PgpException"></exception>
+ public void AddSubKey(
+ PgpKeyPair keyPair,
+ PgpSignatureSubpacketVector hashedPackets,
+ PgpSignatureSubpacketVector unhashedPackets)
+ {
+ try
+ {
+ PgpSignatureGenerator sGen = new PgpSignatureGenerator(
+ masterKey.PublicKey.Algorithm, HashAlgorithmTag.Sha1);
+
+ //
+ // Generate the certification
+ //
+ sGen.InitSign(PgpSignature.SubkeyBinding, masterKey.PrivateKey);
+
+ sGen.SetHashedSubpackets(hashedPackets);
+ sGen.SetUnhashedSubpackets(unhashedPackets);
+
+ IList subSigs = Platform.CreateArrayList();
+
+ subSigs.Add(sGen.GenerateCertification(masterKey.PublicKey, keyPair.PublicKey));
+
+ keys.Add(new PgpSecretKey(keyPair.PrivateKey, new PgpPublicKey(keyPair.PublicKey, null, subSigs), encAlgorithm, passPhrase, useSha1, rand));
+ }
+ catch (PgpException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("exception adding subkey: ", e);
+ }
+ }
+
+ /// <summary>Return the secret key ring.</summary>
+ public PgpSecretKeyRing GenerateSecretKeyRing()
+ {
+ return new PgpSecretKeyRing(keys);
+ }
+
+ /// <summary>Return the public key ring that corresponds to the secret key ring.</summary>
+ public PgpPublicKeyRing GeneratePublicKeyRing()
+ {
+ IList pubKeys = Platform.CreateArrayList();
+
+ IEnumerator enumerator = keys.GetEnumerator();
+ enumerator.MoveNext();
+
+ PgpSecretKey pgpSecretKey = (PgpSecretKey) enumerator.Current;
+ pubKeys.Add(pgpSecretKey.PublicKey);
+
+ while (enumerator.MoveNext())
+ {
+ pgpSecretKey = (PgpSecretKey) enumerator.Current;
+
+ PgpPublicKey k = new PgpPublicKey(pgpSecretKey.PublicKey);
+ k.publicPk = new PublicSubkeyPacket(
+ k.Algorithm, k.CreationTime, k.publicPk.Key);
+
+ pubKeys.Add(k);
+ }
+
+ return new PgpPublicKeyRing(pubKeys);
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpKeyValidationException.cs b/Crypto/src/openpgp/PgpKeyValidationException.cs
new file mode 100644
index 000000000..da07f400f
--- /dev/null
+++ b/Crypto/src/openpgp/PgpKeyValidationException.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>
+ /// Thrown if the key checksum is invalid.
+ /// </remarks>
+ public class PgpKeyValidationException
+ : PgpException
+ {
+ public PgpKeyValidationException() : base() {}
+ public PgpKeyValidationException(string message) : base(message) {}
+ public PgpKeyValidationException(string message, Exception exception) : base(message, exception) {}
+ }
+}
diff --git a/Crypto/src/openpgp/PgpLiteralData.cs b/Crypto/src/openpgp/PgpLiteralData.cs
new file mode 100644
index 000000000..79bbc3984
--- /dev/null
+++ b/Crypto/src/openpgp/PgpLiteralData.cs
@@ -0,0 +1,63 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Utilities.Date;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <summary>Class for processing literal data objects.</summary>
+ public class PgpLiteralData
+ : PgpObject
+ {
+ public const char Binary = 'b';
+ public const char Text = 't';
+ public const char Utf8 = 'u';
+
+ /// <summary>The special name indicating a "for your eyes only" packet.</summary>
+ public const string Console = "_CONSOLE";
+
+ private LiteralDataPacket data;
+
+ public PgpLiteralData(
+ BcpgInputStream bcpgInput)
+ {
+ data = (LiteralDataPacket) bcpgInput.ReadPacket();
+ }
+
+ /// <summary>The format of the data stream - Binary or Text</summary>
+ public int Format
+ {
+ get { return data.Format; }
+ }
+
+ /// <summary>The file name that's associated with the data stream.</summary>
+ public string FileName
+ {
+ get { return data.FileName; }
+ }
+
+ /// Return the file name as an unintrepreted byte array.
+ public byte[] GetRawFileName()
+ {
+ return data.GetRawFileName();
+ }
+
+ /// <summary>The modification time for the file.</summary>
+ public DateTime ModificationTime
+ {
+ get { return DateTimeUtilities.UnixMsToDateTime(data.ModificationTime); }
+ }
+
+ /// <summary>The raw input stream for the data stream.</summary>
+ public Stream GetInputStream()
+ {
+ return data.GetInputStream();
+ }
+
+ /// <summary>The input stream representing the data stream.</summary>
+ public Stream GetDataStream()
+ {
+ return GetInputStream();
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpLiteralDataGenerator.cs b/Crypto/src/openpgp/PgpLiteralDataGenerator.cs
new file mode 100644
index 000000000..b0337c80d
--- /dev/null
+++ b/Crypto/src/openpgp/PgpLiteralDataGenerator.cs
@@ -0,0 +1,180 @@
+using System;
+using System.IO;
+using System.Text;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Date;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>Class for producing literal data packets.</remarks>
+ public class PgpLiteralDataGenerator
+ : IStreamGenerator
+ {
+ public const char Binary = PgpLiteralData.Binary;
+ public const char Text = PgpLiteralData.Text;
+ public const char Utf8 = PgpLiteralData.Utf8;
+
+ /// <summary>The special name indicating a "for your eyes only" packet.</summary>
+ public const string Console = PgpLiteralData.Console;
+
+ private BcpgOutputStream pkOut;
+ private bool oldFormat;
+
+ public PgpLiteralDataGenerator()
+ {
+ }
+
+ /// <summary>
+ /// Generates literal data objects in the old format.
+ /// This is important if you need compatibility with PGP 2.6.x.
+ /// </summary>
+ /// <param name="oldFormat">If true, uses old format.</param>
+ public PgpLiteralDataGenerator(
+ bool oldFormat)
+ {
+ this.oldFormat = oldFormat;
+ }
+
+ private void WriteHeader(
+ BcpgOutputStream outStr,
+ char format,
+ string name,
+ long modificationTime)
+ {
+ byte[] encName = Strings.ToUtf8ByteArray(name);
+
+ outStr.Write(
+ (byte) format,
+ (byte) encName.Length);
+
+ outStr.Write(encName);
+
+ long modDate = modificationTime / 1000L;
+
+ outStr.Write(
+ (byte)(modDate >> 24),
+ (byte)(modDate >> 16),
+ (byte)(modDate >> 8),
+ (byte)modDate);
+ }
+
+ /// <summary>
+ /// <p>
+ /// Open a literal data packet, returning a stream to store the data inside the packet.
+ /// </p>
+ /// <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 Stream parameter <c>outStr</c>.
+ /// </p>
+ /// </summary>
+ /// <param name="outStr">The stream we want the packet in.</param>
+ /// <param name="format">The format we are using.</param>
+ /// <param name="name">The name of the 'file'.</param>
+ /// <param name="length">The length of the data we will write.</param>
+ /// <param name="modificationTime">The time of last modification we want stored.</param>
+ public Stream Open(
+ Stream outStr,
+ char format,
+ string name,
+ long length,
+ DateTime modificationTime)
+ {
+ if (pkOut != null)
+ throw new InvalidOperationException("generator already in open state");
+ if (outStr == null)
+ throw new ArgumentNullException("outStr");
+
+ // Do this first, since it might throw an exception
+ long unixMs = DateTimeUtilities.DateTimeToUnixMs(modificationTime);
+
+ pkOut = new BcpgOutputStream(outStr, PacketTag.LiteralData,
+ length + 2 + name.Length + 4, oldFormat);
+
+ WriteHeader(pkOut, format, name, unixMs);
+
+ return new WrappedGeneratorStream(this, pkOut);
+ }
+
+ /// <summary>
+ /// <p>
+ /// 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>
+ /// <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 Stream parameter <c>outStr</c>.
+ /// </p>
+ /// <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>
+ /// </summary>
+ /// <param name="outStr">The stream we want the packet in.</param>
+ /// <param name="format">The format we are using.</param>
+ /// <param name="name">The name of the 'file'.</param>
+ /// <param name="modificationTime">The time of last modification we want stored.</param>
+ /// <param name="buffer">The buffer to use for collecting data to put into chunks.</param>
+ public Stream Open(
+ Stream outStr,
+ char format,
+ string name,
+ DateTime modificationTime,
+ byte[] buffer)
+ {
+ if (pkOut != null)
+ throw new InvalidOperationException("generator already in open state");
+ if (outStr == null)
+ throw new ArgumentNullException("outStr");
+
+ // Do this first, since it might throw an exception
+ long unixMs = DateTimeUtilities.DateTimeToUnixMs(modificationTime);
+
+ pkOut = new BcpgOutputStream(outStr, PacketTag.LiteralData, buffer);
+
+ WriteHeader(pkOut, format, name, unixMs);
+
+ return new WrappedGeneratorStream(this, pkOut);
+ }
+
+#if !PORTABLE
+ /// <summary>
+ /// <p>
+ /// Open a literal data packet for the passed in <c>FileInfo</c> object, returning
+ /// an output stream for saving the file contents.
+ /// </p>
+ /// <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 Stream parameter <c>outStr</c>.
+ /// </p>
+ /// </summary>
+ /// <param name="outStr">The stream we want the packet in.</param>
+ /// <param name="format">The format we are using.</param>
+ /// <param name="file">The <c>FileInfo</c> object containg the packet details.</param>
+ public Stream Open(
+ Stream outStr,
+ char format,
+ FileInfo file)
+ {
+ return Open(outStr, format, file.Name, file.Length, file.LastWriteTime);
+ }
+#endif
+
+ /// <summary>
+ /// Close the literal data packet - this is equivalent to calling Close()
+ /// on the stream returned by the Open() method.
+ /// </summary>
+ public void Close()
+ {
+ if (pkOut != null)
+ {
+ pkOut.Finish();
+ pkOut.Flush();
+ pkOut = null;
+ }
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpMarker.cs b/Crypto/src/openpgp/PgpMarker.cs
new file mode 100644
index 000000000..733e4e959
--- /dev/null
+++ b/Crypto/src/openpgp/PgpMarker.cs
@@ -0,0 +1,18 @@
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>
+ /// A PGP marker packet - in general these should be ignored other than where
+ /// the idea is to preserve the original input stream.
+ /// </remarks>
+ public class PgpMarker
+ : PgpObject
+ {
+ private readonly MarkerPacket p;
+
+ public PgpMarker(
+ BcpgInputStream bcpgIn)
+ {
+ p = (MarkerPacket) bcpgIn.ReadPacket();
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpObjectFactory.cs b/Crypto/src/openpgp/PgpObjectFactory.cs
new file mode 100644
index 000000000..c5c6fcb68
--- /dev/null
+++ b/Crypto/src/openpgp/PgpObjectFactory.cs
@@ -0,0 +1,143 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>
+ /// General class for reading a PGP object stream.
+ /// <p>
+ /// Note: if this class finds a PgpPublicKey or a PgpSecretKey it
+ /// will create a PgpPublicKeyRing, or a PgpSecretKeyRing for each
+ /// key found. If all you are trying to do is read a key ring file use
+ /// either PgpPublicKeyRingBundle or PgpSecretKeyRingBundle.</p>
+ /// </remarks>
+ public class PgpObjectFactory
+ {
+ private readonly BcpgInputStream bcpgIn;
+
+ public PgpObjectFactory(
+ Stream inputStream)
+ {
+ this.bcpgIn = BcpgInputStream.Wrap(inputStream);
+ }
+
+ public PgpObjectFactory(
+ byte[] bytes)
+ : this(new MemoryStream(bytes, false))
+ {
+ }
+
+ /// <summary>Return the next object in the stream, or null if the end is reached.</summary>
+ /// <exception cref="IOException">On a parse error</exception>
+ public PgpObject NextPgpObject()
+ {
+ PacketTag tag = bcpgIn.NextPacketTag();
+
+ if ((int) tag == -1) return null;
+
+ switch (tag)
+ {
+ case PacketTag.Signature:
+ {
+ IList l = Platform.CreateArrayList();
+
+ while (bcpgIn.NextPacketTag() == PacketTag.Signature)
+ {
+ try
+ {
+ l.Add(new PgpSignature(bcpgIn));
+ }
+ catch (PgpException e)
+ {
+ throw new IOException("can't create signature object: " + e);
+ }
+ }
+
+ PgpSignature[] sigs = new PgpSignature[l.Count];
+ for (int i = 0; i < l.Count; ++i)
+ {
+ sigs[i] = (PgpSignature)l[i];
+ }
+ return new PgpSignatureList(sigs);
+ }
+ case PacketTag.SecretKey:
+ try
+ {
+ return new PgpSecretKeyRing(bcpgIn);
+ }
+ catch (PgpException e)
+ {
+ throw new IOException("can't create secret key object: " + e);
+ }
+ case PacketTag.PublicKey:
+ return new PgpPublicKeyRing(bcpgIn);
+ // TODO Make PgpPublicKey a PgpObject or return a PgpPublicKeyRing
+// case PacketTag.PublicSubkey:
+// return PgpPublicKeyRing.ReadSubkey(bcpgIn);
+ case PacketTag.CompressedData:
+ return new PgpCompressedData(bcpgIn);
+ case PacketTag.LiteralData:
+ return new PgpLiteralData(bcpgIn);
+ case PacketTag.PublicKeyEncryptedSession:
+ case PacketTag.SymmetricKeyEncryptedSessionKey:
+ return new PgpEncryptedDataList(bcpgIn);
+ case PacketTag.OnePassSignature:
+ {
+ IList l = Platform.CreateArrayList();
+
+ while (bcpgIn.NextPacketTag() == PacketTag.OnePassSignature)
+ {
+ try
+ {
+ l.Add(new PgpOnePassSignature(bcpgIn));
+ }
+ catch (PgpException e)
+ {
+ throw new IOException("can't create one pass signature object: " + e);
+ }
+ }
+
+ PgpOnePassSignature[] sigs = new PgpOnePassSignature[l.Count];
+ for (int i = 0; i < l.Count; ++i)
+ {
+ sigs[i] = (PgpOnePassSignature)l[i];
+ }
+ return new PgpOnePassSignatureList(sigs);
+ }
+ case PacketTag.Marker:
+ return new PgpMarker(bcpgIn);
+ case PacketTag.Experimental1:
+ case PacketTag.Experimental2:
+ case PacketTag.Experimental3:
+ case PacketTag.Experimental4:
+ return new PgpExperimental(bcpgIn);
+ }
+
+ throw new IOException("unknown object in stream " + bcpgIn.NextPacketTag());
+ }
+
+ [Obsolete("Use NextPgpObject() instead")]
+ public object NextObject()
+ {
+ return NextPgpObject();
+ }
+
+ /// <summary>
+ /// Return all available objects in a list.
+ /// </summary>
+ /// <returns>An <c>IList</c> containing all objects from this factory, in order.</returns>
+ public IList AllPgpObjects()
+ {
+ IList result = Platform.CreateArrayList();
+ PgpObject pgpObject;
+ while ((pgpObject = NextPgpObject()) != null)
+ {
+ result.Add(pgpObject);
+ }
+ return result;
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpOnePassSignature.cs b/Crypto/src/openpgp/PgpOnePassSignature.cs
new file mode 100644
index 000000000..68fc5994d
--- /dev/null
+++ b/Crypto/src/openpgp/PgpOnePassSignature.cs
@@ -0,0 +1,179 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>A one pass signature object.</remarks>
+ public class PgpOnePassSignature
+ {
+ private OnePassSignaturePacket sigPack;
+ private int signatureType;
+ private ISigner sig;
+ private byte lastb;
+
+ internal PgpOnePassSignature(
+ BcpgInputStream bcpgInput)
+ : this((OnePassSignaturePacket) bcpgInput.ReadPacket())
+ {
+ }
+
+ internal PgpOnePassSignature(
+ OnePassSignaturePacket sigPack)
+ {
+ this.sigPack = sigPack;
+ this.signatureType = sigPack.SignatureType;
+ }
+
+ /// <summary>Initialise the signature object for verification.</summary>
+ public void InitVerify(
+ PgpPublicKey pubKey)
+ {
+ lastb = 0;
+
+ try
+ {
+ sig = SignerUtilities.GetSigner(
+ PgpUtilities.GetSignatureName(sigPack.KeyAlgorithm, sigPack.HashAlgorithm));
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("can't set up signature object.", e);
+ }
+
+ try
+ {
+ sig.Init(false, pubKey.GetKey());
+ }
+ catch (InvalidKeyException e)
+ {
+ throw new PgpException("invalid key.", e);
+ }
+ }
+
+ public void Update(
+ byte b)
+ {
+ if (signatureType == PgpSignature.CanonicalTextDocument)
+ {
+ doCanonicalUpdateByte(b);
+ }
+ else
+ {
+ sig.Update(b);
+ }
+ }
+
+ private void doCanonicalUpdateByte(
+ byte b)
+ {
+ if (b == '\r')
+ {
+ doUpdateCRLF();
+ }
+ else if (b == '\n')
+ {
+ if (lastb != '\r')
+ {
+ doUpdateCRLF();
+ }
+ }
+ else
+ {
+ sig.Update(b);
+ }
+
+ lastb = b;
+ }
+
+ private void doUpdateCRLF()
+ {
+ sig.Update((byte)'\r');
+ sig.Update((byte)'\n');
+ }
+
+ public void Update(
+ byte[] bytes)
+ {
+ if (signatureType == PgpSignature.CanonicalTextDocument)
+ {
+ for (int i = 0; i != bytes.Length; i++)
+ {
+ doCanonicalUpdateByte(bytes[i]);
+ }
+ }
+ else
+ {
+ sig.BlockUpdate(bytes, 0, bytes.Length);
+ }
+ }
+
+ public void Update(
+ byte[] bytes,
+ int off,
+ int length)
+ {
+ if (signatureType == PgpSignature.CanonicalTextDocument)
+ {
+ int finish = off + length;
+
+ for (int i = off; i != finish; i++)
+ {
+ doCanonicalUpdateByte(bytes[i]);
+ }
+ }
+ else
+ {
+ sig.BlockUpdate(bytes, off, length);
+ }
+ }
+
+ /// <summary>Verify the calculated signature against the passed in PgpSignature.</summary>
+ public bool Verify(
+ PgpSignature pgpSig)
+ {
+ byte[] trailer = pgpSig.GetSignatureTrailer();
+
+ sig.BlockUpdate(trailer, 0, trailer.Length);
+
+ return sig.VerifySignature(pgpSig.GetSignature());
+ }
+
+ public long KeyId
+ {
+ get { return sigPack.KeyId; }
+ }
+
+ public int SignatureType
+ {
+ get { return sigPack.SignatureType; }
+ }
+
+ public HashAlgorithmTag HashAlgorithm
+ {
+ get { return sigPack.HashAlgorithm; }
+ }
+
+ public PublicKeyAlgorithmTag KeyAlgorithm
+ {
+ get { return sigPack.KeyAlgorithm; }
+ }
+
+ public byte[] GetEncoded()
+ {
+ MemoryStream bOut = new MemoryStream();
+
+ Encode(bOut);
+
+ return bOut.ToArray();
+ }
+
+ public void Encode(
+ Stream outStr)
+ {
+ BcpgOutputStream.Wrap(outStr).WritePacket(sigPack);
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpOnePassSignatureList.cs b/Crypto/src/openpgp/PgpOnePassSignatureList.cs
new file mode 100644
index 000000000..37c4288e3
--- /dev/null
+++ b/Crypto/src/openpgp/PgpOnePassSignatureList.cs
@@ -0,0 +1,51 @@
+using System;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>Holder for a list of PgpOnePassSignature objects.</remarks>
+ public class PgpOnePassSignatureList
+ : PgpObject
+ {
+ private readonly PgpOnePassSignature[] sigs;
+
+ public PgpOnePassSignatureList(
+ PgpOnePassSignature[] sigs)
+ {
+ this.sigs = (PgpOnePassSignature[]) sigs.Clone();
+ }
+
+ public PgpOnePassSignatureList(
+ PgpOnePassSignature sig)
+ {
+ this.sigs = new PgpOnePassSignature[]{ sig };
+ }
+
+ public PgpOnePassSignature this[int index]
+ {
+ get { return sigs[index]; }
+ }
+
+ [Obsolete("Use 'object[index]' syntax instead")]
+ public PgpOnePassSignature Get(
+ int index)
+ {
+ return this[index];
+ }
+
+ [Obsolete("Use 'Count' property instead")]
+ public int Size
+ {
+ get { return sigs.Length; }
+ }
+
+ public int Count
+ {
+ get { return sigs.Length; }
+ }
+
+ public bool IsEmpty
+ {
+ get { return (sigs.Length == 0); }
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpPbeEncryptedData.cs b/Crypto/src/openpgp/PgpPbeEncryptedData.cs
new file mode 100644
index 000000000..c5fe89407
--- /dev/null
+++ b/Crypto/src/openpgp/PgpPbeEncryptedData.cs
@@ -0,0 +1,135 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.IO;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>A password based encryption object.</remarks>
+ public class PgpPbeEncryptedData
+ : PgpEncryptedData
+ {
+ private readonly SymmetricKeyEncSessionPacket keyData;
+
+ internal PgpPbeEncryptedData(
+ SymmetricKeyEncSessionPacket keyData,
+ InputStreamPacket encData)
+ : base(encData)
+ {
+ this.keyData = keyData;
+ }
+
+ /// <summary>Return the raw input stream for the data stream.</summary>
+ public override Stream GetInputStream()
+ {
+ return encData.GetInputStream();
+ }
+
+ /// <summary>Return the decrypted input stream, using the passed in passphrase.</summary>
+ public Stream GetDataStream(
+ char[] passPhrase)
+ {
+ try
+ {
+ SymmetricKeyAlgorithmTag keyAlgorithm = keyData.EncAlgorithm;
+
+ KeyParameter key = PgpUtilities.MakeKeyFromPassPhrase(
+ keyAlgorithm, keyData.S2k, passPhrase);
+
+
+ byte[] secKeyData = keyData.GetSecKeyData();
+ if (secKeyData != null && secKeyData.Length > 0)
+ {
+ IBufferedCipher keyCipher = CipherUtilities.GetCipher(
+ PgpUtilities.GetSymmetricCipherName(keyAlgorithm) + "/CFB/NoPadding");
+
+ keyCipher.Init(false,
+ new ParametersWithIV(key, new byte[keyCipher.GetBlockSize()]));
+
+ byte[] keyBytes = keyCipher.DoFinal(secKeyData);
+
+ keyAlgorithm = (SymmetricKeyAlgorithmTag) keyBytes[0];
+
+ key = ParameterUtilities.CreateKeyParameter(
+ PgpUtilities.GetSymmetricCipherName(keyAlgorithm),
+ keyBytes, 1, keyBytes.Length - 1);
+ }
+
+
+ IBufferedCipher c = CreateStreamCipher(keyAlgorithm);
+
+ byte[] iv = new byte[c.GetBlockSize()];
+
+ c.Init(false, new ParametersWithIV(key, iv));
+
+ encStream = BcpgInputStream.Wrap(new CipherStream(encData.GetInputStream(), c, null));
+
+ if (encData is SymmetricEncIntegrityPacket)
+ {
+ truncStream = new TruncatedStream(encStream);
+
+ string digestName = PgpUtilities.GetDigestName(HashAlgorithmTag.Sha1);
+ IDigest digest = DigestUtilities.GetDigest(digestName);
+
+ encStream = new DigestStream(truncStream, digest, null);
+ }
+
+ if (Streams.ReadFully(encStream, iv, 0, iv.Length) < iv.Length)
+ throw new EndOfStreamException("unexpected end of stream.");
+
+ int v1 = encStream.ReadByte();
+ int v2 = encStream.ReadByte();
+
+ if (v1 < 0 || v2 < 0)
+ throw new EndOfStreamException("unexpected end of stream.");
+
+
+ // Note: the oracle attack on the "quick check" bytes is not deemed
+ // a security risk for PBE (see PgpPublicKeyEncryptedData)
+
+ bool 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
+ bool zeroesCheckPassed =
+ v1 == 0
+ && v2 == 0;
+
+ if (!repeatCheckPassed && !zeroesCheckPassed)
+ {
+ throw new PgpDataValidationException("quick check failed.");
+ }
+
+
+ return encStream;
+ }
+ catch (PgpException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("Exception creating cipher", e);
+ }
+ }
+
+ private IBufferedCipher CreateStreamCipher(
+ SymmetricKeyAlgorithmTag keyAlgorithm)
+ {
+ string mode = (encData is SymmetricEncIntegrityPacket)
+ ? "CFB"
+ : "OpenPGPCFB";
+
+ string cName = PgpUtilities.GetSymmetricCipherName(keyAlgorithm)
+ + "/" + mode + "/NoPadding";
+
+ return CipherUtilities.GetCipher(cName);
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpPrivateKey.cs b/Crypto/src/openpgp/PgpPrivateKey.cs
new file mode 100644
index 000000000..154c87cd7
--- /dev/null
+++ b/Crypto/src/openpgp/PgpPrivateKey.cs
@@ -0,0 +1,42 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>General class to contain a private key for use with other OpenPGP objects.</remarks>
+ public class PgpPrivateKey
+ {
+ private readonly long keyId;
+ private readonly AsymmetricKeyParameter privateKey;
+
+ /// <summary>
+ /// Create a PgpPrivateKey from a regular private key and the ID of its
+ /// associated public key.
+ /// </summary>
+ /// <param name="privateKey">Private key to use.</param>
+ /// <param name="keyId">ID of the corresponding public key.</param>
+ public PgpPrivateKey(
+ AsymmetricKeyParameter privateKey,
+ long keyId)
+ {
+ if (!privateKey.IsPrivate)
+ throw new ArgumentException("Expected a private key", "privateKey");
+
+ this.privateKey = privateKey;
+ this.keyId = keyId;
+ }
+
+ /// <summary>The keyId associated with the contained private key.</summary>
+ public long KeyId
+ {
+ get { return keyId; }
+ }
+
+ /// <summary>The contained private key.</summary>
+ public AsymmetricKeyParameter Key
+ {
+ get { return privateKey; }
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpPublicKey.cs b/Crypto/src/openpgp/PgpPublicKey.cs
new file mode 100644
index 000000000..b0720146c
--- /dev/null
+++ b/Crypto/src/openpgp/PgpPublicKey.cs
@@ -0,0 +1,890 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.IO;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Collections;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>General class to handle a PGP public key object.</remarks>
+ public class PgpPublicKey
+ {
+ private static readonly int[] MasterKeyCertificationTypes = new int[]
+ {
+ PgpSignature.PositiveCertification,
+ PgpSignature.CasualCertification,
+ PgpSignature.NoCertification,
+ PgpSignature.DefaultCertification
+ };
+
+ private long keyId;
+ private byte[] fingerprint;
+ private int keyStrength;
+
+ internal PublicKeyPacket publicPk;
+ internal TrustPacket trustPk;
+ internal IList keySigs = Platform.CreateArrayList();
+ internal IList ids = Platform.CreateArrayList();
+ internal IList idTrusts = Platform.CreateArrayList();
+ internal IList idSigs = Platform.CreateArrayList();
+ internal IList subSigs;
+
+ private void Init()
+ {
+ IBcpgKey key = publicPk.Key;
+
+ if (publicPk.Version <= 3)
+ {
+ RsaPublicBcpgKey rK = (RsaPublicBcpgKey) key;
+
+ this.keyId = rK.Modulus.LongValue;
+
+ try
+ {
+ IDigest digest = DigestUtilities.GetDigest("MD5");
+
+ byte[] bytes = rK.Modulus.ToByteArrayUnsigned();
+ digest.BlockUpdate(bytes, 0, bytes.Length);
+
+ bytes = rK.PublicExponent.ToByteArrayUnsigned();
+ digest.BlockUpdate(bytes, 0, bytes.Length);
+
+ this.fingerprint = DigestUtilities.DoFinal(digest);
+ }
+ //catch (NoSuchAlgorithmException)
+ catch (Exception e)
+ {
+ throw new IOException("can't find MD5", e);
+ }
+
+ this.keyStrength = rK.Modulus.BitLength;
+ }
+ else
+ {
+ byte[] kBytes = publicPk.GetEncodedContents();
+
+ try
+ {
+ IDigest digest = DigestUtilities.GetDigest("SHA1");
+
+ digest.Update(0x99);
+ digest.Update((byte)(kBytes.Length >> 8));
+ digest.Update((byte)kBytes.Length);
+ digest.BlockUpdate(kBytes, 0, kBytes.Length);
+ this.fingerprint = DigestUtilities.DoFinal(digest);
+ }
+ catch (Exception e)
+ {
+ throw new IOException("can't find SHA1", e);
+ }
+
+ this.keyId = (long)(((ulong)fingerprint[fingerprint.Length - 8] << 56)
+ | ((ulong)fingerprint[fingerprint.Length - 7] << 48)
+ | ((ulong)fingerprint[fingerprint.Length - 6] << 40)
+ | ((ulong)fingerprint[fingerprint.Length - 5] << 32)
+ | ((ulong)fingerprint[fingerprint.Length - 4] << 24)
+ | ((ulong)fingerprint[fingerprint.Length - 3] << 16)
+ | ((ulong)fingerprint[fingerprint.Length - 2] << 8)
+ | (ulong)fingerprint[fingerprint.Length - 1]);
+
+ if (key is RsaPublicBcpgKey)
+ {
+ this.keyStrength = ((RsaPublicBcpgKey)key).Modulus.BitLength;
+ }
+ else if (key is DsaPublicBcpgKey)
+ {
+ this.keyStrength = ((DsaPublicBcpgKey)key).P.BitLength;
+ }
+ else if (key is ElGamalPublicBcpgKey)
+ {
+ this.keyStrength = ((ElGamalPublicBcpgKey)key).P.BitLength;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Create a PgpPublicKey from the passed in lightweight one.
+ /// </summary>
+ /// <remarks>
+ /// Note: the time passed in affects the value of the key's keyId, so you probably only want
+ /// to do this once for a lightweight key, or make sure you keep track of the time you used.
+ /// </remarks>
+ /// <param name="algorithm">Asymmetric algorithm type representing the public key.</param>
+ /// <param name="pubKey">Actual public key to associate.</param>
+ /// <param name="time">Date of creation.</param>
+ /// <exception cref="ArgumentException">If <c>pubKey</c> is not public.</exception>
+ /// <exception cref="PgpException">On key creation problem.</exception>
+ public PgpPublicKey(
+ PublicKeyAlgorithmTag algorithm,
+ AsymmetricKeyParameter pubKey,
+ DateTime time)
+ {
+ if (pubKey.IsPrivate)
+ throw new ArgumentException("Expected a public key", "pubKey");
+
+ IBcpgKey bcpgKey;
+ if (pubKey is RsaKeyParameters)
+ {
+ RsaKeyParameters rK = (RsaKeyParameters) pubKey;
+
+ bcpgKey = new RsaPublicBcpgKey(rK.Modulus, rK.Exponent);
+ }
+ else if (pubKey is DsaPublicKeyParameters)
+ {
+ DsaPublicKeyParameters dK = (DsaPublicKeyParameters) pubKey;
+ DsaParameters dP = dK.Parameters;
+
+ bcpgKey = new DsaPublicBcpgKey(dP.P, dP.Q, dP.G, dK.Y);
+ }
+ else if (pubKey is ElGamalPublicKeyParameters)
+ {
+ ElGamalPublicKeyParameters eK = (ElGamalPublicKeyParameters) pubKey;
+ ElGamalParameters eS = eK.Parameters;
+
+ bcpgKey = new ElGamalPublicBcpgKey(eS.P, eS.G, eK.Y);
+ }
+ else
+ {
+ throw new PgpException("unknown key class");
+ }
+
+ this.publicPk = new PublicKeyPacket(algorithm, time, bcpgKey);
+ this.ids = Platform.CreateArrayList();
+ this.idSigs = Platform.CreateArrayList();
+
+ try
+ {
+ Init();
+ }
+ catch (IOException e)
+ {
+ throw new PgpException("exception calculating keyId", e);
+ }
+ }
+
+ /// <summary>Constructor for a sub-key.</summary>
+ internal PgpPublicKey(
+ PublicKeyPacket publicPk,
+ TrustPacket trustPk,
+ IList sigs)
+ {
+ this.publicPk = publicPk;
+ this.trustPk = trustPk;
+ this.subSigs = sigs;
+
+ Init();
+ }
+
+ internal PgpPublicKey(
+ PgpPublicKey key,
+ TrustPacket trust,
+ IList subSigs)
+ {
+ this.publicPk = key.publicPk;
+ this.trustPk = trust;
+ this.subSigs = subSigs;
+
+ this.fingerprint = key.fingerprint;
+ this.keyId = key.keyId;
+ this.keyStrength = key.keyStrength;
+ }
+
+ /// <summary>Copy constructor.</summary>
+ /// <param name="pubKey">The public key to copy.</param>
+ internal PgpPublicKey(
+ PgpPublicKey pubKey)
+ {
+ this.publicPk = pubKey.publicPk;
+
+ this.keySigs = Platform.CreateArrayList(pubKey.keySigs);
+ this.ids = Platform.CreateArrayList(pubKey.ids);
+ this.idTrusts = Platform.CreateArrayList(pubKey.idTrusts);
+ this.idSigs = Platform.CreateArrayList(pubKey.idSigs.Count);
+ for (int i = 0; i != pubKey.idSigs.Count; i++)
+ {
+ this.idSigs.Add(Platform.CreateArrayList((IList)pubKey.idSigs[i]));
+ }
+
+ if (pubKey.subSigs != null)
+ {
+ this.subSigs = Platform.CreateArrayList(pubKey.subSigs.Count);
+ for (int i = 0; i != pubKey.subSigs.Count; i++)
+ {
+ this.subSigs.Add(pubKey.subSigs[i]);
+ }
+ }
+
+ this.fingerprint = pubKey.fingerprint;
+ this.keyId = pubKey.keyId;
+ this.keyStrength = pubKey.keyStrength;
+ }
+
+ internal PgpPublicKey(
+ PublicKeyPacket publicPk,
+ TrustPacket trustPk,
+ IList keySigs,
+ IList ids,
+ IList idTrusts,
+ IList idSigs)
+ {
+ this.publicPk = publicPk;
+ this.trustPk = trustPk;
+ this.keySigs = keySigs;
+ this.ids = ids;
+ this.idTrusts = idTrusts;
+ this.idSigs = idSigs;
+
+ Init();
+ }
+
+ internal PgpPublicKey(
+ PublicKeyPacket publicPk,
+ IList ids,
+ IList idSigs)
+ {
+ this.publicPk = publicPk;
+ this.ids = ids;
+ this.idSigs = idSigs;
+ Init();
+ }
+
+ /// <summary>The version of this key.</summary>
+ public int Version
+ {
+ get { return publicPk.Version; }
+ }
+
+ /// <summary>The creation time of this key.</summary>
+ public DateTime CreationTime
+ {
+ get { return publicPk.GetTime(); }
+ }
+
+ /// <summary>The number of valid days from creation time - zero means no expiry.</summary>
+ public int ValidDays
+ {
+ get
+ {
+ if (publicPk.Version > 3)
+ {
+ return (int)(GetValidSeconds() / (24 * 60 * 60));
+ }
+
+ return publicPk.ValidDays;
+ }
+ }
+
+ /// <summary>Return the trust data associated with the public key, if present.</summary>
+ /// <returns>A byte array with trust data, null otherwise.</returns>
+ public byte[] GetTrustData()
+ {
+ if (trustPk == null)
+ {
+ return null;
+ }
+
+ return trustPk.GetLevelAndTrustAmount();
+ }
+
+ /// <summary>The number of valid seconds from creation time - zero means no expiry.</summary>
+ public long GetValidSeconds()
+ {
+ if (publicPk.Version > 3)
+ {
+ if (IsMasterKey)
+ {
+ for (int i = 0; i != MasterKeyCertificationTypes.Length; i++)
+ {
+ long seconds = GetExpirationTimeFromSig(true, MasterKeyCertificationTypes[i]);
+
+ if (seconds >= 0)
+ {
+ return seconds;
+ }
+ }
+ }
+ else
+ {
+ long seconds = GetExpirationTimeFromSig(false, PgpSignature.SubkeyBinding);
+
+ if (seconds >= 0)
+ {
+ return seconds;
+ }
+ }
+
+ return 0;
+ }
+
+ return (long) publicPk.ValidDays * 24 * 60 * 60;
+ }
+
+ private long GetExpirationTimeFromSig(
+ bool selfSigned,
+ int signatureType)
+ {
+ foreach (PgpSignature sig in GetSignaturesOfType(signatureType))
+ {
+ if (!selfSigned || sig.KeyId == KeyId)
+ {
+ PgpSignatureSubpacketVector hashed = sig.GetHashedSubPackets();
+
+ if (hashed != null)
+ {
+ return hashed.GetKeyExpirationTime();
+ }
+
+ return 0;
+ }
+ }
+
+ return -1;
+ }
+
+ /// <summary>The keyId associated with the public key.</summary>
+ public long KeyId
+ {
+ get { return keyId; }
+ }
+
+ /// <summary>The fingerprint of the key</summary>
+ public byte[] GetFingerprint()
+ {
+ return (byte[]) fingerprint.Clone();
+ }
+
+ /// <summary>
+ /// Check if this key has an algorithm type that makes it suitable to use for encryption.
+ /// </summary>
+ /// <remarks>
+ /// Note: with version 4 keys KeyFlags subpackets should also be considered when present for
+ /// determining the preferred use of the key.
+ /// </remarks>
+ /// <returns>
+ /// <c>true</c> if this key algorithm is suitable for encryption.
+ /// </returns>
+ public bool IsEncryptionKey
+ {
+ get
+ {
+ switch (publicPk.Algorithm)
+ {
+ case PublicKeyAlgorithmTag.ElGamalEncrypt:
+ case PublicKeyAlgorithmTag.ElGamalGeneral:
+ case PublicKeyAlgorithmTag.RsaEncrypt:
+ case PublicKeyAlgorithmTag.RsaGeneral:
+ return true;
+ default:
+ return false;
+ }
+ }
+ }
+
+ /// <summary>True, if this is a master key.</summary>
+ public bool IsMasterKey
+ {
+ get { return subSigs == null; }
+ }
+
+ /// <summary>The algorithm code associated with the public key.</summary>
+ public PublicKeyAlgorithmTag Algorithm
+ {
+ get { return publicPk.Algorithm; }
+ }
+
+ /// <summary>The strength of the key in bits.</summary>
+ public int BitStrength
+ {
+ get { return keyStrength; }
+ }
+
+ /// <summary>The public key contained in the object.</summary>
+ /// <returns>A lightweight public key.</returns>
+ /// <exception cref="PgpException">If the key algorithm is not recognised.</exception>
+ public AsymmetricKeyParameter GetKey()
+ {
+ try
+ {
+ switch (publicPk.Algorithm)
+ {
+ case PublicKeyAlgorithmTag.RsaEncrypt:
+ case PublicKeyAlgorithmTag.RsaGeneral:
+ case PublicKeyAlgorithmTag.RsaSign:
+ RsaPublicBcpgKey rsaK = (RsaPublicBcpgKey) publicPk.Key;
+ return new RsaKeyParameters(false, rsaK.Modulus, rsaK.PublicExponent);
+ case PublicKeyAlgorithmTag.Dsa:
+ DsaPublicBcpgKey dsaK = (DsaPublicBcpgKey) publicPk.Key;
+ return new DsaPublicKeyParameters(dsaK.Y, new DsaParameters(dsaK.P, dsaK.Q, dsaK.G));
+ case PublicKeyAlgorithmTag.ElGamalEncrypt:
+ case PublicKeyAlgorithmTag.ElGamalGeneral:
+ ElGamalPublicBcpgKey elK = (ElGamalPublicBcpgKey) publicPk.Key;
+ return new ElGamalPublicKeyParameters(elK.Y, new ElGamalParameters(elK.P, elK.G));
+ 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);
+ }
+ }
+
+ /// <summary>Allows enumeration of any user IDs associated with the key.</summary>
+ /// <returns>An <c>IEnumerable</c> of <c>string</c> objects.</returns>
+ public IEnumerable GetUserIds()
+ {
+ IList temp = Platform.CreateArrayList();
+
+ foreach (object o in ids)
+ {
+ if (o is string)
+ {
+ temp.Add(o);
+ }
+ }
+
+ return new EnumerableProxy(temp);
+ }
+
+ /// <summary>Allows enumeration of any user attribute vectors associated with the key.</summary>
+ /// <returns>An <c>IEnumerable</c> of <c>PgpUserAttributeSubpacketVector</c> objects.</returns>
+ public IEnumerable GetUserAttributes()
+ {
+ IList temp = Platform.CreateArrayList();
+
+ foreach (object o in ids)
+ {
+ if (o is PgpUserAttributeSubpacketVector)
+ {
+ temp.Add(o);
+ }
+ }
+
+ return new EnumerableProxy(temp);
+ }
+
+ /// <summary>Allows enumeration of any signatures associated with the passed in id.</summary>
+ /// <param name="id">The ID to be matched.</param>
+ /// <returns>An <c>IEnumerable</c> of <c>PgpSignature</c> objects.</returns>
+ public IEnumerable GetSignaturesForId(
+ string id)
+ {
+ if (id == null)
+ throw new ArgumentNullException("id");
+
+ for (int i = 0; i != ids.Count; i++)
+ {
+ if (id.Equals(ids[i]))
+ {
+ return new EnumerableProxy((IList)idSigs[i]);
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>Allows enumeration of signatures associated with the passed in user attributes.</summary>
+ /// <param name="userAttributes">The vector of user attributes to be matched.</param>
+ /// <returns>An <c>IEnumerable</c> of <c>PgpSignature</c> objects.</returns>
+ public IEnumerable GetSignaturesForUserAttribute(
+ PgpUserAttributeSubpacketVector userAttributes)
+ {
+ for (int i = 0; i != ids.Count; i++)
+ {
+ if (userAttributes.Equals(ids[i]))
+ {
+ return new EnumerableProxy((IList) idSigs[i]);
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>Allows enumeration of signatures of the passed in type that are on this key.</summary>
+ /// <param name="signatureType">The type of the signature to be returned.</param>
+ /// <returns>An <c>IEnumerable</c> of <c>PgpSignature</c> objects.</returns>
+ public IEnumerable GetSignaturesOfType(
+ int signatureType)
+ {
+ IList temp = Platform.CreateArrayList();
+
+ foreach (PgpSignature sig in GetSignatures())
+ {
+ if (sig.SignatureType == signatureType)
+ {
+ temp.Add(sig);
+ }
+ }
+
+ return new EnumerableProxy(temp);
+ }
+
+ /// <summary>Allows enumeration of all signatures/certifications associated with this key.</summary>
+ /// <returns>An <c>IEnumerable</c> with all signatures/certifications.</returns>
+ public IEnumerable GetSignatures()
+ {
+ IList sigs;
+ if (subSigs != null)
+ {
+ sigs = subSigs;
+ }
+ else
+ {
+ sigs = Platform.CreateArrayList(keySigs);
+
+ foreach (ICollection extraSigs in idSigs)
+ {
+ CollectionUtilities.AddRange(sigs, extraSigs);
+ }
+ }
+
+ return new EnumerableProxy(sigs);
+ }
+
+ public byte[] GetEncoded()
+ {
+ MemoryStream bOut = new MemoryStream();
+ Encode(bOut);
+ return bOut.ToArray();
+ }
+
+ public void Encode(
+ Stream outStr)
+ {
+ BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStr);
+
+ bcpgOut.WritePacket(publicPk);
+ if (trustPk != null)
+ {
+ bcpgOut.WritePacket(trustPk);
+ }
+
+ if (subSigs == null) // not a sub-key
+ {
+ foreach (PgpSignature keySig in keySigs)
+ {
+ keySig.Encode(bcpgOut);
+ }
+
+ for (int i = 0; i != ids.Count; i++)
+ {
+ if (ids[i] is string)
+ {
+ string id = (string) ids[i];
+
+ bcpgOut.WritePacket(new UserIdPacket(id));
+ }
+ else
+ {
+ PgpUserAttributeSubpacketVector v = (PgpUserAttributeSubpacketVector)ids[i];
+ bcpgOut.WritePacket(new UserAttributePacket(v.ToSubpacketArray()));
+ }
+
+ if (idTrusts[i] != null)
+ {
+ bcpgOut.WritePacket((ContainedPacket)idTrusts[i]);
+ }
+
+ foreach (PgpSignature sig in (IList) idSigs[i])
+ {
+ sig.Encode(bcpgOut);
+ }
+ }
+ }
+ else
+ {
+ foreach (PgpSignature subSig in subSigs)
+ {
+ subSig.Encode(bcpgOut);
+ }
+ }
+ }
+
+ /// <summary>Check whether this (sub)key has a revocation signature on it.</summary>
+ /// <returns>True, if this (sub)key has been revoked.</returns>
+ public bool IsRevoked()
+ {
+ int ns = 0;
+ bool revoked = false;
+ if (IsMasterKey) // Master key
+ {
+ while (!revoked && (ns < keySigs.Count))
+ {
+ if (((PgpSignature)keySigs[ns++]).SignatureType == PgpSignature.KeyRevocation)
+ {
+ revoked = true;
+ }
+ }
+ }
+ else // Sub-key
+ {
+ while (!revoked && (ns < subSigs.Count))
+ {
+ if (((PgpSignature)subSigs[ns++]).SignatureType == PgpSignature.SubkeyRevocation)
+ {
+ revoked = true;
+ }
+ }
+ }
+ return revoked;
+ }
+
+ /// <summary>Add a certification for an id to the given public key.</summary>
+ /// <param name="key">The key the certification is to be added to.</param>
+ /// <param name="id">The ID the certification is associated with.</param>
+ /// <param name="certification">The new certification.</param>
+ /// <returns>The re-certified key.</returns>
+ public static PgpPublicKey AddCertification(
+ PgpPublicKey key,
+ string id,
+ PgpSignature certification)
+ {
+ return AddCert(key, id, certification);
+ }
+
+ /// <summary>Add a certification for the given UserAttributeSubpackets to the given public key.</summary>
+ /// <param name="key">The key the certification is to be added to.</param>
+ /// <param name="userAttributes">The attributes the certification is associated with.</param>
+ /// <param name="certification">The new certification.</param>
+ /// <returns>The re-certified key.</returns>
+ 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);
+ IList sigList = null;
+
+ for (int i = 0; i != returnKey.ids.Count; i++)
+ {
+ if (id.Equals(returnKey.ids[i]))
+ {
+ sigList = (IList) returnKey.idSigs[i];
+ }
+ }
+
+ if (sigList != null)
+ {
+ sigList.Add(certification);
+ }
+ else
+ {
+ sigList = Platform.CreateArrayList();
+ sigList.Add(certification);
+ returnKey.ids.Add(id);
+ returnKey.idTrusts.Add(null);
+ returnKey.idSigs.Add(sigList);
+ }
+
+ return returnKey;
+ }
+
+ /// <summary>
+ /// Remove any certifications associated with a user attribute subpacket on a key.
+ /// </summary>
+ /// <param name="key">The key the certifications are to be removed from.</param>
+ /// <param name="userAttributes">The attributes to be removed.</param>
+ /// <returns>
+ /// The re-certified key, or null if the user attribute subpacket was not found on the key.
+ /// </returns>
+ public static PgpPublicKey RemoveCertification(
+ PgpPublicKey key,
+ PgpUserAttributeSubpacketVector userAttributes)
+ {
+ return RemoveCert(key, userAttributes);
+ }
+
+ /// <summary>Remove any certifications associated with a given ID on a key.</summary>
+ /// <param name="key">The key the certifications are to be removed from.</param>
+ /// <param name="id">The ID that is to be removed.</param>
+ /// <returns>The re-certified key, or null if the ID was not found on the key.</returns>
+ public static PgpPublicKey RemoveCertification(
+ PgpPublicKey key,
+ string id)
+ {
+ return RemoveCert(key, id);
+ }
+
+ private static PgpPublicKey RemoveCert(
+ PgpPublicKey key,
+ object id)
+ {
+ PgpPublicKey returnKey = new PgpPublicKey(key);
+ bool found = false;
+
+ for (int i = 0; i < returnKey.ids.Count; i++)
+ {
+ if (id.Equals(returnKey.ids[i]))
+ {
+ found = true;
+ returnKey.ids.RemoveAt(i);
+ returnKey.idTrusts.RemoveAt(i);
+ returnKey.idSigs.RemoveAt(i);
+ }
+ }
+
+ return found ? returnKey : null;
+ }
+
+ /// <summary>Remove a certification associated with a given ID on a key.</summary>
+ /// <param name="key">The key the certifications are to be removed from.</param>
+ /// <param name="id">The ID that the certfication is to be removed from.</param>
+ /// <param name="certification">The certfication to be removed.</param>
+ /// <returns>The re-certified key, or null if the certification was not found.</returns>
+ public static PgpPublicKey RemoveCertification(
+ PgpPublicKey key,
+ string id,
+ PgpSignature certification)
+ {
+ return RemoveCert(key, id, certification);
+ }
+
+ /// <summary>Remove a certification associated with a given user attributes on a key.</summary>
+ /// <param name="key">The key the certifications are to be removed from.</param>
+ /// <param name="userAttributes">The user attributes that the certfication is to be removed from.</param>
+ /// <param name="certification">The certification to be removed.</param>
+ /// <returns>The re-certified key, or null if the certification was not found.</returns>
+ 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);
+ bool found = false;
+
+ for (int i = 0; i < returnKey.ids.Count; i++)
+ {
+ if (id.Equals(returnKey.ids[i]))
+ {
+ IList certs = (IList) returnKey.idSigs[i];
+ found = certs.Contains(certification);
+
+ if (found)
+ {
+ certs.Remove(certification);
+ }
+ }
+ }
+
+ return found ? returnKey : null;
+ }
+
+ /// <summary>Add a revocation or some other key certification to a key.</summary>
+ /// <param name="key">The key the revocation is to be added to.</param>
+ /// <param name="certification">The key signature to be added.</param>
+ /// <returns>The new changed public key object.</returns>
+ public static PgpPublicKey AddCertification(
+ PgpPublicKey key,
+ PgpSignature certification)
+ {
+ if (key.IsMasterKey)
+ {
+ if (certification.SignatureType == PgpSignature.SubkeyRevocation)
+ {
+ throw new ArgumentException("signature type incorrect for master key revocation.");
+ }
+ }
+ else
+ {
+ if (certification.SignatureType == PgpSignature.KeyRevocation)
+ {
+ throw new ArgumentException("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;
+ }
+
+ /// <summary>Remove a certification from the key.</summary>
+ /// <param name="key">The key the certifications are to be removed from.</param>
+ /// <param name="certification">The certfication to be removed.</param>
+ /// <returns>The modified key, null if the certification was not found.</returns>
+ public static PgpPublicKey RemoveCertification(
+ PgpPublicKey key,
+ PgpSignature certification)
+ {
+ PgpPublicKey returnKey = new PgpPublicKey(key);
+ IList sigs = returnKey.subSigs != null
+ ? returnKey.subSigs
+ : returnKey.keySigs;
+
+// bool found = sigs.Remove(certification);
+ int pos = sigs.IndexOf(certification);
+ bool found = pos >= 0;
+
+ if (found)
+ {
+ sigs.RemoveAt(pos);
+ }
+ else
+ {
+ foreach (String id in key.GetUserIds())
+ {
+ foreach (object sig in key.GetSignaturesForId(id))
+ {
+ // TODO Is this the right type of equality test?
+ if (certification == sig)
+ {
+ found = true;
+ returnKey = PgpPublicKey.RemoveCertification(returnKey, id, certification);
+ }
+ }
+ }
+
+ if (!found)
+ {
+ foreach (PgpUserAttributeSubpacketVector id in key.GetUserAttributes())
+ {
+ foreach (object sig in key.GetSignaturesForUserAttribute(id))
+ {
+ // TODO Is this the right type of equality test?
+ if (certification == sig)
+ {
+ found = true;
+ returnKey = PgpPublicKey.RemoveCertification(returnKey, id, certification);
+ }
+ }
+ }
+ }
+ }
+
+ return returnKey;
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpPublicKeyEncryptedData.cs b/Crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
new file mode 100644
index 000000000..b6504cbcd
--- /dev/null
+++ b/Crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
@@ -0,0 +1,252 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.IO;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>A public key encrypted data object.</remarks>
+ public class PgpPublicKeyEncryptedData
+ : PgpEncryptedData
+ {
+ private PublicKeyEncSessionPacket keyData;
+
+ internal PgpPublicKeyEncryptedData(
+ PublicKeyEncSessionPacket keyData,
+ InputStreamPacket encData)
+ : base(encData)
+ {
+ this.keyData = keyData;
+ }
+
+ private static IBufferedCipher GetKeyCipher(
+ PublicKeyAlgorithmTag algorithm)
+ {
+ try
+ {
+ switch (algorithm)
+ {
+ case PublicKeyAlgorithmTag.RsaEncrypt:
+ case PublicKeyAlgorithmTag.RsaGeneral:
+ return CipherUtilities.GetCipher("RSA//PKCS1Padding");
+ case PublicKeyAlgorithmTag.ElGamalEncrypt:
+ case PublicKeyAlgorithmTag.ElGamalGeneral:
+ return CipherUtilities.GetCipher("ElGamal/ECB/PKCS1Padding");
+ default:
+ throw new PgpException("unknown asymmetric algorithm: " + algorithm);
+ }
+ }
+ catch (PgpException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("Exception creating cipher", e);
+ }
+ }
+
+ private bool 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));
+ }
+
+ /// <summary>The key ID for the key used to encrypt the data.</summary>
+ public long KeyId
+ {
+ get { return keyData.KeyId; }
+ }
+
+ /// <summary>
+ /// Return the algorithm code for the symmetric algorithm used to encrypt the data.
+ /// </summary>
+ public SymmetricKeyAlgorithmTag GetSymmetricAlgorithm(
+ PgpPrivateKey privKey)
+ {
+ byte[] plain = fetchSymmetricKeyData(privKey);
+
+ return (SymmetricKeyAlgorithmTag) plain[0];
+ }
+
+ /// <summary>Return the decrypted data stream for the packet.</summary>
+ public Stream GetDataStream(
+ PgpPrivateKey privKey)
+ {
+ byte[] plain = fetchSymmetricKeyData(privKey);
+
+ IBufferedCipher c2;
+ string cipherName = PgpUtilities.GetSymmetricCipherName((SymmetricKeyAlgorithmTag) plain[0]);
+ string cName = cipherName;
+
+ try
+ {
+ if (encData is SymmetricEncIntegrityPacket)
+ {
+ cName += "/CFB/NoPadding";
+ }
+ else
+ {
+ cName += "/OpenPGPCFB/NoPadding";
+ }
+
+ c2 = CipherUtilities.GetCipher(cName);
+ }
+ catch (PgpException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("exception creating cipher", e);
+ }
+
+ if (c2 == null)
+ return encData.GetInputStream();
+
+ try
+ {
+ KeyParameter key = ParameterUtilities.CreateKeyParameter(
+ cipherName, plain, 1, plain.Length - 3);
+
+ byte[] iv = new byte[c2.GetBlockSize()];
+
+ c2.Init(false, new ParametersWithIV(key, iv));
+
+ encStream = BcpgInputStream.Wrap(new CipherStream(encData.GetInputStream(), c2, null));
+
+ if (encData is SymmetricEncIntegrityPacket)
+ {
+ truncStream = new TruncatedStream(encStream);
+
+ string digestName = PgpUtilities.GetDigestName(HashAlgorithmTag.Sha1);
+ IDigest digest = DigestUtilities.GetDigest(digestName);
+
+ encStream = new DigestStream(truncStream, digest, null);
+ }
+
+ if (Streams.ReadFully(encStream, iv, 0, iv.Length) < iv.Length)
+ throw new EndOfStreamException("unexpected end of stream.");
+
+ int v1 = encStream.ReadByte();
+ int v2 = encStream.ReadByte();
+
+ if (v1 < 0 || v2 < 0)
+ throw new EndOfStreamException("unexpected end of stream.");
+
+ // Note: the oracle attack on the "quick check" bytes is deemed
+ // a security risk for typical public key encryption usages,
+ // therefore we do not perform the check.
+
+// bool 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
+// bool zeroesCheckPassed =
+// v1 == 0
+// && v2 == 0;
+//
+// if (!repeatCheckPassed && !zeroesCheckPassed)
+// {
+// throw new PgpDataValidationException("quick check failed.");
+// }
+
+ return encStream;
+ }
+ catch (PgpException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("Exception starting decryption", e);
+ }
+ }
+
+ private byte[] fetchSymmetricKeyData(
+ PgpPrivateKey privKey)
+ {
+ IBufferedCipher c1 = GetKeyCipher(keyData.Algorithm);
+
+ try
+ {
+ c1.Init(false, privKey.Key);
+ }
+ catch (InvalidKeyException e)
+ {
+ throw new PgpException("error setting asymmetric cipher", e);
+ }
+
+ BigInteger[] keyD = keyData.GetEncSessionKey();
+
+ if (keyData.Algorithm == PublicKeyAlgorithmTag.RsaEncrypt
+ || keyData.Algorithm == PublicKeyAlgorithmTag.RsaGeneral)
+ {
+ c1.ProcessBytes(keyD[0].ToByteArrayUnsigned());
+ }
+ else
+ {
+ ElGamalPrivateKeyParameters k = (ElGamalPrivateKeyParameters)privKey.Key;
+ int size = (k.Parameters.P.BitLength + 7) / 8;
+
+ byte[] bi = keyD[0].ToByteArray();
+
+ int diff = bi.Length - size;
+ if (diff >= 0)
+ {
+ c1.ProcessBytes(bi, diff, size);
+ }
+ else
+ {
+ byte[] zeros = new byte[-diff];
+ c1.ProcessBytes(zeros);
+ c1.ProcessBytes(bi);
+ }
+
+ bi = keyD[1].ToByteArray();
+
+ diff = bi.Length - size;
+ if (diff >= 0)
+ {
+ c1.ProcessBytes(bi, diff, size);
+ }
+ else
+ {
+ byte[] zeros = new byte[-diff];
+ c1.ProcessBytes(zeros);
+ c1.ProcessBytes(bi);
+ }
+ }
+
+ byte[] plain;
+ try
+ {
+ plain = c1.DoFinal();
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("exception decrypting secret key", e);
+ }
+
+ if (!ConfirmCheckSum(plain))
+ throw new PgpKeyValidationException("key checksum failed");
+
+ return plain;
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpPublicKeyRing.cs b/Crypto/src/openpgp/PgpPublicKeyRing.cs
new file mode 100644
index 000000000..ecb935e4b
--- /dev/null
+++ b/Crypto/src/openpgp/PgpPublicKeyRing.cs
@@ -0,0 +1,200 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Collections;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>
+ /// 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 <c>PgpPublicKeyRingBundle</c> class.
+ /// </p>
+ /// </remarks>
+ public class PgpPublicKeyRing
+ : PgpKeyRing
+ {
+ private readonly IList keys;
+
+ public PgpPublicKeyRing(
+ byte[] encoding)
+ : this(new MemoryStream(encoding, false))
+ {
+ }
+
+ internal PgpPublicKeyRing(
+ IList pubKeys)
+ {
+ this.keys = pubKeys;
+ }
+
+ public PgpPublicKeyRing(
+ Stream inputStream)
+ {
+ this.keys = Platform.CreateArrayList();
+
+ BcpgInputStream bcpgInput = BcpgInputStream.Wrap(inputStream);
+
+ PacketTag initialTag = bcpgInput.NextPacketTag();
+ if (initialTag != PacketTag.PublicKey && initialTag != PacketTag.PublicSubkey)
+ {
+ throw new IOException("public key ring doesn't start with public key tag: "
+ + "tag 0x" + ((int)initialTag).ToString("X"));
+ }
+
+ PublicKeyPacket pubPk = (PublicKeyPacket) bcpgInput.ReadPacket();;
+ TrustPacket trustPk = ReadOptionalTrustPacket(bcpgInput);
+
+ // direct signatures and revocations
+ IList keySigs = ReadSignaturesAndTrust(bcpgInput);
+
+ IList ids, idTrusts, idSigs;
+ ReadUserIDs(bcpgInput, out ids, out idTrusts, out idSigs);
+
+ keys.Add(new PgpPublicKey(pubPk, trustPk, keySigs, ids, idTrusts, idSigs));
+
+
+ // Read subkeys
+ while (bcpgInput.NextPacketTag() == PacketTag.PublicSubkey)
+ {
+ keys.Add(ReadSubkey(bcpgInput));
+ }
+ }
+
+ /// <summary>Return the first public key in the ring.</summary>
+ public PgpPublicKey GetPublicKey()
+ {
+ return (PgpPublicKey) keys[0];
+ }
+
+ /// <summary>Return the public key referred to by the passed in key ID if it is present.</summary>
+ public PgpPublicKey GetPublicKey(
+ long keyId)
+ {
+ foreach (PgpPublicKey k in keys)
+ {
+ if (keyId == k.KeyId)
+ {
+ return k;
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>Allows enumeration of all the public keys.</summary>
+ /// <returns>An <c>IEnumerable</c> of <c>PgpPublicKey</c> objects.</returns>
+ public IEnumerable GetPublicKeys()
+ {
+ return new EnumerableProxy(keys);
+ }
+
+ public byte[] GetEncoded()
+ {
+ MemoryStream bOut = new MemoryStream();
+
+ Encode(bOut);
+
+ return bOut.ToArray();
+ }
+
+ public void Encode(
+ Stream outStr)
+ {
+ if (outStr == null)
+ throw new ArgumentNullException("outStr");
+
+ foreach (PgpPublicKey k in keys)
+ {
+ k.Encode(outStr);
+ }
+ }
+
+ /// <summary>
+ /// Returns a new key ring with the public key passed in either added or
+ /// replacing an existing one.
+ /// </summary>
+ /// <param name="pubRing">The public key ring to be modified.</param>
+ /// <param name="pubKey">The public key to be inserted.</param>
+ /// <returns>A new <c>PgpPublicKeyRing</c></returns>
+ public static PgpPublicKeyRing InsertPublicKey(
+ PgpPublicKeyRing pubRing,
+ PgpPublicKey pubKey)
+ {
+ IList keys = Platform.CreateArrayList(pubRing.keys);
+ bool found = false;
+ bool masterFound = false;
+
+ for (int i = 0; i != keys.Count; i++)
+ {
+ PgpPublicKey key = (PgpPublicKey) keys[i];
+
+ if (key.KeyId == pubKey.KeyId)
+ {
+ found = true;
+ keys[i] = pubKey;
+ }
+ if (key.IsMasterKey)
+ {
+ masterFound = true;
+ }
+ }
+
+ if (!found)
+ {
+ if (pubKey.IsMasterKey)
+ {
+ if (masterFound)
+ throw new ArgumentException("cannot add a master key to a ring that already has one");
+
+ keys.Insert(0, pubKey);
+ }
+ else
+ {
+ keys.Add(pubKey);
+ }
+ }
+
+ return new PgpPublicKeyRing(keys);
+ }
+
+ /// <summary>Returns a new key ring with the public key passed in removed from the key ring.</summary>
+ /// <param name="pubRing">The public key ring to be modified.</param>
+ /// <param name="pubKey">The public key to be removed.</param>
+ /// <returns>A new <c>PgpPublicKeyRing</c>, or null if pubKey is not found.</returns>
+ public static PgpPublicKeyRing RemovePublicKey(
+ PgpPublicKeyRing pubRing,
+ PgpPublicKey pubKey)
+ {
+ IList keys = Platform.CreateArrayList(pubRing.keys);
+ bool found = false;
+
+ for (int i = 0; i < keys.Count; i++)
+ {
+ PgpPublicKey key = (PgpPublicKey) keys[i];
+
+ if (key.KeyId == pubKey.KeyId)
+ {
+ found = true;
+ keys.RemoveAt(i);
+ }
+ }
+
+ return found ? new PgpPublicKeyRing(keys) : null;
+ }
+
+ internal static PgpPublicKey ReadSubkey(BcpgInputStream bcpgInput)
+ {
+ PublicKeyPacket pk = (PublicKeyPacket) bcpgInput.ReadPacket();
+ TrustPacket kTrust = ReadOptionalTrustPacket(bcpgInput);
+
+ // PGP 8 actually leaves out the signature.
+ IList sigList = ReadSignaturesAndTrust(bcpgInput);
+
+ return new PgpPublicKey(pk, kTrust, sigList);
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpPublicKeyRingBundle.cs b/Crypto/src/openpgp/PgpPublicKeyRingBundle.cs
new file mode 100644
index 000000000..77cede4a1
--- /dev/null
+++ b/Crypto/src/openpgp/PgpPublicKeyRingBundle.cs
@@ -0,0 +1,280 @@
+using System;
+using System.Collections;
+using System.Globalization;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Collections;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>
+ /// 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.
+ /// </remarks>
+ public class PgpPublicKeyRingBundle
+ {
+ private readonly IDictionary pubRings;
+ private readonly IList order;
+
+ private PgpPublicKeyRingBundle(
+ IDictionary pubRings,
+ IList order)
+ {
+ this.pubRings = pubRings;
+ this.order = order;
+ }
+
+ public PgpPublicKeyRingBundle(
+ byte[] encoding)
+ : this(new MemoryStream(encoding, false))
+ {
+ }
+
+ /// <summary>Build a PgpPublicKeyRingBundle from the passed in input stream.</summary>
+ /// <param name="inputStream">Input stream containing data.</param>
+ /// <exception cref="IOException">If a problem parsing the stream occurs.</exception>
+ /// <exception cref="PgpException">If an object is encountered which isn't a PgpPublicKeyRing.</exception>
+ public PgpPublicKeyRingBundle(
+ Stream inputStream)
+ : this(new PgpObjectFactory(inputStream).AllPgpObjects())
+ {
+ }
+
+ public PgpPublicKeyRingBundle(
+ IEnumerable e)
+ {
+ this.pubRings = Platform.CreateHashtable();
+ this.order = Platform.CreateArrayList();
+
+ foreach (object obj in e)
+ {
+ PgpPublicKeyRing pgpPub = obj as PgpPublicKeyRing;
+
+ if (pgpPub == null)
+ {
+ throw new PgpException(obj.GetType().FullName + " found where PgpPublicKeyRing expected");
+ }
+
+ long key = pgpPub.GetPublicKey().KeyId;
+ pubRings.Add(key, pgpPub);
+ order.Add(key);
+ }
+ }
+
+ [Obsolete("Use 'Count' property instead")]
+ public int Size
+ {
+ get { return order.Count; }
+ }
+
+ /// <summary>Return the number of key rings in this collection.</summary>
+ public int Count
+ {
+ get { return order.Count; }
+ }
+
+ /// <summary>Allow enumeration of the public key rings making up this collection.</summary>
+ public IEnumerable GetKeyRings()
+ {
+ return new EnumerableProxy(pubRings.Values);
+ }
+
+ /// <summary>Allow enumeration of the key rings associated with the passed in userId.</summary>
+ /// <param name="userId">The user ID to be matched.</param>
+ /// <returns>An <c>IEnumerable</c> of key rings which matched (possibly none).</returns>
+ public IEnumerable GetKeyRings(
+ string userId)
+ {
+ return GetKeyRings(userId, false, false);
+ }
+
+ /// <summary>Allow enumeration of the key rings associated with the passed in userId.</summary>
+ /// <param name="userId">The user ID to be matched.</param>
+ /// <param name="matchPartial">If true, userId need only be a substring of an actual ID string to match.</param>
+ /// <returns>An <c>IEnumerable</c> of key rings which matched (possibly none).</returns>
+ public IEnumerable GetKeyRings(
+ string userId,
+ bool matchPartial)
+ {
+ return GetKeyRings(userId, matchPartial, false);
+ }
+
+ /// <summary>Allow enumeration of the key rings associated with the passed in userId.</summary>
+ /// <param name="userId">The user ID to be matched.</param>
+ /// <param name="matchPartial">If true, userId need only be a substring of an actual ID string to match.</param>
+ /// <param name="ignoreCase">If true, case is ignored in user ID comparisons.</param>
+ /// <returns>An <c>IEnumerable</c> of key rings which matched (possibly none).</returns>
+ public IEnumerable GetKeyRings(
+ string userId,
+ bool matchPartial,
+ bool ignoreCase)
+ {
+ IList rings = Platform.CreateArrayList();
+
+ if (ignoreCase)
+ {
+ userId = userId.ToLowerInvariant();
+ }
+
+ foreach (PgpPublicKeyRing pubRing in GetKeyRings())
+ {
+ foreach (string nextUserID in pubRing.GetPublicKey().GetUserIds())
+ {
+ string next = nextUserID;
+ if (ignoreCase)
+ {
+ next = next.ToLowerInvariant();
+ }
+
+ if (matchPartial)
+ {
+ if (next.IndexOf(userId) > -1)
+ {
+ rings.Add(pubRing);
+ }
+ }
+ else
+ {
+ if (next.Equals(userId))
+ {
+ rings.Add(pubRing);
+ }
+ }
+ }
+ }
+
+ return new EnumerableProxy(rings);
+ }
+
+ /// <summary>Return the PGP public key associated with the given key id.</summary>
+ /// <param name="keyId">The ID of the public key to return.</param>
+ public PgpPublicKey GetPublicKey(
+ long keyId)
+ {
+ foreach (PgpPublicKeyRing pubRing in GetKeyRings())
+ {
+ PgpPublicKey pub = pubRing.GetPublicKey(keyId);
+
+ if (pub != null)
+ {
+ return pub;
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>Return the public key ring which contains the key referred to by keyId</summary>
+ /// <param name="keyId">key ID to match against</param>
+ public PgpPublicKeyRing GetPublicKeyRing(
+ long keyId)
+ {
+ if (pubRings.Contains(keyId))
+ {
+ return (PgpPublicKeyRing)pubRings[keyId];
+ }
+
+ foreach (PgpPublicKeyRing pubRing in GetKeyRings())
+ {
+ PgpPublicKey pub = pubRing.GetPublicKey(keyId);
+
+ if (pub != null)
+ {
+ return pubRing;
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Return true if a key matching the passed in key ID is present, false otherwise.
+ /// </summary>
+ /// <param name="keyID">key ID to look for.</param>
+ public bool Contains(
+ long keyID)
+ {
+ return GetPublicKey(keyID) != null;
+ }
+
+ public byte[] GetEncoded()
+ {
+ MemoryStream bOut = new MemoryStream();
+
+ Encode(bOut);
+
+ return bOut.ToArray();
+ }
+
+ public void Encode(
+ Stream outStr)
+ {
+ BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStr);
+
+ foreach (long key in order)
+ {
+ PgpPublicKeyRing sec = (PgpPublicKeyRing) pubRings[key];
+
+ sec.Encode(bcpgOut);
+ }
+ }
+
+ /// <summary>
+ /// Return a new bundle containing the contents of the passed in bundle and
+ /// the passed in public key ring.
+ /// </summary>
+ /// <param name="bundle">The <c>PgpPublicKeyRingBundle</c> the key ring is to be added to.</param>
+ /// <param name="publicKeyRing">The key ring to be added.</param>
+ /// <returns>A new <c>PgpPublicKeyRingBundle</c> merging the current one with the passed in key ring.</returns>
+ /// <exception cref="ArgumentException">If the keyId for the passed in key ring is already present.</exception>
+ public static PgpPublicKeyRingBundle AddPublicKeyRing(
+ PgpPublicKeyRingBundle bundle,
+ PgpPublicKeyRing publicKeyRing)
+ {
+ long key = publicKeyRing.GetPublicKey().KeyId;
+
+ if (bundle.pubRings.Contains(key))
+ {
+ throw new ArgumentException("Bundle already contains a key with a keyId for the passed in ring.");
+ }
+
+ IDictionary newPubRings = Platform.CreateHashtable(bundle.pubRings);
+ IList newOrder = Platform.CreateArrayList(bundle.order);
+
+ newPubRings[key] = publicKeyRing;
+
+ newOrder.Add(key);
+
+ return new PgpPublicKeyRingBundle(newPubRings, newOrder);
+ }
+
+ /// <summary>
+ /// Return a new bundle containing the contents of the passed in bundle with
+ /// the passed in public key ring removed.
+ /// </summary>
+ /// <param name="bundle">The <c>PgpPublicKeyRingBundle</c> the key ring is to be removed from.</param>
+ /// <param name="publicKeyRing">The key ring to be removed.</param>
+ /// <returns>A new <c>PgpPublicKeyRingBundle</c> not containing the passed in key ring.</returns>
+ /// <exception cref="ArgumentException">If the keyId for the passed in key ring is not present.</exception>
+ public static PgpPublicKeyRingBundle RemovePublicKeyRing(
+ PgpPublicKeyRingBundle bundle,
+ PgpPublicKeyRing publicKeyRing)
+ {
+ long key = publicKeyRing.GetPublicKey().KeyId;
+
+ if (!bundle.pubRings.Contains(key))
+ {
+ throw new ArgumentException("Bundle does not contain a key with a keyId for the passed in ring.");
+ }
+
+ IDictionary newPubRings = Platform.CreateHashtable(bundle.pubRings);
+ IList newOrder = Platform.CreateArrayList(bundle.order);
+
+ newPubRings.Remove(key);
+ newOrder.Remove(key);
+
+ return new PgpPublicKeyRingBundle(newPubRings, newOrder);
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpSecretKey.cs b/Crypto/src/openpgp/PgpSecretKey.cs
new file mode 100644
index 000000000..9d87f49c8
--- /dev/null
+++ b/Crypto/src/openpgp/PgpSecretKey.cs
@@ -0,0 +1,666 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>General class to handle a PGP secret key object.</remarks>
+ public class PgpSecretKey
+ {
+ private readonly SecretKeyPacket secret;
+ private readonly PgpPublicKey pub;
+
+ internal PgpSecretKey(
+ SecretKeyPacket secret,
+ PgpPublicKey pub)
+ {
+ this.secret = secret;
+ this.pub = pub;
+ }
+
+ internal PgpSecretKey(
+ PgpPrivateKey privKey,
+ PgpPublicKey pubKey,
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ char[] passPhrase,
+ bool useSha1,
+ SecureRandom rand)
+ : this(privKey, pubKey, encAlgorithm, passPhrase, useSha1, rand, false)
+ {
+ }
+
+ internal PgpSecretKey(
+ PgpPrivateKey privKey,
+ PgpPublicKey pubKey,
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ char[] passPhrase,
+ bool useSha1,
+ SecureRandom rand,
+ bool isMasterKey)
+ {
+ BcpgObject secKey;
+
+ this.pub = pubKey;
+
+ switch (pubKey.Algorithm)
+ {
+ case PublicKeyAlgorithmTag.RsaEncrypt:
+ case PublicKeyAlgorithmTag.RsaSign:
+ case PublicKeyAlgorithmTag.RsaGeneral:
+ RsaPrivateCrtKeyParameters rsK = (RsaPrivateCrtKeyParameters) privKey.Key;
+ secKey = new RsaSecretBcpgKey(rsK.Exponent, rsK.P, rsK.Q);
+ break;
+ case PublicKeyAlgorithmTag.Dsa:
+ DsaPrivateKeyParameters dsK = (DsaPrivateKeyParameters) privKey.Key;
+ secKey = new DsaSecretBcpgKey(dsK.X);
+ break;
+ case PublicKeyAlgorithmTag.ElGamalEncrypt:
+ case PublicKeyAlgorithmTag.ElGamalGeneral:
+ ElGamalPrivateKeyParameters esK = (ElGamalPrivateKeyParameters) privKey.Key;
+ secKey = new ElGamalSecretBcpgKey(esK.X);
+ break;
+ default:
+ throw new PgpException("unknown key class");
+ }
+
+ try
+ {
+ MemoryStream bOut = new MemoryStream();
+ BcpgOutputStream pOut = new BcpgOutputStream(bOut);
+
+ pOut.WriteObject(secKey);
+
+ byte[] keyData = bOut.ToArray();
+ byte[] checksumBytes = Checksum(useSha1, keyData, keyData.Length);
+
+ pOut.Write(checksumBytes);
+
+ byte[] bOutData = bOut.ToArray();
+
+ if (encAlgorithm == SymmetricKeyAlgorithmTag.Null)
+ {
+ if (isMasterKey)
+ {
+ this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, null, null, bOutData);
+ }
+ else
+ {
+ this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, null, null, bOutData);
+ }
+ }
+ else
+ {
+ S2k s2k;
+ byte[] iv;
+ byte[] encData = EncryptKeyData(bOutData, encAlgorithm, passPhrase, rand, out s2k, out iv);
+
+ int s2kUsage = useSha1
+ ? SecretKeyPacket.UsageSha1
+ : SecretKeyPacket.UsageChecksum;
+
+ if (isMasterKey)
+ {
+ this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, s2kUsage, s2k, iv, encData);
+ }
+ else
+ {
+ this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, s2kUsage, s2k, iv, encData);
+ }
+ }
+ }
+ catch (PgpException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("Exception encrypting key", e);
+ }
+ }
+
+ public PgpSecretKey(
+ int certificationLevel,
+ PgpKeyPair keyPair,
+ string id,
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ char[] passPhrase,
+ PgpSignatureSubpacketVector hashedPackets,
+ PgpSignatureSubpacketVector unhashedPackets,
+ SecureRandom rand)
+ : this(certificationLevel, keyPair, id, encAlgorithm, passPhrase, false, hashedPackets, unhashedPackets, rand)
+ {
+ }
+
+ public PgpSecretKey(
+ int certificationLevel,
+ PgpKeyPair keyPair,
+ string id,
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ char[] passPhrase,
+ bool useSha1,
+ PgpSignatureSubpacketVector hashedPackets,
+ PgpSignatureSubpacketVector unhashedPackets,
+ SecureRandom rand)
+ : this(keyPair.PrivateKey, certifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets), encAlgorithm, passPhrase, useSha1, rand, true)
+ {
+ }
+
+ private static PgpPublicKey certifiedPublicKey(
+ int certificationLevel,
+ PgpKeyPair keyPair,
+ string id,
+ PgpSignatureSubpacketVector hashedPackets,
+ PgpSignatureSubpacketVector unhashedPackets)
+ {
+ PgpSignatureGenerator sGen;
+ try
+ {
+ sGen = new PgpSignatureGenerator(keyPair.PublicKey.Algorithm, HashAlgorithmTag.Sha1);
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("Creating signature generator: " + e.Message, e);
+ }
+
+ //
+ // Generate the certification
+ //
+ sGen.InitSign(certificationLevel, keyPair.PrivateKey);
+
+ sGen.SetHashedSubpackets(hashedPackets);
+ sGen.SetUnhashedSubpackets(unhashedPackets);
+
+ try
+ {
+ PgpSignature certification = sGen.GenerateCertification(id, keyPair.PublicKey);
+ return PgpPublicKey.AddCertification(keyPair.PublicKey, id, certification);
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("Exception doing certification: " + e.Message, e);
+ }
+ }
+
+ public PgpSecretKey(
+ int certificationLevel,
+ PublicKeyAlgorithmTag algorithm,
+ AsymmetricKeyParameter pubKey,
+ AsymmetricKeyParameter privKey,
+ DateTime time,
+ string id,
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ char[] passPhrase,
+ PgpSignatureSubpacketVector hashedPackets,
+ PgpSignatureSubpacketVector unhashedPackets,
+ SecureRandom rand)
+ : this(certificationLevel,
+ new PgpKeyPair(algorithm, pubKey, privKey, time),
+ id, encAlgorithm, passPhrase, hashedPackets, unhashedPackets, rand)
+ {
+ }
+
+ public PgpSecretKey(
+ int certificationLevel,
+ PublicKeyAlgorithmTag algorithm,
+ AsymmetricKeyParameter pubKey,
+ AsymmetricKeyParameter privKey,
+ DateTime time,
+ string id,
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ char[] passPhrase,
+ bool useSha1,
+ PgpSignatureSubpacketVector hashedPackets,
+ PgpSignatureSubpacketVector unhashedPackets,
+ SecureRandom rand)
+ : this(certificationLevel, new PgpKeyPair(algorithm, pubKey, privKey, time), id, encAlgorithm, passPhrase, useSha1, hashedPackets, unhashedPackets, rand)
+ {
+ }
+
+ /// <summary>
+ /// Check if this key has an algorithm type that makes it suitable to use for signing.
+ /// </summary>
+ /// <remarks>
+ /// Note: with version 4 keys KeyFlags subpackets should also be considered when present for
+ /// determining the preferred use of the key.
+ /// </remarks>
+ /// <returns>
+ /// <c>true</c> if this key algorithm is suitable for use with signing.
+ /// </returns>
+ public bool IsSigningKey
+ {
+ get
+ {
+ switch (pub.Algorithm)
+ {
+ case PublicKeyAlgorithmTag.RsaGeneral:
+ case PublicKeyAlgorithmTag.RsaSign:
+ case PublicKeyAlgorithmTag.Dsa:
+ case PublicKeyAlgorithmTag.ECDsa:
+ case PublicKeyAlgorithmTag.ElGamalGeneral:
+ return true;
+ default:
+ return false;
+ }
+ }
+ }
+
+ /// <summary>True, if this is a master key.</summary>
+ public bool IsMasterKey
+ {
+ get { return pub.IsMasterKey; }
+ }
+
+ /// <summary>The algorithm the key is encrypted with.</summary>
+ public SymmetricKeyAlgorithmTag KeyEncryptionAlgorithm
+ {
+ get { return secret.EncAlgorithm; }
+ }
+
+ /// <summary>The key ID of the public key associated with this key.</summary>
+ public long KeyId
+ {
+ get { return pub.KeyId; }
+ }
+
+ /// <summary>The public key associated with this key.</summary>
+ public PgpPublicKey PublicKey
+ {
+ get { return pub; }
+ }
+
+ /// <summary>Allows enumeration of any user IDs associated with the key.</summary>
+ /// <returns>An <c>IEnumerable</c> of <c>string</c> objects.</returns>
+ public IEnumerable UserIds
+ {
+ get { return pub.GetUserIds(); }
+ }
+
+ /// <summary>Allows enumeration of any user attribute vectors associated with the key.</summary>
+ /// <returns>An <c>IEnumerable</c> of <c>string</c> objects.</returns>
+ public IEnumerable UserAttributes
+ {
+ get { return pub.GetUserAttributes(); }
+ }
+
+ private byte[] ExtractKeyData(
+ char[] passPhrase)
+ {
+ SymmetricKeyAlgorithmTag alg = secret.EncAlgorithm;
+ byte[] encData = secret.GetSecretKeyData();
+
+ if (alg == SymmetricKeyAlgorithmTag.Null)
+ return encData;
+
+ byte[] data;
+ IBufferedCipher c = null;
+ try
+ {
+ string cName = PgpUtilities.GetSymmetricCipherName(alg);
+ c = CipherUtilities.GetCipher(cName + "/CFB/NoPadding");
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("Exception creating cipher", e);
+ }
+
+ // TODO Factor this block out as 'encryptData'
+ try
+ {
+ KeyParameter key = PgpUtilities.MakeKeyFromPassPhrase(secret.EncAlgorithm, secret.S2k, passPhrase);
+ byte[] iv = secret.GetIV();
+
+ if (secret.PublicKeyPacket.Version == 4)
+ {
+ c.Init(false, new ParametersWithIV(key, iv));
+
+ data = c.DoFinal(encData);
+
+ bool useSha1 = secret.S2kUsage == SecretKeyPacket.UsageSha1;
+ byte[] check = Checksum(useSha1, 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.
+ {
+ data = new byte[encData.Length];
+
+ //
+ // read in the four numbers
+ //
+ int pos = 0;
+
+ for (int i = 0; i != 4; i++)
+ {
+ c.Init(false, new ParametersWithIV(key, iv));
+
+ int encLen = (((encData[pos] << 8) | (encData[pos + 1] & 0xff)) + 7) / 8;
+
+ data[pos] = encData[pos];
+ data[pos + 1] = encData[pos + 1];
+ pos += 2;
+
+ c.DoFinal(encData, pos, encLen, data, pos);
+ pos += encLen;
+
+ if (i != 3)
+ {
+ Array.Copy(encData, pos - iv.Length, iv, 0, iv.Length);
+ }
+ }
+
+ //
+ // verify Checksum
+ //
+ 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 "
+ + cs.ToString("X")
+ + " found " + calcCs.ToString("X"));
+ }
+ }
+
+ return data;
+ }
+ catch (PgpException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("Exception decrypting key", e);
+ }
+ }
+
+ /// <summary>Extract a <c>PgpPrivateKey</c> from this secret key's encrypted contents.</summary>
+ public PgpPrivateKey ExtractPrivateKey(
+ char[] passPhrase)
+ {
+ byte[] secKeyData = secret.GetSecretKeyData();
+ if (secKeyData == null || secKeyData.Length < 1)
+ return null;
+
+ PublicKeyPacket pubPk = secret.PublicKeyPacket;
+ try
+ {
+ byte[] data = ExtractKeyData(passPhrase);
+ BcpgInputStream bcpgIn = BcpgInputStream.Wrap(new MemoryStream(data, false));
+ AsymmetricKeyParameter privateKey;
+ switch (pubPk.Algorithm)
+ {
+ case PublicKeyAlgorithmTag.RsaEncrypt:
+ case PublicKeyAlgorithmTag.RsaGeneral:
+ case PublicKeyAlgorithmTag.RsaSign:
+ RsaPublicBcpgKey rsaPub = (RsaPublicBcpgKey)pubPk.Key;
+ RsaSecretBcpgKey rsaPriv = new RsaSecretBcpgKey(bcpgIn);
+ RsaPrivateCrtKeyParameters rsaPrivSpec = new RsaPrivateCrtKeyParameters(
+ rsaPriv.Modulus,
+ rsaPub.PublicExponent,
+ rsaPriv.PrivateExponent,
+ rsaPriv.PrimeP,
+ rsaPriv.PrimeQ,
+ rsaPriv.PrimeExponentP,
+ rsaPriv.PrimeExponentQ,
+ rsaPriv.CrtCoefficient);
+ privateKey = rsaPrivSpec;
+ break;
+ case PublicKeyAlgorithmTag.Dsa:
+ DsaPublicBcpgKey dsaPub = (DsaPublicBcpgKey)pubPk.Key;
+ DsaSecretBcpgKey dsaPriv = new DsaSecretBcpgKey(bcpgIn);
+ DsaParameters dsaParams = new DsaParameters(dsaPub.P, dsaPub.Q, dsaPub.G);
+ privateKey = new DsaPrivateKeyParameters(dsaPriv.X, dsaParams);
+ break;
+ case PublicKeyAlgorithmTag.ElGamalEncrypt:
+ case PublicKeyAlgorithmTag.ElGamalGeneral:
+ ElGamalPublicBcpgKey elPub = (ElGamalPublicBcpgKey)pubPk.Key;
+ ElGamalSecretBcpgKey elPriv = new ElGamalSecretBcpgKey(bcpgIn);
+ ElGamalParameters elParams = new ElGamalParameters(elPub.P, elPub.G);
+ privateKey = new ElGamalPrivateKeyParameters(elPriv.X, elParams);
+ break;
+ default:
+ throw new PgpException("unknown public key algorithm encountered");
+ }
+
+ return new PgpPrivateKey(privateKey, KeyId);
+ }
+ catch (PgpException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("Exception constructing key", e);
+ }
+ }
+
+ private static byte[] Checksum(
+ bool useSha1,
+ byte[] bytes,
+ int length)
+ {
+ if (useSha1)
+ {
+ try
+ {
+ IDigest dig = DigestUtilities.GetDigest("SHA1");
+ dig.BlockUpdate(bytes, 0, length);
+ return DigestUtilities.DoFinal(dig);
+ }
+ //catch (NoSuchAlgorithmException e)
+ catch (Exception e)
+ {
+ throw new PgpException("Can't find SHA-1", e);
+ }
+ }
+ else
+ {
+ int Checksum = 0;
+ for (int i = 0; i != length; i++)
+ {
+ Checksum += bytes[i];
+ }
+
+ return new byte[] { (byte)(Checksum >> 8), (byte)Checksum };
+ }
+ }
+
+ public byte[] GetEncoded()
+ {
+ MemoryStream bOut = new MemoryStream();
+ Encode(bOut);
+ return bOut.ToArray();
+ }
+
+ public void Encode(
+ Stream outStr)
+ {
+ BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStr);
+
+ bcpgOut.WritePacket(secret);
+ if (pub.trustPk != null)
+ {
+ bcpgOut.WritePacket(pub.trustPk);
+ }
+
+ if (pub.subSigs == null) // is not a sub key
+ {
+ foreach (PgpSignature keySig in pub.keySigs)
+ {
+ keySig.Encode(bcpgOut);
+ }
+
+ for (int i = 0; i != pub.ids.Count; i++)
+ {
+ object pubID = pub.ids[i];
+ if (pubID is string)
+ {
+ string id = (string) pubID;
+ bcpgOut.WritePacket(new UserIdPacket(id));
+ }
+ else
+ {
+ PgpUserAttributeSubpacketVector v = (PgpUserAttributeSubpacketVector) pubID;
+ bcpgOut.WritePacket(new UserAttributePacket(v.ToSubpacketArray()));
+ }
+
+ if (pub.idTrusts[i] != null)
+ {
+ bcpgOut.WritePacket((ContainedPacket)pub.idTrusts[i]);
+ }
+
+ foreach (PgpSignature sig in (IList) pub.idSigs[i])
+ {
+ sig.Encode(bcpgOut);
+ }
+ }
+ }
+ else
+ {
+ foreach (PgpSignature subSig in pub.subSigs)
+ {
+ subSig.Encode(bcpgOut);
+ }
+ }
+
+ // TODO Check that this is right/necessary
+ //bcpgOut.Finish();
+ }
+
+ /// <summary>
+ /// Return a copy of the passed in secret key, encrypted using a new password
+ /// and the passed in algorithm.
+ /// </summary>
+ /// <param name="key">The PgpSecretKey to be copied.</param>
+ /// <param name="oldPassPhrase">The current password for the key.</param>
+ /// <param name="newPassPhrase">The new password for the key.</param>
+ /// <param name="newEncAlgorithm">The algorithm to be used for the encryption.</param>
+ /// <param name="rand">Source of randomness.</param>
+ public static PgpSecretKey CopyWithNewPassword(
+ PgpSecretKey key,
+ char[] oldPassPhrase,
+ char[] newPassPhrase,
+ SymmetricKeyAlgorithmTag newEncAlgorithm,
+ SecureRandom rand)
+ {
+ byte[] rawKeyData = key.ExtractKeyData(oldPassPhrase);
+ int s2kUsage = key.secret.S2kUsage;
+ byte[] iv = null;
+ S2k s2k = null;
+ byte[] keyData;
+
+ if (newEncAlgorithm == SymmetricKeyAlgorithmTag.Null)
+ {
+ s2kUsage = SecretKeyPacket.UsageNone;
+ if (key.secret.S2kUsage == SecretKeyPacket.UsageSha1) // SHA-1 hash, need to rewrite Checksum
+ {
+ keyData = new byte[rawKeyData.Length - 18];
+
+ Array.Copy(rawKeyData, 0, keyData, 0, keyData.Length - 2);
+
+ byte[] check = Checksum(false, keyData, keyData.Length - 2);
+
+ keyData[keyData.Length - 2] = check[0];
+ keyData[keyData.Length - 1] = check[1];
+ }
+ else
+ {
+ keyData = rawKeyData;
+ }
+ }
+ else
+ {
+ try
+ {
+ keyData = EncryptKeyData(rawKeyData, newEncAlgorithm, newPassPhrase, rand, out s2k, out iv);
+ }
+ catch (PgpException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("Exception encrypting key", e);
+ }
+ }
+
+ SecretKeyPacket secret;
+ if (key.secret is SecretSubkeyPacket)
+ {
+ secret = new SecretSubkeyPacket(key.secret.PublicKeyPacket,
+ newEncAlgorithm, s2kUsage, s2k, iv, keyData);
+ }
+ else
+ {
+ secret = new SecretKeyPacket(key.secret.PublicKeyPacket,
+ newEncAlgorithm, s2kUsage, s2k, iv, keyData);
+ }
+
+ return new PgpSecretKey(secret, key.pub);
+ }
+
+ /// <summary>Replace the passed the public key on the passed in secret key.</summary>
+ /// <param name="secretKey">Secret key to change.</param>
+ /// <param name="publicKey">New public key.</param>
+ /// <returns>A new secret key.</returns>
+ /// <exception cref="ArgumentException">If KeyId's do not match.</exception>
+ public static PgpSecretKey ReplacePublicKey(
+ PgpSecretKey secretKey,
+ PgpPublicKey publicKey)
+ {
+ if (publicKey.KeyId != secretKey.KeyId)
+ throw new ArgumentException("KeyId's do not match");
+
+ return new PgpSecretKey(secretKey.secret, publicKey);
+ }
+
+ private static byte[] EncryptKeyData(
+ byte[] rawKeyData,
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ char[] passPhrase,
+ SecureRandom random,
+ out S2k s2k,
+ out byte[] iv)
+ {
+ IBufferedCipher c;
+ try
+ {
+ string cName = PgpUtilities.GetSymmetricCipherName(encAlgorithm);
+ c = CipherUtilities.GetCipher(cName + "/CFB/NoPadding");
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("Exception creating cipher", e);
+ }
+
+ byte[] s2kIV = new byte[8];
+ random.NextBytes(s2kIV);
+ s2k = new S2k(HashAlgorithmTag.Sha1, s2kIV, 0x60);
+
+ KeyParameter kp = PgpUtilities.MakeKeyFromPassPhrase(encAlgorithm, s2k, passPhrase);
+
+ iv = new byte[c.GetBlockSize()];
+ random.NextBytes(iv);
+
+ c.Init(true, new ParametersWithRandom(new ParametersWithIV(kp, iv), random));
+
+ return c.DoFinal(rawKeyData);
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpSecretKeyRing.cs b/Crypto/src/openpgp/PgpSecretKeyRing.cs
new file mode 100644
index 000000000..3e646eaa1
--- /dev/null
+++ b/Crypto/src/openpgp/PgpSecretKeyRing.cs
@@ -0,0 +1,301 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Collections;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>
+ /// 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 <c>PgpSecretKeyRingBundle</c> class.
+ /// </p>
+ /// </remarks>
+ public class PgpSecretKeyRing
+ : PgpKeyRing
+ {
+ private readonly IList keys;
+ private readonly IList extraPubKeys;
+
+ internal PgpSecretKeyRing(
+ IList keys)
+ : this(keys, Platform.CreateArrayList())
+ {
+ }
+
+ private PgpSecretKeyRing(
+ IList keys,
+ IList extraPubKeys)
+ {
+ this.keys = keys;
+ this.extraPubKeys = extraPubKeys;
+ }
+
+ public PgpSecretKeyRing(
+ byte[] encoding)
+ : this(new MemoryStream(encoding))
+ {
+ }
+
+ public PgpSecretKeyRing(
+ Stream inputStream)
+ {
+ this.keys = Platform.CreateArrayList();
+ this.extraPubKeys = Platform.CreateArrayList();
+
+ BcpgInputStream bcpgInput = BcpgInputStream.Wrap(inputStream);
+
+ PacketTag initialTag = bcpgInput.NextPacketTag();
+ if (initialTag != PacketTag.SecretKey && initialTag != PacketTag.SecretSubkey)
+ {
+ throw new IOException("secret key ring doesn't start with secret key tag: "
+ + "tag 0x" + ((int)initialTag).ToString("X"));
+ }
+
+ SecretKeyPacket secret = (SecretKeyPacket) bcpgInput.ReadPacket();
+
+ //
+ // ignore GPG comment packets if found.
+ //
+ while (bcpgInput.NextPacketTag() == PacketTag.Experimental2)
+ {
+ bcpgInput.ReadPacket();
+ }
+
+ TrustPacket trust = ReadOptionalTrustPacket(bcpgInput);
+
+ // revocation and direct signatures
+ IList keySigs = ReadSignaturesAndTrust(bcpgInput);
+
+ IList ids, idTrusts, idSigs;
+ ReadUserIDs(bcpgInput, out ids, out idTrusts, out idSigs);
+
+ keys.Add(new PgpSecretKey(secret, new PgpPublicKey(secret.PublicKeyPacket, trust, keySigs, ids, idTrusts, idSigs)));
+
+
+ // Read subkeys
+ while (bcpgInput.NextPacketTag() == PacketTag.SecretSubkey
+ || bcpgInput.NextPacketTag() == PacketTag.PublicSubkey)
+ {
+ if (bcpgInput.NextPacketTag() == PacketTag.SecretSubkey)
+ {
+ SecretSubkeyPacket sub = (SecretSubkeyPacket) bcpgInput.ReadPacket();
+
+ //
+ // ignore GPG comment packets if found.
+ //
+ while (bcpgInput.NextPacketTag() == PacketTag.Experimental2)
+ {
+ bcpgInput.ReadPacket();
+ }
+
+ TrustPacket subTrust = ReadOptionalTrustPacket(bcpgInput);
+ IList sigList = ReadSignaturesAndTrust(bcpgInput);
+
+ keys.Add(new PgpSecretKey(sub, new PgpPublicKey(sub.PublicKeyPacket, subTrust, sigList)));
+ }
+ else
+ {
+ PublicSubkeyPacket sub = (PublicSubkeyPacket) bcpgInput.ReadPacket();
+
+ TrustPacket subTrust = ReadOptionalTrustPacket(bcpgInput);
+ IList sigList = ReadSignaturesAndTrust(bcpgInput);
+
+ extraPubKeys.Add(new PgpPublicKey(sub, subTrust, sigList));
+ }
+ }
+ }
+
+ /// <summary>Return the public key for the master key.</summary>
+ public PgpPublicKey GetPublicKey()
+ {
+ return ((PgpSecretKey) keys[0]).PublicKey;
+ }
+
+ /// <summary>Return the master private key.</summary>
+ public PgpSecretKey GetSecretKey()
+ {
+ return (PgpSecretKey) keys[0];
+ }
+
+ /// <summary>Allows enumeration of the secret keys.</summary>
+ /// <returns>An <c>IEnumerable</c> of <c>PgpSecretKey</c> objects.</returns>
+ public IEnumerable GetSecretKeys()
+ {
+ return new EnumerableProxy(keys);
+ }
+
+ public PgpSecretKey GetSecretKey(
+ long keyId)
+ {
+ foreach (PgpSecretKey k in keys)
+ {
+ if (keyId == k.KeyId)
+ {
+ return k;
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <returns>An <c>IEnumerable</c> of unattached, or extra, public keys.</returns>
+ public IEnumerable GetExtraPublicKeys()
+ {
+ return new EnumerableProxy(extraPubKeys);
+ }
+
+ public byte[] GetEncoded()
+ {
+ MemoryStream bOut = new MemoryStream();
+
+ Encode(bOut);
+
+ return bOut.ToArray();
+ }
+
+ public void Encode(
+ Stream outStr)
+ {
+ if (outStr == null)
+ throw new ArgumentNullException("outStr");
+
+ foreach (PgpSecretKey key in keys)
+ {
+ key.Encode(outStr);
+ }
+ foreach (PgpPublicKey extraPubKey in extraPubKeys)
+ {
+ extraPubKey.Encode(outStr);
+ }
+ }
+
+ /// <summary>
+ /// Replace the public key set on the secret ring with the corresponding key off the public ring.
+ /// </summary>
+ /// <param name="secretRing">Secret ring to be changed.</param>
+ /// <param name="publicRing">Public ring containing the new public key set.</param>
+ public static PgpSecretKeyRing ReplacePublicKeys(
+ PgpSecretKeyRing secretRing,
+ PgpPublicKeyRing publicRing)
+ {
+ IList newList = Platform.CreateArrayList(secretRing.keys.Count);
+
+ foreach (PgpSecretKey sk in secretRing.keys)
+ {
+ PgpPublicKey pk = publicRing.GetPublicKey(sk.KeyId);
+
+ newList.Add(PgpSecretKey.ReplacePublicKey(sk, pk));
+ }
+
+ return new PgpSecretKeyRing(newList);
+ }
+
+ /// <summary>
+ /// Return a copy of the passed in secret key ring, with the master key and sub keys encrypted
+ /// using a new password and the passed in algorithm.
+ /// </summary>
+ /// <param name="ring">The <c>PgpSecretKeyRing</c> to be copied.</param>
+ /// <param name="oldPassPhrase">The current password for key.</param>
+ /// <param name="newPassPhrase">The new password for the key.</param>
+ /// <param name="newEncAlgorithm">The algorithm to be used for the encryption.</param>
+ /// <param name="rand">Source of randomness.</param>
+ public static PgpSecretKeyRing CopyWithNewPassword(
+ PgpSecretKeyRing ring,
+ char[] oldPassPhrase,
+ char[] newPassPhrase,
+ SymmetricKeyAlgorithmTag newEncAlgorithm,
+ SecureRandom rand)
+ {
+ IList newKeys = Platform.CreateArrayList(ring.keys.Count);
+ foreach (PgpSecretKey secretKey in ring.GetSecretKeys())
+ {
+ newKeys.Add(PgpSecretKey.CopyWithNewPassword(secretKey, oldPassPhrase, newPassPhrase, newEncAlgorithm, rand));
+ }
+
+ return new PgpSecretKeyRing(newKeys, ring.extraPubKeys);
+ }
+
+ /// <summary>
+ /// Returns a new key ring with the secret key passed in either added or
+ /// replacing an existing one with the same key ID.
+ /// </summary>
+ /// <param name="secRing">The secret key ring to be modified.</param>
+ /// <param name="secKey">The secret key to be inserted.</param>
+ /// <returns>A new <c>PgpSecretKeyRing</c></returns>
+ public static PgpSecretKeyRing InsertSecretKey(
+ PgpSecretKeyRing secRing,
+ PgpSecretKey secKey)
+ {
+ IList keys = Platform.CreateArrayList(secRing.keys);
+ bool found = false;
+ bool masterFound = false;
+
+ for (int i = 0; i != keys.Count; i++)
+ {
+ PgpSecretKey key = (PgpSecretKey) keys[i];
+
+ if (key.KeyId == secKey.KeyId)
+ {
+ found = true;
+ keys[i] = secKey;
+ }
+ if (key.IsMasterKey)
+ {
+ masterFound = true;
+ }
+ }
+
+ if (!found)
+ {
+ if (secKey.IsMasterKey)
+ {
+ if (masterFound)
+ throw new ArgumentException("cannot add a master key to a ring that already has one");
+
+ keys.Insert(0, secKey);
+ }
+ else
+ {
+ keys.Add(secKey);
+ }
+ }
+
+ return new PgpSecretKeyRing(keys, secRing.extraPubKeys);
+ }
+
+ /// <summary>Returns a new key ring with the secret key passed in removed from the key ring.</summary>
+ /// <param name="secRing">The secret key ring to be modified.</param>
+ /// <param name="secKey">The secret key to be removed.</param>
+ /// <returns>A new <c>PgpSecretKeyRing</c>, or null if secKey is not found.</returns>
+ public static PgpSecretKeyRing RemoveSecretKey(
+ PgpSecretKeyRing secRing,
+ PgpSecretKey secKey)
+ {
+ IList keys = Platform.CreateArrayList(secRing.keys);
+ bool found = false;
+
+ for (int i = 0; i < keys.Count; i++)
+ {
+ PgpSecretKey key = (PgpSecretKey)keys[i];
+
+ if (key.KeyId == secKey.KeyId)
+ {
+ found = true;
+ keys.RemoveAt(i);
+ }
+ }
+
+ return found ? new PgpSecretKeyRing(keys, secRing.extraPubKeys) : null;
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpSecretKeyRingBundle.cs b/Crypto/src/openpgp/PgpSecretKeyRingBundle.cs
new file mode 100644
index 000000000..18636dd65
--- /dev/null
+++ b/Crypto/src/openpgp/PgpSecretKeyRingBundle.cs
@@ -0,0 +1,281 @@
+using System;
+using System.Collections;
+using System.Globalization;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Collections;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>
+ /// 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.
+ /// </remarks>
+ public class PgpSecretKeyRingBundle
+ {
+ private readonly IDictionary secretRings;
+ private readonly IList order;
+
+ private PgpSecretKeyRingBundle(
+ IDictionary secretRings,
+ IList order)
+ {
+ this.secretRings = secretRings;
+ this.order = order;
+ }
+
+ public PgpSecretKeyRingBundle(
+ byte[] encoding)
+ : this(new MemoryStream(encoding, false))
+ {
+ }
+
+ /// <summary>Build a PgpSecretKeyRingBundle from the passed in input stream.</summary>
+ /// <param name="inputStream">Input stream containing data.</param>
+ /// <exception cref="IOException">If a problem parsing the stream occurs.</exception>
+ /// <exception cref="PgpException">If an object is encountered which isn't a PgpSecretKeyRing.</exception>
+ public PgpSecretKeyRingBundle(
+ Stream inputStream)
+ : this(new PgpObjectFactory(inputStream).AllPgpObjects())
+ {
+ }
+
+ public PgpSecretKeyRingBundle(
+ IEnumerable e)
+ {
+ this.secretRings = Platform.CreateHashtable();
+ this.order = Platform.CreateArrayList();
+
+ foreach (object obj in e)
+ {
+ PgpSecretKeyRing pgpSecret = obj as PgpSecretKeyRing;
+
+ if (pgpSecret == null)
+ {
+ throw new PgpException(obj.GetType().FullName + " found where PgpSecretKeyRing expected");
+ }
+
+ long key = pgpSecret.GetPublicKey().KeyId;
+ secretRings.Add(key, pgpSecret);
+ order.Add(key);
+ }
+ }
+
+ [Obsolete("Use 'Count' property instead")]
+ public int Size
+ {
+ get { return order.Count; }
+ }
+
+ /// <summary>Return the number of rings in this collection.</summary>
+ public int Count
+ {
+ get { return order.Count; }
+ }
+
+ /// <summary>Allow enumeration of the secret key rings making up this collection.</summary>
+ public IEnumerable GetKeyRings()
+ {
+ return new EnumerableProxy(secretRings.Values);
+ }
+
+ /// <summary>Allow enumeration of the key rings associated with the passed in userId.</summary>
+ /// <param name="userId">The user ID to be matched.</param>
+ /// <returns>An <c>IEnumerable</c> of key rings which matched (possibly none).</returns>
+ public IEnumerable GetKeyRings(
+ string userId)
+ {
+ return GetKeyRings(userId, false, false);
+ }
+
+ /// <summary>Allow enumeration of the key rings associated with the passed in userId.</summary>
+ /// <param name="userId">The user ID to be matched.</param>
+ /// <param name="matchPartial">If true, userId need only be a substring of an actual ID string to match.</param>
+ /// <returns>An <c>IEnumerable</c> of key rings which matched (possibly none).</returns>
+ public IEnumerable GetKeyRings(
+ string userId,
+ bool matchPartial)
+ {
+ return GetKeyRings(userId, matchPartial, false);
+ }
+
+ /// <summary>Allow enumeration of the key rings associated with the passed in userId.</summary>
+ /// <param name="userId">The user ID to be matched.</param>
+ /// <param name="matchPartial">If true, userId need only be a substring of an actual ID string to match.</param>
+ /// <param name="ignoreCase">If true, case is ignored in user ID comparisons.</param>
+ /// <returns>An <c>IEnumerable</c> of key rings which matched (possibly none).</returns>
+ public IEnumerable GetKeyRings(
+ string userId,
+ bool matchPartial,
+ bool ignoreCase)
+ {
+ IList rings = Platform.CreateArrayList();
+
+ if (ignoreCase)
+ {
+ userId = userId.ToLowerInvariant();
+ }
+
+ foreach (PgpSecretKeyRing secRing in GetKeyRings())
+ {
+ foreach (string nextUserID in secRing.GetSecretKey().UserIds)
+ {
+ string next = nextUserID;
+ if (ignoreCase)
+ {
+ next = next.ToLowerInvariant();
+ }
+
+ if (matchPartial)
+ {
+ if (next.IndexOf(userId) > -1)
+ {
+ rings.Add(secRing);
+ }
+ }
+ else
+ {
+ if (next.Equals(userId))
+ {
+ rings.Add(secRing);
+ }
+ }
+ }
+ }
+
+ return new EnumerableProxy(rings);
+ }
+
+ /// <summary>Return the PGP secret key associated with the given key id.</summary>
+ /// <param name="keyId">The ID of the secret key to return.</param>
+ public PgpSecretKey GetSecretKey(
+ long keyId)
+ {
+ foreach (PgpSecretKeyRing secRing in GetKeyRings())
+ {
+ PgpSecretKey sec = secRing.GetSecretKey(keyId);
+
+ if (sec != null)
+ {
+ return sec;
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>Return the secret key ring which contains the key referred to by keyId</summary>
+ /// <param name="keyId">The ID of the secret key</param>
+ public PgpSecretKeyRing GetSecretKeyRing(
+ long keyId)
+ {
+ long id = keyId;
+
+ if (secretRings.Contains(id))
+ {
+ return (PgpSecretKeyRing) secretRings[id];
+ }
+
+ foreach (PgpSecretKeyRing secretRing in GetKeyRings())
+ {
+ PgpSecretKey secret = secretRing.GetSecretKey(keyId);
+
+ if (secret != null)
+ {
+ return secretRing;
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Return true if a key matching the passed in key ID is present, false otherwise.
+ /// </summary>
+ /// <param name="keyID">key ID to look for.</param>
+ public bool Contains(
+ long keyID)
+ {
+ return GetSecretKey(keyID) != null;
+ }
+
+ public byte[] GetEncoded()
+ {
+ MemoryStream bOut = new MemoryStream();
+
+ Encode(bOut);
+
+ return bOut.ToArray();
+ }
+
+ public void Encode(
+ Stream outStr)
+ {
+ BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStr);
+
+ foreach (long key in order)
+ {
+ PgpSecretKeyRing pub = (PgpSecretKeyRing) secretRings[key];
+
+ pub.Encode(bcpgOut);
+ }
+ }
+
+ /// <summary>
+ /// Return a new bundle containing the contents of the passed in bundle and
+ /// the passed in secret key ring.
+ /// </summary>
+ /// <param name="bundle">The <c>PgpSecretKeyRingBundle</c> the key ring is to be added to.</param>
+ /// <param name="secretKeyRing">The key ring to be added.</param>
+ /// <returns>A new <c>PgpSecretKeyRingBundle</c> merging the current one with the passed in key ring.</returns>
+ /// <exception cref="ArgumentException">If the keyId for the passed in key ring is already present.</exception>
+ public static PgpSecretKeyRingBundle AddSecretKeyRing(
+ PgpSecretKeyRingBundle bundle,
+ PgpSecretKeyRing secretKeyRing)
+ {
+ long key = secretKeyRing.GetPublicKey().KeyId;
+
+ if (bundle.secretRings.Contains(key))
+ {
+ throw new ArgumentException("Collection already contains a key with a keyId for the passed in ring.");
+ }
+
+ IDictionary newSecretRings = Platform.CreateHashtable(bundle.secretRings);
+ IList newOrder = Platform.CreateArrayList(bundle.order);
+
+ newSecretRings[key] = secretKeyRing;
+ newOrder.Add(key);
+
+ return new PgpSecretKeyRingBundle(newSecretRings, newOrder);
+ }
+
+ /// <summary>
+ /// Return a new bundle containing the contents of the passed in bundle with
+ /// the passed in secret key ring removed.
+ /// </summary>
+ /// <param name="bundle">The <c>PgpSecretKeyRingBundle</c> the key ring is to be removed from.</param>
+ /// <param name="secretKeyRing">The key ring to be removed.</param>
+ /// <returns>A new <c>PgpSecretKeyRingBundle</c> not containing the passed in key ring.</returns>
+ /// <exception cref="ArgumentException">If the keyId for the passed in key ring is not present.</exception>
+ public static PgpSecretKeyRingBundle RemoveSecretKeyRing(
+ PgpSecretKeyRingBundle bundle,
+ PgpSecretKeyRing secretKeyRing)
+ {
+ long key = secretKeyRing.GetPublicKey().KeyId;
+
+ if (!bundle.secretRings.Contains(key))
+ {
+ throw new ArgumentException("Collection does not contain a key with a keyId for the passed in ring.");
+ }
+
+ IDictionary newSecretRings = Platform.CreateHashtable(bundle.secretRings);
+ IList newOrder = Platform.CreateArrayList(bundle.order);
+
+ newSecretRings.Remove(key);
+ newOrder.Remove(key);
+
+ return new PgpSecretKeyRingBundle(newSecretRings, newOrder);
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpSignature.cs b/Crypto/src/openpgp/PgpSignature.cs
new file mode 100644
index 000000000..cbe0d83b4
--- /dev/null
+++ b/Crypto/src/openpgp/PgpSignature.cs
@@ -0,0 +1,422 @@
+using System;
+using System.IO;
+using Org.BouncyCastle.Asn1;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Date;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>A PGP signature object.</remarks>
+ public class PgpSignature
+ {
+ public const int BinaryDocument = 0x00;
+ public const int CanonicalTextDocument = 0x01;
+ public const int StandAlone = 0x02;
+
+ public const int DefaultCertification = 0x10;
+ public const int NoCertification = 0x11;
+ public const int CasualCertification = 0x12;
+ public const int PositiveCertification = 0x13;
+
+ public const int SubkeyBinding = 0x18;
+ public const int PrimaryKeyBinding = 0x19;
+ public const int DirectKey = 0x1f;
+ public const int KeyRevocation = 0x20;
+ public const int SubkeyRevocation = 0x28;
+ public const int CertificationRevocation = 0x30;
+ public const int Timestamp = 0x40;
+
+ private readonly SignaturePacket sigPck;
+ private readonly int signatureType;
+ private readonly TrustPacket trustPck;
+
+ private ISigner sig;
+ private byte lastb; // Initial value anything but '\r'
+
+ internal PgpSignature(
+ BcpgInputStream bcpgInput)
+ : this((SignaturePacket)bcpgInput.ReadPacket())
+ {
+ }
+
+ internal PgpSignature(
+ SignaturePacket sigPacket)
+ : this(sigPacket, null)
+ {
+ }
+
+ internal PgpSignature(
+ SignaturePacket sigPacket,
+ TrustPacket trustPacket)
+ {
+ if (sigPacket == null)
+ throw new ArgumentNullException("sigPacket");
+
+ this.sigPck = sigPacket;
+ this.signatureType = sigPck.SignatureType;
+ this.trustPck = trustPacket;
+ }
+
+ private void GetSig()
+ {
+ this.sig = SignerUtilities.GetSigner(
+ PgpUtilities.GetSignatureName(sigPck.KeyAlgorithm, sigPck.HashAlgorithm));
+ }
+
+ /// <summary>The OpenPGP version number for this signature.</summary>
+ public int Version
+ {
+ get { return sigPck.Version; }
+ }
+
+ /// <summary>The key algorithm associated with this signature.</summary>
+ public PublicKeyAlgorithmTag KeyAlgorithm
+ {
+ get { return sigPck.KeyAlgorithm; }
+ }
+
+ /// <summary>The hash algorithm associated with this signature.</summary>
+ public HashAlgorithmTag HashAlgorithm
+ {
+ get { return sigPck.HashAlgorithm; }
+ }
+
+ public void InitVerify(
+ PgpPublicKey pubKey)
+ {
+ lastb = 0;
+ if (sig == null)
+ {
+ GetSig();
+ }
+ try
+ {
+ sig.Init(false, pubKey.GetKey());
+ }
+ catch (InvalidKeyException e)
+ {
+ throw new PgpException("invalid key.", e);
+ }
+ }
+
+ public void Update(
+ byte b)
+ {
+ if (signatureType == CanonicalTextDocument)
+ {
+ doCanonicalUpdateByte(b);
+ }
+ else
+ {
+ sig.Update(b);
+ }
+ }
+
+ private void doCanonicalUpdateByte(
+ byte b)
+ {
+ if (b == '\r')
+ {
+ doUpdateCRLF();
+ }
+ else if (b == '\n')
+ {
+ if (lastb != '\r')
+ {
+ doUpdateCRLF();
+ }
+ }
+ else
+ {
+ sig.Update(b);
+ }
+
+ lastb = b;
+ }
+
+ private void doUpdateCRLF()
+ {
+ sig.Update((byte)'\r');
+ sig.Update((byte)'\n');
+ }
+
+ public void Update(
+ params byte[] bytes)
+ {
+ Update(bytes, 0, bytes.Length);
+ }
+
+ public void Update(
+ byte[] bytes,
+ int off,
+ int length)
+ {
+ if (signatureType == CanonicalTextDocument)
+ {
+ int finish = off + length;
+
+ for (int i = off; i != finish; i++)
+ {
+ doCanonicalUpdateByte(bytes[i]);
+ }
+ }
+ else
+ {
+ sig.BlockUpdate(bytes, off, length);
+ }
+ }
+
+ public bool Verify()
+ {
+ byte[] trailer = GetSignatureTrailer();
+ sig.BlockUpdate(trailer, 0, trailer.Length);
+
+ return sig.VerifySignature(GetSignature());
+ }
+
+ private void UpdateWithIdData(
+ int header,
+ byte[] idBytes)
+ {
+ this.Update(
+ (byte) header,
+ (byte)(idBytes.Length >> 24),
+ (byte)(idBytes.Length >> 16),
+ (byte)(idBytes.Length >> 8),
+ (byte)(idBytes.Length));
+ this.Update(idBytes);
+ }
+
+ private void UpdateWithPublicKey(
+ PgpPublicKey key)
+ {
+ byte[] keyBytes = GetEncodedPublicKey(key);
+
+ this.Update(
+ (byte) 0x99,
+ (byte)(keyBytes.Length >> 8),
+ (byte)(keyBytes.Length));
+ this.Update(keyBytes);
+ }
+
+ /// <summary>
+ /// Verify the signature as certifying the passed in public key as associated
+ /// with the passed in user attributes.
+ /// </summary>
+ /// <param name="userAttributes">User attributes the key was stored under.</param>
+ /// <param name="key">The key to be verified.</param>
+ /// <returns>True, if the signature matches, false otherwise.</returns>
+ public bool VerifyCertification(
+ PgpUserAttributeSubpacketVector userAttributes,
+ PgpPublicKey key)
+ {
+ UpdateWithPublicKey(key);
+
+ //
+ // hash in the userAttributes
+ //
+ try
+ {
+ MemoryStream bOut = new MemoryStream();
+ foreach (UserAttributeSubpacket packet in userAttributes.ToSubpacketArray())
+ {
+ packet.Encode(bOut);
+ }
+ UpdateWithIdData(0xd1, bOut.ToArray());
+ }
+ catch (IOException e)
+ {
+ throw new PgpException("cannot encode subpacket array", e);
+ }
+
+ this.Update(sigPck.GetSignatureTrailer());
+
+ return sig.VerifySignature(this.GetSignature());
+ }
+
+ /// <summary>
+ /// Verify the signature as certifying the passed in public key as associated
+ /// with the passed in ID.
+ /// </summary>
+ /// <param name="id">ID the key was stored under.</param>
+ /// <param name="key">The key to be verified.</param>
+ /// <returns>True, if the signature matches, false otherwise.</returns>
+ public bool VerifyCertification(
+ string id,
+ PgpPublicKey key)
+ {
+ UpdateWithPublicKey(key);
+
+ //
+ // hash in the id
+ //
+ UpdateWithIdData(0xb4, Strings.ToByteArray(id));
+
+ Update(sigPck.GetSignatureTrailer());
+
+ return sig.VerifySignature(GetSignature());
+ }
+
+ /// <summary>Verify a certification for the passed in key against the passed in master key.</summary>
+ /// <param name="masterKey">The key we are verifying against.</param>
+ /// <param name="pubKey">The key we are verifying.</param>
+ /// <returns>True, if the certification is valid, false otherwise.</returns>
+ public bool VerifyCertification(
+ PgpPublicKey masterKey,
+ PgpPublicKey pubKey)
+ {
+ UpdateWithPublicKey(masterKey);
+ UpdateWithPublicKey(pubKey);
+
+ Update(sigPck.GetSignatureTrailer());
+
+ return sig.VerifySignature(GetSignature());
+ }
+
+ /// <summary>Verify a key certification, such as revocation, for the passed in key.</summary>
+ /// <param name="pubKey">The key we are checking.</param>
+ /// <returns>True, if the certification is valid, false otherwise.</returns>
+ public bool VerifyCertification(
+ PgpPublicKey pubKey)
+ {
+ if (SignatureType != KeyRevocation
+ && SignatureType != SubkeyRevocation)
+ {
+ throw new InvalidOperationException("signature is not a key signature");
+ }
+
+ UpdateWithPublicKey(pubKey);
+
+ Update(sigPck.GetSignatureTrailer());
+
+ return sig.VerifySignature(GetSignature());
+ }
+
+ public int SignatureType
+ {
+ get { return sigPck.SignatureType; }
+ }
+
+ /// <summary>The ID of the key that created the signature.</summary>
+ public long KeyId
+ {
+ get { return sigPck.KeyId; }
+ }
+
+ [Obsolete("Use 'CreationTime' property instead")]
+ public DateTime GetCreationTime()
+ {
+ return CreationTime;
+ }
+
+ /// <summary>The creation time of this signature.</summary>
+ public DateTime CreationTime
+ {
+ get { return DateTimeUtilities.UnixMsToDateTime(sigPck.CreationTime); }
+ }
+
+ public byte[] GetSignatureTrailer()
+ {
+ return sigPck.GetSignatureTrailer();
+ }
+
+ /// <summary>
+ /// Return true if the signature has either hashed or unhashed subpackets.
+ /// </summary>
+ public bool HasSubpackets
+ {
+ get
+ {
+ 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)
+ {
+ return pcks == null ? null : new PgpSignatureSubpacketVector(pcks);
+ }
+
+ public byte[] GetSignature()
+ {
+ MPInteger[] sigValues = sigPck.GetSignature();
+ byte[] signature;
+
+ if (sigValues != null)
+ {
+ if (sigValues.Length == 1) // an RSA signature
+ {
+ signature = sigValues[0].Value.ToByteArrayUnsigned();
+ }
+ else
+ {
+ try
+ {
+ signature = new DerSequence(
+ new DerInteger(sigValues[0].Value),
+ new DerInteger(sigValues[1].Value)).GetEncoded();
+ }
+ catch (IOException e)
+ {
+ throw new PgpException("exception encoding DSA sig.", e);
+ }
+ }
+ }
+ else
+ {
+ signature = sigPck.GetSignatureBytes();
+ }
+
+ return signature;
+ }
+
+ // TODO Handle the encoding stuff by subclassing BcpgObject?
+ public byte[] GetEncoded()
+ {
+ MemoryStream bOut = new MemoryStream();
+
+ Encode(bOut);
+
+ return bOut.ToArray();
+ }
+
+ public void Encode(
+ Stream outStream)
+ {
+ BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStream);
+
+ bcpgOut.WritePacket(sigPck);
+
+ if (trustPck != null)
+ {
+ bcpgOut.WritePacket(trustPck);
+ }
+ }
+
+ private byte[] GetEncodedPublicKey(
+ PgpPublicKey pubKey)
+ {
+ try
+ {
+ return pubKey.publicPk.GetEncodedContents();
+ }
+ catch (IOException e)
+ {
+ throw new PgpException("exception preparing key.", e);
+ }
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpSignatureGenerator.cs b/Crypto/src/openpgp/PgpSignatureGenerator.cs
new file mode 100644
index 000000000..891397267
--- /dev/null
+++ b/Crypto/src/openpgp/PgpSignatureGenerator.cs
@@ -0,0 +1,393 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Bcpg.Sig;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>Generator for PGP signatures.</remarks>
+ // TODO Should be able to implement ISigner?
+ public class PgpSignatureGenerator
+ {
+ private static readonly SignatureSubpacket[] EmptySignatureSubpackets = new SignatureSubpacket[0];
+
+ private PublicKeyAlgorithmTag keyAlgorithm;
+ private HashAlgorithmTag hashAlgorithm;
+ private PgpPrivateKey privKey;
+ private ISigner sig;
+ private IDigest dig;
+ private int signatureType;
+ private byte lastb;
+
+ private SignatureSubpacket[] unhashed = EmptySignatureSubpackets;
+ private SignatureSubpacket[] hashed = EmptySignatureSubpackets;
+
+ /// <summary>Create a generator for the passed in keyAlgorithm and hashAlgorithm codes.</summary>
+ public PgpSignatureGenerator(
+ PublicKeyAlgorithmTag keyAlgorithm,
+ HashAlgorithmTag hashAlgorithm)
+ {
+ this.keyAlgorithm = keyAlgorithm;
+ this.hashAlgorithm = hashAlgorithm;
+
+ dig = DigestUtilities.GetDigest(PgpUtilities.GetDigestName(hashAlgorithm));
+ sig = SignerUtilities.GetSigner(PgpUtilities.GetSignatureName(keyAlgorithm, hashAlgorithm));
+ }
+
+ /// <summary>Initialise the generator for signing.</summary>
+ public void InitSign(
+ int sigType,
+ PgpPrivateKey key)
+ {
+ InitSign(sigType, key, null);
+ }
+
+ /// <summary>Initialise the generator for signing.</summary>
+ public void InitSign(
+ int sigType,
+ PgpPrivateKey key,
+ SecureRandom random)
+ {
+ this.privKey = key;
+ this.signatureType = sigType;
+
+ try
+ {
+ ICipherParameters cp = key.Key;
+ if (random != null)
+ {
+ cp = new ParametersWithRandom(key.Key, random);
+ }
+
+ sig.Init(true, cp);
+ }
+ catch (InvalidKeyException e)
+ {
+ throw new PgpException("invalid key.", e);
+ }
+
+ dig.Reset();
+ lastb = 0;
+ }
+
+ public void Update(
+ byte b)
+ {
+ if (signatureType == PgpSignature.CanonicalTextDocument)
+ {
+ doCanonicalUpdateByte(b);
+ }
+ else
+ {
+ doUpdateByte(b);
+ }
+ }
+
+ private void doCanonicalUpdateByte(
+ byte b)
+ {
+ if (b == '\r')
+ {
+ doUpdateCRLF();
+ }
+ else if (b == '\n')
+ {
+ if (lastb != '\r')
+ {
+ doUpdateCRLF();
+ }
+ }
+ else
+ {
+ doUpdateByte(b);
+ }
+
+ lastb = b;
+ }
+
+ private void doUpdateCRLF()
+ {
+ doUpdateByte((byte)'\r');
+ doUpdateByte((byte)'\n');
+ }
+
+ private void doUpdateByte(
+ byte b)
+ {
+ sig.Update(b);
+ dig.Update(b);
+ }
+
+ public void Update(
+ params byte[] b)
+ {
+ Update(b, 0, b.Length);
+ }
+
+ public void Update(
+ byte[] b,
+ int off,
+ int len)
+ {
+ if (signatureType == PgpSignature.CanonicalTextDocument)
+ {
+ int finish = off + len;
+
+ for (int i = off; i != finish; i++)
+ {
+ doCanonicalUpdateByte(b[i]);
+ }
+ }
+ else
+ {
+ sig.BlockUpdate(b, off, len);
+ dig.BlockUpdate(b, off, len);
+ }
+ }
+
+ public void SetHashedSubpackets(
+ PgpSignatureSubpacketVector hashedPackets)
+ {
+ hashed = hashedPackets == null
+ ? EmptySignatureSubpackets
+ : hashedPackets.ToSubpacketArray();
+ }
+
+ public void SetUnhashedSubpackets(
+ PgpSignatureSubpacketVector unhashedPackets)
+ {
+ unhashed = unhashedPackets == null
+ ? EmptySignatureSubpackets
+ : unhashedPackets.ToSubpacketArray();
+ }
+
+ /// <summary>Return the one pass header associated with the current signature.</summary>
+ public PgpOnePassSignature GenerateOnePassVersion(
+ bool isNested)
+ {
+ return new PgpOnePassSignature(
+ new OnePassSignaturePacket(
+ signatureType, hashAlgorithm, keyAlgorithm, privKey.KeyId, isNested));
+ }
+
+ /// <summary>Return a signature object containing the current signature state.</summary>
+ public PgpSignature Generate()
+ {
+ SignatureSubpacket[] hPkts = hashed, unhPkts = unhashed;
+
+ if (!packetPresent(hashed, SignatureSubpacketTag.CreationTime))
+ {
+ hPkts = insertSubpacket(hPkts, new SignatureCreationTime(false, DateTime.UtcNow));
+ }
+
+ if (!packetPresent(hashed, SignatureSubpacketTag.IssuerKeyId)
+ && !packetPresent(unhashed, SignatureSubpacketTag.IssuerKeyId))
+ {
+ unhPkts = insertSubpacket(unhPkts, new IssuerKeyId(false, privKey.KeyId));
+ }
+
+ int version = 4;
+ byte[] hData;
+
+ try
+ {
+ MemoryStream hOut = new MemoryStream();
+
+ for (int i = 0; i != hPkts.Length; i++)
+ {
+ hPkts[i].Encode(hOut);
+ }
+
+ byte[] data = hOut.ToArray();
+
+ MemoryStream sOut = new MemoryStream(data.Length + 6);
+ sOut.WriteByte((byte)version);
+ sOut.WriteByte((byte)signatureType);
+ sOut.WriteByte((byte)keyAlgorithm);
+ sOut.WriteByte((byte)hashAlgorithm);
+ sOut.WriteByte((byte)(data.Length >> 8));
+ sOut.WriteByte((byte)data.Length);
+ sOut.Write(data, 0, data.Length);
+
+ hData = sOut.ToArray();
+ }
+ catch (IOException e)
+ {
+ throw new PgpException("exception encoding hashed data.", e);
+ }
+
+ sig.BlockUpdate(hData, 0, hData.Length);
+ dig.BlockUpdate(hData, 0, hData.Length);
+
+ hData = new byte[]
+ {
+ (byte) version,
+ 0xff,
+ (byte)(hData.Length >> 24),
+ (byte)(hData.Length >> 16),
+ (byte)(hData.Length >> 8),
+ (byte) hData.Length
+ };
+
+ sig.BlockUpdate(hData, 0, hData.Length);
+ dig.BlockUpdate(hData, 0, hData.Length);
+
+ byte[] sigBytes = sig.GenerateSignature();
+ byte[] digest = DigestUtilities.DoFinal(dig);
+ byte[] fingerPrint = new byte[] { digest[0], digest[1] };
+
+ // an RSA signature
+ bool isRsa = keyAlgorithm == PublicKeyAlgorithmTag.RsaSign
+ || keyAlgorithm == PublicKeyAlgorithmTag.RsaGeneral;
+
+ MPInteger[] sigValues = isRsa
+ ? PgpUtilities.RsaSigToMpi(sigBytes)
+ : PgpUtilities.DsaSigToMpi(sigBytes);
+
+ return new PgpSignature(
+ new SignaturePacket(signatureType, privKey.KeyId, keyAlgorithm,
+ hashAlgorithm, hPkts, unhPkts, fingerPrint, sigValues));
+ }
+
+ /// <summary>Generate a certification for the passed in ID and key.</summary>
+ /// <param name="id">The ID we are certifying against the public key.</param>
+ /// <param name="pubKey">The key we are certifying against the ID.</param>
+ /// <returns>The certification.</returns>
+ public PgpSignature GenerateCertification(
+ string id,
+ PgpPublicKey pubKey)
+ {
+ UpdateWithPublicKey(pubKey);
+
+ //
+ // hash in the id
+ //
+ UpdateWithIdData(0xb4, Strings.ToByteArray(id));
+
+ return Generate();
+ }
+
+ /// <summary>Generate a certification for the passed in userAttributes.</summary>
+ /// <param name="userAttributes">The ID we are certifying against the public key.</param>
+ /// <param name="pubKey">The key we are certifying against the ID.</param>
+ /// <returns>The certification.</returns>
+ public PgpSignature GenerateCertification(
+ PgpUserAttributeSubpacketVector userAttributes,
+ PgpPublicKey pubKey)
+ {
+ UpdateWithPublicKey(pubKey);
+
+ //
+ // hash in the attributes
+ //
+ try
+ {
+ MemoryStream bOut = new MemoryStream();
+ foreach (UserAttributeSubpacket packet in userAttributes.ToSubpacketArray())
+ {
+ packet.Encode(bOut);
+ }
+ UpdateWithIdData(0xd1, bOut.ToArray());
+ }
+ catch (IOException e)
+ {
+ throw new PgpException("cannot encode subpacket array", e);
+ }
+
+ return this.Generate();
+ }
+
+ /// <summary>Generate a certification for the passed in key against the passed in master key.</summary>
+ /// <param name="masterKey">The key we are certifying against.</param>
+ /// <param name="pubKey">The key we are certifying.</param>
+ /// <returns>The certification.</returns>
+ public PgpSignature GenerateCertification(
+ PgpPublicKey masterKey,
+ PgpPublicKey pubKey)
+ {
+ UpdateWithPublicKey(masterKey);
+ UpdateWithPublicKey(pubKey);
+
+ return Generate();
+ }
+
+ /// <summary>Generate a certification, such as a revocation, for the passed in key.</summary>
+ /// <param name="pubKey">The key we are certifying.</param>
+ /// <returns>The certification.</returns>
+ public PgpSignature GenerateCertification(
+ PgpPublicKey pubKey)
+ {
+ UpdateWithPublicKey(pubKey);
+
+ return Generate();
+ }
+
+ private byte[] GetEncodedPublicKey(
+ PgpPublicKey pubKey)
+ {
+ try
+ {
+ return pubKey.publicPk.GetEncodedContents();
+ }
+ catch (IOException e)
+ {
+ throw new PgpException("exception preparing key.", e);
+ }
+ }
+
+ private bool packetPresent(
+ SignatureSubpacket[] packets,
+ SignatureSubpacketTag type)
+ {
+ for (int i = 0; i != packets.Length; i++)
+ {
+ if (packets[i].SubpacketType == type)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private SignatureSubpacket[] insertSubpacket(
+ SignatureSubpacket[] packets,
+ SignatureSubpacket subpacket)
+ {
+ SignatureSubpacket[] tmp = new SignatureSubpacket[packets.Length + 1];
+ tmp[0] = subpacket;
+ packets.CopyTo(tmp, 1);
+ return tmp;
+ }
+
+ private void UpdateWithIdData(
+ int header,
+ byte[] idBytes)
+ {
+ this.Update(
+ (byte) header,
+ (byte)(idBytes.Length >> 24),
+ (byte)(idBytes.Length >> 16),
+ (byte)(idBytes.Length >> 8),
+ (byte)(idBytes.Length));
+ this.Update(idBytes);
+ }
+
+ private void UpdateWithPublicKey(
+ PgpPublicKey key)
+ {
+ byte[] keyBytes = GetEncodedPublicKey(key);
+
+ this.Update(
+ (byte) 0x99,
+ (byte)(keyBytes.Length >> 8),
+ (byte)(keyBytes.Length));
+ this.Update(keyBytes);
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpSignatureList.cs b/Crypto/src/openpgp/PgpSignatureList.cs
new file mode 100644
index 000000000..61976fc4f
--- /dev/null
+++ b/Crypto/src/openpgp/PgpSignatureList.cs
@@ -0,0 +1,51 @@
+using System;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>A list of PGP signatures - normally in the signature block after literal data.</remarks>
+ public class PgpSignatureList
+ : PgpObject
+ {
+ private PgpSignature[] sigs;
+
+ public PgpSignatureList(
+ PgpSignature[] sigs)
+ {
+ this.sigs = (PgpSignature[]) sigs.Clone();
+ }
+
+ public PgpSignatureList(
+ PgpSignature sig)
+ {
+ this.sigs = new PgpSignature[]{ sig };
+ }
+
+ public PgpSignature this[int index]
+ {
+ get { return sigs[index]; }
+ }
+
+ [Obsolete("Use 'object[index]' syntax instead")]
+ public PgpSignature Get(
+ int index)
+ {
+ return this[index];
+ }
+
+ [Obsolete("Use 'Count' property instead")]
+ public int Size
+ {
+ get { return sigs.Length; }
+ }
+
+ public int Count
+ {
+ get { return sigs.Length; }
+ }
+
+ public bool IsEmpty
+ {
+ get { return (sigs.Length == 0); }
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs b/Crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs
new file mode 100644
index 000000000..4adf64012
--- /dev/null
+++ b/Crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs
@@ -0,0 +1,193 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Bcpg.Sig;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>Generator for signature subpackets.</remarks>
+ public class PgpSignatureSubpacketGenerator
+ {
+ private IList list = Platform.CreateArrayList();
+
+ public void SetRevocable(
+ bool isCritical,
+ bool isRevocable)
+ {
+ list.Add(new Revocable(isCritical, isRevocable));
+ }
+
+ public void SetExportable(
+ bool isCritical,
+ bool isExportable)
+ {
+ list.Add(new Exportable(isCritical, isExportable));
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <param name="isCritical">true if the packet is critical.</param>
+ /// <param name="depth">depth level.</param>
+ /// <param name="trustAmount">trust amount.</param>
+ public void SetTrust(
+ bool isCritical,
+ int depth,
+ int trustAmount)
+ {
+ list.Add(new TrustSignature(isCritical, depth, trustAmount));
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <param name="isCritical">True, if should be treated as critical, false otherwise.</param>
+ /// <param name="seconds">The number of seconds the key is valid, or zero if no expiry.</param>
+ public void SetKeyExpirationTime(
+ bool isCritical,
+ long seconds)
+ {
+ list.Add(new KeyExpirationTime(isCritical, seconds));
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <param name="isCritical">True, if should be treated as critical, false otherwise.</param>
+ /// <param name="seconds">The number of seconds the signature is valid, or zero if no expiry.</param>
+ public void SetSignatureExpirationTime(
+ bool isCritical,
+ long seconds)
+ {
+ list.Add(new SignatureExpirationTime(isCritical, seconds));
+ }
+
+ /// <summary>
+ /// Set the creation time for the signature.
+ /// <p>
+ /// Note: this overrides the generation of a creation time when the signature
+ /// is generated.</p>
+ /// </summary>
+ public void SetSignatureCreationTime(
+ bool isCritical,
+ DateTime date)
+ {
+ list.Add(new SignatureCreationTime(isCritical, date));
+ }
+
+ public void SetPreferredHashAlgorithms(
+ bool isCritical,
+ int[] algorithms)
+ {
+ list.Add(new PreferredAlgorithms(SignatureSubpacketTag.PreferredHashAlgorithms, isCritical, algorithms));
+ }
+
+ public void SetPreferredSymmetricAlgorithms(
+ bool isCritical,
+ int[] algorithms)
+ {
+ list.Add(new PreferredAlgorithms(SignatureSubpacketTag.PreferredSymmetricAlgorithms, isCritical, algorithms));
+ }
+
+ public void SetPreferredCompressionAlgorithms(
+ bool isCritical,
+ int[] algorithms)
+ {
+ list.Add(new PreferredAlgorithms(SignatureSubpacketTag.PreferredCompressionAlgorithms, isCritical, algorithms));
+ }
+
+ public void SetKeyFlags(
+ bool isCritical,
+ int flags)
+ {
+ list.Add(new KeyFlags(isCritical, flags));
+ }
+
+ public void SetSignerUserId(
+ bool isCritical,
+ string userId)
+ {
+ if (userId == null)
+ throw new ArgumentNullException("userId");
+
+ list.Add(new SignerUserId(isCritical, userId));
+ }
+
+ public void SetEmbeddedSignature(
+ bool isCritical,
+ PgpSignature pgpSignature)
+ {
+ byte[] sig = pgpSignature.GetEncoded();
+ byte[] data;
+
+ // TODO Should be >= ?
+ if (sig.Length - 1 > 256)
+ {
+ data = new byte[sig.Length - 3];
+ }
+ else
+ {
+ data = new byte[sig.Length - 2];
+ }
+
+ Array.Copy(sig, sig.Length - data.Length, data, 0, data.Length);
+
+ list.Add(new EmbeddedSignature(isCritical, data));
+ }
+
+ public void SetPrimaryUserId(
+ bool isCritical,
+ bool isPrimaryUserId)
+ {
+ list.Add(new PrimaryUserId(isCritical, isPrimaryUserId));
+ }
+
+ public void SetNotationData(
+ bool isCritical,
+ bool isHumanReadable,
+ string notationName,
+ string notationValue)
+ {
+ list.Add(new NotationData(isCritical, isHumanReadable, notationName, notationValue));
+ }
+
+ /// <summary>
+ /// Sets revocation reason sub packet
+ /// </summary>
+ public void SetRevocationReason(bool isCritical, RevocationReasonTag reason,
+ string description)
+ {
+ list.Add(new RevocationReason(isCritical, reason, description));
+ }
+
+ /// <summary>
+ /// Sets revocation key sub packet
+ /// </summary>
+ public void SetRevocationKey(bool isCritical, PublicKeyAlgorithmTag keyAlgorithm, byte[] fingerprint)
+ {
+ list.Add(new RevocationKey(isCritical, RevocationKeyTag.ClassDefault, keyAlgorithm, fingerprint));
+ }
+
+ /// <summary>
+ /// Sets issuer key sub packet
+ /// </summary>
+ public void SetIssuerKeyID(bool isCritical, long keyID)
+ {
+ list.Add(new IssuerKeyId(isCritical, keyID));
+ }
+
+ public PgpSignatureSubpacketVector Generate()
+ {
+ SignatureSubpacket[] a = new SignatureSubpacket[list.Count];
+ for (int i = 0; i < list.Count; ++i)
+ {
+ a[i] = (SignatureSubpacket)list[i];
+ }
+ return new PgpSignatureSubpacketVector(a);
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpSignatureSubpacketVector.cs b/Crypto/src/openpgp/PgpSignatureSubpacketVector.cs
new file mode 100644
index 000000000..68fe4b594
--- /dev/null
+++ b/Crypto/src/openpgp/PgpSignatureSubpacketVector.cs
@@ -0,0 +1,229 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Bcpg.Sig;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>Container for a list of signature subpackets.</remarks>
+ public class PgpSignatureSubpacketVector
+ {
+ private readonly SignatureSubpacket[] packets;
+
+ internal PgpSignatureSubpacketVector(
+ SignatureSubpacket[] packets)
+ {
+ this.packets = packets;
+ }
+
+ public SignatureSubpacket GetSubpacket(
+ SignatureSubpacketTag type)
+ {
+ for (int i = 0; i != packets.Length; i++)
+ {
+ if (packets[i].SubpacketType == 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 bool HasSubpacket(
+ SignatureSubpacketTag 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(
+ SignatureSubpacketTag type)
+ {
+ int count = 0;
+ for (int i = 0; i < packets.Length; ++i)
+ {
+ if (packets[i].SubpacketType == type)
+ {
+ ++count;
+ }
+ }
+
+ SignatureSubpacket[] result = new SignatureSubpacket[count];
+
+ int pos = 0;
+ for (int i = 0; i < packets.Length; ++i)
+ {
+ if (packets[i].SubpacketType == type)
+ {
+ result[pos++] = packets[i];
+ }
+ }
+
+ return result;
+ }
+
+ public NotationData[] GetNotationDataOccurences()
+ {
+ SignatureSubpacket[] notations = GetSubpackets(SignatureSubpacketTag.NotationData);
+ NotationData[] vals = new NotationData[notations.Length];
+
+ for (int i = 0; i < notations.Length; i++)
+ {
+ vals[i] = (NotationData) notations[i];
+ }
+
+ return vals;
+ }
+
+ public long GetIssuerKeyId()
+ {
+ SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.IssuerKeyId);
+
+ return p == null ? 0 : ((IssuerKeyId) p).KeyId;
+ }
+
+ public bool HasSignatureCreationTime()
+ {
+ return GetSubpacket(SignatureSubpacketTag.CreationTime) != null;
+ }
+
+ public DateTime GetSignatureCreationTime()
+ {
+ SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.CreationTime);
+
+ if (p == null)
+ {
+ throw new PgpException("SignatureCreationTime not available");
+ }
+
+ return ((SignatureCreationTime)p).GetTime();
+ }
+
+ /// <summary>
+ /// Return the number of seconds a signature is valid for after its creation date.
+ /// A value of zero means the signature never expires.
+ /// </summary>
+ /// <returns>Seconds a signature is valid for.</returns>
+ public long GetSignatureExpirationTime()
+ {
+ SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.ExpireTime);
+
+ return p == null ? 0 : ((SignatureExpirationTime) p).Time;
+ }
+
+ /// <summary>
+ /// Return the number of seconds a key is valid for after its creation date.
+ /// A value of zero means the key never expires.
+ /// </summary>
+ /// <returns>Seconds a signature is valid for.</returns>
+ public long GetKeyExpirationTime()
+ {
+ SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.KeyExpireTime);
+
+ return p == null ? 0 : ((KeyExpirationTime) p).Time;
+ }
+
+ public int[] GetPreferredHashAlgorithms()
+ {
+ SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.PreferredHashAlgorithms);
+
+ return p == null ? null : ((PreferredAlgorithms) p).GetPreferences();
+ }
+
+ public int[] GetPreferredSymmetricAlgorithms()
+ {
+ SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.PreferredSymmetricAlgorithms);
+
+ return p == null ? null : ((PreferredAlgorithms) p).GetPreferences();
+ }
+
+ public int[] GetPreferredCompressionAlgorithms()
+ {
+ SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.PreferredCompressionAlgorithms);
+
+ return p == null ? null : ((PreferredAlgorithms) p).GetPreferences();
+ }
+
+ public int GetKeyFlags()
+ {
+ SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.KeyFlags);
+
+ return p == null ? 0 : ((KeyFlags) p).Flags;
+ }
+
+ public string GetSignerUserId()
+ {
+ SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.SignerUserId);
+
+ return p == null ? null : ((SignerUserId) p).GetId();
+ }
+
+ public bool IsPrimaryUserId()
+ {
+ PrimaryUserId primaryId = (PrimaryUserId)
+ this.GetSubpacket(SignatureSubpacketTag.PrimaryUserId);
+
+ if (primaryId != null)
+ {
+ return primaryId.IsPrimaryUserId();
+ }
+
+ return false;
+ }
+
+ public SignatureSubpacketTag[] GetCriticalTags()
+ {
+ int count = 0;
+ for (int i = 0; i != packets.Length; i++)
+ {
+ if (packets[i].IsCritical())
+ {
+ count++;
+ }
+ }
+
+ SignatureSubpacketTag[] list = new SignatureSubpacketTag[count];
+
+ count = 0;
+
+ for (int i = 0; i != packets.Length; i++)
+ {
+ if (packets[i].IsCritical())
+ {
+ list[count++] = packets[i].SubpacketType;
+ }
+ }
+
+ return list;
+ }
+
+ [Obsolete("Use 'Count' property instead")]
+ public int Size
+ {
+ get { return packets.Length; }
+ }
+
+ /// <summary>Return the number of packets this vector contains.</summary>
+ public int Count
+ {
+ get { return packets.Length; }
+ }
+
+ internal SignatureSubpacket[] ToSubpacketArray()
+ {
+ return packets;
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpUserAttributeSubpacketVector.cs b/Crypto/src/openpgp/PgpUserAttributeSubpacketVector.cs
new file mode 100644
index 000000000..4cdbeda54
--- /dev/null
+++ b/Crypto/src/openpgp/PgpUserAttributeSubpacketVector.cs
@@ -0,0 +1,81 @@
+using Org.BouncyCastle.Bcpg.Attr;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>Container for a list of user attribute subpackets.</remarks>
+ public class PgpUserAttributeSubpacketVector
+ {
+ private readonly UserAttributeSubpacket[] packets;
+
+ internal PgpUserAttributeSubpacketVector(
+ UserAttributeSubpacket[] packets)
+ {
+ this.packets = packets;
+ }
+
+ public UserAttributeSubpacket GetSubpacket(
+ UserAttributeSubpacketTag type)
+ {
+ for (int i = 0; i != packets.Length; i++)
+ {
+ if (packets[i].SubpacketType == type)
+ {
+ return packets[i];
+ }
+ }
+
+ return null;
+ }
+
+ public ImageAttrib GetImageAttribute()
+ {
+ UserAttributeSubpacket p = GetSubpacket(UserAttributeSubpacketTag.ImageAttribute);
+
+ return p == null ? null : (ImageAttrib) p;
+ }
+
+ internal UserAttributeSubpacket[] ToSubpacketArray()
+ {
+ return packets;
+ }
+
+ public override bool Equals(
+ object obj)
+ {
+ if (obj == this)
+ return true;
+
+ PgpUserAttributeSubpacketVector other = obj as PgpUserAttributeSubpacketVector;
+
+ if (other == null)
+ return false;
+
+ 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;
+ }
+
+ public override int GetHashCode()
+ {
+ int code = 0;
+
+ foreach (object o in packets)
+ {
+ code ^= o.GetHashCode();
+ }
+
+ return code;
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpUtilities.cs b/Crypto/src/openpgp/PgpUtilities.cs
new file mode 100644
index 000000000..e22381bb1
--- /dev/null
+++ b/Crypto/src/openpgp/PgpUtilities.cs
@@ -0,0 +1,428 @@
+using System;
+using System.IO;
+using System.Text;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>Basic utility class.</remarks>
+ public sealed class PgpUtilities
+ {
+ private PgpUtilities()
+ {
+ }
+
+ public static MPInteger[] DsaSigToMpi(
+ byte[] encoding)
+ {
+ DerInteger i1, i2;
+
+ try
+ {
+ Asn1Sequence s = (Asn1Sequence) Asn1Object.FromByteArray(encoding);
+
+ i1 = (DerInteger) s[0];
+ i2 = (DerInteger) s[1];
+ }
+ catch (IOException e)
+ {
+ throw new PgpException("exception encoding signature", e);
+ }
+
+ return new MPInteger[]{ new MPInteger(i1.Value), new MPInteger(i2.Value) };
+ }
+
+ public static MPInteger[] RsaSigToMpi(
+ byte[] encoding)
+ {
+ return new MPInteger[]{ new MPInteger(new BigInteger(1, encoding)) };
+ }
+
+ public static string GetDigestName(
+ HashAlgorithmTag hashAlgorithm)
+ {
+ switch (hashAlgorithm)
+ {
+ case HashAlgorithmTag.Sha1:
+ return "SHA1";
+ case HashAlgorithmTag.MD2:
+ return "MD2";
+ case HashAlgorithmTag.MD5:
+ return "MD5";
+ case HashAlgorithmTag.RipeMD160:
+ return "RIPEMD160";
+ case HashAlgorithmTag.Sha224:
+ return "SHA224";
+ case HashAlgorithmTag.Sha256:
+ return "SHA256";
+ case HashAlgorithmTag.Sha384:
+ return "SHA384";
+ case HashAlgorithmTag.Sha512:
+ return "SHA512";
+ default:
+ throw new PgpException("unknown hash algorithm tag in GetDigestName: " + hashAlgorithm);
+ }
+ }
+
+ public static string GetSignatureName(
+ PublicKeyAlgorithmTag keyAlgorithm,
+ HashAlgorithmTag hashAlgorithm)
+ {
+ string encAlg;
+ switch (keyAlgorithm)
+ {
+ case PublicKeyAlgorithmTag.RsaGeneral:
+ case PublicKeyAlgorithmTag.RsaSign:
+ encAlg = "RSA";
+ break;
+ case PublicKeyAlgorithmTag.Dsa:
+ encAlg = "DSA";
+ break;
+ case PublicKeyAlgorithmTag.ElGamalEncrypt: // in some malformed cases.
+ case PublicKeyAlgorithmTag.ElGamalGeneral:
+ encAlg = "ElGamal";
+ break;
+ default:
+ throw new PgpException("unknown algorithm tag in signature:" + keyAlgorithm);
+ }
+
+ return GetDigestName(hashAlgorithm) + "with" + encAlg;
+ }
+
+ public static string GetSymmetricCipherName(
+ SymmetricKeyAlgorithmTag algorithm)
+ {
+ switch (algorithm)
+ {
+ case SymmetricKeyAlgorithmTag.Null:
+ return null;
+ case SymmetricKeyAlgorithmTag.TripleDes:
+ return "DESEDE";
+ case SymmetricKeyAlgorithmTag.Idea:
+ return "IDEA";
+ case SymmetricKeyAlgorithmTag.Cast5:
+ return "CAST5";
+ case SymmetricKeyAlgorithmTag.Blowfish:
+ return "Blowfish";
+ case SymmetricKeyAlgorithmTag.Safer:
+ return "SAFER";
+ case SymmetricKeyAlgorithmTag.Des:
+ return "DES";
+ case SymmetricKeyAlgorithmTag.Aes128:
+ return "AES";
+ case SymmetricKeyAlgorithmTag.Aes192:
+ return "AES";
+ case SymmetricKeyAlgorithmTag.Aes256:
+ return "AES";
+ case SymmetricKeyAlgorithmTag.Twofish:
+ return "Twofish";
+ default:
+ throw new PgpException("unknown symmetric algorithm: " + algorithm);
+ }
+ }
+
+ public static int GetKeySize(SymmetricKeyAlgorithmTag algorithm)
+ {
+ int keySize;
+ switch (algorithm)
+ {
+ case SymmetricKeyAlgorithmTag.Des:
+ keySize = 64;
+ break;
+ case SymmetricKeyAlgorithmTag.Idea:
+ case SymmetricKeyAlgorithmTag.Cast5:
+ case SymmetricKeyAlgorithmTag.Blowfish:
+ case SymmetricKeyAlgorithmTag.Safer:
+ case SymmetricKeyAlgorithmTag.Aes128:
+ keySize = 128;
+ break;
+ case SymmetricKeyAlgorithmTag.TripleDes:
+ case SymmetricKeyAlgorithmTag.Aes192:
+ keySize = 192;
+ break;
+ case SymmetricKeyAlgorithmTag.Aes256:
+ case SymmetricKeyAlgorithmTag.Twofish:
+ keySize = 256;
+ break;
+ default:
+ throw new PgpException("unknown symmetric algorithm: " + algorithm);
+ }
+
+ return keySize;
+ }
+
+ public static KeyParameter MakeKey(
+ SymmetricKeyAlgorithmTag algorithm,
+ byte[] keyBytes)
+ {
+ string algName = GetSymmetricCipherName(algorithm);
+
+ return ParameterUtilities.CreateKeyParameter(algName, keyBytes);
+ }
+
+ public static KeyParameter MakeRandomKey(
+ SymmetricKeyAlgorithmTag algorithm,
+ SecureRandom random)
+ {
+ int keySize = GetKeySize(algorithm);
+ byte[] keyBytes = new byte[(keySize + 7) / 8];
+ random.NextBytes(keyBytes);
+ return MakeKey(algorithm, keyBytes);
+ }
+
+ public static KeyParameter MakeKeyFromPassPhrase(
+ SymmetricKeyAlgorithmTag algorithm,
+ S2k s2k,
+ char[] passPhrase)
+ {
+ int keySize = GetKeySize(algorithm);
+ byte[] pBytes = Strings.ToByteArray(new string(passPhrase));
+ byte[] keyBytes = new byte[(keySize + 7) / 8];
+
+ int generatedBytes = 0;
+ int loopCount = 0;
+
+ while (generatedBytes < keyBytes.Length)
+ {
+ IDigest digest;
+ if (s2k != null)
+ {
+ string digestName = GetDigestName(s2k.HashAlgorithm);
+
+ try
+ {
+ digest = DigestUtilities.GetDigest(digestName);
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("can't find S2k digest", e);
+ }
+
+ for (int i = 0; i != loopCount; i++)
+ {
+ digest.Update(0);
+ }
+
+ byte[] iv = s2k.GetIV();
+
+ switch (s2k.Type)
+ {
+ case S2k.Simple:
+ digest.BlockUpdate(pBytes, 0, pBytes.Length);
+ break;
+ case S2k.Salted:
+ digest.BlockUpdate(iv, 0, iv.Length);
+ digest.BlockUpdate(pBytes, 0, pBytes.Length);
+ break;
+ case S2k.SaltedAndIterated:
+ long count = s2k.IterationCount;
+ digest.BlockUpdate(iv, 0, iv.Length);
+ digest.BlockUpdate(pBytes, 0, pBytes.Length);
+
+ count -= iv.Length + pBytes.Length;
+
+ while (count > 0)
+ {
+ if (count < iv.Length)
+ {
+ digest.BlockUpdate(iv, 0, (int)count);
+ break;
+ }
+ else
+ {
+ digest.BlockUpdate(iv, 0, iv.Length);
+ count -= iv.Length;
+ }
+
+ if (count < pBytes.Length)
+ {
+ digest.BlockUpdate(pBytes, 0, (int)count);
+ count = 0;
+ }
+ else
+ {
+ digest.BlockUpdate(pBytes, 0, pBytes.Length);
+ count -= pBytes.Length;
+ }
+ }
+ break;
+ default:
+ throw new PgpException("unknown S2k type: " + s2k.Type);
+ }
+ }
+ else
+ {
+ try
+ {
+ digest = DigestUtilities.GetDigest("MD5");
+
+ for (int i = 0; i != loopCount; i++)
+ {
+ digest.Update(0);
+ }
+
+ digest.BlockUpdate(pBytes, 0, pBytes.Length);
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("can't find MD5 digest", e);
+ }
+ }
+
+ byte[] dig = DigestUtilities.DoFinal(digest);
+
+ if (dig.Length > (keyBytes.Length - generatedBytes))
+ {
+ Array.Copy(dig, 0, keyBytes, generatedBytes, keyBytes.Length - generatedBytes);
+ }
+ else
+ {
+ Array.Copy(dig, 0, keyBytes, generatedBytes, dig.Length);
+ }
+
+ generatedBytes += dig.Length;
+
+ loopCount++;
+ }
+
+ Array.Clear(pBytes, 0, pBytes.Length);
+
+ return MakeKey(algorithm, keyBytes);
+ }
+
+#if !PORTABLE
+ /// <summary>Write out the passed in file as a literal data packet.</summary>
+ public static void WriteFileToLiteralData(
+ Stream output,
+ char fileType,
+ FileInfo file)
+ {
+ PgpLiteralDataGenerator lData = new PgpLiteralDataGenerator();
+ Stream pOut = lData.Open(output, fileType, file.Name, file.Length, file.LastWriteTime);
+ PipeFileContents(file, pOut, 4096);
+ }
+
+ /// <summary>Write out the passed in file as a literal data packet in partial packet format.</summary>
+ public static void WriteFileToLiteralData(
+ Stream output,
+ char fileType,
+ FileInfo file,
+ byte[] buffer)
+ {
+ PgpLiteralDataGenerator lData = new PgpLiteralDataGenerator();
+ Stream pOut = lData.Open(output, fileType, file.Name, file.LastWriteTime, buffer);
+ PipeFileContents(file, pOut, buffer.Length);
+ }
+
+ private static void PipeFileContents(FileInfo file, Stream pOut, int bufSize)
+ {
+ FileStream inputStream = file.OpenRead();
+ byte[] buf = new byte[bufSize];
+
+ int len;
+ while ((len = inputStream.Read(buf, 0, buf.Length)) > 0)
+ {
+ pOut.Write(buf, 0, len);
+ }
+
+ pOut.Close();
+ inputStream.Close();
+ }
+#endif
+
+ private const int ReadAhead = 60;
+
+ private static bool IsPossiblyBase64(
+ int ch)
+ {
+ return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')
+ || (ch >= '0' && ch <= '9') || (ch == '+') || (ch == '/')
+ || (ch == '\r') || (ch == '\n');
+ }
+
+ /// <summary>
+ /// Return either an ArmoredInputStream or a BcpgInputStream based on whether
+ /// the initial characters of the stream are binary PGP encodings or not.
+ /// </summary>
+ public static Stream GetDecoderStream(
+ Stream inputStream)
+ {
+ // TODO Remove this restriction?
+ if (!inputStream.CanSeek)
+ throw new ArgumentException("inputStream must be seek-able", "inputStream");
+
+ long markedPos = inputStream.Position;
+
+ int ch = inputStream.ReadByte();
+ if ((ch & 0x80) != 0)
+ {
+ inputStream.Position = markedPos;
+
+ return inputStream;
+ }
+ else
+ {
+ if (!IsPossiblyBase64(ch))
+ {
+ inputStream.Position = markedPos;
+
+ return new ArmoredInputStream(inputStream);
+ }
+
+ byte[] buf = new byte[ReadAhead];
+ int count = 1;
+ int index = 1;
+
+ buf[0] = (byte)ch;
+ while (count != ReadAhead && (ch = inputStream.ReadByte()) >= 0)
+ {
+ if (!IsPossiblyBase64(ch))
+ {
+ inputStream.Position = markedPos;
+
+ return new ArmoredInputStream(inputStream);
+ }
+
+ if (ch != '\n' && ch != '\r')
+ {
+ buf[index++] = (byte)ch;
+ }
+
+ count++;
+ }
+
+ inputStream.Position = markedPos;
+
+ //
+ // nothing but new lines, little else, assume regular armoring
+ //
+ if (count < 4)
+ {
+ return new ArmoredInputStream(inputStream);
+ }
+
+ //
+ // test our non-blank data
+ //
+ byte[] firstBlock = new byte[8];
+ Array.Copy(buf, 0, firstBlock, 0, firstBlock.Length);
+ byte[] decoded = Base64.Decode(firstBlock);
+
+ //
+ // it's a base64 PGP block.
+ //
+ bool hasHeaders = (decoded[0] & 0x80) == 0;
+
+ return new ArmoredInputStream(inputStream, hasHeaders);
+ }
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/PgpV3SignatureGenerator.cs b/Crypto/src/openpgp/PgpV3SignatureGenerator.cs
new file mode 100644
index 000000000..fc8b42df2
--- /dev/null
+++ b/Crypto/src/openpgp/PgpV3SignatureGenerator.cs
@@ -0,0 +1,199 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities.Date;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ /// <remarks>Generator for old style PGP V3 Signatures.</remarks>
+ // TODO Should be able to implement ISigner?
+ public class PgpV3SignatureGenerator
+ {
+ private PublicKeyAlgorithmTag keyAlgorithm;
+ private HashAlgorithmTag hashAlgorithm;
+ private PgpPrivateKey privKey;
+ private ISigner sig;
+ private IDigest dig;
+ private int signatureType;
+ private byte lastb;
+
+ /// <summary>Create a generator for the passed in keyAlgorithm and hashAlgorithm codes.</summary>
+ public PgpV3SignatureGenerator(
+ PublicKeyAlgorithmTag keyAlgorithm,
+ HashAlgorithmTag hashAlgorithm)
+ {
+ this.keyAlgorithm = keyAlgorithm;
+ this.hashAlgorithm = hashAlgorithm;
+
+ dig = DigestUtilities.GetDigest(PgpUtilities.GetDigestName(hashAlgorithm));
+ sig = SignerUtilities.GetSigner(PgpUtilities.GetSignatureName(keyAlgorithm, hashAlgorithm));
+ }
+
+ /// <summary>Initialise the generator for signing.</summary>
+ public void InitSign(
+ int sigType,
+ PgpPrivateKey key)
+ {
+ InitSign(sigType, key, null);
+ }
+
+ /// <summary>Initialise the generator for signing.</summary>
+ public void InitSign(
+ int sigType,
+ PgpPrivateKey key,
+ SecureRandom random)
+ {
+ this.privKey = key;
+ this.signatureType = sigType;
+
+ try
+ {
+ ICipherParameters cp = key.Key;
+ if (random != null)
+ {
+ cp = new ParametersWithRandom(key.Key, random);
+ }
+
+ sig.Init(true, cp);
+ }
+ catch (InvalidKeyException e)
+ {
+ throw new PgpException("invalid key.", e);
+ }
+
+ dig.Reset();
+ lastb = 0;
+ }
+
+ public void Update(
+ byte b)
+ {
+ if (signatureType == PgpSignature.CanonicalTextDocument)
+ {
+ doCanonicalUpdateByte(b);
+ }
+ else
+ {
+ doUpdateByte(b);
+ }
+ }
+
+ private void doCanonicalUpdateByte(
+ byte b)
+ {
+ if (b == '\r')
+ {
+ doUpdateCRLF();
+ }
+ else if (b == '\n')
+ {
+ if (lastb != '\r')
+ {
+ doUpdateCRLF();
+ }
+ }
+ else
+ {
+ doUpdateByte(b);
+ }
+
+ lastb = b;
+ }
+
+ private void doUpdateCRLF()
+ {
+ doUpdateByte((byte)'\r');
+ doUpdateByte((byte)'\n');
+ }
+
+ private void doUpdateByte(
+ byte b)
+ {
+ sig.Update(b);
+ dig.Update(b);
+ }
+
+ public void Update(
+ byte[] b)
+ {
+ if (signatureType == PgpSignature.CanonicalTextDocument)
+ {
+ for (int i = 0; i != b.Length; i++)
+ {
+ doCanonicalUpdateByte(b[i]);
+ }
+ }
+ else
+ {
+ sig.BlockUpdate(b, 0, b.Length);
+ dig.BlockUpdate(b, 0, b.Length);
+ }
+ }
+
+ public void Update(
+ byte[] b,
+ int off,
+ int len)
+ {
+ if (signatureType == PgpSignature.CanonicalTextDocument)
+ {
+ int finish = off + len;
+
+ for (int i = off; i != finish; i++)
+ {
+ doCanonicalUpdateByte(b[i]);
+ }
+ }
+ else
+ {
+ sig.BlockUpdate(b, off, len);
+ dig.BlockUpdate(b, off, len);
+ }
+ }
+
+ /// <summary>Return the one pass header associated with the current signature.</summary>
+ public PgpOnePassSignature GenerateOnePassVersion(
+ bool isNested)
+ {
+ return new PgpOnePassSignature(
+ new OnePassSignaturePacket(signatureType, hashAlgorithm, keyAlgorithm, privKey.KeyId, isNested));
+ }
+
+ /// <summary>Return a V3 signature object containing the current signature state.</summary>
+ public PgpSignature Generate()
+ {
+ long creationTime = DateTimeUtilities.CurrentUnixMs() / 1000L;
+
+ byte[] hData = new byte[]
+ {
+ (byte) signatureType,
+ (byte)(creationTime >> 24),
+ (byte)(creationTime >> 16),
+ (byte)(creationTime >> 8),
+ (byte) creationTime
+ };
+
+ sig.BlockUpdate(hData, 0, hData.Length);
+ dig.BlockUpdate(hData, 0, hData.Length);
+
+ byte[] sigBytes = sig.GenerateSignature();
+ byte[] digest = DigestUtilities.DoFinal(dig);
+ byte[] fingerPrint = new byte[]{ digest[0], digest[1] };
+
+ // an RSA signature
+ bool isRsa = keyAlgorithm == PublicKeyAlgorithmTag.RsaSign
+ || keyAlgorithm == PublicKeyAlgorithmTag.RsaGeneral;
+
+ MPInteger[] sigValues = isRsa
+ ? PgpUtilities.RsaSigToMpi(sigBytes)
+ : PgpUtilities.DsaSigToMpi(sigBytes);
+
+ return new PgpSignature(
+ new SignaturePacket(3, signatureType, privKey.KeyId, keyAlgorithm,
+ hashAlgorithm, creationTime * 1000L, fingerPrint, sigValues));
+ }
+ }
+}
diff --git a/Crypto/src/openpgp/WrappedGeneratorStream.cs b/Crypto/src/openpgp/WrappedGeneratorStream.cs
new file mode 100644
index 000000000..baad0d429
--- /dev/null
+++ b/Crypto/src/openpgp/WrappedGeneratorStream.cs
@@ -0,0 +1,28 @@
+using System.IO;
+
+using Org.BouncyCastle.Asn1.Utilities;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ public class WrappedGeneratorStream
+ : FilterStream
+ {
+ private readonly IStreamGenerator gen;
+
+ public WrappedGeneratorStream(
+ IStreamGenerator gen,
+ Stream str)
+ : base(str)
+ {
+ this.gen = gen;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ gen.Close();
+ }
+ }
+ }
+}
|