diff options
author | Peter Dettman <peter.dettman@bouncycastle.org> | 2013-06-28 15:26:06 +0700 |
---|---|---|
committer | Peter Dettman <peter.dettman@bouncycastle.org> | 2013-06-28 15:26:06 +0700 |
commit | 44288db4414158ac9b98a507b15e81d0d3c66ca6 (patch) | |
tree | aa5ef88948ebb68ed6c8df81eb5da889641a9b50 /crypto/src/openpgp | |
parent | Set up text/binary handling for existing file types (diff) | |
download | BouncyCastle.NET-ed25519-44288db4414158ac9b98a507b15e81d0d3c66ca6.tar.xz |
Initial import of old CVS repository
Diffstat (limited to 'crypto/src/openpgp')
40 files changed, 7124 insertions, 0 deletions
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..7f4ec8e53 --- /dev/null +++ b/crypto/src/openpgp/PgpCompressedDataGenerator.cs @@ -0,0 +1,197 @@ +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.Close(); + dOut.Flush(); + } + + dOut = null; + + pkOut.Finish(); + pkOut.Flush(); + pkOut = null; + } + } + + private class SafeCBZip2OutputStream : CBZip2OutputStream + { + public SafeCBZip2OutputStream(Stream output) + : base(output) + { + } + + public override void Close() + { + Finish(); + } + } + + private class SafeZOutputStream : ZOutputStream + { + public SafeZOutputStream(Stream output, int level, bool nowrap) + : base(output, level, nowrap) + { + } + + public override void Close() + { + Finish(); + End(); + } + } + } +} diff --git a/crypto/src/openpgp/PgpDataValidationException.cs b/crypto/src/openpgp/PgpDataValidationException.cs new file mode 100644 index 000000000..aab5165b2 --- /dev/null +++ b/crypto/src/openpgp/PgpDataValidationException.cs @@ -0,0 +1,18 @@ +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> +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT) + [Serializable] +#endif + 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..378b16a56 --- /dev/null +++ b/crypto/src/openpgp/PgpException.cs @@ -0,0 +1,22 @@ +using System; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// <remarks>Generic exception class for PGP encoding/decoding problems.</remarks> +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT) + [Serializable] +#endif + 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..d6419b27b --- /dev/null +++ b/crypto/src/openpgp/PgpKeyValidationException.cs @@ -0,0 +1,18 @@ +using System; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /// <remarks> + /// Thrown if the key checksum is invalid. + /// </remarks> +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT) + [Serializable] +#endif + 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..3b1f2fe74 --- /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, + byte[] encName, + long modificationTime) + { + 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); + + byte[] encName = Strings.ToUtf8ByteArray(name); + + pkOut = new BcpgOutputStream(outStr, PacketTag.LiteralData, + length + 2 + encName.Length + 4, oldFormat); + + WriteHeader(pkOut, format, encName, 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); + + byte[] encName = Strings.ToUtf8ByteArray(name); + + pkOut = new BcpgOutputStream(outStr, PacketTag.LiteralData, buffer); + + WriteHeader(pkOut, format, encName, unixMs); + + return new WrappedGeneratorStream(this, pkOut); + } + + /// <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); + } + + /// <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..519a2f884 --- /dev/null +++ b/crypto/src/openpgp/PgpPublicKeyRingBundle.cs @@ -0,0 +1,279 @@ +using System; +using System.Collections; +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 = Platform.ToLowerInvariant(userId); + } + + foreach (PgpPublicKeyRing pubRing in GetKeyRings()) + { + foreach (string nextUserID in pubRing.GetPublicKey().GetUserIds()) + { + string next = nextUserID; + if (ignoreCase) + { + next = Platform.ToLowerInvariant(next); + } + + 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..12c7c098c --- /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 = Platform.ToLowerInvariant(userId); + } + + foreach (PgpSecretKeyRing secRing in GetKeyRings()) + { + foreach (string nextUserID in secRing.GetSecretKey().UserIds) + { + string next = nextUserID; + if (ignoreCase) + { + next = Platform.ToLowerInvariant(next); + } + + 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..3bb6f2f0e --- /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.ToUtf8ByteArray(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..c5309689f --- /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.ToUtf8ByteArray(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..7cd3dfc4f --- /dev/null +++ b/crypto/src/openpgp/PgpUtilities.cs @@ -0,0 +1,426 @@ +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); + } + + /// <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(); + } + + 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..6fc7329af --- /dev/null +++ b/crypto/src/openpgp/WrappedGeneratorStream.cs @@ -0,0 +1,25 @@ +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; + } + + public override void Close() + { + gen.Close(); + } + } +} |