diff options
Diffstat (limited to 'crypto/src/openpgp')
40 files changed, 4458 insertions, 713 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 index c758ecb05..7f4ec8e53 100644 --- a/crypto/src/openpgp/PgpCompressedDataGenerator.cs +++ b/crypto/src/openpgp/PgpCompressedDataGenerator.cs @@ -155,7 +155,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { if (dOut != pkOut) { - dOut.Dispose(); + dOut.Close(); dOut.Flush(); } @@ -174,13 +174,10 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { } - protected override void Dispose(bool disposing) - { - if (disposing) - { - Finish(); - } - } + public override void Close() + { + Finish(); + } } private class SafeZOutputStream : ZOutputStream @@ -190,14 +187,11 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { } - protected override void Dispose(bool disposing) - { - if (disposing) - { - Finish(); - End(); - } - } + public override void Close() + { + Finish(); + End(); + } } } } diff --git a/crypto/src/openpgp/PgpDataValidationException.cs b/crypto/src/openpgp/PgpDataValidationException.cs index 74674da59..aab5165b2 100644 --- a/crypto/src/openpgp/PgpDataValidationException.cs +++ b/crypto/src/openpgp/PgpDataValidationException.cs @@ -5,6 +5,9 @@ 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 { 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 index 3048116fa..378b16a56 100644 --- a/crypto/src/openpgp/PgpException.cs +++ b/crypto/src/openpgp/PgpException.cs @@ -3,7 +3,10 @@ using System; namespace Org.BouncyCastle.Bcpg.OpenPgp { /// <remarks>Generic exception class for PGP encoding/decoding problems.</remarks> - public class PgpException +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT) + [Serializable] +#endif + public class PgpException : Exception { public PgpException() : base() {} 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 index da07f400f..d6419b27b 100644 --- a/crypto/src/openpgp/PgpKeyValidationException.cs +++ b/crypto/src/openpgp/PgpKeyValidationException.cs @@ -5,7 +5,10 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// <remarks> /// Thrown if the key checksum is invalid. /// </remarks> - public class PgpKeyValidationException +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT) + [Serializable] +#endif + public class PgpKeyValidationException : PgpException { public PgpKeyValidationException() : base() {} 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 index b0337c80d..3b1f2fe74 100644 --- a/crypto/src/openpgp/PgpLiteralDataGenerator.cs +++ b/crypto/src/openpgp/PgpLiteralDataGenerator.cs @@ -39,11 +39,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp private void WriteHeader( BcpgOutputStream outStr, char format, - string name, + byte[] encName, long modificationTime) { - byte[] encName = Strings.ToUtf8ByteArray(name); - outStr.Write( (byte) format, (byte) encName.Length); @@ -89,15 +87,17 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp // Do this first, since it might throw an exception long unixMs = DateTimeUtilities.DateTimeToUnixMs(modificationTime); - pkOut = new BcpgOutputStream(outStr, PacketTag.LiteralData, - length + 2 + name.Length + 4, oldFormat); + byte[] encName = Strings.ToUtf8ByteArray(name); + + pkOut = new BcpgOutputStream(outStr, PacketTag.LiteralData, + length + 2 + encName.Length + 4, oldFormat); - WriteHeader(pkOut, format, name, unixMs); + WriteHeader(pkOut, format, encName, unixMs); return new WrappedGeneratorStream(this, pkOut); } - /// <summary> + /// <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 @@ -132,14 +132,15 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp // 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, name, unixMs); + WriteHeader(pkOut, format, encName, unixMs); return new WrappedGeneratorStream(this, pkOut); } -#if !PORTABLE /// <summary> /// <p> /// Open a literal data packet for the passed in <c>FileInfo</c> object, returning @@ -161,7 +162,6 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { return Open(outStr, format, file.Name, file.Length, file.LastWriteTime); } -#endif /// <summary> /// Close the literal data packet - this is equivalent to calling Close() 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 index ecb935e4b..7b1ac93bf 100644 --- a/crypto/src/openpgp/PgpPublicKeyRing.cs +++ b/crypto/src/openpgp/PgpPublicKeyRing.cs @@ -7,164 +7,164 @@ 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 + /// <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( + public PgpPublicKeyRing( byte[] encoding) : this(new MemoryStream(encoding, false)) { } - internal PgpPublicKeyRing( + internal PgpPublicKeyRing( IList pubKeys) { this.keys = pubKeys; } - public PgpPublicKeyRing( + public PgpPublicKeyRing( Stream inputStream) { - this.keys = Platform.CreateArrayList(); + this.keys = Platform.CreateArrayList(); BcpgInputStream bcpgInput = BcpgInputStream.Wrap(inputStream); - PacketTag initialTag = bcpgInput.NextPacketTag(); + 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")); + + "tag 0x" + ((int)initialTag).ToString("X")); } - PublicKeyPacket pubPk = (PublicKeyPacket) bcpgInput.ReadPacket();; - TrustPacket trustPk = ReadOptionalTrustPacket(bcpgInput); + PublicKeyPacket pubPk = (PublicKeyPacket) bcpgInput.ReadPacket();; + TrustPacket trustPk = ReadOptionalTrustPacket(bcpgInput); // direct signatures and revocations - IList keySigs = ReadSignaturesAndTrust(bcpgInput); + IList keySigs = ReadSignaturesAndTrust(bcpgInput); - IList ids, idTrusts, idSigs; - ReadUserIDs(bcpgInput, out ids, out idTrusts, out idSigs); + IList ids, idTrusts, idSigs; + ReadUserIDs(bcpgInput, out ids, out idTrusts, out idSigs); - keys.Add(new PgpPublicKey(pubPk, trustPk, keySigs, ids, idTrusts, idSigs)); + keys.Add(new PgpPublicKey(pubPk, trustPk, keySigs, ids, idTrusts, idSigs)); - // Read subkeys - while (bcpgInput.NextPacketTag() == PacketTag.PublicSubkey) + // Read subkeys + while (bcpgInput.NextPacketTag() == PacketTag.PublicSubkey) { - keys.Add(ReadSubkey(bcpgInput)); + keys.Add(ReadSubkey(bcpgInput)); } } - /// <summary>Return the first public key in the ring.</summary> - public PgpPublicKey GetPublicKey() + /// <summary>Return the first public key in the ring.</summary> + public virtual 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( + /// <summary>Return the public key referred to by the passed in key ID if it is present.</summary> + public virtual PgpPublicKey GetPublicKey( long keyId) { - foreach (PgpPublicKey k in keys) - { - if (keyId == k.KeyId) + foreach (PgpPublicKey k in keys) + { + if (keyId == k.KeyId) { return k; } } - return null; + 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() + /// <summary>Allows enumeration of all the public keys.</summary> + /// <returns>An <c>IEnumerable</c> of <c>PgpPublicKey</c> objects.</returns> + public virtual IEnumerable GetPublicKeys() { return new EnumerableProxy(keys); } - public byte[] GetEncoded() + public virtual byte[] GetEncoded() { MemoryStream bOut = new MemoryStream(); - Encode(bOut); + Encode(bOut); - return bOut.ToArray(); + return bOut.ToArray(); } - public void Encode( + public virtual void Encode( Stream outStr) { - if (outStr == null) - throw new ArgumentNullException("outStr"); + if (outStr == null) + throw new ArgumentNullException("outStr"); - foreach (PgpPublicKey k in keys) - { - k.Encode(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> + /// <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; + bool masterFound = false; - for (int i = 0; i != keys.Count; i++) + for (int i = 0; i != keys.Count; i++) { PgpPublicKey key = (PgpPublicKey) keys[i]; - if (key.KeyId == pubKey.KeyId) + if (key.KeyId == pubKey.KeyId) { found = true; keys[i] = pubKey; } - if (key.IsMasterKey) - { - masterFound = true; - } - } + if (key.IsMasterKey) + { + masterFound = true; + } + } - if (!found) + 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); + 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> + /// <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) @@ -172,29 +172,29 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp IList keys = Platform.CreateArrayList(pubRing.keys); bool found = false; - for (int i = 0; i < keys.Count; i++) + for (int i = 0; i < keys.Count; i++) { PgpPublicKey key = (PgpPublicKey) keys[i]; - if (key.KeyId == pubKey.KeyId) + if (key.KeyId == pubKey.KeyId) { found = true; keys.RemoveAt(i); } } - return found ? new PgpPublicKeyRing(keys) : null; + return found ? new PgpPublicKeyRing(keys) : null; } - internal static PgpPublicKey ReadSubkey(BcpgInputStream bcpgInput) - { + internal static PgpPublicKey ReadSubkey(BcpgInputStream bcpgInput) + { PublicKeyPacket pk = (PublicKeyPacket) bcpgInput.ReadPacket(); - TrustPacket kTrust = ReadOptionalTrustPacket(bcpgInput); + TrustPacket kTrust = ReadOptionalTrustPacket(bcpgInput); - // PGP 8 actually leaves out the signature. - IList sigList = ReadSignaturesAndTrust(bcpgInput); + // PGP 8 actually leaves out the signature. + IList sigList = ReadSignaturesAndTrust(bcpgInput); - return new PgpPublicKey(pk, kTrust, sigList); - } + return new PgpPublicKey(pk, kTrust, sigList); + } } } diff --git a/crypto/src/openpgp/PgpPublicKeyRingBundle.cs b/crypto/src/openpgp/PgpPublicKeyRingBundle.cs index 77cede4a1..519a2f884 100644 --- a/crypto/src/openpgp/PgpPublicKeyRingBundle.cs +++ b/crypto/src/openpgp/PgpPublicKeyRingBundle.cs @@ -1,6 +1,5 @@ using System; using System.Collections; -using System.Globalization; using System.IO; using Org.BouncyCastle.Utilities; @@ -114,7 +113,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp if (ignoreCase) { - userId = userId.ToLowerInvariant(); + userId = Platform.ToLowerInvariant(userId); } foreach (PgpPublicKeyRing pubRing in GetKeyRings()) @@ -124,8 +123,8 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp string next = nextUserID; if (ignoreCase) { - next = next.ToLowerInvariant(); - } + next = Platform.ToLowerInvariant(next); + } if (matchPartial) { diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs index 9d87f49c8..872316dd7 100644 --- a/crypto/src/openpgp/PgpSecretKey.cs +++ b/crypto/src/openpgp/PgpSecretKey.cs @@ -5,112 +5,121 @@ using System.IO; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Bcpg.OpenPgp { - /// <remarks>General class to handle a PGP secret key object.</remarks> + /// <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, + 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, - bool isMasterKey) + bool useSha1, + SecureRandom rand) + : this(privKey, pubKey, encAlgorithm, passPhrase, useSha1, rand, false) { - BcpgObject secKey; + } - this.pub = pubKey; + internal PgpSecretKey( + PgpPrivateKey privKey, + PgpPublicKey pubKey, + SymmetricKeyAlgorithmTag encAlgorithm, + char[] passPhrase, + bool useSha1, + SecureRandom rand, + bool isMasterKey) + { + BcpgObject secKey; - switch (pubKey.Algorithm) + 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"); + 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 + 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.WriteObject(secKey); - pOut.Write(checksumBytes); + byte[] keyData = bOut.ToArray(); + byte[] checksumData = Checksum(useSha1, keyData, keyData.Length); - byte[] bOutData = bOut.ToArray(); + keyData = Arrays.Concatenate(keyData, checksumData); - 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 + if (encAlgorithm == SymmetricKeyAlgorithmTag.Null) { - 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); - } - } + if (isMasterKey) + { + this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, null, null, keyData); + } + else + { + this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, null, null, keyData); + } + } + else + { + S2k s2k; + byte[] iv; + + byte[] encData; + if (pub.Version >= 4) + { + encData = EncryptKeyData(keyData, encAlgorithm, passPhrase, rand, out s2k, out iv); + } + else + { + // TODO v3 RSA key encryption + throw Platform.CreateNotImplementedException("v3 RSA"); + } + + 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) { @@ -122,7 +131,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } - public PgpSecretKey( + public PgpSecretKey( int certificationLevel, PgpKeyPair keyPair, string id, @@ -131,61 +140,61 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp 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 + : 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 { - PgpSignature certification = sGen.GenerateCertification(id, keyPair.PublicKey); + 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); - } + throw new PgpException("Exception doing certification: " + e.Message, e); + } } - public PgpSecretKey( + public PgpSecretKey( int certificationLevel, PublicKeyAlgorithmTag algorithm, AsymmetricKeyParameter pubKey, @@ -203,181 +212,199 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { } - 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 + 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) { - 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> + /// <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; } + { + get { return pub.IsMasterKey; } + } + + /// <summary>Detect if the Secret Key's Private Key is empty or not</summary> + public bool IsPrivateKeyEmpty + { + get + { + byte[] secKeyData = secret.GetSecretKeyData(); + + return secKeyData == null || secKeyData.Length < 1; + } } - /// <summary>The algorithm the key is encrypted with.</summary> + /// <summary>The algorithm the key is encrypted with.</summary> public SymmetricKeyAlgorithmTag KeyEncryptionAlgorithm { - get { return secret.EncAlgorithm; } + get { return secret.EncAlgorithm; } } - /// <summary>The key ID of the public key associated with this key.</summary> + /// <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> + /// <summary>The public key associated with this key.</summary> public PgpPublicKey PublicKey { - get { return pub; } + 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> + /// <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(); } + 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> + /// <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(); } + get { return pub.GetUserAttributes(); } } - private byte[] ExtractKeyData( + 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 + byte[] encData = secret.GetSecretKeyData(); + + if (alg == SymmetricKeyAlgorithmTag.Null) + // TODO Check checksum here? + return encData; + + 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 'decryptData' + try { - KeyParameter key = PgpUtilities.MakeKeyFromPassPhrase(secret.EncAlgorithm, secret.S2k, passPhrase); - byte[] iv = secret.GetIV(); + KeyParameter key = PgpUtilities.MakeKeyFromPassPhrase(secret.EncAlgorithm, secret.S2k, passPhrase); + byte[] iv = secret.GetIV(); + byte[] data; - if (secret.PublicKeyPacket.Version == 4) + if (secret.PublicKeyPacket.Version >= 4) { - c.Init(false, new ParametersWithIV(key, iv)); + c.Init(false, new ParametersWithIV(key, iv)); - data = c.DoFinal(encData); + data = c.DoFinal(encData); - bool useSha1 = secret.S2kUsage == SecretKeyPacket.UsageSha1; - byte[] check = Checksum(useSha1, data, (useSha1) ? data.Length - 20 : data.Length - 2); + 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); - } - } - } + 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]; + data = new byte[encData.Length]; + + iv = Arrays.Clone(iv); - // + // // read in the four numbers // int pos = 0; - for (int i = 0; i != 4; i++) + 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; + int encLen = (((encData[pos] << 8) | (encData[pos + 1] & 0xff)) + 7) / 8; - data[pos] = encData[pos]; - data[pos + 1] = encData[pos + 1]; - pos += 2; + data[pos] = encData[pos]; + data[pos + 1] = encData[pos + 1]; + pos += 2; - c.DoFinal(encData, pos, encLen, data, pos); - pos += encLen; + c.DoFinal(encData, pos, encLen, data, pos); + pos += encLen; - if (i != 3) + 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); + // verify and copy checksum + // + + data[pos] = encData[pos]; + data[pos + 1] = encData[pos + 1]; + + int cs = ((encData[pos] << 8) & 0xff00) | (encData[pos + 1] & 0xff); int calcCs = 0; - for (int j=0; j < data.Length-2; j++) + for (int j = 0; j < pos; j++) { calcCs += data[j] & 0xff; } - calcCs &= 0xffff; + calcCs &= 0xffff; if (calcCs != cs) { throw new PgpException("Checksum mismatch: passphrase wrong, expected " - + cs.ToString("X") - + " found " + calcCs.ToString("X")); + + cs.ToString("X") + + " found " + calcCs.ToString("X")); } } - return data; + return data; } catch (PgpException e) { @@ -389,15 +416,14 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } - /// <summary>Extract a <c>PgpPrivateKey</c> from this secret key's encrypted contents.</summary> + /// <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) + if (IsPrivateKeyEmpty) return null; - PublicKeyPacket pubPk = secret.PublicKeyPacket; + PublicKeyPacket pubPk = secret.PublicKeyPacket; try { byte[] data = ExtractKeyData(passPhrase); @@ -438,7 +464,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp throw new PgpException("unknown public key algorithm encountered"); } - return new PgpPrivateKey(privateKey, KeyId); + return new PgpPrivateKey(privateKey, KeyId); } catch (PgpException e) { @@ -450,65 +476,65 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } - 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() + 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( + public void Encode( Stream outStr) { BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStr); - bcpgOut.WritePacket(secret); + bcpgOut.WritePacket(secret); if (pub.trustPk != null) { bcpgOut.WritePacket(pub.trustPk); } - if (pub.subSigs == null) // is not a sub key + if (pub.subSigs == null) // is not a sub key { - foreach (PgpSignature keySig in pub.keySigs) - { - keySig.Encode(bcpgOut); + foreach (PgpSignature keySig in pub.keySigs) + { + keySig.Encode(bcpgOut); } - for (int i = 0; i != pub.ids.Count; i++) + for (int i = 0; i != pub.ids.Count; i++) { - object pubID = pub.ids[i]; + object pubID = pub.ids[i]; if (pubID is string) { string id = (string) pubID; @@ -520,38 +546,38 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp bcpgOut.WritePacket(new UserAttributePacket(v.ToSubpacketArray())); } - if (pub.idTrusts[i] != null) + if (pub.idTrusts[i] != null) { bcpgOut.WritePacket((ContainedPacket)pub.idTrusts[i]); } - foreach (PgpSignature sig in (IList) pub.idSigs[i]) - { - sig.Encode(bcpgOut); + foreach (PgpSignature sig in (IList) pub.idSigs[i]) + { + sig.Encode(bcpgOut); } } } else { - foreach (PgpSignature subSig in pub.subSigs) - { - subSig.Encode(bcpgOut); + foreach (PgpSignature subSig in pub.subSigs) + { + subSig.Encode(bcpgOut); } } - // TODO Check that this is right/necessary - //bcpgOut.Finish(); + // 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> + /// <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, @@ -559,36 +585,48 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp SymmetricKeyAlgorithmTag newEncAlgorithm, SecureRandom rand) { + if (key.IsPrivateKeyEmpty) + throw new PgpException("no private key in this SecretKey - public key present only."); + byte[] rawKeyData = key.ExtractKeyData(oldPassPhrase); - int s2kUsage = key.secret.S2kUsage; - byte[] iv = null; + int s2kUsage = key.secret.S2kUsage; + byte[] iv = null; S2k s2k = null; byte[] keyData; + PublicKeyPacket pubKeyPacket = key.secret.PublicKeyPacket; - if (newEncAlgorithm == SymmetricKeyAlgorithmTag.Null) + 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; - } - } + 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); + if (pubKeyPacket.Version >= 4) + { + keyData = EncryptKeyData(rawKeyData, newEncAlgorithm, newPassPhrase, rand, out s2k, out iv); + } + else + { + // TODO v3 RSA key encryption + throw Platform.CreateNotImplementedException("v3 RSA"); + } } catch (PgpException e) { @@ -600,67 +638,65 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } - SecretKeyPacket secret; + SecretKeyPacket secret; if (key.secret is SecretSubkeyPacket) { - secret = new SecretSubkeyPacket(key.secret.PublicKeyPacket, - newEncAlgorithm, s2kUsage, s2k, iv, keyData); + secret = new SecretSubkeyPacket(pubKeyPacket, newEncAlgorithm, s2kUsage, s2k, iv, keyData); } else { - secret = new SecretKeyPacket(key.secret.PublicKeyPacket, - newEncAlgorithm, s2kUsage, s2k, iv, keyData); + secret = new SecretKeyPacket(pubKeyPacket, newEncAlgorithm, s2kUsage, s2k, iv, keyData); } - return new PgpSecretKey(secret, key.pub); + 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); } - /// <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); - } + 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 index 3e646eaa1..70cd7217c 100644 --- a/crypto/src/openpgp/PgpSecretKeyRing.cs +++ b/crypto/src/openpgp/PgpSecretKeyRing.cs @@ -8,57 +8,57 @@ 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 + /// <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( + 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( + public PgpSecretKeyRing( Stream inputStream) { - this.keys = Platform.CreateArrayList(); + this.keys = Platform.CreateArrayList(); this.extraPubKeys = Platform.CreateArrayList(); - BcpgInputStream bcpgInput = BcpgInputStream.Wrap(inputStream); + BcpgInputStream bcpgInput = BcpgInputStream.Wrap(inputStream); - PacketTag initialTag = bcpgInput.NextPacketTag(); - if (initialTag != PacketTag.SecretKey && initialTag != PacketTag.SecretSubkey) + 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")); + + "tag 0x" + ((int)initialTag).ToString("X")); } - SecretKeyPacket secret = (SecretKeyPacket) bcpgInput.ReadPacket(); + SecretKeyPacket secret = (SecretKeyPacket) bcpgInput.ReadPacket(); - // + // // ignore GPG comment packets if found. // while (bcpgInput.NextPacketTag() == PacketTag.Experimental2) @@ -66,65 +66,65 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp bcpgInput.ReadPacket(); } - TrustPacket trust = ReadOptionalTrustPacket(bcpgInput); + TrustPacket trust = ReadOptionalTrustPacket(bcpgInput); - // revocation and direct signatures - IList keySigs = ReadSignaturesAndTrust(bcpgInput); + // revocation and direct signatures + IList keySigs = ReadSignaturesAndTrust(bcpgInput); - IList ids, idTrusts, idSigs; - ReadUserIDs(bcpgInput, out ids, out idTrusts, out idSigs); + 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))); + 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) + // 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)); - } + 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> + /// <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> + /// <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() + /// <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); } @@ -132,29 +132,29 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp public PgpSecretKey GetSecretKey( long keyId) { - foreach (PgpSecretKey k in keys) - { - if (keyId == k.KeyId) - { - return k; - } - } - - return null; + 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); } - /// <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() + public byte[] GetEncoded() { MemoryStream bOut = new MemoryStream(); @@ -166,117 +166,124 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp 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); - } + 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) - { + /// <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) - { + 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( + foreach (PgpSecretKey secretKey in ring.GetSecretKeys()) + { + if (secretKey.IsPrivateKeyEmpty) + { + newKeys.Add(secretKey); + } + else + { + 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; + bool masterFound = false; - for (int i = 0; i != keys.Count; i++) + for (int i = 0; i != keys.Count; i++) { PgpSecretKey key = (PgpSecretKey) keys[i]; - if (key.KeyId == secKey.KeyId) + if (key.KeyId == secKey.KeyId) { found = true; keys[i] = secKey; } - if (key.IsMasterKey) - { - masterFound = true; - } - } + 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); - } + 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); - } + 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> + /// <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) @@ -284,18 +291,18 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp IList keys = Platform.CreateArrayList(secRing.keys); bool found = false; - for (int i = 0; i < keys.Count; i++) + for (int i = 0; i < keys.Count; i++) { PgpSecretKey key = (PgpSecretKey)keys[i]; - if (key.KeyId == secKey.KeyId) + if (key.KeyId == secKey.KeyId) { found = true; keys.RemoveAt(i); } } - return found ? new PgpSecretKeyRing(keys, secRing.extraPubKeys) : null; + return found ? new PgpSecretKeyRing(keys, secRing.extraPubKeys) : null; } } } diff --git a/crypto/src/openpgp/PgpSecretKeyRingBundle.cs b/crypto/src/openpgp/PgpSecretKeyRingBundle.cs index 18636dd65..12c7c098c 100644 --- a/crypto/src/openpgp/PgpSecretKeyRingBundle.cs +++ b/crypto/src/openpgp/PgpSecretKeyRingBundle.cs @@ -114,8 +114,8 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp if (ignoreCase) { - userId = userId.ToLowerInvariant(); - } + userId = Platform.ToLowerInvariant(userId); + } foreach (PgpSecretKeyRing secRing in GetKeyRings()) { @@ -124,8 +124,8 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp string next = nextUserID; if (ignoreCase) { - next = next.ToLowerInvariant(); - } + next = Platform.ToLowerInvariant(next); + } if (matchPartial) { diff --git a/crypto/src/openpgp/PgpSignature.cs b/crypto/src/openpgp/PgpSignature.cs index cbe0d83b4..3bb6f2f0e 100644 --- a/crypto/src/openpgp/PgpSignature.cs +++ b/crypto/src/openpgp/PgpSignature.cs @@ -253,7 +253,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp // // hash in the id // - UpdateWithIdData(0xb4, Strings.ToByteArray(id)); + UpdateWithIdData(0xb4, Strings.ToUtf8ByteArray(id)); Update(sigPck.GetSignatureTrailer()); diff --git a/crypto/src/openpgp/PgpSignatureGenerator.cs b/crypto/src/openpgp/PgpSignatureGenerator.cs index 891397267..c5309689f 100644 --- a/crypto/src/openpgp/PgpSignatureGenerator.cs +++ b/crypto/src/openpgp/PgpSignatureGenerator.cs @@ -267,9 +267,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp // // hash in the id // - UpdateWithIdData(0xb4, Strings.ToByteArray(id)); + UpdateWithIdData(0xb4, Strings.ToUtf8ByteArray(id)); - return Generate(); + return Generate(); } /// <summary>Generate a certification for the passed in userAttributes.</summary> 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 index e22381bb1..32e37b819 100644 --- a/crypto/src/openpgp/PgpUtilities.cs +++ b/crypto/src/openpgp/PgpUtilities.cs @@ -97,7 +97,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp return GetDigestName(hashAlgorithm) + "with" + encAlg; } - public static string GetSymmetricCipherName( + public static string GetSymmetricCipherName( SymmetricKeyAlgorithmTag algorithm) { switch (algorithm) @@ -124,12 +124,18 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp return "AES"; case SymmetricKeyAlgorithmTag.Twofish: return "Twofish"; + case SymmetricKeyAlgorithmTag.Camellia128: + return "Camellia"; + case SymmetricKeyAlgorithmTag.Camellia192: + return "Camellia"; + case SymmetricKeyAlgorithmTag.Camellia256: + return "Camellia"; default: throw new PgpException("unknown symmetric algorithm: " + algorithm); } } - public static int GetKeySize(SymmetricKeyAlgorithmTag algorithm) + public static int GetKeySize(SymmetricKeyAlgorithmTag algorithm) { int keySize; switch (algorithm) @@ -142,14 +148,17 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp case SymmetricKeyAlgorithmTag.Blowfish: case SymmetricKeyAlgorithmTag.Safer: case SymmetricKeyAlgorithmTag.Aes128: + case SymmetricKeyAlgorithmTag.Camellia128: keySize = 128; break; case SymmetricKeyAlgorithmTag.TripleDes: case SymmetricKeyAlgorithmTag.Aes192: + case SymmetricKeyAlgorithmTag.Camellia192: keySize = 192; break; case SymmetricKeyAlgorithmTag.Aes256: case SymmetricKeyAlgorithmTag.Twofish: + case SymmetricKeyAlgorithmTag.Camellia256: keySize = 256; break; default: @@ -298,7 +307,6 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp return MakeKey(algorithm, keyBytes); } -#if !PORTABLE /// <summary>Write out the passed in file as a literal data packet.</summary> public static void WriteFileToLiteralData( Stream output, @@ -336,7 +344,6 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp pOut.Close(); inputStream.Close(); } -#endif private const int ReadAhead = 60; 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 index baad0d429..6fc7329af 100644 --- a/crypto/src/openpgp/WrappedGeneratorStream.cs +++ b/crypto/src/openpgp/WrappedGeneratorStream.cs @@ -4,25 +4,22 @@ using Org.BouncyCastle.Asn1.Utilities; namespace Org.BouncyCastle.Bcpg.OpenPgp { - public class WrappedGeneratorStream - : FilterStream - { - private readonly IStreamGenerator gen; + public class WrappedGeneratorStream + : FilterStream + { + private readonly IStreamGenerator gen; - public WrappedGeneratorStream( - IStreamGenerator gen, - Stream str) - : base(str) - { - this.gen = gen; - } + public WrappedGeneratorStream( + IStreamGenerator gen, + Stream str) + : base(str) + { + this.gen = gen; + } - protected override void Dispose(bool disposing) - { - if (disposing) - { - gen.Close(); - } - } - } + public override void Close() + { + gen.Close(); + } + } } |