diff options
author | Peter Dettman <peter.dettman@bouncycastle.org> | 2023-02-28 18:49:21 +0700 |
---|---|---|
committer | Peter Dettman <peter.dettman@bouncycastle.org> | 2023-02-28 18:49:21 +0700 |
commit | 5ac39b19f346e72005f41f13ff956f4fa7c97f86 (patch) | |
tree | 0ca4585a968a8b11044267b0268d3e65f29fa559 | |
parent | Update Asn1Tags (diff) | |
download | BouncyCastle.NET-ed25519-5ac39b19f346e72005f41f13ff956f4fa7c97f86.tar.xz |
OpenPGP updates from bc-java
32 files changed, 1035 insertions, 466 deletions
diff --git a/crypto/src/bcpg/AeadAlgorithmTag.cs b/crypto/src/bcpg/AeadAlgorithmTag.cs new file mode 100644 index 000000000..632f88838 --- /dev/null +++ b/crypto/src/bcpg/AeadAlgorithmTag.cs @@ -0,0 +1,11 @@ +using System; + +namespace Org.BouncyCastle.Bcpg +{ + public enum AeadAlgorithmTag : byte + { + Eax = 1, // EAX (IV len: 16 octets, Tag len: 16 octets) + Ocb = 2, // OCB (IV len: 15 octets, Tag len: 16 octets) + Gcm = 3, // GCM (IV len: 12 octets, Tag len: 16 octets) + } +} diff --git a/crypto/src/bcpg/AeadEncDataPacket.cs b/crypto/src/bcpg/AeadEncDataPacket.cs new file mode 100644 index 000000000..2c7ec97f6 --- /dev/null +++ b/crypto/src/bcpg/AeadEncDataPacket.cs @@ -0,0 +1,77 @@ +using System; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Bcpg +{ + /** + * Packet representing AEAD encrypted data. At the moment this appears to exist in the following + * expired draft only, but it's appearing despite this. + * + * @ref https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-rfc4880bis-04#section-5.16 + */ + public class AeadEncDataPacket + : InputStreamPacket + { + private readonly byte m_version; + private readonly SymmetricKeyAlgorithmTag m_algorithm; + private readonly AeadAlgorithmTag m_aeadAlgorithm; + private readonly byte m_chunkSize; + private readonly byte[] m_iv; + + public AeadEncDataPacket(BcpgInputStream bcpgIn) + : base(bcpgIn) + { + m_version = (byte)bcpgIn.ReadByte(); + if (m_version != 1) + throw new ArgumentException("wrong AEAD packet version: " + m_version); + + m_algorithm = (SymmetricKeyAlgorithmTag)bcpgIn.ReadByte(); + m_aeadAlgorithm = (AeadAlgorithmTag)bcpgIn.ReadByte(); + m_chunkSize = (byte)bcpgIn.ReadByte(); + + m_iv = new byte[GetIVLength(m_aeadAlgorithm)]; + bcpgIn.ReadFully(m_iv); + } + + public AeadEncDataPacket(SymmetricKeyAlgorithmTag algorithm, AeadAlgorithmTag aeadAlgorithm, int chunkSize, + byte[] iv) + : base(null) + { + m_version = 1; + m_algorithm = algorithm; + m_aeadAlgorithm = aeadAlgorithm; + m_chunkSize = (byte)chunkSize; + m_iv = Arrays.Clone(iv); + } + + public byte Version => m_version; + + public SymmetricKeyAlgorithmTag Algorithm => m_algorithm; + + public AeadAlgorithmTag AeadAlgorithm => m_aeadAlgorithm; + + public int ChunkSize => m_chunkSize; + + public byte[] GetIV() + { + return m_iv; + } + + public static int GetIVLength(AeadAlgorithmTag aeadAlgorithm) + { + switch (aeadAlgorithm) + { + case AeadAlgorithmTag.Eax: + return 16; + case AeadAlgorithmTag.Ocb: + return 15; + case AeadAlgorithmTag.Gcm: + return 12; + default: + throw new ArgumentException("unknown mode: " + aeadAlgorithm); + } + } + } +} diff --git a/crypto/src/bcpg/ArmoredInputStream.cs b/crypto/src/bcpg/ArmoredInputStream.cs index b493d04fc..eacba17dd 100644 --- a/crypto/src/bcpg/ArmoredInputStream.cs +++ b/crypto/src/bcpg/ArmoredInputStream.cs @@ -304,7 +304,7 @@ namespace Org.BouncyCastle.Bcpg { c = input.ReadByte(); } - while (c == ' ' || c == '\t' || c == '\f' || c == '\u000B'); // \u000B ~ \v + while (c == ' ' || c == '\t' || c == '\f' || c == '\v'); if (c >= 128) throw new IOException("invalid armor"); diff --git a/crypto/src/bcpg/BcpgOutputStream.cs b/crypto/src/bcpg/BcpgOutputStream.cs index 3bae7e383..29ed9e22a 100644 --- a/crypto/src/bcpg/BcpgOutputStream.cs +++ b/crypto/src/bcpg/BcpgOutputStream.cs @@ -19,6 +19,7 @@ namespace Org.BouncyCastle.Bcpg } private Stream outStr; + private bool useOldFormat; private byte[] partialBuffer; private int partialBufferLength; private int partialPower; @@ -27,26 +28,28 @@ namespace Org.BouncyCastle.Bcpg /// <summary>Create a stream representing a general packet.</summary> /// <param name="outStr">Output stream to write to.</param> - public BcpgOutputStream( - Stream outStr) + public BcpgOutputStream(Stream outStr) + : this(outStr, false) { - if (outStr == null) - throw new ArgumentNullException("outStr"); - - this.outStr = outStr; } - /// <summary>Create a stream representing an old style partial object.</summary> + /// <summary>Base constructor specifying whether or not to use packets in the new format wherever possible. + /// </summary> /// <param name="outStr">Output stream to write to.</param> - /// <param name="tag">The packet tag for the object.</param> - public BcpgOutputStream( - Stream outStr, - PacketTag tag) + /// <param name="newFormatOnly"><c>true</c> if use new format packets, <c>false</c> if backwards compatible + /// preferred.</param> + public BcpgOutputStream(Stream outStr, bool newFormatOnly) { - if (outStr == null) - throw new ArgumentNullException("outStr"); + this.outStr = outStr ?? throw new ArgumentNullException(nameof(outStr)); + this.useOldFormat = !newFormatOnly; + } - this.outStr = outStr; + /// <summary>Create a stream representing an old style partial object.</summary> + /// <param name="outStr">Output stream to write to.</param> + /// <param name="tag">The packet tag for the object.</param> + public BcpgOutputStream(Stream outStr, PacketTag tag) + { + this.outStr = outStr ?? throw new ArgumentNullException(nameof(outStr)); this.WriteHeader(tag, true, true, 0); } @@ -55,16 +58,9 @@ namespace Org.BouncyCastle.Bcpg /// <param name="tag">Packet tag.</param> /// <param name="length">Size of chunks making up the packet.</param> /// <param name="oldFormat">If true, the header is written out in old format.</param> - public BcpgOutputStream( - Stream outStr, - PacketTag tag, - long length, - bool oldFormat) + public BcpgOutputStream(Stream outStr, PacketTag tag, long length, bool oldFormat) { - if (outStr == null) - throw new ArgumentNullException("outStr"); - - this.outStr = outStr; + this.outStr = outStr ?? throw new ArgumentNullException(nameof(outStr)); if (length > 0xFFFFFFFFL) { @@ -84,15 +80,9 @@ namespace Org.BouncyCastle.Bcpg /// <param name="outStr">Output stream to write to.</param> /// <param name="tag">Packet tag.</param> /// <param name="length">Size of chunks making up the packet.</param> - public BcpgOutputStream( - Stream outStr, - PacketTag tag, - long length) + public BcpgOutputStream(Stream outStr, PacketTag tag, long length) { - if (outStr == null) - throw new ArgumentNullException("outStr"); - - this.outStr = outStr; + this.outStr = outStr ?? throw new ArgumentNullException(nameof(outStr)); this.WriteHeader(tag, false, false, length); } @@ -100,15 +90,9 @@ namespace Org.BouncyCastle.Bcpg /// <param name="outStr">Output stream to write to.</param> /// <param name="tag">Packet tag.</param> /// <param name="buffer">Buffer to use for collecting chunks.</param> - public BcpgOutputStream( - Stream outStr, - PacketTag tag, - byte[] buffer) + public BcpgOutputStream(Stream outStr, PacketTag tag, byte[] buffer) { - if (outStr == null) - throw new ArgumentNullException("outStr"); - - this.outStr = outStr; + this.outStr = outStr ?? throw new ArgumentNullException(nameof(outStr)); this.WriteHeader(tag, false, true, 0); this.partialBuffer = buffer; @@ -120,15 +104,13 @@ namespace Org.BouncyCastle.Bcpg } if (partialPower > 30) - { throw new IOException("Buffer cannot be greater than 2^30 in length."); - } + this.partialBufferLength = 1 << partialPower; this.partialOffset = 0; } - private void WriteNewPacketLength( - long bodyLen) + private void WriteNewPacketLength(long bodyLen) { if (bodyLen < 192) { @@ -378,29 +360,28 @@ namespace Org.BouncyCastle.Bcpg (byte)n); } - public void WritePacket( - ContainedPacket p) + public void WritePacket(ContainedPacket p) { p.Encode(this); } - internal void WritePacket( - PacketTag tag, - byte[] body, - bool oldFormat) + internal void WritePacket(PacketTag tag, byte[] body) + { + WritePacket(tag, body, useOldFormat); + } + + internal void WritePacket(PacketTag tag, byte[] body, bool oldFormat) { this.WriteHeader(tag, oldFormat, false, body.Length); this.Write(body); } - public void WriteObject( - BcpgObject bcpgObject) + public void WriteObject(BcpgObject bcpgObject) { bcpgObject.Encode(this); } - public void WriteObjects( - params BcpgObject[] v) + public void WriteObjects(params BcpgObject[] v) { foreach (BcpgObject o in v) { diff --git a/crypto/src/bcpg/ExperimentalPacket.cs b/crypto/src/bcpg/ExperimentalPacket.cs index 36a254be1..3d413db09 100644 --- a/crypto/src/bcpg/ExperimentalPacket.cs +++ b/crypto/src/bcpg/ExperimentalPacket.cs @@ -5,34 +5,24 @@ namespace Org.BouncyCastle.Bcpg { /// <remarks>Basic packet for an experimental packet.</remarks> public class ExperimentalPacket - : ContainedPacket //, PublicKeyAlgorithmTag + : ContainedPacket { - private readonly PacketTag tag; - private readonly byte[] contents; + private readonly PacketTag m_tag; + private readonly byte[] m_contents; - internal ExperimentalPacket( - PacketTag tag, - BcpgInputStream bcpgIn) + internal ExperimentalPacket(PacketTag tag, BcpgInputStream bcpgIn) { - this.tag = tag; - - this.contents = bcpgIn.ReadAll(); + m_tag = tag; + m_contents = bcpgIn.ReadAll(); } - public PacketTag Tag - { - get { return tag; } - } + public PacketTag Tag => m_tag; - public byte[] GetContents() - { - return (byte[]) contents.Clone(); - } + public byte[] GetContents() => (byte[])m_contents.Clone(); - public override void Encode( - BcpgOutputStream bcpgOut) + public override void Encode(BcpgOutputStream bcpgOut) { - bcpgOut.WritePacket(tag, contents, true); + bcpgOut.WritePacket(m_tag, m_contents); } } } diff --git a/crypto/src/bcpg/HashAlgorithmTags.cs b/crypto/src/bcpg/HashAlgorithmTags.cs index 96c009153..a13725de6 100644 --- a/crypto/src/bcpg/HashAlgorithmTags.cs +++ b/crypto/src/bcpg/HashAlgorithmTags.cs @@ -15,5 +15,15 @@ namespace Org.BouncyCastle.Bcpg Sha384 = 9, // SHA-384 Sha512 = 10, // SHA-512 Sha224 = 11, // SHA-224 + Sha3_256 = 12, // SHA3-256 + Sha3_512 = 14, // SHA3-512 + + MD4 = 301, + Sha3_224 = 312, // SHA3-224 + Sha3_256_Old = 313, //SHA3-256 + Sha3_384 = 314, // SHA3-384 + Sha3_512_Old = 315, // SHA3-512 + + SM3 = 326, // SM3 } } diff --git a/crypto/src/bcpg/IBcpgKey.cs b/crypto/src/bcpg/IBcpgKey.cs index 275461772..86832eb8b 100644 --- a/crypto/src/bcpg/IBcpgKey.cs +++ b/crypto/src/bcpg/IBcpgKey.cs @@ -2,6 +2,7 @@ using System; namespace Org.BouncyCastle.Bcpg { + // TODO[api] Extend IEncodable /// <remarks>Base interface for a PGP key.</remarks> public interface IBcpgKey { diff --git a/crypto/src/bcpg/IUserDataPacket.cs b/crypto/src/bcpg/IUserDataPacket.cs new file mode 100644 index 000000000..1519b5007 --- /dev/null +++ b/crypto/src/bcpg/IUserDataPacket.cs @@ -0,0 +1,6 @@ +namespace Org.BouncyCastle.Bcpg +{ + public interface IUserDataPacket + { + } +} diff --git a/crypto/src/bcpg/InputStreamPacket.cs b/crypto/src/bcpg/InputStreamPacket.cs index c45efab7b..d4946ebf2 100644 --- a/crypto/src/bcpg/InputStreamPacket.cs +++ b/crypto/src/bcpg/InputStreamPacket.cs @@ -5,8 +5,7 @@ namespace Org.BouncyCastle.Bcpg { private readonly BcpgInputStream bcpgIn; - public InputStreamPacket( - BcpgInputStream bcpgIn) + public InputStreamPacket(BcpgInputStream bcpgIn) { this.bcpgIn = bcpgIn; } diff --git a/crypto/src/bcpg/MarkerPacket.cs b/crypto/src/bcpg/MarkerPacket.cs index 4dc4b5a83..f2c450793 100644 --- a/crypto/src/bcpg/MarkerPacket.cs +++ b/crypto/src/bcpg/MarkerPacket.cs @@ -7,18 +7,16 @@ namespace Org.BouncyCastle.Bcpg : ContainedPacket { // "PGP" - byte[] marker = { (byte)0x50, (byte)0x47, (byte)0x50 }; + private readonly byte[] marker = { (byte)0x50, (byte)0x47, (byte)0x50 }; - public MarkerPacket( - BcpgInputStream bcpgIn) + public MarkerPacket(BcpgInputStream bcpgIn) { bcpgIn.ReadFully(marker); } - public override void Encode( - BcpgOutputStream bcpgOut) + public override void Encode(BcpgOutputStream bcpgOut) { - bcpgOut.WritePacket(PacketTag.Marker, marker, true); + bcpgOut.WritePacket(PacketTag.Marker, marker); } } } diff --git a/crypto/src/bcpg/OnePassSignaturePacket.cs b/crypto/src/bcpg/OnePassSignaturePacket.cs index 70f0a2207..4efae1d63 100644 --- a/crypto/src/bcpg/OnePassSignaturePacket.cs +++ b/crypto/src/bcpg/OnePassSignaturePacket.cs @@ -81,7 +81,7 @@ namespace Org.BouncyCastle.Bcpg pOut.WriteByte((byte)nested); } - bcpgOut.WritePacket(PacketTag.OnePassSignature, bOut.ToArray(), true); + bcpgOut.WritePacket(PacketTag.OnePassSignature, bOut.ToArray()); } } } diff --git a/crypto/src/bcpg/PublicKeyEncSessionPacket.cs b/crypto/src/bcpg/PublicKeyEncSessionPacket.cs index f0b1577ec..25d297890 100644 --- a/crypto/src/bcpg/PublicKeyEncSessionPacket.cs +++ b/crypto/src/bcpg/PublicKeyEncSessionPacket.cs @@ -105,7 +105,7 @@ namespace Org.BouncyCastle.Bcpg } } - bcpgOut.WritePacket(PacketTag.PublicKeyEncryptedSession, bOut.ToArray(), true); + bcpgOut.WritePacket(PacketTag.PublicKeyEncryptedSession, bOut.ToArray()); } } } diff --git a/crypto/src/bcpg/PublicKeyPacket.cs b/crypto/src/bcpg/PublicKeyPacket.cs index 40c696a37..b3b5d1600 100644 --- a/crypto/src/bcpg/PublicKeyPacket.cs +++ b/crypto/src/bcpg/PublicKeyPacket.cs @@ -115,10 +115,9 @@ namespace Org.BouncyCastle.Bcpg return bOut.ToArray(); } - public override void Encode( - BcpgOutputStream bcpgOut) + public override void Encode(BcpgOutputStream bcpgOut) { - bcpgOut.WritePacket(PacketTag.PublicKey, GetEncodedContents(), true); + bcpgOut.WritePacket(PacketTag.PublicKey, GetEncodedContents()); } } } diff --git a/crypto/src/bcpg/PublicSubkeyPacket.cs b/crypto/src/bcpg/PublicSubkeyPacket.cs index 6e1aeda98..0e1065b72 100644 --- a/crypto/src/bcpg/PublicSubkeyPacket.cs +++ b/crypto/src/bcpg/PublicSubkeyPacket.cs @@ -1,5 +1,6 @@ using System; using System.IO; + namespace Org.BouncyCastle.Bcpg { /// <remarks>Basic packet for a PGP public subkey</remarks> @@ -21,10 +22,9 @@ namespace Org.BouncyCastle.Bcpg { } - public override void Encode( - BcpgOutputStream bcpgOut) + public override void Encode(BcpgOutputStream bcpgOut) { - bcpgOut.WritePacket(PacketTag.PublicSubkey, GetEncodedContents(), true); + bcpgOut.WritePacket(PacketTag.PublicSubkey, GetEncodedContents()); } } } diff --git a/crypto/src/bcpg/SecretKeyPacket.cs b/crypto/src/bcpg/SecretKeyPacket.cs index d9ceab4f1..1bc684a69 100644 --- a/crypto/src/bcpg/SecretKeyPacket.cs +++ b/crypto/src/bcpg/SecretKeyPacket.cs @@ -161,10 +161,9 @@ namespace Org.BouncyCastle.Bcpg return bOut.ToArray(); } - public override void Encode( - BcpgOutputStream bcpgOut) + public override void Encode(BcpgOutputStream bcpgOut) { - bcpgOut.WritePacket(PacketTag.SecretKey, GetEncodedContents(), true); + bcpgOut.WritePacket(PacketTag.SecretKey, GetEncodedContents()); } } } diff --git a/crypto/src/bcpg/SecretSubkeyPacket.cs b/crypto/src/bcpg/SecretSubkeyPacket.cs index 8f1746942..2d405aec2 100644 --- a/crypto/src/bcpg/SecretSubkeyPacket.cs +++ b/crypto/src/bcpg/SecretSubkeyPacket.cs @@ -34,10 +34,9 @@ namespace Org.BouncyCastle.Bcpg { } - public override void Encode( - BcpgOutputStream bcpgOut) + public override void Encode(BcpgOutputStream bcpgOut) { - bcpgOut.WritePacket(PacketTag.SecretSubkey, GetEncodedContents(), true); + bcpgOut.WritePacket(PacketTag.SecretSubkey, GetEncodedContents()); } } } diff --git a/crypto/src/bcpg/SignaturePacket.cs b/crypto/src/bcpg/SignaturePacket.cs index 09e6b059d..99ca7df40 100644 --- a/crypto/src/bcpg/SignaturePacket.cs +++ b/crypto/src/bcpg/SignaturePacket.cs @@ -392,7 +392,7 @@ namespace Org.BouncyCastle.Bcpg } } - bcpgOut.WritePacket(PacketTag.Signature, bOut.ToArray(), true); + bcpgOut.WritePacket(PacketTag.Signature, bOut.ToArray()); } private static void EncodeLengthAndData( diff --git a/crypto/src/bcpg/SignatureSubpacket.cs b/crypto/src/bcpg/SignatureSubpacket.cs index c2186f373..f7f82b2a6 100644 --- a/crypto/src/bcpg/SignatureSubpacket.cs +++ b/crypto/src/bcpg/SignatureSubpacket.cs @@ -103,8 +103,7 @@ namespace Org.BouncyCastle.Bcpg if (obj == this) return true; - SignatureSubpacket other = obj as SignatureSubpacket; - if (null == other) + if (!(obj is SignatureSubpacket other)) return false; return this.type == other.type diff --git a/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs b/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs index 85bcc2874..901088e33 100644 --- a/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs +++ b/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs @@ -82,7 +82,7 @@ namespace Org.BouncyCastle.Bcpg } } - bcpgOut.WritePacket(PacketTag.SymmetricKeyEncryptedSessionKey, bOut.ToArray(), true); + bcpgOut.WritePacket(PacketTag.SymmetricKeyEncryptedSessionKey, bOut.ToArray()); } } } diff --git a/crypto/src/bcpg/TrustPacket.cs b/crypto/src/bcpg/TrustPacket.cs index 6f1969c2a..a2a30177f 100644 --- a/crypto/src/bcpg/TrustPacket.cs +++ b/crypto/src/bcpg/TrustPacket.cs @@ -9,8 +9,7 @@ namespace Org.BouncyCastle.Bcpg { private readonly byte[] levelAndTrustAmount; - public TrustPacket( - BcpgInputStream bcpgIn) + public TrustPacket(BcpgInputStream bcpgIn) { MemoryStream bOut = new MemoryStream(); @@ -23,10 +22,9 @@ namespace Org.BouncyCastle.Bcpg levelAndTrustAmount = bOut.ToArray(); } - public TrustPacket( - int trustCode) + public TrustPacket(int trustCode) { - this.levelAndTrustAmount = new byte[]{ (byte) trustCode }; + this.levelAndTrustAmount = new byte[]{ (byte)trustCode }; } public byte[] GetLevelAndTrustAmount() @@ -34,10 +32,9 @@ namespace Org.BouncyCastle.Bcpg return (byte[]) levelAndTrustAmount.Clone(); } - public override void Encode( - BcpgOutputStream bcpgOut) + public override void Encode(BcpgOutputStream bcpgOut) { - bcpgOut.WritePacket(PacketTag.Trust, levelAndTrustAmount, true); + bcpgOut.WritePacket(PacketTag.Trust, levelAndTrustAmount); } } } diff --git a/crypto/src/bcpg/UserAttributePacket.cs b/crypto/src/bcpg/UserAttributePacket.cs index 861f62f5d..0be24e006 100644 --- a/crypto/src/bcpg/UserAttributePacket.cs +++ b/crypto/src/bcpg/UserAttributePacket.cs @@ -37,8 +37,7 @@ namespace Org.BouncyCastle.Bcpg return subpackets; } - public override void Encode( - BcpgOutputStream bcpgOut) + public override void Encode(BcpgOutputStream bcpgOut) { MemoryStream bOut = new MemoryStream(); diff --git a/crypto/src/bcpg/UserIdPacket.cs b/crypto/src/bcpg/UserIdPacket.cs index a175e74a6..7d3d3a846 100644 --- a/crypto/src/bcpg/UserIdPacket.cs +++ b/crypto/src/bcpg/UserIdPacket.cs @@ -1,37 +1,56 @@ using System; using System.Text; +using Org.BouncyCastle.Utilities; + namespace Org.BouncyCastle.Bcpg { /** * Basic type for a user ID packet. */ public class UserIdPacket - : ContainedPacket + : ContainedPacket, IUserDataPacket { private readonly byte[] idData; - public UserIdPacket( - BcpgInputStream bcpgIn) + public UserIdPacket(BcpgInputStream bcpgIn) { this.idData = bcpgIn.ReadAll(); } - public UserIdPacket( - string id) + public UserIdPacket(string id) { this.idData = Encoding.UTF8.GetBytes(id); } - public string GetId() + public UserIdPacket(byte[] rawId) + { + this.idData = Arrays.Clone(rawId); + } + + public string GetId() { return Encoding.UTF8.GetString(idData, 0, idData.Length); } - public override void Encode( - BcpgOutputStream bcpgOut) + public byte[] GetRawId() => Arrays.Clone(idData); + + public override bool Equals(object obj) + { + if (!(obj is UserIdPacket other)) + return false; + + return Arrays.AreEqual(this.idData, other.idData); + } + + public override int GetHashCode() + { + return Arrays.GetHashCode(this.idData); + } + + public override void Encode(BcpgOutputStream bcpgOut) { - bcpgOut.WritePacket(PacketTag.UserId, idData, true); + bcpgOut.WritePacket(PacketTag.UserId, idData); } } } diff --git a/crypto/src/bcpg/sig/SignerUserId.cs b/crypto/src/bcpg/sig/SignerUserId.cs index 6f812e210..bfa00d078 100644 --- a/crypto/src/bcpg/sig/SignerUserId.cs +++ b/crypto/src/bcpg/sig/SignerUserId.cs @@ -1,3 +1,5 @@ +using Org.BouncyCastle.Utilities; + namespace Org.BouncyCastle.Bcpg.Sig { /** @@ -6,44 +8,18 @@ namespace Org.BouncyCastle.Bcpg.Sig public class SignerUserId : SignatureSubpacket { - private static byte[] UserIdToBytes( - string id) - { - byte[] idData = new byte[id.Length]; - - for (int i = 0; i != id.Length; i++) - { - idData[i] = (byte)id[i]; - } - - return idData; - } - - public SignerUserId( - bool critical, - bool isLongLength, - byte[] data) + public SignerUserId(bool critical, bool isLongLength, byte[] data) : base(SignatureSubpacketTag.SignerUserId, critical, isLongLength, data) { } - public SignerUserId( - bool critical, - string userId) - : base(SignatureSubpacketTag.SignerUserId, critical, false, UserIdToBytes(userId)) + public SignerUserId(bool critical, string userId) + : base(SignatureSubpacketTag.SignerUserId, critical, false, Strings.ToUtf8ByteArray(userId)) { } - public string GetId() - { - char[] chars = new char[data.Length]; - - for (int i = 0; i != chars.Length; i++) - { - chars[i] = (char)(data[i] & 0xff); - } + public string GetId() => Strings.FromUtf8ByteArray(data); - return new string(chars); - } + public byte[] GetRawId() => Arrays.Clone(data); } } diff --git a/crypto/src/openpgp/PGPKeyRing.cs b/crypto/src/openpgp/PGPKeyRing.cs index 52db80209..63069350e 100644 --- a/crypto/src/openpgp/PGPKeyRing.cs +++ b/crypto/src/openpgp/PGPKeyRing.cs @@ -42,20 +42,19 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } - internal static void ReadUserIDs(BcpgInputStream pIn, out IList<object> ids, out IList<TrustPacket> idTrusts, - out IList<IList<PgpSignature>> idSigs) + internal static void ReadUserIDs(BcpgInputStream pIn, out IList<IUserDataPacket> ids, + out IList<TrustPacket> idTrusts, out IList<IList<PgpSignature>> idSigs) { - ids = new List<object>(); + ids = new List<IUserDataPacket>(); idTrusts = new List<TrustPacket>(); idSigs = new List<IList<PgpSignature>>(); while (IsUserTag(pIn.SkipMarkerPackets())) { Packet obj = pIn.ReadPacket(); - if (obj is UserIdPacket) + if (obj is UserIdPacket id) { - UserIdPacket id = (UserIdPacket)obj; - ids.Add(id.GetId()); + ids.Add(id); } else { diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs index 6fa7cfae8..b4ae92838 100644 --- a/crypto/src/openpgp/PgpPublicKey.cs +++ b/crypto/src/openpgp/PgpPublicKey.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.IO; using Org.BouncyCastle.Asn1.Cryptlib; @@ -86,17 +87,18 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp PgpSignature.DirectKey, }; - private long keyId; - private byte[] fingerprint; - private int keyStrength; - internal PublicKeyPacket publicPk; internal TrustPacket trustPk; internal IList<PgpSignature> keySigs = new List<PgpSignature>(); - internal IList<object> ids = new List<object>(); + internal IList<IUserDataPacket> ids = new List<IUserDataPacket>(); internal IList<TrustPacket> idTrusts = new List<TrustPacket>(); internal IList<IList<PgpSignature>> idSigs = new List<IList<PgpSignature>>(); - internal IList<PgpSignature> subSigs; + + internal IList<PgpSignature> subSigs = null; + + private long keyId; + private byte[] fingerprint; + private int keyStrength; private void Init() { @@ -261,7 +263,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } this.publicPk = new PublicKeyPacket(algorithm, time, bcpgKey); - this.ids = new List<object>(); + this.ids = new List<IUserDataPacket>(); this.idSigs = new List<IList<PgpSignature>>(); try @@ -275,7 +277,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } public PgpPublicKey(PublicKeyPacket publicPk) - : this(publicPk, new List<object>(), new List<IList<PgpSignature>>()) + : this(publicPk, new List<IUserDataPacket>(), new List<IList<PgpSignature>>()) { } @@ -311,7 +313,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp this.publicPk = pubKey.publicPk; this.keySigs = new List<PgpSignature>(pubKey.keySigs); - this.ids = new List<object>(pubKey.ids); + this.ids = new List<IUserDataPacket>(pubKey.ids); this.idTrusts = new List<TrustPacket>(pubKey.idTrusts); this.idSigs = new List<IList<PgpSignature>>(pubKey.idSigs.Count); @@ -334,7 +336,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp PublicKeyPacket publicPk, TrustPacket trustPk, IList<PgpSignature> keySigs, - IList<object> ids, + IList<IUserDataPacket> ids, IList<TrustPacket> idTrusts, IList<IList<PgpSignature>> idSigs) { @@ -350,7 +352,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp internal PgpPublicKey( PublicKeyPacket publicPk, - IList<object> ids, + IList<IUserDataPacket> ids, IList<IList<PgpSignature>> idSigs) { this.publicPk = publicPk; @@ -359,6 +361,26 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp Init(); } + internal PgpPublicKey( + PgpPublicKey original, + TrustPacket trustPk, + List<PgpSignature> keySigs, + List<IUserDataPacket> ids, + List<TrustPacket> idTrusts, + IList<IList<PgpSignature>> idSigs) + { + this.publicPk = original.publicPk; + this.fingerprint = original.fingerprint; + this.keyStrength = original.keyStrength; + this.keyId = original.keyId; + + this.trustPk = trustPk; + this.keySigs = keySigs; + this.ids = ids; + this.idTrusts = idTrusts; + this.idSigs = idSigs; + } + /// <summary>The version of this key.</summary> public int Version { @@ -456,13 +478,13 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp return expiryTime; } - /// <summary>The keyId associated with the public key.</summary> + /// <summary>The key ID associated with the public key.</summary> public long KeyId { get { return keyId; } } - /// <summary>The fingerprint of the key</summary> + /// <summary>The fingerprint of the public key</summary> public byte[] GetFingerprint() { return (byte[]) fingerprint.Clone(); @@ -648,11 +670,29 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { var result = new List<string>(); - foreach (object id in ids) + foreach (var id in ids) { - if (id is string s) + if (id is UserIdPacket userId) { - result.Add(s); + result.Add(userId.GetId()); + } + } + + return CollectionUtilities.Proxy(result); + } + + /// <summary>Return any userIDs associated with the key in raw byte form.</summary> + /// <remarks>No attempt is made to convert the IDs into strings.</remarks> + /// <returns>An <c>IEnumerable</c> of <c>byte[]</c>.</returns> + public IEnumerable<byte[]> GetRawUserIds() + { + var result = new List<byte[]>(); + + foreach (var id in ids) + { + if (id is UserIdPacket userId) + { + result.Add(userId.GetRawId()); } } @@ -665,9 +705,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { var result = new List<PgpUserAttributeSubpacketVector>(); - foreach (object o in ids) + foreach (var id in ids) { - if (o is PgpUserAttributeSubpacketVector v) + if (id is PgpUserAttributeSubpacketVector v) { result.Add(v); } @@ -684,22 +724,18 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp if (id == null) throw new ArgumentNullException(nameof(id)); - var result = new List<PgpSignature>(); - bool userIdFound = false; + return GetSignaturesForId(new UserIdPacket(id)); + } - for (int i = 0; i != ids.Count; i++) - { - if (id.Equals(ids[i])) - { - userIdFound = true; - result.AddRange(idSigs[i]); - } - } + public IEnumerable<PgpSignature> GetSignaturesForId(byte[] rawId) + { + if (rawId == null) + throw new ArgumentNullException(nameof(rawId)); - return userIdFound ? CollectionUtilities.Proxy(result) : null; + return GetSignaturesForId(new UserIdPacket(rawId)); } - private IEnumerable<PgpSignature> GetSignaturesForID(UserIdPacket id) + private IEnumerable<PgpSignature> GetSignaturesForId(UserIdPacket id) { var signatures = new List<PgpSignature>(); bool userIdFound = false; @@ -819,13 +855,24 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp return bOut.ToArray(); } - public void Encode( - Stream outStr) + public void Encode(Stream outStr) + { + Encode(outStr, false); + } + + /** + * Encode the key to outStream, with trust packets stripped out if forTransfer is true. + * + * @param outStream stream to write the key encoding to. + * @param forTransfer if the purpose of encoding is to send key to other users. + * @throws IOException in case of encoding error. + */ + public void Encode(Stream outStr, bool forTransfer) { BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStr); bcpgOut.WritePacket(publicPk); - if (trustPk != null) + if (!forTransfer && trustPk != null) { bcpgOut.WritePacket(trustPk); } @@ -839,11 +886,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp for (int i = 0; i != ids.Count; i++) { - if (ids[i] is string) + if (ids[i] is UserIdPacket id) { - string id = (string) ids[i]; - - bcpgOut.WritePacket(new UserIdPacket(id)); + bcpgOut.WritePacket(id); } else { @@ -851,14 +896,14 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp bcpgOut.WritePacket(new UserAttributePacket(v.ToSubpacketArray())); } - if (idTrusts[i] != null) + if (!forTransfer && idTrusts[i] != null) { - bcpgOut.WritePacket((ContainedPacket)idTrusts[i]); + bcpgOut.WritePacket((TrustPacket)idTrusts[i]); } foreach (PgpSignature sig in idSigs[i]) { - sig.Encode(bcpgOut); + sig.Encode(bcpgOut, forTransfer); } } } @@ -910,7 +955,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp string id, PgpSignature certification) { - return AddCert(key, id, certification); + return AddCert(key, new UserIdPacket(id), certification); } /// <summary>Add a certification for the given UserAttributeSubpackets to the given public key.</summary> @@ -928,7 +973,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp private static PgpPublicKey AddCert( PgpPublicKey key, - object id, + IUserDataPacket id, PgpSignature certification) { PgpPublicKey returnKey = new PgpPublicKey(key); @@ -966,8 +1011,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// <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, + public static PgpPublicKey RemoveCertification(PgpPublicKey key, PgpUserAttributeSubpacketVector userAttributes) { return RemoveCert(key, userAttributes); @@ -977,16 +1021,21 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// <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) + public static PgpPublicKey RemoveCertification(PgpPublicKey key, string id) { - return RemoveCert(key, id); + return RemoveCert(key, new UserIdPacket(id)); } - private static PgpPublicKey RemoveCert( - PgpPublicKey key, - object id) + /// <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="rawId">The ID that is to be removed in raw byte form.</param> + /// <returns>The re-certified key, or null if the ID was not found on the key.</returns> + public static PgpPublicKey RemoveCertification(PgpPublicKey key, byte[] rawId) + { + return RemoveCert(key, new UserIdPacket(rawId)); + } + + private static PgpPublicKey RemoveCert(PgpPublicKey key, IUserDataPacket id) { PgpPublicKey returnKey = new PgpPublicKey(key); bool found = false; @@ -1007,15 +1056,22 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// <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 (in its raw byte form).</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, byte[] id, PgpSignature certification) + { + return RemoveCert(key, new UserIdPacket(id), certification); + } + + /// <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) + public static PgpPublicKey RemoveCertification(PgpPublicKey key, string id, PgpSignature certification) { - return RemoveCert(key, id, certification); + return RemoveCert(key, new UserIdPacket(id), certification); } /// <summary>Remove a certification associated with a given user attributes on a key.</summary> @@ -1023,18 +1079,13 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// <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) + 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) + private static PgpPublicKey RemoveCert(PgpPublicKey key, IUserDataPacket id, PgpSignature certification) { PgpPublicKey returnKey = new PgpPublicKey(key); bool found = false; @@ -1043,13 +1094,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { if (id.Equals(returnKey.ids[i])) { - var certs = returnKey.idSigs[i]; - found = certs.Contains(certification); - - if (found) - { - certs.Remove(certification); - } + found |= returnKey.idSigs[i].Remove(certification); } } @@ -1060,35 +1105,23 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// <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) + 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); + var sigs = returnKey.subSigs ?? returnKey.keySigs; - if (returnKey.subSigs != null) - { - returnKey.subSigs.Add(certification); - } - else - { - returnKey.keySigs.Add(certification); - } + sigs.Add(certification); return returnKey; } @@ -1102,33 +1135,171 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp var returnKey = new PgpPublicKey(key); var sigs = returnKey.subSigs ?? returnKey.keySigs; - if (sigs.Remove(certification)) - return returnKey; + bool found = sigs.Remove(certification); - // TODO Java uses getRawUserIDs - foreach (string id in key.GetUserIds()) + foreach (var idSigs in returnKey.idSigs) { - if (ContainsSignature(key.GetSignaturesForId(id), certification)) - return RemoveCertification(returnKey, id, certification); + found |= idSigs.Remove(certification); } - foreach (PgpUserAttributeSubpacketVector id in key.GetUserAttributes()) + return found ? returnKey : null; + } + + /** + * Merge this the given local public key with another, potentially fresher copy. + * The resulting {@link PGPPublicKey} contains the sum of both keys user-ids and signatures. + * <p> + * If joinTrustPackets is set to true and the copy carries a trust packet, + * the joined key will copy the trust-packet from the copy. + * Otherwise, it will carry the trust packet of the local key. + * + * @param key local public key + * @param copy copy of the public key (e.g. from a key server) + * @param joinTrustPackets if true, trust packets from the copy are copied over into the resulting key + * @param allowSubkeySigsOnNonSubkey if true, subkey signatures on the copy will be present in the merged key, even if key was not a subkey before. + * @return joined key + * @throws PGPException + */ + public static PgpPublicKey Join(PgpPublicKey key, PgpPublicKey copy, bool joinTrustPackets, + bool allowSubkeySigsOnNonSubkey) + { + if (key.KeyId != copy.keyId) + throw new ArgumentException("Key-ID mismatch."); + + TrustPacket trustPk = key.trustPk; + List<PgpSignature> keySigs = new List<PgpSignature>(key.keySigs); + List<IUserDataPacket> ids = new List<IUserDataPacket>(key.ids); + List<TrustPacket> idTrusts = new List<TrustPacket>(key.idTrusts); + List<IList<PgpSignature>> idSigs = new List<IList<PgpSignature>>(key.idSigs); + List<PgpSignature> subSigs = key.subSigs == null ? null : new List<PgpSignature>(key.subSigs); + + if (joinTrustPackets) { - if (ContainsSignature(key.GetSignaturesForUserAttribute(id), certification)) - return RemoveCertification(returnKey, id, certification); + if (copy.trustPk != null) + { + trustPk = copy.trustPk; + } } - return returnKey; - } + // key signatures + foreach (PgpSignature keySig in copy.keySigs) + { + bool found = false; + for (int i = 0; i < keySigs.Count; i++) + { + PgpSignature existingKeySig = keySigs[i]; + if (PgpSignature.IsSignatureEncodingEqual(existingKeySig, keySig)) + { + found = true; + // join existing sig with copy to apply modifications in unhashed subpackets + existingKeySig = PgpSignature.Join(existingKeySig, keySig); + keySigs[i] = existingKeySig; + break; + } + } + if (found) + break; - private static bool ContainsSignature(IEnumerable<PgpSignature> signatures, PgpSignature signature) - { - foreach (PgpSignature candidate in signatures) + keySigs.Add(keySig); + } + + // user-ids and id sigs + for (int idIdx = 0; idIdx < copy.ids.Count; idIdx++) + { + IUserDataPacket copyId = copy.ids[idIdx]; + List<PgpSignature> copyIdSigs = new List<PgpSignature>(copy.idSigs[idIdx]); + TrustPacket copyTrust = copy.idTrusts[idIdx]; + + int existingIdIndex = -1; + for (int i = 0; i < ids.Count; i++) + { + IUserDataPacket existingId = ids[i]; + if (existingId.Equals(copyId)) + { + existingIdIndex = i; + break; + } + } + + // new user-id + if (existingIdIndex == -1) + { + ids.Add(copyId); + idSigs.Add(copyIdSigs); + idTrusts.Add(joinTrustPackets ? copyTrust : null); + continue; + } + + // existing user-id + if (joinTrustPackets && copyTrust != null) + { + TrustPacket existingTrust = idTrusts[existingIdIndex]; + if (existingTrust == null || + Arrays.AreEqual(copyTrust.GetLevelAndTrustAmount(), existingTrust.GetLevelAndTrustAmount())) + { + idTrusts[existingIdIndex] = copyTrust; + } + } + + var existingIdSigs = idSigs[existingIdIndex]; + foreach (PgpSignature newSig in copyIdSigs) + { + bool found = false; + for (int i = 0; i < existingIdSigs.Count; i++) + { + PgpSignature existingSig = existingIdSigs[i]; + if (PgpSignature.IsSignatureEncodingEqual(newSig, existingSig)) + { + found = true; + // join existing sig with copy to apply modifications in unhashed subpackets + existingSig = PgpSignature.Join(existingSig, newSig); + existingIdSigs[i] = existingSig; + break; + } + } + if (!found) + { + existingIdSigs.Add(newSig); + } + } + } + + // subSigs + if (copy.subSigs != null) { - if (signature == candidate) - return true; + if (subSigs == null && allowSubkeySigsOnNonSubkey) + { + subSigs = new List<PgpSignature>(copy.subSigs); + } + else + { + foreach (PgpSignature copySubSig in copy.subSigs) + { + bool found = false; + for (int i = 0; subSigs != null && i < subSigs.Count; i++) + { + PgpSignature existingSubSig = subSigs[i]; + if (PgpSignature.IsSignatureEncodingEqual(existingSubSig, copySubSig)) + { + found = true; + // join existing sig with copy to apply modifications in unhashed subpackets + existingSubSig = PgpSignature.Join(existingSubSig, copySubSig); + subSigs[i] = existingSubSig; + break; + } + } + if (!found && subSigs != null) + { + subSigs.Add(copySubSig); + } + } + } } - return false; + + PgpPublicKey merged = new PgpPublicKey(key, trustPk, keySigs, ids, idTrusts, idSigs); + merged.subSigs = subSigs; + + return merged; } } } diff --git a/crypto/src/openpgp/PgpPublicKeyRing.cs b/crypto/src/openpgp/PgpPublicKeyRing.cs index ebbb95634..e1a2a83f2 100644 --- a/crypto/src/openpgp/PgpPublicKeyRing.cs +++ b/crypto/src/openpgp/PgpPublicKeyRing.cs @@ -50,10 +50,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp // direct signatures and revocations var keySigs = ReadSignaturesAndTrust(bcpgInput); - IList<object> ids; - IList<TrustPacket> idTrusts; - IList<IList<PgpSignature>> idSigs; - ReadUserIDs(bcpgInput, out ids, out idTrusts, out idSigs); + ReadUserIDs(bcpgInput, out var ids, out var idTrusts, out var idSigs); keys.Add(new PgpPublicKey(pubPk, trustPk, keySigs, ids, idTrusts, idSigs)); @@ -204,5 +201,74 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp return new PgpPublicKey(pk, kTrust, sigList); } + + /** + * Join two copies of the same certificate. + * The certificates must have the same primary key, but may carry different subkeys, user-ids and signatures. + * The resulting certificate will carry the sum of both certificates subkeys, user-ids and signatures. + * <p> + * This method will ignore trust packets on the second copy of the certificate and instead + * copy the local certificate's trust packets to the joined certificate. + * + * @param first local copy of the certificate + * @param second remote copy of the certificate (e.g. from a key server) + * @return joined key ring + * @throws PGPException + */ + public static PgpPublicKeyRing Join(PgpPublicKeyRing first, PgpPublicKeyRing second) + { + return Join(first, second, false, false); + } + + /** + * Join two copies of the same certificate. + * The certificates must have the same primary key, but may carry different subkeys, user-ids and signatures. + * The resulting certificate will carry the sum of both certificates subkeys, user-ids and signatures. + * <p> + * For each subkey holds: If joinTrustPackets is set to true and the second key is carrying a trust packet, + * the trust packet will be copied to the joined key. + * Otherwise, the joined key will carry the trust packet of the local copy. + * + * @param first local copy of the certificate + * @param second remote copy of the certificate (e.g. from a key server) + * @param joinTrustPackets if true, trust packets from the second certificate copy will be carried over into the joined certificate + * @param allowSubkeySigsOnNonSubkey if true, the resulting joined certificate may carry subkey signatures on its primary key + * @return joined certificate + * @throws PGPException + */ + public static PgpPublicKeyRing Join(PgpPublicKeyRing first, PgpPublicKeyRing second, bool joinTrustPackets, + bool allowSubkeySigsOnNonSubkey) + { + if (!Arrays.AreEqual(first.GetPublicKey().GetFingerprint(), second.GetPublicKey().GetFingerprint())) + throw new ArgumentException("Cannot merge certificates with differing primary keys."); + + var secondKeys = new HashSet<long>(); + foreach (var key in second.GetPublicKeys()) + { + secondKeys.Add(key.KeyId); + } + + var merged = new List<PgpPublicKey>(); + foreach (var key in first.GetPublicKeys()) + { + var copy = second.GetPublicKey(key.KeyId); + if (copy != null) + { + merged.Add(PgpPublicKey.Join(key, copy, joinTrustPackets, allowSubkeySigsOnNonSubkey)); + secondKeys.Remove(key.KeyId); + } + else + { + merged.Add(key); + } + } + + foreach (var additionalKeyId in secondKeys) + { + merged.Add(second.GetPublicKey(additionalKeyId)); + } + + return new PgpPublicKeyRing(merged); + } } } diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs index 0103cc187..a120f97d8 100644 --- a/crypto/src/openpgp/PgpSecretKey.cs +++ b/crypto/src/openpgp/PgpSecretKey.cs @@ -479,6 +479,12 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp get { return pub.KeyId; } } + /// <summary>The fingerprint of the public key associated with this key.</summary> + public byte[] GetFingerprint() + { + return pub.GetFingerprint(); + } + /// <summary>Return the S2K usage associated with this key.</summary> public int S2kUsage { @@ -832,10 +838,10 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp for (int i = 0; i != pub.ids.Count; i++) { - object pubID = pub.ids[i]; - if (pubID is string id) + var pubID = pub.ids[i]; + if (pubID is UserIdPacket id) { - bcpgOut.WritePacket(new UserIdPacket(id)); + bcpgOut.WritePacket(id); } else if (pubID is PgpUserAttributeSubpacketVector v) { diff --git a/crypto/src/openpgp/PgpSignature.cs b/crypto/src/openpgp/PgpSignature.cs index 9b596f279..d1146183a 100644 --- a/crypto/src/openpgp/PgpSignature.cs +++ b/crypto/src/openpgp/PgpSignature.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using Org.BouncyCastle.Asn1; @@ -38,6 +39,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp public const int SubkeyRevocation = 0x28; public const int CertificationRevocation = 0x30; public const int Timestamp = 0x40; + public const int ThirdPartyConfirmation = 0x50; private readonly SignaturePacket sigPck; private readonly int signatureType; @@ -52,8 +54,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { } - internal PgpSignature( - SignaturePacket sigPacket) + internal PgpSignature(SignaturePacket sigPacket) : this(sigPacket, null) { } @@ -83,6 +84,12 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp get { return sigPck.HashAlgorithm; } } + /// <summary>Return the digest prefix of the signature.</summary> + public byte[] GetDigestPrefix() + { + return sigPck.GetFingerprint(); + } + /// <summary>Return true if this signature represents a certification.</summary> public bool IsCertification() { @@ -194,14 +201,13 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp public bool Verify() { byte[] trailer = GetSignatureTrailer(); + sig.BlockUpdate(trailer, 0, trailer.Length); return sig.VerifySignature(GetSignature()); } - private void UpdateWithIdData( - int header, - byte[] idBytes) + private void UpdateWithIdData(int header, byte[] idBytes) { this.Update( (byte) header, @@ -212,8 +218,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp this.Update(idBytes); } - private void UpdateWithPublicKey( - PgpPublicKey key) + private void UpdateWithPublicKey(PgpPublicKey key) { byte[] keyBytes = GetEncodedPublicKey(key); @@ -231,9 +236,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// <param name="userAttributes">User attributes the key was stored under.</param> /// <param name="key">The key to be verified.</param> /// <returns>True, if the signature matches, false otherwise.</returns> - public bool VerifyCertification( - PgpUserAttributeSubpacketVector userAttributes, - PgpPublicKey key) + public bool VerifyCertification(PgpUserAttributeSubpacketVector userAttributes, PgpPublicKey key) { UpdateWithPublicKey(key); @@ -254,9 +257,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp throw new PgpException("cannot encode subpacket array", e); } - this.Update(sigPck.GetSignatureTrailer()); - - return sig.VerifySignature(GetSignature()); + return Verify(); } /// <summary> @@ -266,9 +267,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// <param name="id">ID the key was stored under.</param> /// <param name="key">The key to be verified.</param> /// <returns>True, if the signature matches, false otherwise.</returns> - public bool VerifyCertification( - string id, - PgpPublicKey key) + public bool VerifyCertification(string id, PgpPublicKey key) { UpdateWithPublicKey(key); @@ -277,9 +276,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp // UpdateWithIdData(0xb4, Strings.ToUtf8ByteArray(id)); - Update(sigPck.GetSignatureTrailer()); - - return sig.VerifySignature(GetSignature()); + return Verify(); } /// <summary>Verify a certification for the passed in key against the passed in master key.</summary> @@ -293,9 +290,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp UpdateWithPublicKey(masterKey); UpdateWithPublicKey(pubKey); - Update(sigPck.GetSignatureTrailer()); - - return sig.VerifySignature(GetSignature()); + return Verify(); } /// <summary>Verify a key certification, such as revocation, for the passed in key.</summary> @@ -312,9 +307,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp UpdateWithPublicKey(pubKey); - Update(sigPck.GetSignatureTrailer()); - - return sig.VerifySignature(GetSignature()); + return Verify(); } public int SignatureType @@ -438,13 +431,28 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp return bOut.ToArray(); } - public void Encode(Stream outStream) + public void Encode(Stream outStream) { - var bcpgOut = BcpgOutputStream.Wrap(outStream); + Encode(outStream, false); + } - bcpgOut.WritePacket(sigPck); + /** + * Encode the signature to outStream, with trust packets stripped out if forTransfer is true. + * + * @param outStream stream to write the key encoding to. + * @param forTransfer if the purpose of encoding is to send key to other users. + * @throws IOException in case of encoding error. + */ + public void Encode(Stream outStream, bool forTransfer) + { + // Exportable signatures MUST NOT be exported if forTransfer==true + if (forTransfer && (!GetHashedSubPackets().IsExportable() || !GetUnhashedSubPackets().IsExportable())) + return; - if (trustPck != null) + var bcpgOut = BcpgOutputStream.Wrap(outStream); + + bcpgOut.WritePacket(sigPck); + if (!forTransfer && trustPck != null) { bcpgOut.WritePacket(trustPck); } @@ -480,5 +488,44 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp return false; } } + + public static bool IsSignatureEncodingEqual(PgpSignature sig1, PgpSignature sig2) + { + return Arrays.AreEqual(sig1.sigPck.GetSignatureBytes(), sig2.sigPck.GetSignatureBytes()); + } + + public static PgpSignature Join(PgpSignature sig1, PgpSignature sig2) + { + if (!IsSignatureEncodingEqual(sig1, sig2)) + throw new ArgumentException("These are different signatures."); + + // merge unhashed subpackets + SignatureSubpacket[] sig1Unhashed = sig1.GetUnhashedSubPackets().ToSubpacketArray(); + SignatureSubpacket[] sig2Unhashed = sig2.GetUnhashedSubPackets().ToSubpacketArray(); + + var merged = new List<SignatureSubpacket>(sig1Unhashed); + + foreach (var subpacket in sig2Unhashed) + { + if (!merged.Contains(subpacket)) + { + merged.Add(subpacket); + } + } + + SignatureSubpacket[] unhashed = merged.ToArray(); + return new PgpSignature( + new SignaturePacket( + sig1.SignatureType, + sig1.KeyId, + sig1.KeyAlgorithm, + sig1.HashAlgorithm, + sig1.GetHashedSubPackets().ToSubpacketArray(), + unhashed, + sig1.GetDigestPrefix(), + sig1.sigPck.GetSignature() + ) + ); + } } } diff --git a/crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs b/crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs index 07b9fee17..010e7e052 100644 --- a/crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs +++ b/crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs @@ -5,7 +5,7 @@ using Org.BouncyCastle.Bcpg.Sig; namespace Org.BouncyCastle.Bcpg.OpenPgp { - /// <remarks>Generator for signature subpackets.</remarks> + /// <remarks>Generator for signature subpackets.</remarks> public class PgpSignatureSubpacketGenerator { private readonly List<SignatureSubpacket> list = new List<SignatureSubpacket>(); @@ -35,23 +35,17 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } - public void SetRevocable( - bool isCritical, - bool isRevocable) + public void SetRevocable(bool isCritical, bool isRevocable) { list.Add(new Revocable(isCritical, isRevocable)); } - public void SetExportable( - bool isCritical, - bool isExportable) + public void SetExportable(bool isCritical, bool isExportable) { list.Add(new Exportable(isCritical, isExportable)); } - public void SetFeature( - bool isCritical, - byte feature) + public void SetFeature(bool isCritical, byte feature) { list.Add(new Features(isCritical, feature)); } @@ -63,10 +57,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// <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) + public void SetTrust(bool isCritical, int depth, int trustAmount) { list.Add(new TrustSignature(isCritical, depth, trustAmount)); } @@ -77,9 +68,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// </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) + public void SetKeyExpirationTime(bool isCritical, long seconds) { list.Add(new KeyExpirationTime(isCritical, seconds)); } @@ -90,9 +79,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// </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) + public void SetSignatureExpirationTime(bool isCritical, long seconds) { list.Add(new SignatureExpirationTime(isCritical, seconds)); } @@ -103,54 +90,56 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// Note: this overrides the generation of a creation time when the signature /// is generated.</p> /// </summary> - public void SetSignatureCreationTime( - bool isCritical, - DateTime date) + public void SetSignatureCreationTime(bool isCritical, DateTime date) { list.Add(new SignatureCreationTime(isCritical, date)); } - public void SetPreferredHashAlgorithms( - bool isCritical, - int[] algorithms) + public void SetPreferredHashAlgorithms(bool isCritical, int[] algorithms) { list.Add(new PreferredAlgorithms(SignatureSubpacketTag.PreferredHashAlgorithms, isCritical, algorithms)); } - public void SetPreferredSymmetricAlgorithms( - bool isCritical, - int[] algorithms) + public void SetPreferredSymmetricAlgorithms(bool isCritical, int[] algorithms) { list.Add(new PreferredAlgorithms(SignatureSubpacketTag.PreferredSymmetricAlgorithms, isCritical, algorithms)); } - public void SetPreferredCompressionAlgorithms( - bool isCritical, - int[] algorithms) + public void SetPreferredCompressionAlgorithms(bool isCritical, int[] algorithms) { list.Add(new PreferredAlgorithms(SignatureSubpacketTag.PreferredCompressionAlgorithms, isCritical, algorithms)); } - public void SetKeyFlags( - bool isCritical, - int flags) + public void SetPreferredAeadAlgorithms(bool isCritical, int[] algorithms) + { + list.Add(new PreferredAlgorithms(SignatureSubpacketTag.PreferredAeadAlgorithms, isCritical, algorithms)); + } + + public void AddPolicyUrl(bool isCritical, string policyUrl) + { + list.Add(new PolicyUrl(isCritical, policyUrl)); + } + + public void SetKeyFlags(bool isCritical, int flags) { list.Add(new KeyFlags(isCritical, flags)); } - public void SetSignerUserId( - bool isCritical, - string userId) + [Obsolete("Use 'AddSignerUserId' instead")] + public void SetSignerUserId(bool isCritical, string userId) + { + AddSignerUserId(isCritical, userId); + } + + public void AddSignerUserId(bool isCritical, string userId) { if (userId == null) throw new ArgumentNullException("userId"); - list.Add(new SignerUserId(isCritical, userId)); + list.Add(new SignerUserId(isCritical, userId)); } - public void SetSignerUserId( - bool isCritical, - byte[] rawUserId) + public void SetSignerUserId(bool isCritical, byte[] rawUserId) { if (rawUserId == null) throw new ArgumentNullException("rawUserId"); @@ -158,70 +147,116 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp list.Add(new SignerUserId(isCritical, false, rawUserId)); } - public void SetEmbeddedSignature( - bool isCritical, - PgpSignature pgpSignature) + [Obsolete("Use 'AddEmbeddedSignature' instead")] + 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, false, data)); + AddEmbeddedSignature(isCritical, pgpSignature); } - public void SetPrimaryUserId( - bool isCritical, - bool isPrimaryUserId) + public void AddEmbeddedSignature(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, false, 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) + [Obsolete("Use 'AddNotationData' instead")] + public void SetNotationData(bool isCritical, bool isHumanReadable, string notationName, string notationValue) { - list.Add(new NotationData(isCritical, isHumanReadable, notationName, notationValue)); + AddNotationData(isCritical, isHumanReadable, notationName, notationValue); } - /// <summary> - /// Sets revocation reason sub packet - /// </summary> - public void SetRevocationReason(bool isCritical, RevocationReasonTag reason, - string description) + public void AddNotationData(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> + [Obsolete("Use 'AddRevocationKey' instead")] public void SetRevocationKey(bool isCritical, PublicKeyAlgorithmTag keyAlgorithm, byte[] fingerprint) { - list.Add(new RevocationKey(isCritical, RevocationKeyTag.ClassDefault, keyAlgorithm, fingerprint)); + AddRevocationKey(isCritical, keyAlgorithm, fingerprint); } - /// <summary> - /// Sets issuer key sub packet - /// </summary> - public void SetIssuerKeyID(bool isCritical, long keyID) + public void AddRevocationKey(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 void SetSignatureTarget(bool isCritical, int publicKeyAlgorithm, int hashAlgorithm, byte[] hashData) + { + list.Add(new SignatureTarget(isCritical, publicKeyAlgorithm, hashAlgorithm, hashData)); + } + + public void SetIssuerFingerprint(bool isCritical, PgpSecretKey secretKey) + { + SetIssuerFingerprint(isCritical, secretKey.PublicKey); + } + + public void SetIssuerFingerprint(bool isCritical, PgpPublicKey publicKey) + { + list.Add(new IssuerFingerprint(isCritical, publicKey.Version, publicKey.GetFingerprint())); + } + + public void AddIntendedRecipientFingerprint(bool isCritical, PgpPublicKey publicKey) + { + list.Add(new IntendedRecipientFingerprint(isCritical, publicKey.Version, publicKey.GetFingerprint())); + } + + public void AddCustomSubpacket(SignatureSubpacket subpacket) + { + list.Add(subpacket); + } + + public bool RemovePacket(SignatureSubpacket packet) + { + return list.Remove(packet); + } + + public bool HasSubpacket(SignatureSubpacketTag type) + { + return null != list.Find(subpacket => subpacket.SubpacketType == type); + } + + public SignatureSubpacket[] GetSubpackets(SignatureSubpacketTag type) + { + return list.FindAll(subpacket => subpacket.SubpacketType == type).ToArray(); + } - public PgpSignatureSubpacketVector Generate() + public PgpSignatureSubpacketVector Generate() { return new PgpSignatureSubpacketVector(list.ToArray()); } diff --git a/crypto/src/openpgp/PgpSignatureSubpacketVector.cs b/crypto/src/openpgp/PgpSignatureSubpacketVector.cs index 9417b14fa..ae387198c 100644 --- a/crypto/src/openpgp/PgpSignatureSubpacketVector.cs +++ b/crypto/src/openpgp/PgpSignatureSubpacketVector.cs @@ -1,39 +1,32 @@ using System; +using System.Collections.Generic; using System.IO; using Org.BouncyCastle.Bcpg.Sig; namespace Org.BouncyCastle.Bcpg.OpenPgp { - /// <remarks>Container for a list of signature subpackets.</remarks> + /// <remarks>Container for a list of signature subpackets.</remarks> public class PgpSignatureSubpacketVector { public static PgpSignatureSubpacketVector FromSubpackets(SignatureSubpacket[] packets) { - if (packets == null) - { - packets = new SignatureSubpacket[0]; - } - return new PgpSignatureSubpacketVector(packets); + return new PgpSignatureSubpacketVector(packets ?? new SignatureSubpacket[0]); } private readonly SignatureSubpacket[] packets; - internal PgpSignatureSubpacketVector( - SignatureSubpacket[] packets) + internal PgpSignatureSubpacketVector(SignatureSubpacket[] packets) { this.packets = packets; } - public SignatureSubpacket GetSubpacket( - SignatureSubpacketTag type) + public SignatureSubpacket GetSubpacket(SignatureSubpacketTag type) { for (int i = 0; i != packets.Length; i++) { if (packets[i].SubpacketType == type) - { return packets[i]; - } } return null; @@ -45,8 +38,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp * @param type type to look for. * @return true if present, false otherwise. */ - public bool HasSubpacket( - SignatureSubpacketTag type) + public bool HasSubpacket(SignatureSubpacketTag type) { return GetSubpacket(type) != null; } @@ -56,8 +48,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp * @param type subpacket type code * @return an array of zero or more matching subpackets. */ - public SignatureSubpacket[] GetSubpackets( - SignatureSubpacketTag type) + public SignatureSubpacket[] GetSubpackets(SignatureSubpacketTag type) { int count = 0; for (int i = 0; i < packets.Length; ++i) @@ -82,6 +73,27 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp return result; } + /// <exception cref="PgpException"/> + public PgpSignatureList GetEmbeddedSignatures() + { + SignatureSubpacket[] sigs = GetSubpackets(SignatureSubpacketTag.EmbeddedSignature); + PgpSignature[] l = new PgpSignature[sigs.Length]; + + for (int i = 0; i < sigs.Length; i++) + { + try + { + l[i] = new PgpSignature(SignaturePacket.FromByteArray(sigs[i].GetData())); + } + catch (IOException e) + { + throw new PgpException("Unable to parse signature packet: " + e.Message, e); + } + } + + return new PgpSignatureList(l); + } + public NotationData[] GetNotationDataOccurrences() { SignatureSubpacket[] notations = GetSubpackets(SignatureSubpacketTag.NotationData); @@ -95,11 +107,26 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp return vals; } - public long GetIssuerKeyId() + public NotationData[] GetNotationDataOccurrences(string notationName) + { + NotationData[] notations = GetNotationDataOccurrences(); + var notationsWithName = new List<NotationData>(); + for (int i = 0; i != notations.Length; i++) + { + NotationData notation = notations[i]; + if (notation.GetNotationName().Equals(notationName)) + { + notationsWithName.Add(notation); + } + } + return notationsWithName.ToArray(); + } + + public long GetIssuerKeyId() { SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.IssuerKeyId); - return p == null ? 0 : ((IssuerKeyId) p).KeyId; + return p == null ? 0 : ((IssuerKeyId)p).KeyId; } public bool HasSignatureCreationTime() @@ -109,26 +136,27 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp public DateTime GetSignatureCreationTime() { - SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.CreationTime); - - if (p == null) - { - throw new PgpException("SignatureCreationTime not available"); - } + SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.CreationTime) + ?? 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 bool HasSignatureExpirationTime() + { + return GetSubpacket(SignatureSubpacketTag.ExpireTime) != null; + } + + /// <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; + return p == null ? 0 : ((SignatureExpirationTime)p).Time; } /// <summary> @@ -140,77 +168,58 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.KeyExpireTime); - return p == null ? 0 : ((KeyExpirationTime) p).Time; + return p == null ? 0 : ((KeyExpirationTime)p).Time; } public int[] GetPreferredHashAlgorithms() { SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.PreferredHashAlgorithms); - return p == null ? null : ((PreferredAlgorithms) p).GetPreferences(); + return p == null ? null : ((PreferredAlgorithms)p).GetPreferences(); } public int[] GetPreferredSymmetricAlgorithms() { SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.PreferredSymmetricAlgorithms); - return p == null ? null : ((PreferredAlgorithms) p).GetPreferences(); + return p == null ? null : ((PreferredAlgorithms)p).GetPreferences(); } public int[] GetPreferredCompressionAlgorithms() { SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.PreferredCompressionAlgorithms); - return p == null ? null : ((PreferredAlgorithms) p).GetPreferences(); + return p == null ? null : ((PreferredAlgorithms)p).GetPreferences(); + } + + public int[] GetPreferredAeadAlgorithms() + { + SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.PreferredAeadAlgorithms); + + return p == null ? null : ((PreferredAlgorithms)p).GetPreferences(); } - public int GetKeyFlags() + public int GetKeyFlags() { SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.KeyFlags); - return p == null ? 0 : ((KeyFlags) p).Flags; + return p == null ? 0 : ((KeyFlags)p).Flags; } public string GetSignerUserId() { SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.SignerUserId); - return p == null ? null : ((SignerUserId) p).GetId(); + return p == null ? null : ((SignerUserId)p).GetId(); } public bool IsPrimaryUserId() { - PrimaryUserId primaryId = (PrimaryUserId) - this.GetSubpacket(SignatureSubpacketTag.PrimaryUserId); - - if (primaryId != null) - { - return primaryId.IsPrimaryUserId(); - } + PrimaryUserId primaryId = (PrimaryUserId)GetSubpacket(SignatureSubpacketTag.PrimaryUserId); - return false; + return primaryId != null && primaryId.IsPrimaryUserId(); } - public PgpSignatureList GetEmbeddedSignatures() - { - SignatureSubpacket [] sigs = GetSubpackets(SignatureSubpacketTag.EmbeddedSignature); - PgpSignature[] l = new PgpSignature[sigs.Length]; - - for (int i = 0; i < sigs.Length; i++) - { - try - { - l[i] = new PgpSignature(SignaturePacket.FromByteArray(sigs[i].GetData())); - } - catch (IOException e) - { - throw new PgpException("Unable to parse signature packet: " + e.Message, e); - } - } - - return new PgpSignatureList(l); - } - public SignatureSubpacketTag[] GetCriticalTags() { int count = 0; @@ -237,25 +246,152 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp return list; } + public SignatureTarget GetSignatureTarget() + { + SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.SignatureTarget); + + return p == null ? null : new SignatureTarget(p.IsCritical(), p.IsLongLength(), p.GetData()); + } + public Features GetFeatures() { - SignatureSubpacket p = this.GetSubpacket(SignatureSubpacketTag.Features); + SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.Features); - if (p == null) - return null; + return p == null ? null : new Features(p.IsCritical(), p.IsLongLength(), p.GetData()); + } - return new Features(p.IsCritical(), p.IsLongLength(), p.GetData()); + public IssuerFingerprint GetIssuerFingerprint() + { + SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.IssuerFingerprint); + + return p == null ? null : new IssuerFingerprint(p.IsCritical(), p.IsLongLength(), p.GetData()); + } + + public IntendedRecipientFingerprint GetIntendedRecipientFingerprint() + { + SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.IntendedRecipientFingerprint); + + return p == null ? null : new IntendedRecipientFingerprint(p.IsCritical(), p.IsLongLength(), p.GetData()); + } + + public IntendedRecipientFingerprint[] GetIntendedRecipientFingerprints() + { + SignatureSubpacket[] subpackets = GetSubpackets(SignatureSubpacketTag.IntendedRecipientFingerprint); + IntendedRecipientFingerprint[] recipients = new IntendedRecipientFingerprint[subpackets.Length]; + for (int i = 0; i < recipients.Length; i++) + { + SignatureSubpacket p = subpackets[i]; + recipients[i] = new IntendedRecipientFingerprint(p.IsCritical(), p.IsLongLength(), p.GetData()); + } + return recipients; + } + + public Exportable GetExportable() + { + SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.Exportable); + + return p == null ? null : new Exportable(p.IsCritical(), p.IsLongLength(), p.GetData()); + } + + public bool IsExportable() + { + Exportable exportable = GetExportable(); + return exportable == null || exportable.IsExportable(); + } + + public PolicyUrl GetPolicyUrl() + { + SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.PolicyUrl); + + return p == null ? null : new PolicyUrl(p.IsCritical(), p.IsLongLength(), p.GetData()); + } + + public PolicyUrl[] GetPolicyUrls() + { + SignatureSubpacket[] subpackets = GetSubpackets(SignatureSubpacketTag.PolicyUrl); + PolicyUrl[] policyUrls = new PolicyUrl[subpackets.Length]; + for (int i = 0; i < subpackets.Length; i++) + { + SignatureSubpacket p = subpackets[i]; + policyUrls[i] = new PolicyUrl(p.IsCritical(), p.IsLongLength(), p.GetData()); + } + return policyUrls; + } + + public RegularExpression GetRegularExpression() + { + SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.RegExp); + + return p == null ? null : new RegularExpression(p.IsCritical(), p.IsLongLength(), p.GetData()); + } + + public RegularExpression[] GetRegularExpressions() + { + SignatureSubpacket[] subpackets = GetSubpackets(SignatureSubpacketTag.RegExp); + RegularExpression[] regexes = new RegularExpression[subpackets.Length]; + for (int i = 0; i < regexes.Length; i++) + { + SignatureSubpacket p = subpackets[i]; + regexes[i] = new RegularExpression(p.IsCritical(), p.IsLongLength(), p.GetData()); + } + return regexes; + } + + public Revocable GetRevocable() + { + SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.Revocable); + + return p == null ? null : new Revocable(p.IsCritical(), p.IsLongLength(), p.GetData()); + } + + public bool IsRevocable() + { + Revocable revocable = GetRevocable(); + return revocable == null || revocable.IsRevocable(); + } + + public RevocationKey[] GetRevocationKeys() + { + SignatureSubpacket[] subpackets = GetSubpackets(SignatureSubpacketTag.RevocationKey); + RevocationKey[] revocationKeys = new RevocationKey[subpackets.Length]; + for (int i = 0; i < revocationKeys.Length; i++) + { + SignatureSubpacket p = subpackets[i]; + revocationKeys[i] = new RevocationKey(p.IsCritical(), p.IsLongLength(), p.GetData()); + } + return revocationKeys; + } + + public RevocationReason GetRevocationReason() + { + SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.RevocationReason); + + return p == null ? null : new RevocationReason(p.IsCritical(), p.IsLongLength(), p.GetData()); + } + + public TrustSignature GetTrust() + { + SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.TrustSig); + + return p == null ? null : new TrustSignature(p.IsCritical(), p.IsLongLength(), p.GetData()); } /// <summary>Return the number of packets this vector contains.</summary> - public int Count - { - get { return packets.Length; } - } + public int Count => packets.Length; internal SignatureSubpacket[] ToSubpacketArray() { return packets; } + + /** + * Return a copy of the subpackets in this vector. + * + * @return an array containing the vector subpackets in order. + */ + public SignatureSubpacket[] ToArray() + { + return (SignatureSubpacket[])packets.Clone(); + } } } diff --git a/crypto/src/openpgp/PgpUserAttributeSubpacketVector.cs b/crypto/src/openpgp/PgpUserAttributeSubpacketVector.cs index df5081390..e9a42e4b7 100644 --- a/crypto/src/openpgp/PgpUserAttributeSubpacketVector.cs +++ b/crypto/src/openpgp/PgpUserAttributeSubpacketVector.cs @@ -4,6 +4,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { /// <remarks>Container for a list of user attribute subpackets.</remarks> public class PgpUserAttributeSubpacketVector + : IUserDataPacket { public static PgpUserAttributeSubpacketVector FromSubpackets(UserAttributeSubpacket[] packets) { @@ -22,15 +23,12 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp this.packets = packets; } - public UserAttributeSubpacket GetSubpacket( - UserAttributeSubpacketTag type) + public UserAttributeSubpacket GetSubpacket(UserAttributeSubpacketTag type) { for (int i = 0; i != packets.Length; i++) { if (packets[i].SubpacketType == type) - { return packets[i]; - } } return null; @@ -48,28 +46,21 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp return packets; } - public override bool Equals( - object obj) + public override bool Equals(object obj) { if (obj == this) return true; - PgpUserAttributeSubpacketVector other = obj as PgpUserAttributeSubpacketVector; - - if (other == null) + if (!(obj is PgpUserAttributeSubpacketVector other)) 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; diff --git a/crypto/test/src/openpgp/test/PolicyUrlTest.cs b/crypto/test/src/openpgp/test/PolicyUrlTest.cs new file mode 100644 index 000000000..d98397f39 --- /dev/null +++ b/crypto/test/src/openpgp/test/PolicyUrlTest.cs @@ -0,0 +1,58 @@ +using System.IO; + +using NUnit.Framework; + +using Org.BouncyCastle.Bcpg.Sig; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests +{ + [TestFixture] + public class PolicyUrlTest + { + [Test] + public void TestGetUrl() + { + PolicyUrl policyUrl = new PolicyUrl(true, "https://bouncycastle.org/policy/alice.txt"); + Assert.IsTrue(policyUrl.IsCritical()); + Assert.AreEqual("https://bouncycastle.org/policy/alice.txt", policyUrl.Url); + + policyUrl = new PolicyUrl(false, "https://bouncycastle.org/policy/bob.txt"); + Assert.IsFalse(policyUrl.IsCritical()); + Assert.AreEqual("https://bouncycastle.org/policy/bob.txt", policyUrl.Url); + } + + [Test] + public void TestParsingFromSignature() + { + string signatureWithPolicyUrl = "-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "iKQEHxYKAFYFAmIRIAgJEDXXpSQjWzWvFiEEVSc3S9X9kRTsyfjqNdelJCNbNa8u\n" + + "Gmh0dHBzOi8vZXhhbXBsZS5vcmcvfmFsaWNlL3NpZ25pbmctcG9saWN5LnR4dAAA\n" + + "NnwBAImA2KdiS/7kLWoQpwc+A6N2PtAvLxG0gkZmGzYgRWvGAP9g4GLAA/GQ0plr\n" + + "Xn7uLnOG49S1fFA9P+R1Dd8Qoa4+Dg==\n" + + "=OPUu\n" + + "-----END PGP SIGNATURE-----\n"; + + MemoryStream byteIn = new MemoryStream(Strings.ToByteArray(signatureWithPolicyUrl), false); + ArmoredInputStream armorIn = new ArmoredInputStream(byteIn); + PgpObjectFactory objectFactory = new PgpObjectFactory(armorIn); + + PgpSignatureList signatures = (PgpSignatureList)objectFactory.NextPgpObject(); + PgpSignature signature = signatures[0]; + + PolicyUrl policyUrl = signature.GetHashedSubPackets().GetPolicyUrl(); + Assert.AreEqual("https://example.org/~alice/signing-policy.txt", policyUrl.Url); + + PolicyUrl other = new PolicyUrl(false, "https://example.org/~alice/signing-policy.txt"); + + MemoryStream first = new MemoryStream(); + policyUrl.Encode(first); + + MemoryStream second = new MemoryStream(); + other.Encode(second); + + Assert.IsTrue(Arrays.AreEqual(first.ToArray(), second.ToArray())); + } + } +} |