diff --git a/crypto/src/bcpg/AeadAlgorithmTag.cs b/crypto/src/bcpg/AeadAlgorithmTag.cs
index 632f888381..f02bc90aee 100644
--- a/crypto/src/bcpg/AeadAlgorithmTag.cs
+++ b/crypto/src/bcpg/AeadAlgorithmTag.cs
@@ -7,5 +7,17 @@ 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)
+
+ Experimental_1 = 100,
+ Experimental_2 = 101,
+ Experimental_3 = 102,
+ Experimental_4 = 103,
+ Experimental_5 = 104,
+ Experimental_6 = 105,
+ Experimental_7 = 106,
+ Experimental_8 = 107,
+ Experimental_9 = 108,
+ Experimental_10 = 109,
+ Experimental_11 = 110,
}
}
diff --git a/crypto/src/bcpg/AeadEncDataPacket.cs b/crypto/src/bcpg/AeadEncDataPacket.cs
index 78e54fcd08..13dd281643 100644
--- a/crypto/src/bcpg/AeadEncDataPacket.cs
+++ b/crypto/src/bcpg/AeadEncDataPacket.cs
@@ -19,11 +19,13 @@ public class AeadEncDataPacket
private readonly byte m_chunkSize;
private readonly byte[] m_iv;
+ public const int Version1 = 1;
+
public AeadEncDataPacket(BcpgInputStream bcpgIn)
- : base(bcpgIn)
+ : base(bcpgIn, PacketTag.ReservedAeadEncryptedData)
{
m_version = bcpgIn.RequireByte();
- if (m_version != 1)
+ if (m_version != Version1)
throw new ArgumentException("wrong AEAD packet version: " + m_version);
m_algorithm = (SymmetricKeyAlgorithmTag)bcpgIn.RequireByte();
@@ -36,9 +38,9 @@ public AeadEncDataPacket(BcpgInputStream bcpgIn)
public AeadEncDataPacket(SymmetricKeyAlgorithmTag algorithm, AeadAlgorithmTag aeadAlgorithm, int chunkSize,
byte[] iv)
- : base(null)
+ : base(null, PacketTag.ReservedAeadEncryptedData)
{
- m_version = 1;
+ m_version = Version1;
m_algorithm = algorithm;
m_aeadAlgorithm = aeadAlgorithm;
m_chunkSize = (byte)chunkSize;
@@ -69,5 +71,24 @@ public static int GetIVLength(AeadAlgorithmTag aeadAlgorithm)
throw new ArgumentException("unknown mode: " + aeadAlgorithm);
}
}
+
+ public byte[] GetAAData()
+ {
+ return CreateAAData(Version, Algorithm, AeadAlgorithm, ChunkSize);
+ }
+
+ public static byte[] CreateAAData(int version, SymmetricKeyAlgorithmTag symAlgorithm, AeadAlgorithmTag aeadAlgorithm, int chunkSize)
+ {
+ byte[] aaData = new byte[]
+ {
+ 0xC0 | (byte)PacketTag.ReservedAeadEncryptedData,
+ (byte)version,
+ (byte)symAlgorithm,
+ (byte)aeadAlgorithm,
+ (byte)chunkSize
+ };
+
+ return aaData;
+ }
}
}
diff --git a/crypto/src/bcpg/AeadInputStream.cs b/crypto/src/bcpg/AeadInputStream.cs
new file mode 100644
index 0000000000..e88334ab2e
--- /dev/null
+++ b/crypto/src/bcpg/AeadInputStream.cs
@@ -0,0 +1,191 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.IO;
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Bcpg
+{
+ internal class AeadInputStream
+ : BaseInputStream
+ {
+ // ported from bc-java
+
+ private readonly Stream inputStream;
+ private readonly byte[] buf;
+ private readonly BufferedAeadBlockCipher cipher;
+ private readonly KeyParameter secretKey;
+ private readonly byte[] aaData;
+ private readonly byte[] iv;
+ private readonly int chunkLength;
+ private readonly int tagLen;
+
+ private byte[] data;
+ private int dataOff;
+ private long chunkIndex = 0;
+ private long totalBytes = 0;
+ private readonly bool isV5StyleAead;
+
+ private static int GetChunkLength(int chunkSize)
+ {
+ return 1 << (chunkSize + 6);
+ }
+
+ ///
+ /// InputStream for decrypting AEAD encrypted data.
+ ///
+ /// underlying InputStream
+ /// encryption cipher
+ /// decryption key
+ /// initialization vector
+ /// AEAD algorithm
+ /// chunk size of the AEAD encryption
+ /// associated data
+ public AeadInputStream(
+ Stream inputStream,
+ BufferedAeadBlockCipher cipher,
+ KeyParameter secretKey,
+ byte[] iv,
+ AeadAlgorithmTag aeadAlgorithm,
+ int chunkSize,
+ byte[] aaData)
+ :this(false, inputStream, cipher, secretKey, iv, aeadAlgorithm, chunkSize, aaData)
+ {
+ }
+
+ ///
+ /// InputStream for decrypting AEAD encrypted data.
+ ///
+ /// flavour of AEAD (OpenPGP v5 or v6)
+ /// underlying InputStream
+ /// encryption cipher
+ /// decryption key
+ /// initialization vector
+ /// AEAD algorithm
+ /// chunk size of the AEAD encryption
+ /// associated data
+ public AeadInputStream(
+ bool isV5StyleAead,
+ Stream inputStream,
+ BufferedAeadBlockCipher cipher,
+ KeyParameter secretKey,
+ byte[] iv,
+ AeadAlgorithmTag aeadAlgorithm,
+ int chunkSize,
+ byte[] aaData)
+ {
+ this.inputStream = inputStream;
+ this.cipher = cipher;
+ this.secretKey = secretKey;
+ this.aaData = aaData;
+ this.iv = iv;
+ this.isV5StyleAead = isV5StyleAead;
+
+ chunkLength = GetChunkLength(chunkSize);
+ tagLen = AeadUtils.GetAuthTagLength(aeadAlgorithm);
+
+ buf = new byte[chunkLength + tagLen + tagLen]; // allow room for chunk tag and message tag
+
+ Streams.ReadFully(inputStream, buf, 0, tagLen + tagLen);
+
+ // load the first block
+ data = ReadBlock();
+ dataOff = 0;
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ Streams.ValidateBufferArguments(buffer, offset, count);
+
+ if (count == 0)
+ {
+ return 0;
+ }
+
+ if (data != null && dataOff == data.Length)
+ {
+ data = ReadBlock();
+ dataOff = 0;
+ }
+
+ if (data == null)
+ {
+ return 0;
+ }
+
+ int available = data.Length - dataOff;
+ int supplyLen = count < available ? count : available;
+ Array.Copy(data, dataOff, buffer, offset, supplyLen);
+ dataOff += supplyLen;
+
+ return supplyLen;
+ }
+
+ private byte[] ReadBlock()
+ {
+ int dataLen = Streams.ReadFully(inputStream, buf, tagLen + tagLen, chunkLength);
+ if (dataLen == 0)
+ {
+ return null;
+ }
+
+ byte[] adata = Arrays.Clone(aaData);
+ byte[] decData = new byte[dataLen];
+
+ try
+ {
+ AeadParameters aeadParams = new AeadParameters(
+ secretKey,
+ 8 * tagLen,
+ AeadUtils.CreateNonce(iv, chunkIndex),
+ adata);
+
+ cipher.Init(false, aeadParams);
+
+ int len = cipher.ProcessBytes(buf, 0, dataLen + tagLen, decData, 0);
+
+ cipher.DoFinal(decData, len);
+ }
+ catch (InvalidCipherTextException e)
+ {
+ throw new IOException($"exception processing chunk {chunkIndex}: {e.Message}");
+ }
+
+ totalBytes += decData.Length;
+ chunkIndex++;
+ Array.Copy(buf, dataLen + tagLen, buf, 0, tagLen); // copy back the "tag"
+
+ if (dataLen != chunkLength)
+ {
+ // last block
+ try
+ {
+ adata = AeadUtils.CreateLastBlockAAData(isV5StyleAead, aaData, chunkIndex, totalBytes);
+ AeadParameters aeadParams = new AeadParameters(
+ secretKey,
+ 8 * tagLen,
+ AeadUtils.CreateNonce(iv, chunkIndex),
+ adata);
+
+ cipher.Init(false, aeadParams);
+
+ cipher.ProcessBytes(buf, 0, tagLen, buf, 0);
+
+ cipher.DoFinal(buf, 0); // check final tag
+ }
+ catch (InvalidCipherTextException e)
+ {
+ throw new IOException($"exception processing final tag: {e.Message}");
+ }
+ }
+ else
+ {
+ Streams.ReadFully(inputStream, buf, tagLen, tagLen); // read the next tag bytes
+ }
+
+ return decData;
+ }
+ }
+}
\ No newline at end of file
diff --git a/crypto/src/bcpg/AeadOutputStream.cs b/crypto/src/bcpg/AeadOutputStream.cs
new file mode 100644
index 0000000000..8d49718661
--- /dev/null
+++ b/crypto/src/bcpg/AeadOutputStream.cs
@@ -0,0 +1,228 @@
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Utilities.IO;
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Bcpg
+{
+ internal class AeadOutputStream
+ : BaseOutputStream
+ {
+ // ported from bc-java
+
+ private readonly bool isV5StyleAead;
+ private readonly Stream outputStream;
+ private readonly byte[] data;
+ private readonly BufferedAeadBlockCipher cipher;
+ private readonly KeyParameter secretKey;
+ private readonly byte[] aaData;
+ private readonly byte[] iv;
+ private readonly int chunkLength;
+ private readonly int tagLen;
+
+ private int dataOff;
+ private long chunkIndex = 0;
+ private long totalBytes = 0;
+
+ private static int GetChunkLength(int chunkSize)
+ {
+ return 1 << (chunkSize + 6);
+ }
+
+ ///
+ /// OutputStream for AEAD encryption.
+ ///
+ /// underlying OutputStream
+ /// AEAD cipher
+ /// encryption key
+ /// initialization vector
+ /// encryption algorithm
+ /// AEAD algorithm
+ /// chunk size octet of the AEAD encryption
+ public AeadOutputStream(
+ Stream outputStream,
+ BufferedAeadBlockCipher cipher,
+ KeyParameter secretKey,
+ byte[] iv,
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ AeadAlgorithmTag aeadAlgorithm,
+ int chunkSize)
+ :this(false, outputStream, cipher, secretKey, iv, encAlgorithm, aeadAlgorithm, chunkSize)
+ {
+
+ }
+
+ ///
+ /// OutputStream for AEAD encryption.
+ ///
+ /// flavour of AEAD (OpenPGP v5 or v6)
+ /// underlying OutputStream
+ /// AEAD cipher
+ /// encryption key
+ /// initialization vector
+ /// encryption algorithm
+ /// AEAD algorithm
+ /// chunk size octet of the AEAD encryption
+ public AeadOutputStream(
+ bool isV5StyleAead,
+ Stream outputStream,
+ BufferedAeadBlockCipher cipher,
+ KeyParameter secretKey,
+ byte[] iv,
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ AeadAlgorithmTag aeadAlgorithm,
+ int chunkSize)
+ {
+ this.isV5StyleAead = isV5StyleAead;
+ this.outputStream = outputStream;
+ this.iv = iv;
+ this.chunkLength = GetChunkLength(chunkSize);
+ this.tagLen = AeadUtils.GetAuthTagLength(aeadAlgorithm);
+ this.data = new byte[chunkLength];
+ this.cipher = cipher;
+ this.secretKey = secretKey;
+
+ aaData = CreateAAD(isV5StyleAead, encAlgorithm, aeadAlgorithm, chunkSize);
+ }
+
+ private static byte[] CreateAAD(bool isV5StyleAEAD, SymmetricKeyAlgorithmTag encAlgorithm, AeadAlgorithmTag aeadAlgorithm, int chunkSize)
+ {
+ if (isV5StyleAEAD)
+ {
+ return AeadEncDataPacket.CreateAAData(AeadEncDataPacket.Version1, encAlgorithm, aeadAlgorithm, chunkSize);
+ }
+ else
+ {
+ return SymmetricEncIntegrityPacket.CreateAAData(SymmetricEncIntegrityPacket.Version2, encAlgorithm, aeadAlgorithm, chunkSize);
+ }
+ }
+
+ public override void WriteByte(byte b)
+ {
+ if (dataOff == data.Length)
+ {
+ WriteBlock();
+ }
+ data[dataOff++] = (byte) b;
+ }
+
+ public override void Write(byte[] b, int off, int len)
+ {
+ if (dataOff == data.Length)
+ {
+ WriteBlock();
+ }
+
+ if (len= data.Length)
+ {
+ Array.Copy(b, off, data, 0, data.Length);
+ dataOff = data.Length;
+ WriteBlock();
+ len -= data.Length;
+ off += data.Length;
+ }
+ if (len > 0)
+ {
+ Array.Copy(b, off, data, 0, len);
+ dataOff = len;
+ }
+ }
+ }
+
+ private void WriteBlock()
+ {
+ //bool v5StyleAEAD = isV5StyleAEAD;
+
+ //byte[] adata = v5StyleAEAD ? new byte[13] : new byte[aaData.Length];
+ byte[] adata = new byte[aaData.Length];
+ Array.Copy(aaData, 0, adata, 0, aaData.Length);
+
+ //if (v5StyleAEAD)
+ //{
+ // xorChunkId(adata, chunkIndex);
+ //}
+
+ try
+ {
+ AeadParameters aeadParams = new AeadParameters(
+ secretKey,
+ 8 * tagLen,
+ AeadUtils.CreateNonce(iv, chunkIndex),
+ adata);
+
+ cipher.Init(true, aeadParams);
+
+ int len = cipher.ProcessBytes(data, 0, dataOff, data, 0);
+ outputStream.Write(data, 0, len);
+
+ len = cipher.DoFinal(data, 0);
+ outputStream.Write(data, 0, len);
+ }
+ catch (InvalidCipherTextException e)
+ {
+ throw new IOException("exception processing chunk " + chunkIndex + ": " + e.Message);
+ }
+
+ totalBytes += dataOff;
+ chunkIndex++;
+ dataOff = 0;
+ }
+
+ private bool disposed = false;
+ protected override void Dispose(bool disponing)
+ {
+ if (!disposed)
+ {
+ base.Dispose(disponing);
+ Finish();
+ disposed = true;
+ }
+ }
+
+ private void Finish()
+ {
+ if (dataOff > 0)
+ {
+ WriteBlock();
+ }
+
+ byte[] adata = AeadUtils.CreateLastBlockAAData(isV5StyleAead, aaData, chunkIndex, totalBytes);
+ try
+ {
+ AeadParameters aeadParams = new AeadParameters(
+ secretKey,
+ 8 * tagLen,
+ AeadUtils.CreateNonce(iv, chunkIndex),
+ adata);
+
+ cipher.Init(true, aeadParams);
+ //if (isV5StyleAead)
+ //{
+ // cipher.processAADBytes(Pack.longToBigEndian(totalBytes), 0, 8);
+ //}
+ cipher.DoFinal(data, 0);
+ outputStream.Write(data, 0, tagLen); // output final tag
+ }
+ catch (InvalidCipherTextException e)
+ {
+ throw new IOException("exception processing final tag: " + e.Message);
+ }
+ outputStream.Close();
+ }
+ }
+}
diff --git a/crypto/src/bcpg/AeadUtils.cs b/crypto/src/bcpg/AeadUtils.cs
new file mode 100644
index 0000000000..0f2adce49f
--- /dev/null
+++ b/crypto/src/bcpg/AeadUtils.cs
@@ -0,0 +1,167 @@
+using System;
+using Org.BouncyCastle.Bcpg.OpenPgp;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Generators;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+
+namespace Org.BouncyCastle.Bcpg
+{
+ public sealed class AeadUtils
+ {
+ ///
+ /// Return the length of the IV used by the given AEAD algorithm in octets.
+ ///
+ /// AEAD algorithm identifier
+ /// length of the IV
+ /// Thrown when aeadAlgorithmTag is unknown/invalid
+ public static int GetIVLength(AeadAlgorithmTag aeadAlgorithmTag)
+ {
+ switch (aeadAlgorithmTag)
+ {
+ case AeadAlgorithmTag.Eax:
+ return 16;
+ case AeadAlgorithmTag.Ocb:
+ return 15;
+ case AeadAlgorithmTag.Gcm:
+ return 12;
+ default:
+ throw new ArgumentException($"Invalid AEAD algorithm tag: {aeadAlgorithmTag}");
+ }
+ }
+
+ ///
+ /// Returns the name of the AEAD Algorithm
+ ///
+ /// AEAD algorithm identifier
+ /// name of the AEAD Algorithm
+ ///
+
+ public static string GetAeadAlgorithmName(AeadAlgorithmTag aeadAlgorithmTag)
+ {
+ switch (aeadAlgorithmTag)
+ {
+ case AeadAlgorithmTag.Eax:
+ return "EAX";
+ case AeadAlgorithmTag.Ocb:
+ return "OCB";
+ case AeadAlgorithmTag.Gcm:
+ return "GCM";
+ default:
+ throw new ArgumentException($"Invalid AEAD algorithm tag: {aeadAlgorithmTag}");
+ }
+ }
+
+ ///
+ /// Return the length of the authentication tag used by the given AEAD algorithm in octets.
+ ///
+ /// AEAD algorithm identifier
+ /// length of the auth tag
+ /// Thrown when aeadAlgorithmTag is unknown/invalid
+ public static int GetAuthTagLength(AeadAlgorithmTag aeadAlgorithmTag)
+ {
+ switch (aeadAlgorithmTag)
+ {
+ case AeadAlgorithmTag.Eax:
+ case AeadAlgorithmTag.Ocb:
+ case AeadAlgorithmTag.Gcm:
+ return 16;
+ default:
+ throw new ArgumentException($"Invalid AEAD algorithm tag: {aeadAlgorithmTag}");
+ }
+ }
+
+ ///
+ /// Split a given byte array containing m bytes of key and n-8 bytes of IV into
+ /// two separate byte arrays.
+ /// m is the key length of the cipher algorithm, while n is the IV length of the AEAD algorithm.
+ /// Note, that the IV is filled with n-8
bytes only, the remainder is left as 0s.
+ ///
+ /// m+n-8 bytes of concatenated message key and IV
+ /// symmetric cipher algorithm
+ /// AEAD algorithm
+ /// Message key
+ /// IV
+ public static void SplitMessageKeyAndIv(byte[] messageKeyAndIv, SymmetricKeyAlgorithmTag cipherAlgo, AeadAlgorithmTag aeadAlgo, out byte[] messageKey, out byte[] iv)
+ {
+ int keyLen = PgpUtilities.GetKeySizeInOctets(cipherAlgo);
+ int ivLen = GetIVLength(aeadAlgo);
+
+ messageKey = new byte[keyLen];
+ iv = new byte[ivLen];
+
+ Array.Copy(messageKeyAndIv, messageKey, messageKey.Length);
+ Array.Copy(messageKeyAndIv, messageKey.Length, iv, 0, ivLen-8);
+ }
+
+ ///
+ /// Derive a message key and IV from the given session key.
+ ///
+ /// session key
+ /// symmetric cipher algorithm tag
+ /// AEAD algorithm tag
+ /// salt
+ /// HKDF info
+ ///
+ ///
+ public static void DeriveAeadMessageKeyAndIv(
+ KeyParameter sessionKey,
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ AeadAlgorithmTag aeadAlgorithm,
+ byte[] salt,
+ byte[] hkdfInfo,
+ out KeyParameter messageKey,
+ out byte[] iv)
+ {
+ var hkdfGen = new HkdfBytesGenerator(PgpUtilities.CreateDigest(HashAlgorithmTag.Sha256));
+ var hkdfParams = new HkdfParameters(sessionKey.GetKey(), salt, hkdfInfo);
+ hkdfGen.Init(hkdfParams);
+ var hkdfOutput = new byte[PgpUtilities.GetKeySizeInOctets(encAlgorithm) + AeadUtils.GetIVLength(aeadAlgorithm) - 8];
+ hkdfGen.GenerateBytes(hkdfOutput, 0, hkdfOutput.Length);
+
+ AeadUtils.SplitMessageKeyAndIv(hkdfOutput, encAlgorithm, aeadAlgorithm, out var messageKeyBytes, out iv);
+
+ messageKey = new KeyParameter(messageKeyBytes);
+ }
+
+ public static byte[] CreateNonce(byte[] iv, long chunkIndex)
+ {
+ byte[] nonce = Arrays.Clone(iv);
+ byte[] chunkid = Pack.UInt64_To_BE((ulong)chunkIndex);
+ Array.Copy(chunkid, 0, nonce, nonce.Length - 8, 8);
+
+ return nonce;
+ }
+
+ public static byte[] CreateLastBlockAAData(bool isV5StyleAead, byte[] aaData, long chunkIndex, long totalBytes)
+ {
+ byte[] adata;
+ if (isV5StyleAead)
+ {
+ adata = new byte[13];
+ Array.Copy(aaData, 0, adata, 0, aaData.Length);
+ Array.Copy(Pack.UInt64_To_BE((ulong)chunkIndex), 0, adata, aaData.Length, 8);
+ }
+ else
+ {
+ adata = new byte[aaData.Length + 8];
+ Array.Copy(aaData, 0, adata, 0, aaData.Length);
+ Array.Copy(Pack.UInt64_To_BE((ulong)totalBytes), 0, adata, aaData.Length, 8);
+ }
+ return adata;
+ }
+
+ public static BufferedAeadBlockCipher CreateAeadCipher(
+ SymmetricKeyAlgorithmTag encAlgorithm, AeadAlgorithmTag aeadAlgorithm)
+ {
+ string algo = PgpUtilities.GetSymmetricCipherName(encAlgorithm);
+ string mode = GetAeadAlgorithmName(aeadAlgorithm);
+ string cName = $"{algo}/{mode}/NoPadding";
+
+ return CipherUtilities.GetCipher(cName) as BufferedAeadBlockCipher;
+ }
+ }
+}
\ No newline at end of file
diff --git a/crypto/src/bcpg/ArmoredOutputStream.cs b/crypto/src/bcpg/ArmoredOutputStream.cs
index 0f4d410b3b..a63200f1dc 100644
--- a/crypto/src/bcpg/ArmoredOutputStream.cs
+++ b/crypto/src/bcpg/ArmoredOutputStream.cs
@@ -239,21 +239,42 @@ public void ResetHeaders()
}
}
- /**
- * Start a clear text signed message.
- * @param hashAlgorithm
- */
+ ///
+ /// Start a clear text signed message.
+ ///
+ public void BeginClearText()
+ {
+ DoWrite("-----BEGIN PGP SIGNED MESSAGE-----" + NewLine + NewLine);
+
+ clearText = true;
+ newLine = true;
+ lastb = 0;
+
+ }
+
+ ///
+ /// Start a clear text signed message with a "Hash" Armor Header.
+ /// This header is deprecated and SHOULD NOT be emitted unless:
+ /// * The cleartext signed message contains a v4 signature made using a SHA2-based digest(SHA224, SHA256, SHA384, or SHA512), and
+ /// * The cleartext signed message might be verified by a legacy OpenPGP implementation that requires this header.
+ ///
+ ///
+ ///
+ ///
public void BeginClearText(
HashAlgorithmTag hashAlgorithm)
{
- string hash;
+ string hash;
switch (hashAlgorithm)
{
case HashAlgorithmTag.Sha1:
hash = "SHA1";
break;
- case HashAlgorithmTag.Sha256:
+ case HashAlgorithmTag.Sha224:
+ hash = "SHA224";
+ break;
+ case HashAlgorithmTag.Sha256:
hash = "SHA256";
break;
case HashAlgorithmTag.Sha384:
diff --git a/crypto/src/bcpg/BcpgInputStream.cs b/crypto/src/bcpg/BcpgInputStream.cs
index 623696ceb8..578723c516 100644
--- a/crypto/src/bcpg/BcpgInputStream.cs
+++ b/crypto/src/bcpg/BcpgInputStream.cs
@@ -9,7 +9,7 @@ namespace Org.BouncyCastle.Bcpg
public class BcpgInputStream
: BaseInputStream
{
- private Stream m_in;
+ private readonly Stream m_in;
private bool next = false;
private int nextB;
@@ -119,9 +119,9 @@ public Packet ReadPacket()
throw new IOException("invalid header encountered");
bool newPacket = (hdr & 0x40) != 0;
- PacketTag tag = 0;
+ PacketTag tag = PacketTag.Reserved;
// TODO[pgp] Is the length field supposed to support full uint range?
- int bodyLen;
+ int bodyLen = 0;
bool partial = false;
if (newPacket)
@@ -170,56 +170,65 @@ public Packet ReadPacket()
switch (tag)
{
- case PacketTag.Reserved:
- return new InputStreamPacket(objStream);
- case PacketTag.PublicKeyEncryptedSession:
- return new PublicKeyEncSessionPacket(objStream);
- case PacketTag.Signature:
- return new SignaturePacket(objStream);
- case PacketTag.SymmetricKeyEncryptedSessionKey:
- return new SymmetricKeyEncSessionPacket(objStream);
- case PacketTag.OnePassSignature:
- return new OnePassSignaturePacket(objStream);
- case PacketTag.SecretKey:
- return new SecretKeyPacket(objStream);
- case PacketTag.PublicKey:
- return new PublicKeyPacket(objStream);
- case PacketTag.SecretSubkey:
- return new SecretSubkeyPacket(objStream);
- case PacketTag.CompressedData:
- return new CompressedDataPacket(objStream);
- case PacketTag.SymmetricKeyEncrypted:
- return new SymmetricEncDataPacket(objStream);
- case PacketTag.Marker:
- return new MarkerPacket(objStream);
- case PacketTag.LiteralData:
- return new LiteralDataPacket(objStream);
- case PacketTag.Trust:
- return new TrustPacket(objStream);
- case PacketTag.UserId:
- return new UserIdPacket(objStream);
- case PacketTag.UserAttribute:
- return new UserAttributePacket(objStream);
- case PacketTag.PublicSubkey:
- return new PublicSubkeyPacket(objStream);
- case PacketTag.SymmetricEncryptedIntegrityProtected:
- return new SymmetricEncIntegrityPacket(objStream);
- case PacketTag.ModificationDetectionCode:
- return new ModDetectionCodePacket(objStream);
- case PacketTag.Experimental1:
- case PacketTag.Experimental2:
- case PacketTag.Experimental3:
- case PacketTag.Experimental4:
- return new ExperimentalPacket(tag, objStream);
- default:
- throw new IOException("unknown packet type encountered: " + tag);
+ case PacketTag.Reserved:
+ return new InputStreamPacket(objStream, PacketTag.Reserved);
+ case PacketTag.PublicKeyEncryptedSession:
+ return new PublicKeyEncSessionPacket(objStream);
+ case PacketTag.Signature:
+ return new SignaturePacket(objStream);
+ case PacketTag.SymmetricKeyEncryptedSessionKey:
+ return new SymmetricKeyEncSessionPacket(objStream);
+ case PacketTag.OnePassSignature:
+ return new OnePassSignaturePacket(objStream);
+ case PacketTag.SecretKey:
+ return new SecretKeyPacket(objStream);
+ case PacketTag.PublicKey:
+ return new PublicKeyPacket(objStream);
+ case PacketTag.SecretSubkey:
+ return new SecretSubkeyPacket(objStream);
+ case PacketTag.CompressedData:
+ return new CompressedDataPacket(objStream);
+ case PacketTag.SymmetricKeyEncrypted:
+ return new SymmetricEncDataPacket(objStream);
+ case PacketTag.Marker:
+ return new MarkerPacket(objStream);
+ case PacketTag.LiteralData:
+ return new LiteralDataPacket(objStream);
+ case PacketTag.Trust:
+ return new TrustPacket(objStream);
+ case PacketTag.UserId:
+ return new UserIdPacket(objStream);
+ case PacketTag.UserAttribute:
+ return new UserAttributePacket(objStream);
+ case PacketTag.PublicSubkey:
+ return new PublicSubkeyPacket(objStream);
+ case PacketTag.SymmetricEncryptedIntegrityProtected:
+ return new SymmetricEncIntegrityPacket(objStream);
+ case PacketTag.ModificationDetectionCode:
+ return new ModDetectionCodePacket(objStream);
+ case PacketTag.ReservedAeadEncryptedData:
+ return new AeadEncDataPacket(objStream);
+ case PacketTag.Padding:
+ return new PaddingPacket(objStream);
+ case PacketTag.Experimental1:
+ case PacketTag.Experimental2:
+ case PacketTag.Experimental3:
+ case PacketTag.Experimental4:
+ return new ExperimentalPacket(tag, objStream);
+ default:
+ throw new IOException("unknown packet type encountered: " + tag);
}
}
public PacketTag SkipMarkerPackets()
+ {
+ return SkipMarkerAndPaddingPackets();
+ }
+
+ public PacketTag SkipMarkerAndPaddingPackets()
{
PacketTag tag;
- while ((tag = NextPacketTag()) == PacketTag.Marker)
+ while ((tag = NextPacketTag()) == PacketTag.Marker || tag == PacketTag.Padding)
{
ReadPacket();
}
@@ -243,7 +252,7 @@ protected override void Dispose(bool disposing)
private class PartialInputStream
: BaseInputStream
{
- private BcpgInputStream m_in;
+ private readonly BcpgInputStream m_in;
private bool partial;
private int dataLength;
diff --git a/crypto/src/bcpg/CompressedDataPacket.cs b/crypto/src/bcpg/CompressedDataPacket.cs
index b066621d83..7f72066a1e 100644
--- a/crypto/src/bcpg/CompressedDataPacket.cs
+++ b/crypto/src/bcpg/CompressedDataPacket.cs
@@ -6,8 +6,9 @@ public class CompressedDataPacket
{
private readonly CompressionAlgorithmTag m_algorithm;
- internal CompressedDataPacket(BcpgInputStream bcpgIn)
- : base(bcpgIn)
+ internal CompressedDataPacket(
+ BcpgInputStream bcpgIn)
+ : base(bcpgIn, PacketTag.CompressedData)
{
m_algorithm = (CompressionAlgorithmTag)bcpgIn.RequireByte();
}
diff --git a/crypto/src/bcpg/ContainedPacket.cs b/crypto/src/bcpg/ContainedPacket.cs
index ca39fd985c..812e36aa66 100644
--- a/crypto/src/bcpg/ContainedPacket.cs
+++ b/crypto/src/bcpg/ContainedPacket.cs
@@ -1,4 +1,3 @@
-using System;
using System.IO;
namespace Org.BouncyCastle.Bcpg
@@ -7,6 +6,11 @@ namespace Org.BouncyCastle.Bcpg
public abstract class ContainedPacket
: Packet
{
+ protected ContainedPacket(PacketTag packetTag)
+ : base(packetTag)
+ {
+ }
+
public byte[] GetEncoded()
{
MemoryStream bOut = new MemoryStream();
diff --git a/crypto/src/bcpg/Ed25519PublicBCPGKey.cs b/crypto/src/bcpg/Ed25519PublicBCPGKey.cs
new file mode 100644
index 0000000000..8c23d82180
--- /dev/null
+++ b/crypto/src/bcpg/Ed25519PublicBCPGKey.cs
@@ -0,0 +1,19 @@
+namespace Org.BouncyCastle.Bcpg
+{
+ public sealed class Ed25519PublicBcpgKey
+ : OctetArrayBcpgKey
+ {
+ // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-part-for-ed2
+ public const int length = 32;
+
+ public Ed25519PublicBcpgKey(BcpgInputStream bcpgIn)
+ : base(length, bcpgIn)
+ {
+ }
+
+ public Ed25519PublicBcpgKey(byte[] key)
+ :base(length, key)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/crypto/src/bcpg/Ed25519SecretBCPGKey.cs b/crypto/src/bcpg/Ed25519SecretBCPGKey.cs
new file mode 100644
index 0000000000..67e5497387
--- /dev/null
+++ b/crypto/src/bcpg/Ed25519SecretBCPGKey.cs
@@ -0,0 +1,19 @@
+namespace Org.BouncyCastle.Bcpg
+{
+ public sealed class Ed25519SecretBcpgKey
+ : OctetArrayBcpgKey
+ {
+ // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-part-for-ed2
+ public const int length = 32;
+
+ public Ed25519SecretBcpgKey(BcpgInputStream bcpgIn)
+ : base(length, bcpgIn)
+ {
+ }
+
+ public Ed25519SecretBcpgKey(byte[] key)
+ : base(length, key)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/crypto/src/bcpg/Ed448PublicBCPGKey.cs b/crypto/src/bcpg/Ed448PublicBCPGKey.cs
new file mode 100644
index 0000000000..102fbf7a5f
--- /dev/null
+++ b/crypto/src/bcpg/Ed448PublicBCPGKey.cs
@@ -0,0 +1,19 @@
+namespace Org.BouncyCastle.Bcpg
+{
+ public sealed class Ed448PublicBcpgKey
+ : OctetArrayBcpgKey
+ {
+ // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-part-for-ed4
+ public const int length = 57;
+
+ public Ed448PublicBcpgKey(BcpgInputStream bcpgIn)
+ : base(length, bcpgIn)
+ {
+ }
+
+ public Ed448PublicBcpgKey(byte[] key)
+ : base(length, key)
+ {
+ }
+ }
+}
diff --git a/crypto/src/bcpg/Ed448SecretBCPGKey.cs b/crypto/src/bcpg/Ed448SecretBCPGKey.cs
new file mode 100644
index 0000000000..489692cf3f
--- /dev/null
+++ b/crypto/src/bcpg/Ed448SecretBCPGKey.cs
@@ -0,0 +1,19 @@
+namespace Org.BouncyCastle.Bcpg
+{
+ public sealed class Ed448SecretBcpgKey
+ : OctetArrayBcpgKey
+ {
+ // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-part-for-ed4
+ public const int length = 57;
+
+ public Ed448SecretBcpgKey(BcpgInputStream bcpgIn)
+ : base(length, bcpgIn)
+ {
+ }
+
+ public Ed448SecretBcpgKey(byte[] key)
+ : base(length, key)
+ {
+ }
+ }
+}
diff --git a/crypto/src/bcpg/ExperimentalPacket.cs b/crypto/src/bcpg/ExperimentalPacket.cs
index 3d413db099..dea370b599 100644
--- a/crypto/src/bcpg/ExperimentalPacket.cs
+++ b/crypto/src/bcpg/ExperimentalPacket.cs
@@ -7,22 +7,19 @@ namespace Org.BouncyCastle.Bcpg
public class ExperimentalPacket
: ContainedPacket
{
- private readonly PacketTag m_tag;
private readonly byte[] m_contents;
internal ExperimentalPacket(PacketTag tag, BcpgInputStream bcpgIn)
+ :base(tag)
{
- m_tag = tag;
m_contents = bcpgIn.ReadAll();
}
- public PacketTag Tag => m_tag;
-
public byte[] GetContents() => (byte[])m_contents.Clone();
public override void Encode(BcpgOutputStream bcpgOut)
{
- bcpgOut.WritePacket(m_tag, m_contents);
+ bcpgOut.WritePacket(Tag, m_contents);
}
}
}
diff --git a/crypto/src/bcpg/InputStreamPacket.cs b/crypto/src/bcpg/InputStreamPacket.cs
index d4946ebf26..97d61e5282 100644
--- a/crypto/src/bcpg/InputStreamPacket.cs
+++ b/crypto/src/bcpg/InputStreamPacket.cs
@@ -5,7 +5,16 @@ public class InputStreamPacket
{
private readonly BcpgInputStream bcpgIn;
- public InputStreamPacket(BcpgInputStream bcpgIn)
+ // for API backward compatibility
+ // it's unlikely this is being used, but just in case we'll mark
+ // unknown inputs as reserved.
+ public InputStreamPacket(BcpgInputStream bcpgIn)
+ : this(bcpgIn, PacketTag.Reserved)
+ {
+ }
+
+ public InputStreamPacket(BcpgInputStream bcpgIn, PacketTag packetTag)
+ :base(packetTag)
{
this.bcpgIn = bcpgIn;
}
diff --git a/crypto/src/bcpg/LiteralDataPacket.cs b/crypto/src/bcpg/LiteralDataPacket.cs
index 0b0aab6716..7e44420468 100644
--- a/crypto/src/bcpg/LiteralDataPacket.cs
+++ b/crypto/src/bcpg/LiteralDataPacket.cs
@@ -10,8 +10,9 @@ public class LiteralDataPacket
private readonly byte[] m_fileName;
private readonly long m_modDate;
- internal LiteralDataPacket(BcpgInputStream bcpgIn)
- : base(bcpgIn)
+ internal LiteralDataPacket(
+ BcpgInputStream bcpgIn)
+ : base(bcpgIn, PacketTag.LiteralData)
{
m_format = bcpgIn.RequireByte();
diff --git a/crypto/src/bcpg/MarkerPacket.cs b/crypto/src/bcpg/MarkerPacket.cs
index f2c4507935..b5a8e9639f 100644
--- a/crypto/src/bcpg/MarkerPacket.cs
+++ b/crypto/src/bcpg/MarkerPacket.cs
@@ -10,6 +10,7 @@ public class MarkerPacket
private readonly byte[] marker = { (byte)0x50, (byte)0x47, (byte)0x50 };
public MarkerPacket(BcpgInputStream bcpgIn)
+ :base(PacketTag.Marker)
{
bcpgIn.ReadFully(marker);
}
diff --git a/crypto/src/bcpg/ModDetectionCodePacket.cs b/crypto/src/bcpg/ModDetectionCodePacket.cs
index ae8283aef5..56cd8d7cad 100644
--- a/crypto/src/bcpg/ModDetectionCodePacket.cs
+++ b/crypto/src/bcpg/ModDetectionCodePacket.cs
@@ -11,9 +11,10 @@ public class ModDetectionCodePacket
internal ModDetectionCodePacket(
BcpgInputStream bcpgIn)
+ :base(PacketTag.ModificationDetectionCode)
{
if (bcpgIn == null)
- throw new ArgumentNullException("bcpgIn");
+ throw new ArgumentNullException(nameof(bcpgIn));
this.digest = new byte[20];
bcpgIn.ReadFully(this.digest);
@@ -21,9 +22,10 @@ internal ModDetectionCodePacket(
public ModDetectionCodePacket(
byte[] digest)
+ : base(PacketTag.ModificationDetectionCode)
{
if (digest == null)
- throw new ArgumentNullException("digest");
+ throw new ArgumentNullException(nameof(digest));
this.digest = (byte[]) digest.Clone();
}
diff --git a/crypto/src/bcpg/OctetArrayBCPGKey.cs b/crypto/src/bcpg/OctetArrayBCPGKey.cs
new file mode 100644
index 0000000000..73d0d34a4f
--- /dev/null
+++ b/crypto/src/bcpg/OctetArrayBCPGKey.cs
@@ -0,0 +1,47 @@
+using Org.BouncyCastle.Utilities;
+using System;
+
+namespace Org.BouncyCastle.Bcpg
+{
+ ///
+ /// Public/Secret BcpgKey which is encoded as an array of octets rather than an MPI
+ ///
+ public abstract class OctetArrayBcpgKey
+ : BcpgObject, IBcpgKey
+ {
+ private readonly byte[] key;
+
+ protected OctetArrayBcpgKey(int length, BcpgInputStream bcpgIn)
+ {
+ key = new byte[length];
+ bcpgIn.ReadFully(key);
+ }
+
+ protected OctetArrayBcpgKey(int length, byte[] key)
+ {
+ if (key.Length != length)
+ {
+ throw new ArgumentException("unexpected key encoding length: expected " + length + " bytes, got " + key.Length);
+ }
+
+ this.key = Arrays.Clone(key);
+ }
+
+ ///
+ public string Format
+ {
+ get { return "PGP"; }
+ }
+
+ ///
+ public override void Encode(BcpgOutputStream bcpgOut)
+ {
+ bcpgOut.Write(key);
+ }
+
+ public byte[] GetKey()
+ {
+ return Arrays.Clone(key);
+ }
+ }
+}
\ No newline at end of file
diff --git a/crypto/src/bcpg/OnePassSignaturePacket.cs b/crypto/src/bcpg/OnePassSignaturePacket.cs
index 37d0b45864..3898117e3a 100644
--- a/crypto/src/bcpg/OnePassSignaturePacket.cs
+++ b/crypto/src/bcpg/OnePassSignaturePacket.cs
@@ -1,3 +1,7 @@
+using Org.BouncyCastle.Bcpg.OpenPgp;
+using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Utilities;
+using System;
using System.IO;
namespace Org.BouncyCastle.Bcpg
@@ -6,40 +10,129 @@ namespace Org.BouncyCastle.Bcpg
public class OnePassSignaturePacket
: ContainedPacket
{
- private int version;
- private int sigType;
- private HashAlgorithmTag hashAlgorithm;
- private PublicKeyAlgorithmTag keyAlgorithm;
- private long keyId;
- private int nested;
-
- internal OnePassSignaturePacket(
+ public const int Version3 = 3;
+ public const int Version6 = 6;
+
+ private readonly int version;
+ private readonly int sigType;
+ private readonly HashAlgorithmTag hashAlgorithm;
+ private readonly PublicKeyAlgorithmTag keyAlgorithm;
+ private readonly long keyId;
+ private readonly int nested;
+
+ // fields for V6
+ private readonly byte[] salt;
+ private readonly byte[] fingerprint;
+
+ private void EnforceConstraints()
+ {
+ int expectedSaltSize = PgpUtilities.GetSaltSize(hashAlgorithm);
+
+ if (version == Version6 && salt.Length != expectedSaltSize)
+ {
+ // https://www.rfc-editor.org/rfc/rfc9580#name-one-pass-signature-packet-t
+ // The salt size MUST match the value defined for the hash algorithm as specified in Table 23
+ // https://www.rfc-editor.org/rfc/rfc9580#hash-algorithms-registry
+ throw new IOException($"invalid salt size for v6 signature: expected {expectedSaltSize} got {salt.Length}");
+ }
+ }
+
+ internal OnePassSignaturePacket(
BcpgInputStream bcpgIn)
+ :base(PacketTag.OnePassSignature)
{
version = bcpgIn.RequireByte();
+ if (version != Version3 && version != Version6)
+ {
+ throw new UnsupportedPacketVersionException($"unsupported OpenPGP One Pass Signature packet version: {version}");
+ }
+
sigType = bcpgIn.RequireByte();
- hashAlgorithm = (HashAlgorithmTag)bcpgIn.RequireByte();
- keyAlgorithm = (PublicKeyAlgorithmTag)bcpgIn.RequireByte();
- keyId = (long)StreamUtilities.RequireUInt64BE(bcpgIn);
+ hashAlgorithm = (HashAlgorithmTag) bcpgIn.RequireByte();
+ keyAlgorithm = (PublicKeyAlgorithmTag) bcpgIn.RequireByte();
+
+ if (version == Version3)
+ {
+ keyId = (long)StreamUtilities.RequireUInt64BE(bcpgIn);
+ }
+ else
+ {
+ //Version 6
+ int saltSize = bcpgIn.ReadByte();
+ salt = new byte[saltSize];
+ bcpgIn.ReadFully(salt);
+
+ fingerprint = new byte[32];
+ bcpgIn.ReadFully(fingerprint);
+ keyId = (long)Pack.BE_To_UInt64(fingerprint);
+ }
+
nested = bcpgIn.RequireByte();
- }
+ EnforceConstraints();
+ }
+
+ ///
+ /// Create a Version 3 OPS Packet
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
public OnePassSignaturePacket(
int sigType,
HashAlgorithmTag hashAlgorithm,
PublicKeyAlgorithmTag keyAlgorithm,
long keyId,
bool isNested)
- {
- this.version = 3;
+ : base(PacketTag.OnePassSignature)
+ {
+ this.version = Version3;
this.sigType = sigType;
this.hashAlgorithm = hashAlgorithm;
this.keyAlgorithm = keyAlgorithm;
this.keyId = keyId;
this.nested = (isNested) ? 0 : 1;
+
+ EnforceConstraints();
+ }
+
+ ///
+ /// Create a Version 6 OPS Packet
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public OnePassSignaturePacket(
+ int sigType,
+ HashAlgorithmTag hashAlgorithm,
+ PublicKeyAlgorithmTag keyAlgorithm,
+ byte[] salt,
+ byte[] fingerprint,
+ bool isNested)
+ : base(PacketTag.OnePassSignature)
+ {
+ this.version = Version6;
+ this.sigType = sigType;
+ this.hashAlgorithm = hashAlgorithm;
+ this.keyAlgorithm = keyAlgorithm;
+ this.salt = Arrays.Clone(salt);
+ this.fingerprint = Arrays.Clone(fingerprint);
+ this.keyId = (long)Pack.BE_To_UInt64(fingerprint);
+ this.nested = (isNested) ? 0 : 1;
+
+ EnforceConstraints();
+ }
+
+ public int Version {
+ get { return version; }
}
- public int SignatureType
+ public int SignatureType
{
get { return sigType; }
}
@@ -61,17 +154,40 @@ public long KeyId
get { return keyId; }
}
- public override void Encode(BcpgOutputStream bcpgOut)
+ public byte[] GetFingerprint()
+ {
+ return Arrays.Clone(fingerprint);
+ }
+
+ public byte[] GetSignatureSalt()
+ {
+ return Arrays.Clone(salt);
+ }
+
+ public override void Encode(BcpgOutputStream bcpgOut)
{
- MemoryStream bOut = new MemoryStream();
- using (var pOut = new BcpgOutputStream(bOut))
- {
- pOut.Write((byte)version, (byte)sigType, (byte)hashAlgorithm, (byte)keyAlgorithm);
- pOut.WriteLong(keyId);
- pOut.WriteByte((byte)nested);
- }
+ using (MemoryStream bOut = new MemoryStream())
+ {
+ using (var pOut = new BcpgOutputStream(bOut))
+ {
+ pOut.Write((byte)version, (byte)sigType, (byte)hashAlgorithm, (byte)keyAlgorithm);
+
+ if (version == Version3)
+ {
+ pOut.WriteLong(keyId);
+ }
+ else
+ {
+ // V6
+ pOut.WriteByte((byte)salt.Length);
+ pOut.Write(salt);
+ pOut.Write(fingerprint);
+ }
+ pOut.WriteByte((byte)nested);
+ }
- bcpgOut.WritePacket(PacketTag.OnePassSignature, bOut.ToArray());
+ bcpgOut.WritePacket(PacketTag.OnePassSignature, bOut.ToArray());
+ }
}
}
}
diff --git a/crypto/src/bcpg/Packet.cs b/crypto/src/bcpg/Packet.cs
index 964102a715..ffca0bf939 100644
--- a/crypto/src/bcpg/Packet.cs
+++ b/crypto/src/bcpg/Packet.cs
@@ -1,8 +1,36 @@
+using System;
+
namespace Org.BouncyCastle.Bcpg
{
// TODO Add packet tag at this level (see bc-java), and IsCritical property
public class Packet
//: PacketTag
{
+ private readonly PacketTag packetTag;
+
+ // for API backward compatibility
+ // it's unlikely this is being used, but just in case we'll mark
+ // unknown inputs as reserved.
+ public Packet()
+ : this(PacketTag.Reserved)
+ {
+ }
+
+ public Packet(PacketTag packetTag)
+ {
+ this.packetTag = packetTag;
+ }
+
+ public PacketTag Tag => packetTag;
+
+ ///
+ /// Returns whether the packet is to be considered critical for v6 implementations.
+ /// * Packets with tags less or equal to 39 are critical.
+ /// * Tags 40 to 59 are reserved for unassigned, non-critical packets.
+ /// * Tags 60 to 63 are non-critical private or experimental packets.
+ ///
+ ///
+ public bool IsCritical => (int)Tag <= 39;
}
-}
+
+}
\ No newline at end of file
diff --git a/crypto/src/bcpg/PacketTags.cs b/crypto/src/bcpg/PacketTags.cs
index 5a53d4e957..43590e5f9c 100644
--- a/crypto/src/bcpg/PacketTags.cs
+++ b/crypto/src/bcpg/PacketTags.cs
@@ -19,8 +19,12 @@ public enum PacketTag
UserId = 13, // User ID Packet
PublicSubkey = 14, // Public Subkey Packet
UserAttribute = 17, // User attribute
- SymmetricEncryptedIntegrityProtected = 18, // Symmetric encrypted, integrity protected
- ModificationDetectionCode = 19, // Modification detection code
+ SymmetricEncryptedIntegrityProtected = 18, // Symmetric encrypted, integrity protected
+
+ ModificationDetectionCode = 19, // Reserved (formerly Modification Detection Code Packet)
+ ReservedAeadEncryptedData = 20, // Reserved (defined as AEAD Encrypted Data Packet in retired draft [draft-koch-openpgp-rfc4880bis])
+
+ Padding = 21, // Padding Packet (https://www.rfc-editor.org/rfc/rfc9580#padding-packet)
Experimental1 = 60, // Private or Experimental Values
Experimental2 = 61,
diff --git a/crypto/src/bcpg/PaddingPacket.cs b/crypto/src/bcpg/PaddingPacket.cs
new file mode 100644
index 0000000000..d91ed9b287
--- /dev/null
+++ b/crypto/src/bcpg/PaddingPacket.cs
@@ -0,0 +1,52 @@
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Bcpg
+{
+ public class PaddingPacket
+ : ContainedPacket
+ {
+ private readonly byte[] padding;
+
+ public PaddingPacket(BcpgInputStream bcpgIn)
+ :base(PacketTag.Padding)
+ {
+ padding = bcpgIn.ReadAll();
+ }
+
+ public PaddingPacket(int length, BcpgInputStream bcpgIn)
+ : base(PacketTag.Padding)
+ {
+ padding = new byte[length];
+ bcpgIn.ReadFully(padding);
+ }
+
+ public PaddingPacket(byte[] padding)
+ : base(PacketTag.Padding)
+ {
+ this.padding = Arrays.Clone(padding);
+ }
+
+ public PaddingPacket(int length, SecureRandom random)
+ : this(RandomBytes(length, random))
+ {
+ }
+
+ private static byte[] RandomBytes(int length, SecureRandom random)
+ {
+ byte[] bytes = new byte[length];
+ random.NextBytes(bytes);
+ return bytes;
+ }
+
+ public byte[] GetPadding()
+ {
+ return Arrays.Clone(padding);
+ }
+
+ public override void Encode(BcpgOutputStream bcpgOut)
+ {
+ bcpgOut.WritePacket(PacketTag.Padding, padding);
+ }
+ }
+}
\ No newline at end of file
diff --git a/crypto/src/bcpg/PublicKeyAlgorithmTags.cs b/crypto/src/bcpg/PublicKeyAlgorithmTags.cs
index a309b65ae4..07ac3c2602 100644
--- a/crypto/src/bcpg/PublicKeyAlgorithmTags.cs
+++ b/crypto/src/bcpg/PublicKeyAlgorithmTags.cs
@@ -12,7 +12,8 @@ public enum PublicKeyAlgorithmTag
Dsa = 17, // DSA (Digital Signature Standard)
ECDH = 18, // Reserved for Elliptic Curve (actual algorithm name)
ECDsa = 19, // Reserved for ECDSA
- ElGamalGeneral = 20, // Elgamal (Encrypt or Sign)
+
+ ElGamalGeneral = 20, // Reserved (formerly Elgamal Encrypt or Sign)
DiffieHellman = 21, // Reserved for Diffie-Hellman (X9.42, as defined for IETF-S/MIME)
// TODO Mark obsolete once Ed25519, Ed448 available
@@ -20,6 +21,16 @@ public enum PublicKeyAlgorithmTag
EdDsa = 22, // EdDSA - (internet draft, but appearing in use)
EdDsa_Legacy = 22, // new name for old EdDSA tag.
+ // defined as Reserved by RFC 9580
+ AEDH = 23,
+ AEDSA = 24,
+
+ // https://www.rfc-editor.org/rfc/rfc9580
+ X25519 = 25,
+ X448 = 26,
+ Ed25519 = 27,
+ Ed448 = 28,
+
Experimental_1 = 100,
Experimental_2 = 101,
Experimental_3 = 102,
diff --git a/crypto/src/bcpg/PublicKeyEncSessionPacket.cs b/crypto/src/bcpg/PublicKeyEncSessionPacket.cs
index c56b2bd70e..c3edf00bfe 100644
--- a/crypto/src/bcpg/PublicKeyEncSessionPacket.cs
+++ b/crypto/src/bcpg/PublicKeyEncSessionPacket.cs
@@ -2,6 +2,8 @@
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.Utilities.IO;
+using Org.BouncyCastle.Crypto.Utilities;
+using System;
namespace Org.BouncyCastle.Bcpg
{
@@ -9,47 +11,131 @@ namespace Org.BouncyCastle.Bcpg
public class PublicKeyEncSessionPacket
: ContainedPacket //, PublicKeyAlgorithmTag
{
- private int version;
- private long keyId;
- private PublicKeyAlgorithmTag algorithm;
- private byte[][] data;
+ private readonly int version; // V3, V6
+ private readonly long keyId; // V3 only
+ private readonly PublicKeyAlgorithmTag algorithm; // V3, V6
+ private readonly byte[][] data; // V3, V6
+ private readonly int keyVersion; // V6 only
+ private readonly byte[] keyFingerprint; // V6 only
- internal PublicKeyEncSessionPacket(
+ ///
+ /// Version 3 PKESK packet.
+ ///
+ ///
+ public const int Version3 = 3;
+
+ ///
+ /// Version 6 PKESK packet.
+ ///
+ ///
+ public const int Version6 = 6;
+
+ internal PublicKeyEncSessionPacket(
BcpgInputStream bcpgIn)
+ :base(PacketTag.PublicKeyEncryptedSession)
{
version = bcpgIn.RequireByte();
- keyId = (long)StreamUtilities.RequireUInt64BE(bcpgIn);
- algorithm = (PublicKeyAlgorithmTag)bcpgIn.RequireByte();
+ switch (version)
+ {
+ case Version3:
+ keyId = (long)StreamUtilities.RequireUInt64BE(bcpgIn);
+ break;
+ case Version6:
+ int keyInfoLength = bcpgIn.RequireByte();
+ if (keyInfoLength == 0)
+ {
+ // anonymous recipient
+ keyVersion = 0;
+ keyFingerprint = Array.Empty();
+ keyId = 0;
+ }
+ else
+ {
+ keyVersion = bcpgIn.RequireByte();
+ keyFingerprint = new byte[keyInfoLength - 1];
+ bcpgIn.ReadFully(keyFingerprint);
+
+ switch (keyVersion)
+ {
+ case PublicKeyPacket.Version4:
+ keyId = (long)Pack.BE_To_UInt64(keyFingerprint, keyFingerprint.Length - 8);
+ break;
+ case PublicKeyPacket.Version5:
+ case PublicKeyPacket.Version6:
+ keyId = (long)Pack.BE_To_UInt64(keyFingerprint);
+ break;
+ default:
+ throw new InvalidOperationException($"unsupported OpenPGP key packet version: {keyVersion}");
+ }
+ }
+ break;
+ default:
+ throw new UnsupportedPacketVersionException($"Unsupported PGP public key encrypted session key packet version encountered: {version}");
+ }
+
+ algorithm = (PublicKeyAlgorithmTag) bcpgIn.RequireByte();
- switch ((PublicKeyAlgorithmTag) algorithm)
+ switch (algorithm)
{
- case PublicKeyAlgorithmTag.RsaEncrypt:
- case PublicKeyAlgorithmTag.RsaGeneral:
- data = new byte[][]{ new MPInteger(bcpgIn).GetEncoded() };
- break;
- case PublicKeyAlgorithmTag.ElGamalEncrypt:
- case PublicKeyAlgorithmTag.ElGamalGeneral:
- MPInteger p = new MPInteger(bcpgIn);
- MPInteger g = new MPInteger(bcpgIn);
- data = new byte[][]{
- p.GetEncoded(),
- g.GetEncoded(),
- };
- break;
- case PublicKeyAlgorithmTag.ECDH:
- data = new byte[][]{ Streams.ReadAll(bcpgIn) };
- break;
- default:
- throw new IOException("unknown PGP public key algorithm encountered");
+ case PublicKeyAlgorithmTag.RsaEncrypt:
+ case PublicKeyAlgorithmTag.RsaGeneral:
+ data = new byte[][]{ new MPInteger(bcpgIn).GetEncoded() };
+ break;
+ case PublicKeyAlgorithmTag.ElGamalEncrypt:
+ case PublicKeyAlgorithmTag.ElGamalGeneral:
+ MPInteger p = new MPInteger(bcpgIn);
+ MPInteger g = new MPInteger(bcpgIn);
+ data = new byte[][]{
+ p.GetEncoded(),
+ g.GetEncoded(),
+ };
+ break;
+ case PublicKeyAlgorithmTag.ECDH:
+ data = new byte[][]{ Streams.ReadAll(bcpgIn) };
+ break;
+ case PublicKeyAlgorithmTag.X25519:
+ case PublicKeyAlgorithmTag.X448:
+ // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-fields-for-
+ // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-fields-for-x
+
+ // 32 (for X25519) or 56 (for X448) octets representing an ephemeral public key.
+ int keylen = algorithm == PublicKeyAlgorithmTag.X25519 ? 32 : 56;
+ byte[] ephemeralPubKey = new byte[keylen];
+ bcpgIn.ReadFully(ephemeralPubKey);
+
+ // A one-octet size of the following fields.
+ int esklen = bcpgIn.ReadByte();
+
+ // The one-octet algorithm identifier, if it was passed (in the case of a v3 PKESK packet).
+ // The encrypted session key.
+ byte[] encryptedSessionKey = new byte[esklen];
+ bcpgIn.ReadFully(encryptedSessionKey);
+
+ data = new byte[][]{
+ ephemeralPubKey,
+ encryptedSessionKey,
+ };
+ break;
+
+ default:
+ throw new IOException("unknown PGP public key algorithm encountered");
}
}
+
+ ///
+ /// Create a new V3 PKESK packet.
+ ///
+ /// ID of the recipient key, 0 for anonymo
+ /// public key algorithm
+ /// session data
public PublicKeyEncSessionPacket(
long keyId,
PublicKeyAlgorithmTag algorithm,
byte[][] data)
- {
- this.version = 3;
+ : base(PacketTag.PublicKeyEncryptedSession)
+ {
+ this.version = Version3;
this.keyId = keyId;
this.algorithm = algorithm;
this.data = new byte[data.Length][];
@@ -59,6 +145,32 @@ public PublicKeyEncSessionPacket(
}
}
+ ///
+ /// Create a new V6 PKESK packet.
+ ///
+ /// version of the key
+ /// fingerprint of the key
+ /// public key algorith
+ /// session data
+ public PublicKeyEncSessionPacket(
+ int keyVersion,
+ byte[] keyFingerprint,
+ PublicKeyAlgorithmTag algorithm,
+ byte[][] data)
+ : base(PacketTag.PublicKeyEncryptedSession)
+ {
+ this.version = Version6;
+ this.keyVersion = keyVersion;
+ this.keyFingerprint = Arrays.Clone(keyFingerprint);
+ this.algorithm = algorithm;
+ this.data = new byte[data.Length][];
+
+ for (int i = 0; i < data.Length; i++)
+ {
+ this.data[i] = Arrays.Clone(data[i]);
+ }
+ }
+
public int Version
{
get { return version; }
@@ -69,7 +181,22 @@ public long KeyId
get { return keyId; }
}
- public PublicKeyAlgorithmTag Algorithm
+ public bool IsRecipientAnonymous
+ {
+ get { return keyId == 0; }
+ }
+
+ public byte[] GetKeyFingerprint()
+ {
+ return Arrays.Clone(keyFingerprint);
+ }
+
+ public int KeyVersion
+ {
+ get { return keyVersion; }
+ }
+
+ public PublicKeyAlgorithmTag Algorithm
{
get { return algorithm; }
}
@@ -81,20 +208,58 @@ public byte[][] GetEncSessionKey()
public override void Encode(BcpgOutputStream bcpgOut)
{
- MemoryStream bOut = new MemoryStream();
- using (var pOut = new BcpgOutputStream(bOut))
- {
- pOut.WriteByte((byte)version);
- pOut.WriteLong(keyId);
- pOut.WriteByte((byte)algorithm);
-
- for (int i = 0; i < data.Length; ++i)
- {
- pOut.Write(data[i]);
- }
- }
+ using (MemoryStream bOut = new MemoryStream())
+ {
+ using (var pOut = new BcpgOutputStream(bOut))
+ {
+ pOut.WriteByte((byte)version);
+
+ switch (version)
+ {
+ case Version3:
+ pOut.WriteLong(keyId);
+ break;
+
+ case Version6:
+ if (keyVersion == 0)
+ {
+ // anonymous recipient
+ pOut.WriteByte(0);
+ }
+ else
+ {
+ pOut.WriteByte((byte)(keyFingerprint.Length + 1));
+ pOut.WriteByte((byte)keyVersion);
+ pOut.Write(keyFingerprint);
+ }
+ break;
+ default:
+ throw new UnsupportedPacketVersionException($"Unsupported PGP public key encrypted session key packet version encountered: {version}");
+ }
- bcpgOut.WritePacket(PacketTag.PublicKeyEncryptedSession, bOut.ToArray());
+ pOut.WriteByte((byte)algorithm);
+
+ if (algorithm == PublicKeyAlgorithmTag.X25519 || algorithm == PublicKeyAlgorithmTag.X448)
+ {
+ // ephemeral public key.
+ pOut.Write(data[0]);
+ // One-octet size of the encrypted session key (prefixed by the one-octet
+ // algorithm identifier, in the case of a v3 PKESK packet).
+ pOut.WriteByte((byte)(data[1].Length));
+ // encrypted session key
+ pOut.Write(data[1]);
+ }
+ else
+ {
+ for (int i = 0; i < data.Length; ++i)
+ {
+ pOut.Write(data[i]);
+ }
+ }
+ }
+
+ bcpgOut.WritePacket(PacketTag.PublicKeyEncryptedSession, bOut.ToArray());
+ }
}
}
}
diff --git a/crypto/src/bcpg/PublicKeyPacket.cs b/crypto/src/bcpg/PublicKeyPacket.cs
index a0f1baee5b..6c6add125b 100644
--- a/crypto/src/bcpg/PublicKeyPacket.cs
+++ b/crypto/src/bcpg/PublicKeyPacket.cs
@@ -9,26 +9,49 @@ namespace Org.BouncyCastle.Bcpg
public class PublicKeyPacket
: ContainedPacket //, PublicKeyAlgorithmTag
{
- private int version;
- private long time;
- private int validDays;
- private PublicKeyAlgorithmTag algorithm;
- private IBcpgKey key;
-
- internal PublicKeyPacket(
- BcpgInputStream bcpgIn)
+ public const int Version2 = 2;
+ public const int Version3 = 3;
+ public const int Version4 = 4;
+ public const int Version5 = 5;
+ public const int Version6 = 6;
+
+ public const int DefaultVersion = Version4;
+
+ private readonly int version;
+ private readonly long time;
+ private readonly int validDays;
+ private readonly PublicKeyAlgorithmTag algorithm;
+ private readonly IBcpgKey key;
+
+ private readonly long v6KeyLen;
+
+ internal PublicKeyPacket(BcpgInputStream bcpgIn)
+ : this(bcpgIn, PacketTag.PublicKey)
+ {
+ }
+
+ protected PublicKeyPacket(
+ BcpgInputStream bcpgIn,
+ PacketTag tag)
+ : base(tag)
{
version = bcpgIn.RequireByte();
time = StreamUtilities.RequireUInt32BE(bcpgIn);
- if (version <= 3)
+ if (version <= Version3)
{
validDays = StreamUtilities.RequireUInt16BE(bcpgIn);
}
algorithm = (PublicKeyAlgorithmTag)bcpgIn.RequireByte();
+ if (version == Version5 || version == Version6)
+ {
+ v6KeyLen = ((uint)bcpgIn.ReadByte() << 24) | ((uint)bcpgIn.ReadByte() << 16)
+ | ((uint)bcpgIn.ReadByte() << 8) | (uint)bcpgIn.ReadByte();
+ }
+
switch (algorithm)
{
case PublicKeyAlgorithmTag.RsaEncrypt:
@@ -52,8 +75,49 @@ internal PublicKeyPacket(
case PublicKeyAlgorithmTag.EdDsa_Legacy:
key = new EdDsaPublicBcpgKey(bcpgIn);
break;
+ case PublicKeyAlgorithmTag.Ed25519:
+ key = new Ed25519PublicBcpgKey(bcpgIn);
+ break;
+ case PublicKeyAlgorithmTag.Ed448:
+ key = new Ed448PublicBcpgKey(bcpgIn);
+ break;
+ case PublicKeyAlgorithmTag.X25519:
+ key = new X25519PublicBcpgKey(bcpgIn);
+ break;
+ case PublicKeyAlgorithmTag.X448:
+ key = new X448PublicBcpgKey(bcpgIn);
+ break;
default:
- throw new IOException("unknown PGP public key algorithm encountered");
+ throw new IOException("unknown PGP public key algorithm encountered");
+ }
+ }
+
+
+ /// Construct a public key packet.
+ public PublicKeyPacket(
+ int version,
+ PublicKeyAlgorithmTag algorithm,
+ DateTime time,
+ IBcpgKey key)
+ : this(version, algorithm, time, key, PacketTag.PublicKey)
+ { }
+
+ protected PublicKeyPacket(
+ int version,
+ PublicKeyAlgorithmTag algorithm,
+ DateTime time,
+ IBcpgKey key,
+ PacketTag tag)
+ : base(tag)
+ {
+ this.version = version;
+ this.time = DateTimeUtilities.DateTimeToUnixMs(time) / 1000L;
+ this.algorithm = algorithm;
+ this.key = key;
+
+ if (version == Version5 || version == Version6)
+ {
+ v6KeyLen = ((BcpgObject)key).GetEncoded().Length;
}
}
@@ -62,11 +126,8 @@ public PublicKeyPacket(
PublicKeyAlgorithmTag algorithm,
DateTime time,
IBcpgKey key)
+ :this(DefaultVersion, algorithm, time, key)
{
- this.version = 4;
- this.time = DateTimeUtilities.DateTimeToUnixMs(time) / 1000L;
- this.algorithm = algorithm;
- this.key = key;
}
public virtual int Version
@@ -102,12 +163,18 @@ public virtual byte[] GetEncodedContents()
pOut.WriteByte((byte)version);
pOut.WriteInt((int)time);
- if (version <= 3)
+ if (version <= Version3)
{
pOut.WriteShort((short)validDays);
}
pOut.WriteByte((byte)algorithm);
+
+ if (version == Version5 || version == Version6)
+ {
+ pOut.WriteInt((int)v6KeyLen);
+ }
+
pOut.WriteObject((BcpgObject)key);
}
return bOut.ToArray();
diff --git a/crypto/src/bcpg/PublicSubkeyPacket.cs b/crypto/src/bcpg/PublicSubkeyPacket.cs
index 0e1065b720..0e70790a17 100644
--- a/crypto/src/bcpg/PublicSubkeyPacket.cs
+++ b/crypto/src/bcpg/PublicSubkeyPacket.cs
@@ -13,12 +13,22 @@ internal PublicSubkeyPacket(
{
}
- /// Construct a version 4 public subkey packet.
+ /// Construct a public subkey packet.
+ public PublicSubkeyPacket(
+ int version,
+ PublicKeyAlgorithmTag algorithm,
+ DateTime time,
+ IBcpgKey key)
+ : base(version, algorithm, time, key, PacketTag.PublicSubkey)
+ {
+ }
+
+ /// Construct a version 4 public subkey packet.
public PublicSubkeyPacket(
PublicKeyAlgorithmTag algorithm,
DateTime time,
IBcpgKey key)
- : base(algorithm, time, key)
+ : base(DefaultVersion, algorithm, time, key, PacketTag.PublicSubkey)
{
}
diff --git a/crypto/src/bcpg/S2k.cs b/crypto/src/bcpg/S2k.cs
index d68dc38282..28cb12c8b5 100644
--- a/crypto/src/bcpg/S2k.cs
+++ b/crypto/src/bcpg/S2k.cs
@@ -1,6 +1,7 @@
using System;
using System.IO;
-
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.Utilities.IO;
@@ -15,75 +16,123 @@ public class S2k
public const int Simple = 0;
public const int Salted = 1;
public const int SaltedAndIterated = 3;
+ public const int Argon2 = 4;
public const int GnuDummyS2K = 101;
public const int GnuProtectionModeNoPrivateKey = 1;
public const int GnuProtectionModeDivertToCard = 2;
- internal int type;
- internal HashAlgorithmTag algorithm;
- internal byte[] iv;
- internal int itCount = -1;
- internal int protectionMode = -1;
+ private readonly int type;
+ private readonly HashAlgorithmTag algorithm;
+ private readonly byte[] iv;
+
+ private readonly int itCount = -1;
+ private readonly int protectionMode = -1;
+
+ // params for Argon2
+ private readonly int passes;
+ private readonly int parallelism;
+ private readonly int memorySizeExponent;
- internal S2k(Stream inStr)
+ internal S2k(
+ Stream inStr)
{
type = StreamUtilities.RequireByte(inStr);
- algorithm = (HashAlgorithmTag)StreamUtilities.RequireByte(inStr);
- //
- // if this happens we have a dummy-S2k packet.
- //
- if (type != GnuDummyS2K)
+ switch (type)
{
- if (type != 0)
- {
- iv = new byte[8];
+ case Simple:
+ algorithm = (HashAlgorithmTag)StreamUtilities.RequireByte(inStr);
+ break;
+
+ case Salted:
+ algorithm = (HashAlgorithmTag)StreamUtilities.RequireByte(inStr);
+ iv = new byte[8];
StreamUtilities.RequireBytes(inStr, iv);
+ break;
- if (type == 3)
- {
- itCount = inStr.ReadByte();
- }
- }
- }
- else
- {
- //inStr.ReadByte(); // G
- //inStr.ReadByte(); // N
- //inStr.ReadByte(); // U
- //protectionMode = inStr.ReadByte(); // protection mode
- uint GNU_ = StreamUtilities.RequireUInt32BE(inStr);
- protectionMode = (byte)GNU_;
+ case SaltedAndIterated:
+ algorithm = (HashAlgorithmTag)StreamUtilities.RequireByte(inStr);
+ iv = new byte[8];
+ StreamUtilities.RequireBytes(inStr, iv);
+ itCount = StreamUtilities.RequireByte(inStr);
+ break;
+
+ case Argon2:
+ iv = new byte[16];
+ StreamUtilities.RequireBytes(inStr, iv);
+ passes = StreamUtilities.RequireByte(inStr);
+ parallelism = StreamUtilities.RequireByte(inStr);
+ memorySizeExponent = StreamUtilities.RequireByte(inStr);
+ break;
+
+ case GnuDummyS2K:
+ algorithm = (HashAlgorithmTag)StreamUtilities.RequireByte(inStr);
+ //inStr.ReadByte(); // G
+ //inStr.ReadByte(); // N
+ //inStr.ReadByte(); // U
+ //protectionMode = inStr.ReadByte(); // protection mode
+ uint GNU_ = StreamUtilities.RequireUInt32BE(inStr);
+ protectionMode = (byte)GNU_;
+ break;
+
+ default:
+ throw new UnsupportedPacketVersionException($"Invalid S2K type: {type}");
}
+
}
+ /// Constructs a specifier for a simple S2K generation
+ /// the digest algorithm to use.
public S2k(
HashAlgorithmTag algorithm)
{
- this.type = 0;
+ this.type = Simple;
this.algorithm = algorithm;
}
+ /// Constructs a specifier for a salted S2K generation
+ /// the digest algorithm to use.
+ /// the salt to apply to input to the key generation
public S2k(
HashAlgorithmTag algorithm,
byte[] iv)
{
- this.type = 1;
+ this.type = Salted;
this.algorithm = algorithm;
- this.iv = iv;
+ this.iv = Arrays.Clone(iv);
}
+ /// Constructs a specifier for a salted and iterated S2K generation
+ /// the digest algorithm to iterate.
+ /// the salt to apply to input to the key generation
+ /// the single byte iteration count specifier
public S2k(
HashAlgorithmTag algorithm,
byte[] iv,
int itCount)
{
- this.type = 3;
+ this.type = SaltedAndIterated;
this.algorithm = algorithm;
- this.iv = iv;
+ this.iv = Arrays.Clone(iv);
this.itCount = itCount;
}
+ /// Constructs a specifier for an S2K method using Argon2
+ public S2k(byte[] salt, int passes, int parallelism, int memorySizeExponent)
+ {
+ this.type = Argon2;
+ this.iv = Arrays.Clone(salt);
+ this.passes = passes;
+ this.parallelism = parallelism;
+ this.memorySizeExponent = memorySizeExponent;
+ }
+
+ /// Constructs a specifier for an S2K method using Argon2
+ public S2k(Argon2Parameters argon2Params)
+ :this(argon2Params.Salt, argon2Params.Passes, argon2Params.Parallelism, argon2Params.MemSizeExp)
+ {
+ }
+
public virtual int Type
{
get { return type; }
@@ -113,30 +162,183 @@ public virtual int ProtectionMode
get { return protectionMode; }
}
+ /// The number of passes - only if Argon2
+ public int Passes
+ {
+ get { return passes; }
+ }
+
+ /// The degree of parallelism - only if Argon2
+ public int Parallelism
+ {
+ get { return parallelism; }
+ }
+
+ /// The memory size exponent - only if Argon2
+ public int MemorySizeExponent
+ {
+ get { return memorySizeExponent; }
+ }
+
public override void Encode(
BcpgOutputStream bcpgOut)
{
- bcpgOut.WriteByte((byte) type);
- bcpgOut.WriteByte((byte) algorithm);
+ switch (type)
+ {
+ case Simple:
+ bcpgOut.WriteByte((byte)type);
+ bcpgOut.WriteByte((byte)algorithm);
+ break;
+
+ case Salted:
+ bcpgOut.WriteByte((byte)type);
+ bcpgOut.WriteByte((byte)algorithm);
+ bcpgOut.Write(iv);
+ break;
+
+ case SaltedAndIterated:
+ bcpgOut.WriteByte((byte)type);
+ bcpgOut.WriteByte((byte)algorithm);
+ bcpgOut.Write(iv);
+ bcpgOut.WriteByte((byte)itCount);
+ break;
- if (type != GnuDummyS2K)
+ case Argon2:
+ bcpgOut.WriteByte((byte)type);
+ bcpgOut.Write(iv);
+ bcpgOut.WriteByte((byte)passes);
+ bcpgOut.WriteByte((byte)parallelism);
+ bcpgOut.WriteByte((byte)memorySizeExponent);
+ break;
+
+ case GnuDummyS2K:
+ bcpgOut.WriteByte((byte)type);
+ bcpgOut.WriteByte((byte)algorithm);
+ bcpgOut.WriteByte((byte)'G');
+ bcpgOut.WriteByte((byte)'N');
+ bcpgOut.WriteByte((byte)'U');
+ bcpgOut.WriteByte((byte)protectionMode);
+ break;
+
+ default:
+ throw new InvalidOperationException($"Unknown S2K type {type}");
+ }
+ }
+
+ ///
+ /// Parameters for Argon2 S2K
+ /// Sect. 3.7.1.4 of RFC 9580
+ ///
+ public class Argon2Parameters
+ {
+ private readonly byte[] salt;
+ private readonly int passes;
+ private readonly int parallelism;
+ private readonly int memSizeExp;
+
+ internal byte[] Salt => salt;
+ internal int Passes => passes;
+ internal int Parallelism => parallelism;
+ internal int MemSizeExp => memSizeExp;
+
+ ///
+ /// Uniformly safe and recommended parameters not tailored to any hardware.
+ /// Uses Argon2id, 1 pass, 4 parallelism, 2 GiB RAM.
+ /// RFC 9106: §4. Parameter Choice
+ ///
+ public Argon2Parameters()
+ :this (CryptoServicesRegistrar.GetSecureRandom())
{
- if (type != 0)
+ }
+
+
+ ///
+ /// Uniformly safe and recommended parameters not tailored to any hardware.
+ /// Uses Argon2id, 1 pass, 4 parallelism, 2 GiB RAM.
+ /// RFC 9106: §4. Parameter Choice
+ ///
+ ///
+ public Argon2Parameters(SecureRandom secureRandom)
+ : this(1, 4, 21, secureRandom)
+ {
+ }
+
+ ///
+ /// Create customized Argon2 S2K parameters.
+ ///
+ /// number of iterations, must be greater than 0
+ /// number of lanes, must be greater 0
+ /// exponent for memory consumption, must be between 3+ceil(log_2(p)) and 31
+ /// A secure random generator
+ ///
+ public Argon2Parameters(int passes, int parallelism, int memSizeExp, SecureRandom secureRandom)
+ :this(GenerateSalt(secureRandom), passes, parallelism, memSizeExp)
+ {
+ }
+
+ ///
+ /// Create customized Argon2 S2K parameters.
+ ///
+ /// 16 bytes of random salt
+ /// number of iterations, must be greater than 0
+ /// number of lanes, must be greater 0
+ /// exponent for memory consumption, must be between 3+ceil(log_2(p)) and 31
+ ///
+ public Argon2Parameters(byte[] salt, int passes, int parallelism, int memSizeExp)
+ {
+ if (salt.Length != 16)
{
- bcpgOut.Write(iv);
+ throw new ArgumentException("Argon2 uses 16 bytes of salt");
+ }
+ this.salt = salt;
+
+ if (passes < 1)
+ {
+ throw new ArgumentException("Number of passes MUST be positive, non-zero");
}
+ this.passes = passes;
- if (type == 3)
+ if (parallelism < 1)
{
- bcpgOut.WriteByte((byte) itCount);
+ throw new ArgumentException("Parallelism MUST be positive, non-zero.");
}
+ this.parallelism = parallelism;
+
+ // log_2(p) = log_e(p) / log_e(2)
+ double log2_p = System.Math.Log(parallelism) / System.Math.Log(2);
+ // see https://www.rfc-editor.org/rfc/rfc9580#name-argon2
+ if (memSizeExp < (3 + System.Math.Ceiling(log2_p)) || memSizeExp > 31)
+ {
+ throw new ArgumentException("Memory size exponent MUST be between 3+ceil(log_2(parallelism)) and 31");
+ }
+ this.memSizeExp = memSizeExp;
+ }
+
+ ///
+ /// Uniformly safe and recommended parameters not tailored to any hardware.
+ /// Uses Argon2id, 1 pass, 4 parallelism, 2 GiB RAM.
+ /// RFC 9106: §4. Parameter Choice
+ ///
+ public static Argon2Parameters UniversallyRecommendedParameters()
+ {
+ return new Argon2Parameters(1, 4, 21, CryptoServicesRegistrar.GetSecureRandom());
}
- else
+
+ ///
+ /// Recommended parameters for memory constrained environments(64MiB RAM).
+ /// Uses Argon2id with 3 passes, 4 lanes and 64 MiB RAM.
+ /// RFC 9106: §4. Parameter Choice
+ ///
+ public static Argon2Parameters MemoryConstrainedParameters()
+ {
+ return new Argon2Parameters(3, 4, 16, CryptoServicesRegistrar.GetSecureRandom());
+ }
+
+ private static byte[] GenerateSalt(SecureRandom secureRandom)
{
- bcpgOut.WriteByte((byte) 'G');
- bcpgOut.WriteByte((byte) 'N');
- bcpgOut.WriteByte((byte) 'U');
- bcpgOut.WriteByte((byte) protectionMode);
+ byte[] salt = new byte[16];
+ secureRandom.NextBytes(salt);
+ return salt;
}
}
}
diff --git a/crypto/src/bcpg/SecretKeyPacket.cs b/crypto/src/bcpg/SecretKeyPacket.cs
index e048a1195a..235604b23a 100644
--- a/crypto/src/bcpg/SecretKeyPacket.cs
+++ b/crypto/src/bcpg/SecretKeyPacket.cs
@@ -1,6 +1,6 @@
-using System.IO;
-
using Org.BouncyCastle.Utilities;
+using System;
+using System.IO;
namespace Org.BouncyCastle.Bcpg
{
@@ -8,19 +8,74 @@ namespace Org.BouncyCastle.Bcpg
public class SecretKeyPacket
: ContainedPacket //, PublicKeyAlgorithmTag
{
- public const int UsageNone = 0x00;
- public const int UsageChecksum = 0xff;
- public const int UsageSha1 = 0xfe;
- private PublicKeyPacket pubKeyPacket;
+ ///
+ /// Unprotected secret key
+ ///
+ public const int UsageNone = 0x00;
+
+ ///
+ /// Malleable CFB.
+ /// Malleable-CFB-encrypted keys are vulnerable to corruption attacks
+ /// that can cause leakage of secret data when the secret key is used.
+ ///
+ ///
+ /// Klíma, V.and T. Rosa,
+ /// "Attack on Private Signature Keys of the OpenPGP Format,
+ /// PGP(TM) Programs and Other Applications Compatible with OpenPGP"
+ ///
+ /// Bruseghini, L., Paterson, K.G., and D. Huigens,
+ /// "Victory by KO: Attacking OpenPGP Using Key Overwriting"
+ ///
+ public const int UsageChecksum = 0xff;
+
+
+ ///
+ /// CFB.
+ /// CFB-encrypted keys are vulnerable to corruption attacks that can
+ /// cause leakage of secret data when the secret key is use.
+ ///
+ ///
+ /// Klíma, V. and T.Rosa,
+ /// "Attack on Private Signature Keys of the OpenPGP Format,
+ /// PGP(TM) Programs and Other Applications Compatible with OpenPGP"
+ ///
+ /// Bruseghini, L., Paterson, K.G., and D. Huigens,
+ /// "Victory by KO: Attacking OpenPGP Using Key Overwriting"
+ ///
+ public const int UsageSha1 = 0xfe;
+
+ ///
+ /// AEAD.
+ /// This usage protects against corruption attacks.
+ /// Passphrase-protected secret key material in a v6 Secret Key or
+ /// v6 Secret Subkey packet SHOULD be protected with AEAD encryption
+ /// unless it will be transferred to an implementation that is known
+ /// to not support AEAD.
+ /// Users should migrate to AEAD with all due speed.
+ ///
+ public const int UsageAead = 0xfd;
+
+
+ private readonly PublicKeyPacket pubKeyPacket;
private readonly byte[] secKeyData;
- private int s2kUsage;
- private SymmetricKeyAlgorithmTag encAlgorithm;
- private S2k s2k;
- private byte[] iv;
+ private readonly int s2kUsage;
+ private readonly SymmetricKeyAlgorithmTag encAlgorithm;
+ private readonly S2k s2k;
+ private readonly byte[] iv;
+ private readonly AeadAlgorithmTag aeadAlgorithm;
+
+ private bool HasS2KSpecifier
+ => (s2kUsage == UsageChecksum || s2kUsage == UsageSha1 || s2kUsage == UsageAead);
- internal SecretKeyPacket(
- BcpgInputStream bcpgIn)
+ internal SecretKeyPacket(BcpgInputStream bcpgIn)
+ : this(bcpgIn, PacketTag.SecretKey)
+ {
+ }
+
+ protected SecretKeyPacket(
+ BcpgInputStream bcpgIn, PacketTag tag)
+ :base(tag)
{
if (this is SecretSubkeyPacket)
{
@@ -31,23 +86,53 @@ internal SecretKeyPacket(
pubKeyPacket = new PublicKeyPacket(bcpgIn);
}
- s2kUsage = bcpgIn.RequireByte();
+ int version = pubKeyPacket.Version;
+ s2kUsage = bcpgIn.RequireByte();
+
+ if (version == PublicKeyPacket.Version6 && s2kUsage != UsageNone)
+ {
+ // TODO: Use length to parse unknown parameters
+ int conditionalParameterLength = bcpgIn.RequireByte();
+ }
- if (s2kUsage == UsageChecksum || s2kUsage == UsageSha1)
+ if (HasS2KSpecifier)
{
encAlgorithm = (SymmetricKeyAlgorithmTag)bcpgIn.RequireByte();
- s2k = new S2k(bcpgIn);
}
else
{
encAlgorithm = (SymmetricKeyAlgorithmTag)s2kUsage;
- }
+ }
+
+ if (s2kUsage == UsageAead)
+ {
+ aeadAlgorithm = (AeadAlgorithmTag)bcpgIn.RequireByte();
+ }
+
+ if (HasS2KSpecifier)
+ {
+ if (version == PublicKeyPacket.Version6)
+ {
+ // TODO: Use length to parse unknown S2Ks
+ int s2kLen = bcpgIn.RequireByte();
+ }
+ s2k = new S2k(bcpgIn);
+ }
+ if (s2kUsage == UsageAead)
+ {
+ iv = new byte[AeadUtils.GetIVLength(aeadAlgorithm)];
+ bcpgIn.ReadFully(iv);
+ }
- if (!(s2k != null && s2k.Type == S2k.GnuDummyS2K && s2k.ProtectionMode == 0x01))
+ bool isGNUDummyNoPrivateKey = s2k != null
+ && s2k.Type == S2k.GnuDummyS2K
+ && s2k.ProtectionMode == S2k.GnuProtectionModeNoPrivateKey;
+
+ if (!(isGNUDummyNoPrivateKey))
{
- if (s2kUsage != 0)
- {
- if (((int) encAlgorithm) < 7)
+ if (s2kUsage != 0 && iv == null)
+ {
+ if ((int)encAlgorithm < 7)
{
iv = new byte[8];
}
@@ -59,15 +144,28 @@ internal SecretKeyPacket(
}
}
- secKeyData = bcpgIn.ReadAll();
+ secKeyData = bcpgIn.ReadAll();
+ }
+
+
+ public SecretKeyPacket(
+ PublicKeyPacket pubKeyPacket,
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ S2k s2k,
+ byte[] iv,
+ byte[] secKeyData)
+ : this(pubKeyPacket, encAlgorithm, s2k, iv, secKeyData, PacketTag.SecretKey)
+ {
}
- public SecretKeyPacket(
+ protected SecretKeyPacket(
PublicKeyPacket pubKeyPacket,
SymmetricKeyAlgorithmTag encAlgorithm,
S2k s2k,
byte[] iv,
- byte[] secKeyData)
+ byte[] secKeyData,
+ PacketTag tag)
+ :base(tag)
{
this.pubKeyPacket = pubKeyPacket;
this.encAlgorithm = encAlgorithm;
@@ -86,13 +184,26 @@ public SecretKeyPacket(
this.secKeyData = secKeyData;
}
- public SecretKeyPacket(
+ public SecretKeyPacket(
+ PublicKeyPacket pubKeyPacket,
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ int s2kUsage,
+ S2k s2k,
+ byte[] iv,
+ byte[] secKeyData)
+ : this(pubKeyPacket, encAlgorithm, s2kUsage, s2k, iv, secKeyData, PacketTag.SecretKey)
+ {
+ }
+
+ protected SecretKeyPacket(
PublicKeyPacket pubKeyPacket,
SymmetricKeyAlgorithmTag encAlgorithm,
int s2kUsage,
S2k s2k,
byte[] iv,
- byte[] secKeyData)
+ byte[] secKeyData,
+ PacketTag tag)
+ :base(tag)
{
this.pubKeyPacket = pubKeyPacket;
this.encAlgorithm = encAlgorithm;
@@ -102,12 +213,62 @@ public SecretKeyPacket(
this.secKeyData = secKeyData;
}
- public SymmetricKeyAlgorithmTag EncAlgorithm
+ public SecretKeyPacket(
+ PublicKeyPacket pubKeyPacket,
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ AeadAlgorithmTag aeadAlgorithm,
+ int s2kUsage,
+ S2k s2k,
+ byte[] iv,
+ byte[] secKeyData)
+ : this(pubKeyPacket, encAlgorithm, aeadAlgorithm, s2kUsage, s2k, iv, secKeyData, PacketTag.SecretKey)
+ {
+ }
+
+ protected SecretKeyPacket(
+ PublicKeyPacket pubKeyPacket,
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ AeadAlgorithmTag aeadAlgorithm,
+ int s2kUsage,
+ S2k s2k,
+ byte[] iv,
+ byte[] secKeyData,
+ PacketTag tag)
+ :base(tag)
+ {
+ this.pubKeyPacket = pubKeyPacket;
+ this.encAlgorithm = encAlgorithm;
+ this.aeadAlgorithm = aeadAlgorithm;
+ this.s2kUsage = s2kUsage;
+ this.s2k = s2k;
+ this.iv = Arrays.Clone(iv);
+ this.secKeyData = secKeyData;
+
+ if (s2k != null && s2k.Type == S2k.Argon2 && s2kUsage != UsageAead)
+ {
+ throw new ArgumentException("Argon2 is only used with AEAD (S2K usage octet 253)");
+ }
+
+ if (pubKeyPacket.Version == PublicKeyPacket.Version6)
+ {
+ if (s2kUsage == UsageChecksum)
+ {
+ throw new ArgumentException("Version 6 keys MUST NOT use S2K usage UsageChecksum");
+ }
+ }
+ }
+
+ public SymmetricKeyAlgorithmTag EncAlgorithm
{
get { return encAlgorithm; }
}
- public int S2kUsage
+ public AeadAlgorithmTag AeadAlgorithm
+ {
+ get { return aeadAlgorithm; }
+ }
+
+ public int S2kUsage
{
get { return s2kUsage; }
}
@@ -132,31 +293,73 @@ public byte[] GetSecretKeyData()
return secKeyData;
}
- public byte[] GetEncodedContents()
+ internal byte[] GetAAData()
{
- MemoryStream bOut = new MemoryStream();
- using (var pOut = new BcpgOutputStream(bOut))
+ // HKDF Info used for key derivation in UsageAead
+ return new byte[]
{
- pOut.Write(pubKeyPacket.GetEncodedContents());
- pOut.WriteByte((byte)s2kUsage);
+ (byte)(0xC0 | (byte)Tag),
+ (byte)pubKeyPacket.Version,
+ (byte)encAlgorithm,
+ (byte)aeadAlgorithm
+ };
+ }
- if (s2kUsage == UsageChecksum || s2kUsage == UsageSha1)
+ private byte[] EncodeConditionalParameters()
+ {
+ using (MemoryStream conditionalParameters = new MemoryStream())
+ {
+ if (HasS2KSpecifier)
{
- pOut.WriteByte((byte)encAlgorithm);
- pOut.WriteObject(s2k);
+ conditionalParameters.WriteByte((byte)encAlgorithm);
+ if (s2kUsage == UsageAead)
+ {
+ conditionalParameters.WriteByte((byte)aeadAlgorithm);
+ }
+ byte[] encodedS2K = s2k.GetEncoded();
+ if (pubKeyPacket.Version == PublicKeyPacket.Version6)
+ {
+ conditionalParameters.WriteByte((byte)encodedS2K.Length);
+ }
+ conditionalParameters.Write(encodedS2K, 0, encodedS2K.Length);
}
-
if (iv != null)
{
- pOut.Write(iv);
+ // since USAGE_AEAD and other types that use an IV are mutually exclusive,
+ // we use the IV field for both v4 IVs and v6 AEAD nonces
+ conditionalParameters.Write(iv, 0, iv.Length);
}
- if (secKeyData != null && secKeyData.Length > 0)
+ return conditionalParameters.ToArray();
+ }
+ }
+
+ public byte[] GetEncodedContents()
+ {
+ using (MemoryStream bOut = new MemoryStream())
+ {
+ using (var pOut = new BcpgOutputStream(bOut))
{
- pOut.Write(secKeyData);
+ pOut.Write(pubKeyPacket.GetEncodedContents());
+ pOut.WriteByte((byte)s2kUsage);
+
+ // conditional parameters
+ byte[] conditionalParameters = EncodeConditionalParameters();
+ if (pubKeyPacket.Version == PublicKeyPacket.Version6 && s2kUsage != UsageNone)
+ {
+ pOut.WriteByte((byte)conditionalParameters.Length);
+ }
+ pOut.Write(conditionalParameters);
+
+ // encrypted secret key
+ if (secKeyData != null && secKeyData.Length > 0)
+ {
+ pOut.Write(secKeyData);
+ }
+ pOut.Close();
}
+ return bOut.ToArray();
}
- return bOut.ToArray();
}
public override void Encode(BcpgOutputStream bcpgOut)
diff --git a/crypto/src/bcpg/SecretSubkeyPacket.cs b/crypto/src/bcpg/SecretSubkeyPacket.cs
index 2d405aec27..5fb249a254 100644
--- a/crypto/src/bcpg/SecretSubkeyPacket.cs
+++ b/crypto/src/bcpg/SecretSubkeyPacket.cs
@@ -9,7 +9,7 @@ public class SecretSubkeyPacket
{
internal SecretSubkeyPacket(
BcpgInputStream bcpgIn)
- : base(bcpgIn)
+ : base(bcpgIn, PacketTag.SecretSubkey)
{
}
@@ -19,7 +19,7 @@ public SecretSubkeyPacket(
S2k s2k,
byte[] iv,
byte[] secKeyData)
- : base(pubKeyPacket, encAlgorithm, s2k, iv, secKeyData)
+ : base(pubKeyPacket, encAlgorithm, s2k, iv, secKeyData, PacketTag.SecretSubkey)
{
}
@@ -30,11 +30,23 @@ public SecretSubkeyPacket(
S2k s2k,
byte[] iv,
byte[] secKeyData)
- : base(pubKeyPacket, encAlgorithm, s2kUsage, s2k, iv, secKeyData)
+ : base(pubKeyPacket, encAlgorithm, s2kUsage, s2k, iv, secKeyData, PacketTag.SecretSubkey)
{
}
- public override void Encode(BcpgOutputStream bcpgOut)
+ public SecretSubkeyPacket(
+ PublicKeyPacket pubKeyPacket,
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ AeadAlgorithmTag aeadAlgorithm,
+ int s2kUsage,
+ S2k s2k,
+ byte[] iv,
+ byte[] secKeyData)
+ :base(pubKeyPacket, encAlgorithm, aeadAlgorithm, s2kUsage, s2k, iv, secKeyData, PacketTag.SecretSubkey)
+ {
+ }
+
+ public override void Encode(BcpgOutputStream bcpgOut)
{
bcpgOut.WritePacket(PacketTag.SecretSubkey, GetEncodedContents());
}
diff --git a/crypto/src/bcpg/SignaturePacket.cs b/crypto/src/bcpg/SignaturePacket.cs
index a9e805f2d0..8149c11758 100644
--- a/crypto/src/bcpg/SignaturePacket.cs
+++ b/crypto/src/bcpg/SignaturePacket.cs
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
-
+using Org.BouncyCastle.Bcpg.OpenPgp;
using Org.BouncyCastle.Bcpg.Sig;
using Org.BouncyCastle.Crypto.Utilities;
using Org.BouncyCastle.Utilities;
@@ -14,23 +14,69 @@ namespace Org.BouncyCastle.Bcpg
public class SignaturePacket
: ContainedPacket
{
- private int version;
- private int signatureType;
- private long creationTime;
- private long keyId;
- private PublicKeyAlgorithmTag keyAlgorithm;
- private HashAlgorithmTag hashAlgorithm;
- private MPInteger[] signature;
- private byte[] fingerprint;
- private SignatureSubpacket[] hashedData;
- private SignatureSubpacket[] unhashedData;
- private byte[] signatureEncoding;
-
- internal SignaturePacket(BcpgInputStream bcpgIn)
+ public const int Version2 = 2;
+ public const int Version3 = 3;
+ public const int Version4 = 4;
+ public const int Version5 = 5;
+ public const int Version6 = 6;
+
+ public const int DefaultVersion = Version4;
+
+ private readonly int version;
+ private readonly int signatureType;
+ private long creationTime;
+ private long keyId;
+ private bool keyIdAlreadySet = false;
+ private readonly PublicKeyAlgorithmTag keyAlgorithm;
+ private readonly HashAlgorithmTag hashAlgorithm;
+ private readonly MPInteger[] signature;
+ private readonly byte[] fingerprint;
+ private readonly SignatureSubpacket[] hashedData;
+ private readonly SignatureSubpacket[] unhashedData;
+ private readonly byte[] signatureEncoding;
+
+ // fields for v6 signatures
+ private readonly byte[] salt;
+ private byte[] issuerFingerprint = null;
+
+ private void CheckIssuerSubpacket(SignatureSubpacket p)
+ {
+ if (p is IssuerFingerprint issuerFingerprintPkt && issuerFingerprint is null)
+ {
+ issuerFingerprint = issuerFingerprintPkt.GetFingerprint();
+
+ if (issuerFingerprintPkt.KeyVersion == PublicKeyPacket.Version4)
+ {
+ keyId = (long)Pack.BE_To_UInt64(issuerFingerprint, issuerFingerprint.Length - 8);
+ }
+ else
+ {
+ // v5 or v6
+ keyId = (long)Pack.BE_To_UInt64(issuerFingerprint);
+ }
+ keyIdAlreadySet = true;
+ }
+
+ else if (p is IssuerKeyId issuerKeyId && !keyIdAlreadySet)
+ {
+ // https://www.rfc-editor.org/rfc/rfc9580#name-issuer-key-id
+ // https://www.rfc-editor.org/rfc/rfc9580#issuer-fingerprint-subpacket
+ // V6 signatures MUST NOT include an IssuerKeyId subpacket and SHOULD include an IssuerFingerprint subpacket
+ if (version == Version6)
+ {
+ throw new IOException("V6 signatures MUST NOT include an IssuerKeyId subpacket");
+ }
+ keyId = issuerKeyId.KeyId;
+ keyIdAlreadySet = true;
+ }
+ }
+
+ internal SignaturePacket(BcpgInputStream bcpgIn)
+ :base(PacketTag.Signature)
{
version = bcpgIn.RequireByte();
- if (version == 3 || version == 2)
+ if (version == Version2 || version == Version3)
{
// int l =
bcpgIn.RequireByte();
@@ -41,13 +87,22 @@ internal SignaturePacket(BcpgInputStream bcpgIn)
keyAlgorithm = (PublicKeyAlgorithmTag)bcpgIn.RequireByte();
hashAlgorithm = (HashAlgorithmTag)bcpgIn.RequireByte();
}
- else if (version == 4)
+ else if (version >= Version4 && version <= Version6)
{
signatureType = bcpgIn.RequireByte();
keyAlgorithm = (PublicKeyAlgorithmTag)bcpgIn.RequireByte();
hashAlgorithm = (HashAlgorithmTag)bcpgIn.RequireByte();
- int hashedLength = StreamUtilities.RequireUInt16BE(bcpgIn);
+ int hashedLength;
+
+ if (version == Version6)
+ {
+ hashedLength = (int)StreamUtilities.RequireUInt32BE(bcpgIn);
+ }
+ else
+ {
+ hashedLength = StreamUtilities.RequireUInt16BE(bcpgIn);
+ }
byte[] hashed = new byte[hashedLength];
bcpgIn.ReadFully(hashed);
@@ -69,17 +124,25 @@ internal SignaturePacket(BcpgInputStream bcpgIn)
foreach (var p in hashedData)
{
- if (p is IssuerKeyId issuerKeyId)
- {
- keyId = issuerKeyId.KeyId;
- }
- else if (p is SignatureCreationTime sigCreationTime)
+ CheckIssuerSubpacket(p);
+
+ if (p is SignatureCreationTime sigCreationTime)
{
creationTime = DateTimeUtilities.DateTimeToUnixMs(sigCreationTime.GetTime());
}
}
- int unhashedLength = StreamUtilities.RequireUInt16BE(bcpgIn);
+ int unhashedLength;
+
+ if (version == Version6)
+ {
+ unhashedLength = (int)StreamUtilities.RequireUInt32BE(bcpgIn);
+ }
+ else
+ {
+ unhashedLength = StreamUtilities.RequireUInt16BE(bcpgIn);
+ }
+
byte[] unhashed = new byte[unhashedLength];
bcpgIn.ReadFully(unhashed);
@@ -96,10 +159,7 @@ internal SignaturePacket(BcpgInputStream bcpgIn)
foreach (var p in unhashedData)
{
- if (p is IssuerKeyId issuerKeyId)
- {
- keyId = issuerKeyId.KeyId;
- }
+ CheckIssuerSubpacket(p);
}
}
else
@@ -112,7 +172,24 @@ internal SignaturePacket(BcpgInputStream bcpgIn)
fingerprint = new byte[2];
bcpgIn.ReadFully(fingerprint);
- switch (keyAlgorithm)
+ if (version == Version6)
+ {
+ int saltSize = bcpgIn.ReadByte();
+
+ if (saltSize != PgpUtilities.GetSaltSize(hashAlgorithm))
+ {
+ // https://www.rfc-editor.org/rfc/rfc9580#name-versions-4-and-6-signature-
+ // The salt size MUST match the value defined for the hash algorithm as specified in Table 23
+ // https://www.rfc-editor.org/rfc/rfc9580#hash-algorithms-registry
+
+ throw new IOException($"invalid salt size for v6 signature: expected {PgpUtilities.GetSaltSize(hashAlgorithm)} got {saltSize}");
+ }
+
+ salt = new byte[saltSize];
+ bcpgIn.ReadFully(salt);
+ }
+
+ switch (keyAlgorithm)
{
case PublicKeyAlgorithmTag.RsaGeneral:
case PublicKeyAlgorithmTag.RsaSign:
@@ -132,6 +209,20 @@ internal SignaturePacket(BcpgInputStream bcpgIn)
MPInteger ecS = new MPInteger(bcpgIn);
signature = new MPInteger[2]{ ecR, ecS };
break;
+
+ case PublicKeyAlgorithmTag.Ed25519:
+ // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-fields-for-ed2
+ signature = null;
+ signatureEncoding = new byte[64];
+ bcpgIn.ReadFully(signatureEncoding);
+ break;
+ case PublicKeyAlgorithmTag.Ed448:
+ // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-fields-for-ed4
+ signature = null;
+ signatureEncoding = new byte[114];
+ bcpgIn.ReadFully(signatureEncoding);
+ break;
+
default:
if (keyAlgorithm < PublicKeyAlgorithmTag.Experimental_1 || keyAlgorithm > PublicKeyAlgorithmTag.Experimental_11)
throw new IOException("unknown signature key algorithm: " + keyAlgorithm);
@@ -142,7 +233,7 @@ internal SignaturePacket(BcpgInputStream bcpgIn)
}
}
- /**
+ /**
* Generate a version 4 signature packet.
*
* @param signatureType
@@ -162,7 +253,7 @@ public SignaturePacket(
SignatureSubpacket[] unhashedData,
byte[] fingerprint,
MPInteger[] signature)
- : this(4, signatureType, keyId, keyAlgorithm, hashAlgorithm, hashedData, unhashedData, fingerprint, signature)
+ : this(Version4, signatureType, keyId, keyAlgorithm, hashAlgorithm, hashedData, unhashedData, fingerprint, null, null, signature)
{
}
@@ -184,7 +275,7 @@ public SignaturePacket(
long creationTime,
byte[] fingerprint,
MPInteger[] signature)
- : this(version, signatureType, keyId, keyAlgorithm, hashAlgorithm, null, null, fingerprint, signature)
+ : this(version, signatureType, keyId, keyAlgorithm, hashAlgorithm, null, null, fingerprint, null, null, signature)
{
this.creationTime = creationTime;
}
@@ -199,6 +290,56 @@ public SignaturePacket(
SignatureSubpacket[] unhashedData,
byte[] fingerprint,
MPInteger[] signature)
+ :this(version, signatureType, keyId, keyAlgorithm, hashAlgorithm, hashedData, unhashedData, fingerprint, null, null, signature)
+ {
+ }
+
+ public SignaturePacket(
+ int version,
+ int signatureType,
+ long keyId,
+ PublicKeyAlgorithmTag keyAlgorithm,
+ HashAlgorithmTag hashAlgorithm,
+ SignatureSubpacket[] hashedData,
+ SignatureSubpacket[] unhashedData,
+ byte[] fingerprint,
+ byte[] salt,
+ byte[] issuerFingerprint,
+ byte[] signatureEncoding)
+ : this(version, signatureType, keyId, keyAlgorithm, hashAlgorithm, hashedData, unhashedData, fingerprint, salt, issuerFingerprint)
+ {
+ this.signatureEncoding = Arrays.Clone(signatureEncoding);
+ }
+
+ public SignaturePacket(
+ int version,
+ int signatureType,
+ long keyId,
+ PublicKeyAlgorithmTag keyAlgorithm,
+ HashAlgorithmTag hashAlgorithm,
+ SignatureSubpacket[] hashedData,
+ SignatureSubpacket[] unhashedData,
+ byte[] fingerprint,
+ byte[] salt,
+ byte[] issuerFingerprint,
+ MPInteger[] signature)
+ : this(version, signatureType, keyId, keyAlgorithm, hashAlgorithm, hashedData, unhashedData, fingerprint, salt, issuerFingerprint)
+ {
+ this.signature = signature;
+ }
+
+ private SignaturePacket(
+ int version,
+ int signatureType,
+ long keyId,
+ PublicKeyAlgorithmTag keyAlgorithm,
+ HashAlgorithmTag hashAlgorithm,
+ SignatureSubpacket[] hashedData,
+ SignatureSubpacket[] unhashedData,
+ byte[] fingerprint,
+ byte[] salt,
+ byte[] issuerFingerprint)
+ : base(PacketTag.Signature)
{
this.version = version;
this.signatureType = signatureType;
@@ -207,16 +348,17 @@ public SignaturePacket(
this.hashAlgorithm = hashAlgorithm;
this.hashedData = hashedData;
this.unhashedData = unhashedData;
- this.fingerprint = fingerprint;
- this.signature = signature;
+ this.fingerprint = Arrays.Clone(fingerprint);
+ this.salt = Arrays.Clone(salt);
+ this.issuerFingerprint = Arrays.Clone(issuerFingerprint);
- if (hashedData != null)
- {
- SetCreationTime();
- }
- }
+ if (hashedData != null)
+ {
+ SetCreationTime();
+ }
+ }
- public int Version => version;
+ public int Version => version;
public int SignatureType => signatureType;
@@ -226,6 +368,11 @@ public SignaturePacket(
*/
public long KeyId => keyId;
+ public byte[] GetIssuerFingerprint()
+ {
+ return Arrays.Clone(issuerFingerprint);
+ }
+
/**
* Return the signatures fingerprint.
* @return fingerprint (digest prefix) of the signature
@@ -235,15 +382,21 @@ public byte[] GetFingerprint()
return Arrays.Clone(fingerprint);
}
- /**
+ /**
* return the signature trailer that must be included with the data
* to reconstruct the signature
*
* @return byte[]
*/
+
public byte[] GetSignatureTrailer()
{
- if (version == 3)
+ return GetSignatureTrailer(Array.Empty());
+ }
+
+ public byte[] GetSignatureTrailer(byte[] additionalMetadata)
+ {
+ if (version == Version3)
{
long time = creationTime / 1000L;
@@ -253,40 +406,93 @@ public byte[] GetSignatureTrailer()
return trailer;
}
- MemoryStream sOut = new MemoryStream();
+ using (MemoryStream sOut = new MemoryStream())
+ {
+ sOut.WriteByte((byte)Version);
+ sOut.WriteByte((byte)SignatureType);
+ sOut.WriteByte((byte)KeyAlgorithm);
+ sOut.WriteByte((byte)HashAlgorithm);
+
+ // Mark position an reserve two bytes (version4) or four bytes (version6)
+ // for length
+ long lengthPosition = sOut.Position;
+ if (version == Version6)
+ {
+ sOut.WriteByte(0x00);
+ sOut.WriteByte(0x00);
+ }
+ sOut.WriteByte(0x00);
+ sOut.WriteByte(0x00);
- sOut.WriteByte((byte)Version);
- sOut.WriteByte((byte)SignatureType);
- sOut.WriteByte((byte)KeyAlgorithm);
- sOut.WriteByte((byte)HashAlgorithm);
+ SignatureSubpacket[] hashed = GetHashedSubPackets();
+ for (int i = 0; i != hashed.Length; i++)
+ {
+ hashed[i].Encode(sOut);
+ }
- // Mark position an reserve two bytes for length
- long lengthPosition = sOut.Position;
- sOut.WriteByte(0x00);
- sOut.WriteByte(0x00);
+ ushort dataLength = Convert.ToUInt16(sOut.Position - lengthPosition - 2);
+ if (version == Version6)
+ {
+ dataLength -= 2;
+ }
- SignatureSubpacket[] hashed = GetHashedSubPackets();
- for (int i = 0; i != hashed.Length; i++)
- {
- hashed[i].Encode(sOut);
- }
+ uint hDataLength = Convert.ToUInt32(sOut.Position);
- ushort dataLength = Convert.ToUInt16(sOut.Position - lengthPosition - 2);
- uint hDataLength = Convert.ToUInt32(sOut.Position);
+ // Additional metadata for v5 signatures
+ // https://www.ietf.org/archive/id/draft-ietf-openpgp-rfc4880bis-10.html#name-computing-signatures
+ // Only for document signatures (type 0x00 or 0x01) the following three data items are
+ // hashed here:
+ // * the one-octet content format,
+ // * the file name as a string (one octet length, followed by the file name)
+ // * a four-octet number that indicates a date,
+ // The three data items hashed for document signatures need to mirror the values of the
+ // Literal Data packet.
+ // For detached and cleartext signatures 6 zero bytes are hashed instead.
- sOut.WriteByte((byte)Version);
- sOut.WriteByte(0xff);
- sOut.WriteByte((byte)(hDataLength >> 24));
- sOut.WriteByte((byte)(hDataLength >> 16));
- sOut.WriteByte((byte)(hDataLength >> 8));
- sOut.WriteByte((byte)(hDataLength ));
+ if (version == Version5 && (signatureType == 0x00 || signatureType == 0x01))
+ {
+ if (additionalMetadata != null && additionalMetadata.Length > 0)
+ {
+ sOut.Write(additionalMetadata, 0, additionalMetadata.Length);
+ }
+ else
+ {
+ sOut.WriteByte(0x00);
+ sOut.WriteByte(0x00);
+ sOut.WriteByte(0x00);
+ sOut.WriteByte(0x00);
+ sOut.WriteByte(0x00);
+ sOut.WriteByte(0x00);
+ }
+ }
- // Reset position and fill in length
- sOut.Position = lengthPosition;
- sOut.WriteByte((byte)(dataLength >> 8));
- sOut.WriteByte((byte)(dataLength ));
+ sOut.WriteByte((byte)Version);
+ sOut.WriteByte(0xff);
- return sOut.ToArray();
+ if (version == Version5)
+ {
+ sOut.WriteByte((byte)((ulong)hDataLength >> 56));
+ sOut.WriteByte((byte)((ulong)hDataLength >> 48));
+ sOut.WriteByte((byte)((ulong)hDataLength >> 40));
+ sOut.WriteByte((byte)((ulong)hDataLength >> 32));
+ }
+ sOut.WriteByte((byte)(hDataLength >> 24));
+ sOut.WriteByte((byte)(hDataLength >> 16));
+ sOut.WriteByte((byte)(hDataLength >> 8));
+ sOut.WriteByte((byte)(hDataLength ));
+
+ // Reset position and fill in length
+ sOut.Position = lengthPosition;
+ if (version == Version6)
+ {
+ sOut.WriteByte((byte)(dataLength >> 24));
+ sOut.WriteByte((byte)(dataLength >> 16));
+ }
+ sOut.WriteByte((byte)(dataLength >> 8));
+ sOut.WriteByte((byte)(dataLength ));
+
+ return sOut.ToArray();
+ }
}
public PublicKeyAlgorithmTag KeyAlgorithm => keyAlgorithm;
@@ -299,6 +505,11 @@ public byte[] GetSignatureTrailer()
*/
public MPInteger[] GetSignature() => signature;
+ public byte[] GetSignatureSalt()
+ {
+ return Arrays.Clone(salt);
+ }
+
/**
* Return the byte encoding of the signature section.
* @return uninterpreted signature bytes.
@@ -306,7 +517,7 @@ public byte[] GetSignatureTrailer()
public byte[] GetSignatureBytes()
{
if (signatureEncoding != null)
- return (byte[])signatureEncoding.Clone();
+ return Arrays.Clone(signatureEncoding);
MemoryStream bOut = new MemoryStream();
@@ -342,7 +553,7 @@ public override void Encode(BcpgOutputStream bcpgOut)
{
pOut.WriteByte((byte)version);
- if (version == 3 || version == 2)
+ if (version == Version3 || version == Version2)
{
byte nextBlockLength = 5;
pOut.Write(nextBlockLength, (byte)signatureType);
@@ -350,11 +561,11 @@ public override void Encode(BcpgOutputStream bcpgOut)
pOut.WriteLong(keyId);
pOut.Write((byte)keyAlgorithm, (byte)hashAlgorithm);
}
- else if (version == 4)
+ else if (version >= Version4 && version <= Version6)
{
pOut.Write((byte)signatureType, (byte)keyAlgorithm, (byte)hashAlgorithm);
- EncodeLengthAndData(pOut, GetEncodedSubpackets(hashedData));
- EncodeLengthAndData(pOut, GetEncodedSubpackets(unhashedData));
+ EncodeLengthAndData(version, pOut, GetEncodedSubpackets(hashedData));
+ EncodeLengthAndData(version, pOut, GetEncodedSubpackets(unhashedData));
}
else
{
@@ -363,6 +574,12 @@ public override void Encode(BcpgOutputStream bcpgOut)
pOut.Write(fingerprint);
+ if (version == Version6)
+ {
+ pOut.WriteByte((byte)salt.Length);
+ pOut.Write(salt);
+ }
+
if (signature != null)
{
pOut.WriteObjects(signature);
@@ -377,10 +594,18 @@ public override void Encode(BcpgOutputStream bcpgOut)
}
private static void EncodeLengthAndData(
+ int version,
BcpgOutputStream pOut,
byte[] data)
{
- pOut.WriteShort((short) data.Length);
+ if (version == Version6)
+ {
+ pOut.WriteInt(data.Length);
+ }
+ else
+ {
+ pOut.WriteShort((short)data.Length);
+ }
pOut.Write(data);
}
diff --git a/crypto/src/bcpg/SignatureSubpacketTags.cs b/crypto/src/bcpg/SignatureSubpacketTags.cs
index 0574c274b7..082d668393 100644
--- a/crypto/src/bcpg/SignatureSubpacketTags.cs
+++ b/crypto/src/bcpg/SignatureSubpacketTags.cs
@@ -30,7 +30,7 @@ public enum SignatureSubpacketTag
SignatureTarget = 31, // signature target
EmbeddedSignature = 32, // embedded signature
IssuerFingerprint = 33, // issuer key fingerprint
- //PreferredAeadAlgorithms = 34, // RESERVED since crypto-refresh-05
+ //PreferredAeadAlgorithms = 34, // RESERVED since RFC 9580
IntendedRecipientFingerprint = 35, // intended recipient fingerprint
AttestedCertifications = 37, // attested certifications (RESERVED)
KeyBlock = 38, // Key Block (RESERVED)
diff --git a/crypto/src/bcpg/SymmetricEncDataPacket.cs b/crypto/src/bcpg/SymmetricEncDataPacket.cs
index 17ee55bb73..013a7fd39e 100644
--- a/crypto/src/bcpg/SymmetricEncDataPacket.cs
+++ b/crypto/src/bcpg/SymmetricEncDataPacket.cs
@@ -8,7 +8,7 @@ public class SymmetricEncDataPacket
{
public SymmetricEncDataPacket(
BcpgInputStream bcpgIn)
- : base(bcpgIn)
+ : base(bcpgIn, PacketTag.SymmetricKeyEncrypted)
{
}
}
diff --git a/crypto/src/bcpg/SymmetricEncIntegrityPacket.cs b/crypto/src/bcpg/SymmetricEncIntegrityPacket.cs
index fe09ba7d95..bbac8c1ebd 100644
--- a/crypto/src/bcpg/SymmetricEncIntegrityPacket.cs
+++ b/crypto/src/bcpg/SymmetricEncIntegrityPacket.cs
@@ -1,14 +1,87 @@
+using Org.BouncyCastle.Utilities;
+using System;
+using System.IO;
+
namespace Org.BouncyCastle.Bcpg
{
public class SymmetricEncIntegrityPacket
: InputStreamPacket
{
- internal readonly int m_version;
+ ///
+ /// Version 3 SEIPD packet.
+ ///
+ ///
+ public const int Version1 = 1;
+ ///
+ /// Version 2 SEIPD packet.
+ ///
+ ///
+ public const int Version2 = 2;
+
+ private readonly int m_version; // V1, V2
+ private readonly SymmetricKeyAlgorithmTag cipherAlgorithm; // V2 Only
+ private readonly AeadAlgorithmTag aeadAlgorithm; // V2 Only
+ private readonly int chunkSize; // V2 Only
+ private readonly byte[] salt; // V2 Only
- internal SymmetricEncIntegrityPacket(BcpgInputStream bcpgIn)
- : base(bcpgIn)
+ internal SymmetricEncIntegrityPacket(
+ BcpgInputStream bcpgIn)
+ : base(bcpgIn, PacketTag.SymmetricEncryptedIntegrityProtected)
{
m_version = bcpgIn.RequireByte();
+ if (m_version == Version2)
+ {
+ cipherAlgorithm = (SymmetricKeyAlgorithmTag)bcpgIn.RequireByte();
+ aeadAlgorithm = (AeadAlgorithmTag)bcpgIn.RequireByte();
+ chunkSize = bcpgIn.RequireByte();
+
+ salt = new byte[32];
+ if (bcpgIn.Read(salt, 0, 32) != salt.Length)
+ {
+ throw new IOException("Premature end of stream.");
+ }
+ }
+ }
+
+ public int Version
+ {
+ get { return m_version; }
+ }
+
+ public SymmetricKeyAlgorithmTag CipherAlgorithm
+ {
+ get { return cipherAlgorithm; }
+ }
+
+ public AeadAlgorithmTag AeadAlgorithm
+ {
+ get { return aeadAlgorithm; }
+ }
+
+ public int ChunkSize
+ {
+ get { return chunkSize; }
+ }
+
+ public byte[] GetSalt()
+ {
+ return Arrays.Clone(salt);
+ }
+
+ internal byte[] GetAAData()
+ {
+ return CreateAAData(Version, cipherAlgorithm, aeadAlgorithm, chunkSize);
+ }
+
+ internal static byte[] CreateAAData(int version, SymmetricKeyAlgorithmTag cipherAlgorithm, AeadAlgorithmTag aeadAlgorithm, int chunkSize)
+ {
+ return new byte[]{
+ 0xC0 | (byte)PacketTag.SymmetricEncryptedIntegrityProtected,
+ (byte)version,
+ (byte)cipherAlgorithm,
+ (byte)aeadAlgorithm,
+ (byte)chunkSize
+ };
}
}
}
diff --git a/crypto/src/bcpg/SymmetricKeyAlgorithmTags.cs b/crypto/src/bcpg/SymmetricKeyAlgorithmTags.cs
index e05a486163..abd00fa218 100644
--- a/crypto/src/bcpg/SymmetricKeyAlgorithmTags.cs
+++ b/crypto/src/bcpg/SymmetricKeyAlgorithmTags.cs
@@ -10,14 +10,27 @@ public enum SymmetricKeyAlgorithmTag
TripleDes = 2, // Triple-DES (DES-EDE, as per spec -168 bit key derived from 192)
Cast5 = 3, // Cast5 (128 bit key, as per RFC 2144)
Blowfish = 4, // Blowfish (128 bit key, 16 rounds) [Blowfish]
- Safer = 5, // Safer-SK128 (13 rounds) [Safer]
+ Safer = 5, // Reserved - formerly Safer-SK128 (13 rounds) [Safer]
Des = 6, // Reserved for DES/SK
- Aes128 = 7, // Reserved for AES with 128-bit key
- Aes192 = 8, // Reserved for AES with 192-bit key
- Aes256 = 9, // Reserved for AES with 256-bit key
- Twofish = 10, // Reserved for Twofish
- Camellia128 = 11, // Reserved for AES with 128-bit key
- Camellia192 = 12, // Reserved for AES with 192-bit key
- Camellia256 = 13 // Reserved for AES with 256-bit key
+ Aes128 = 7, // AES with 128-bit key
+ Aes192 = 8, // AES with 192-bit key
+ Aes256 = 9, // AES with 256-bit key
+ Twofish = 10, // Twofish with 256-bit key [TWOFISH]
+ Camellia128 = 11, // Camellia with 128-bit key [RFC3713]
+ Camellia192 = 12, // Camellia with 192-bit key
+ Camellia256 = 13, // Camellia with 256-bit key
+
+
+ Experimental_1 = 100,
+ Experimental_2 = 101,
+ Experimental_3 = 102,
+ Experimental_4 = 103,
+ Experimental_5 = 104,
+ Experimental_6 = 105,
+ Experimental_7 = 106,
+ Experimental_8 = 107,
+ Experimental_9 = 108,
+ Experimental_10 = 109,
+ Experimental_11 = 110
}
}
diff --git a/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs b/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs
index 569deec265..0a4a96a6c8 100644
--- a/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs
+++ b/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs
@@ -1,3 +1,5 @@
+using Org.BouncyCastle.Utilities;
+using System;
using System.IO;
namespace Org.BouncyCastle.Bcpg
@@ -8,32 +10,123 @@ namespace Org.BouncyCastle.Bcpg
public class SymmetricKeyEncSessionPacket
: ContainedPacket
{
- private int version;
- private SymmetricKeyAlgorithmTag encAlgorithm;
- private S2k s2k;
+ ///
+ /// Version 4 SKESK packet.
+ /// Used only with V1 SEIPD or SED packets.
+ ///
+ public const int Version4 = 4;
+
+ ///
+ /// Version 5 SKESK packet.
+ /// Used only with AEADEncDataPacket AED packets.
+ /// Defined in retired "RFC4880-bis" draft
+ ///
+ ///
+ public const int Version5 = 5;
+
+ ///
+ /// Version 6 SKESK packet.
+ /// Used only with V2 SEIPD packets.
+ ///
+ public const int Version6 = 6;
+
+ private readonly int version;
+ private readonly SymmetricKeyAlgorithmTag encAlgorithm;
+ private readonly S2k s2k;
private readonly byte[] secKeyData;
- public SymmetricKeyEncSessionPacket(BcpgInputStream bcpgIn)
+ private readonly byte[] s2kBytes;
+ private readonly AeadAlgorithmTag aeadAlgorithm;
+ private readonly byte[] iv;
+
+ public SymmetricKeyEncSessionPacket(
+ BcpgInputStream bcpgIn)
+ :base(PacketTag.SymmetricKeyEncryptedSessionKey)
{
version = bcpgIn.RequireByte();
- encAlgorithm = (SymmetricKeyAlgorithmTag)bcpgIn.RequireByte();
- s2k = new S2k(bcpgIn);
+ switch (version)
+ {
+ case Version4:
+ encAlgorithm = (SymmetricKeyAlgorithmTag)bcpgIn.RequireByte();
+ s2k = new S2k(bcpgIn);
+ secKeyData = bcpgIn.ReadAll();
+ break;
+
+ case Version5:
+ case Version6:
+ // https://www.rfc-editor.org/rfc/rfc9580#name-version-6-symmetric-key-enc
+ // SymmAlgo + AEADAlgo + S2KCount + S2K + IV
+ int next5Fields5Count = bcpgIn.ReadByte();
+ encAlgorithm = (SymmetricKeyAlgorithmTag)bcpgIn.RequireByte();
+ aeadAlgorithm = (AeadAlgorithmTag)bcpgIn.RequireByte();
+
+ int s2kOctetCount = bcpgIn.ReadByte();
+ s2kBytes = new byte[s2kOctetCount];
+ bcpgIn.ReadFully(s2kBytes);
+ s2k = new S2k(new MemoryStream(s2kBytes));
+
+ int ivsize = AeadUtils.GetIVLength(aeadAlgorithm);
+ iv = new byte[ivsize];
+ bcpgIn.ReadFully(iv);
- secKeyData = bcpgIn.ReadAll();
+ // contains both the encrypted session key and the AEAD authentication tag
+ secKeyData = bcpgIn.ReadAll();
+ break;
+ }
}
- public SymmetricKeyEncSessionPacket(
- SymmetricKeyAlgorithmTag encAlgorithm,
+ ///
+ /// Create a v4 SKESK packet.
+ ///
+ /// symmetric encryption algorithm
+ /// s2k specifier
+ /// encrypted session key
+ public SymmetricKeyEncSessionPacket(
+ SymmetricKeyAlgorithmTag encAlgorithm,
S2k s2k,
byte[] secKeyData)
+ : base(PacketTag.SymmetricKeyEncryptedSessionKey)
{
- this.version = 4;
+ this.version = Version4;
this.encAlgorithm = encAlgorithm;
this.s2k = s2k;
this.secKeyData = secKeyData;
}
+
+ ///
+ /// Create a v6 SKESK packet.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public SymmetricKeyEncSessionPacket(
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ AeadAlgorithmTag aeadAlgorithm,
+ byte[] iv,
+ S2k s2k,
+ byte[] secKeyData)
+ : base(PacketTag.SymmetricKeyEncryptedSessionKey)
+ {
+ this.version = Version6;
+ this.encAlgorithm = encAlgorithm;
+ this.aeadAlgorithm = aeadAlgorithm;
+ this.s2k = s2k;
+ this.secKeyData = Arrays.Clone(secKeyData);
+
+ int expectedIVLen = AeadUtils.GetIVLength(aeadAlgorithm);
+ if (expectedIVLen != iv.Length)
+ {
+ throw new ArgumentException($"Mismatched AEAD IV length. Expected {expectedIVLen}, got {iv.Length}");
+ }
+
+ this.iv = Arrays.Clone(iv);
+ }
+
/**
* @return int
*/
@@ -42,6 +135,11 @@ public SymmetricKeyAlgorithmTag EncAlgorithm
get { return encAlgorithm; }
}
+ public AeadAlgorithmTag AeadAlgorithm
+ {
+ get { return aeadAlgorithm; }
+ }
+
/**
* @return S2k
*/
@@ -66,21 +164,69 @@ public int Version
get { return version; }
}
- public override void Encode(BcpgOutputStream bcpgOut)
+ internal byte[] GetAAData()
+ {
+ return CreateAAData(Version, EncAlgorithm, AeadAlgorithm);
+ }
+
+ internal static byte[] CreateAAData(int version, SymmetricKeyAlgorithmTag encAlgorithm, AeadAlgorithmTag aeadAlgorithm)
{
- MemoryStream bOut = new MemoryStream();
- using (var pOut = new BcpgOutputStream(bOut))
+ return new byte[]
{
- pOut.Write((byte)version, (byte)encAlgorithm);
- pOut.WriteObject(s2k);
+ 0xC0 | (byte)PacketTag.SymmetricKeyEncryptedSessionKey,
+ (byte)version,
+ (byte)encAlgorithm,
+ (byte)aeadAlgorithm
+ };
+ }
+
+ internal byte[] GetAeadIV()
+ {
+ return Arrays.Clone(iv);
+ }
- if (secKeyData != null && secKeyData.Length > 0)
+ public override void Encode(BcpgOutputStream bcpgOut)
+ {
+ using (MemoryStream bOut = new MemoryStream())
+ {
+ using (var pOut = new BcpgOutputStream(bOut))
{
- pOut.Write(secKeyData);
+ pOut.WriteByte((byte)version);
+
+ if (version == Version4)
+ {
+ pOut.WriteByte((byte)encAlgorithm);
+ pOut.WriteObject(s2k);
+
+ if (secKeyData != null && secKeyData.Length > 0)
+ {
+ pOut.Write(secKeyData);
+ }
+ }
+ else if (version == Version5 || version == Version6)
+ {
+ var s2kenc = s2k.GetEncoded();
+ int s2kLen = s2kenc.Length;
+
+ // len of 5 following fields
+ int count = 1 + 1 + 1 + s2kLen + iv.Length;
+ pOut.WriteByte((byte)count);
+
+ pOut.WriteByte((byte)encAlgorithm);
+ pOut.WriteByte((byte)aeadAlgorithm);
+ pOut.WriteByte((byte)s2kLen);
+ pOut.Write(s2kenc);
+ pOut.Write(iv);
+
+ if (secKeyData != null && secKeyData.Length > 0)
+ {
+ pOut.Write(secKeyData);
+ }
+ }
}
- }
- bcpgOut.WritePacket(PacketTag.SymmetricKeyEncryptedSessionKey, bOut.ToArray());
+ bcpgOut.WritePacket(PacketTag.SymmetricKeyEncryptedSessionKey, bOut.ToArray());
+ }
}
}
}
diff --git a/crypto/src/bcpg/TrustPacket.cs b/crypto/src/bcpg/TrustPacket.cs
index ad227ddc88..4d143ebccf 100644
--- a/crypto/src/bcpg/TrustPacket.cs
+++ b/crypto/src/bcpg/TrustPacket.cs
@@ -7,11 +7,13 @@ public class TrustPacket
private readonly byte[] m_levelAndTrustAmount;
public TrustPacket(BcpgInputStream bcpgIn)
+ :base(PacketTag.Trust)
{
m_levelAndTrustAmount = bcpgIn.ReadAll();
}
public TrustPacket(int trustCode)
+ : base(PacketTag.Trust)
{
m_levelAndTrustAmount = new byte[]{ (byte)trustCode };
}
diff --git a/crypto/src/bcpg/UserAttributePacket.cs b/crypto/src/bcpg/UserAttributePacket.cs
index e976f1215f..8d1261f700 100644
--- a/crypto/src/bcpg/UserAttributePacket.cs
+++ b/crypto/src/bcpg/UserAttributePacket.cs
@@ -13,6 +13,7 @@ public class UserAttributePacket
public UserAttributePacket(
BcpgInputStream bcpgIn)
+ :base(PacketTag.UserAttribute)
{
UserAttributeSubpacketsParser sIn = new UserAttributeSubpacketsParser(bcpgIn);
UserAttributeSubpacket sub;
@@ -28,6 +29,7 @@ public UserAttributePacket(
public UserAttributePacket(
UserAttributeSubpacket[] subpackets)
+ : base(PacketTag.UserAttribute)
{
this.subpackets = subpackets;
}
diff --git a/crypto/src/bcpg/UserIdPacket.cs b/crypto/src/bcpg/UserIdPacket.cs
index 7d3d3a846f..86ae6d7b35 100644
--- a/crypto/src/bcpg/UserIdPacket.cs
+++ b/crypto/src/bcpg/UserIdPacket.cs
@@ -14,16 +14,19 @@ public class UserIdPacket
private readonly byte[] idData;
public UserIdPacket(BcpgInputStream bcpgIn)
+ :base(PacketTag.UserId)
{
this.idData = bcpgIn.ReadAll();
}
public UserIdPacket(string id)
+ : base(PacketTag.UserId)
{
this.idData = Encoding.UTF8.GetBytes(id);
}
public UserIdPacket(byte[] rawId)
+ : base(PacketTag.UserId)
{
this.idData = Arrays.Clone(rawId);
}
diff --git a/crypto/src/bcpg/X25519PublicBCPGKey.cs b/crypto/src/bcpg/X25519PublicBCPGKey.cs
new file mode 100644
index 0000000000..edb8f67e8e
--- /dev/null
+++ b/crypto/src/bcpg/X25519PublicBCPGKey.cs
@@ -0,0 +1,19 @@
+namespace Org.BouncyCastle.Bcpg
+{
+ public sealed class X25519PublicBcpgKey
+ : OctetArrayBcpgKey
+ {
+ // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-part-for-x
+ public const int length = 32;
+
+ public X25519PublicBcpgKey(BcpgInputStream bcpgIn)
+ : base(length, bcpgIn)
+ {
+ }
+
+ public X25519PublicBcpgKey(byte[] key)
+ : base(length, key)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/crypto/src/bcpg/X25519SecretBCPGKey.cs b/crypto/src/bcpg/X25519SecretBCPGKey.cs
new file mode 100644
index 0000000000..cbdf8abd58
--- /dev/null
+++ b/crypto/src/bcpg/X25519SecretBCPGKey.cs
@@ -0,0 +1,19 @@
+namespace Org.BouncyCastle.Bcpg
+{
+ public sealed class X25519SecretBcpgKey
+ : OctetArrayBcpgKey
+ {
+ // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-part-for-x
+ public const int length = 32;
+
+ public X25519SecretBcpgKey(BcpgInputStream bcpgIn)
+ : base(length, bcpgIn)
+ {
+ }
+
+ public X25519SecretBcpgKey(byte[] key)
+ : base(length, key)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/crypto/src/bcpg/X448PublicBCPGKey.cs b/crypto/src/bcpg/X448PublicBCPGKey.cs
new file mode 100644
index 0000000000..c6f370bb12
--- /dev/null
+++ b/crypto/src/bcpg/X448PublicBCPGKey.cs
@@ -0,0 +1,19 @@
+namespace Org.BouncyCastle.Bcpg
+{
+ public sealed class X448PublicBcpgKey
+ : OctetArrayBcpgKey
+ {
+ // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-part-for-x4
+ public const int length = 56;
+
+ public X448PublicBcpgKey(BcpgInputStream bcpgIn)
+ : base(length, bcpgIn)
+ {
+ }
+
+ public X448PublicBcpgKey(byte[] key)
+ : base(length, key)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/crypto/src/bcpg/X448SecretBCPGKey.cs b/crypto/src/bcpg/X448SecretBCPGKey.cs
new file mode 100644
index 0000000000..16bfab2f5d
--- /dev/null
+++ b/crypto/src/bcpg/X448SecretBCPGKey.cs
@@ -0,0 +1,19 @@
+namespace Org.BouncyCastle.Bcpg
+{
+ public sealed class X448SecretBcpgKey
+ : OctetArrayBcpgKey
+ {
+ // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-part-for-x4
+ public const int length = 56;
+
+ public X448SecretBcpgKey(BcpgInputStream bcpgIn)
+ : base(length, bcpgIn)
+ {
+ }
+
+ public X448SecretBcpgKey(byte[] key)
+ : base(length, key)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/crypto/src/bcpg/sig/Features.cs b/crypto/src/bcpg/sig/Features.cs
index a04d2cf9da..df80bde32e 100644
--- a/crypto/src/bcpg/sig/Features.cs
+++ b/crypto/src/bcpg/sig/Features.cs
@@ -19,6 +19,10 @@ public class Features
fingerprint format */
public static readonly byte FEATURE_VERSION_5_PUBLIC_KEY = 0x04;
+ /** Identifier for the Version 2 Symmetrically Encrypted and Integrity Protected
+ Data packet */
+ public static readonly byte FEATURE_VERSION_2_SEIPD = 0x08;
+
private static byte[] FeatureToByteArray(byte feature)
{
return new byte[1]{ feature };
diff --git a/crypto/src/crypto/generators/Argon2BytesGenerator.cs b/crypto/src/crypto/generators/Argon2BytesGenerator.cs
index d9e4fc3d3e..8e182f85e0 100644
--- a/crypto/src/crypto/generators/Argon2BytesGenerator.cs
+++ b/crypto/src/crypto/generators/Argon2BytesGenerator.cs
@@ -770,3 +770,4 @@ internal Position(int pass, int slice, int lane)
}
}
}
+
diff --git a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
index ac847ddb6b..2c55469530 100644
--- a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
+++ b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
-
+using System.Text;
using Org.BouncyCastle.Asn1.Cryptlib;
using Org.BouncyCastle.Asn1.EdEC;
using Org.BouncyCastle.Crypto;
@@ -22,9 +22,10 @@ public class PgpEncryptedDataGenerator
{
private BcpgOutputStream pOut;
private CipherStream cOut;
+ private AeadOutputStream aeadOut;
private IBufferedCipher c;
- private bool withIntegrityPacket;
- private bool oldFormat;
+ private readonly bool withIntegrityPacket;
+ private readonly bool oldFormat;
private DigestStream digestOut;
private abstract class EncMethod
@@ -32,24 +33,37 @@ private abstract class EncMethod
{
protected byte[] sessionInfo;
protected SymmetricKeyAlgorithmTag encAlgorithm;
+ protected AeadAlgorithmTag aeadAlgorithm;
protected KeyParameter key;
+ protected byte[] aeadIv;
- public abstract void AddSessionInfo(byte[] si, SecureRandom random);
+ protected EncMethod(PacketTag packetTag)
+ : base(packetTag)
+ {
+ }
+
+ public abstract void AddSessionInfo(byte[] si, SecureRandom random);
}
private class PbeMethod
: EncMethod
{
- private S2k s2k;
+ private readonly S2k s2k;
+ private readonly int skeskVersion;
internal PbeMethod(
SymmetricKeyAlgorithmTag encAlgorithm,
+ AeadAlgorithmTag aeadAlgorithm,
S2k s2k,
- KeyParameter key)
+ KeyParameter key,
+ int skeskVersion)
+ : base(PacketTag.SymmetricKeyEncryptedSessionKey)
{
this.encAlgorithm = encAlgorithm;
+ this.aeadAlgorithm = aeadAlgorithm;
this.s2k = s2k;
this.key = key;
+ this.skeskVersion = skeskVersion;
}
public KeyParameter GetKey()
@@ -57,23 +71,71 @@ public KeyParameter GetKey()
return key;
}
- public override void AddSessionInfo(
- byte[] si,
- SecureRandom random)
+ private byte[] EncryptSessionInfoForVersion4(byte[] si, SecureRandom random)
{
string cName = PgpUtilities.GetSymmetricCipherName(encAlgorithm);
- IBufferedCipher c = CipherUtilities.GetCipher(cName + "/CFB/NoPadding");
+ IBufferedCipher cipher = CipherUtilities.GetCipher($"{cName}/CFB/NoPadding");
- byte[] iv = new byte[c.GetBlockSize()];
- c.Init(true, new ParametersWithRandom(new ParametersWithIV(key, iv), random));
+ byte[] iv = new byte[cipher.GetBlockSize()];
+ cipher.Init(true, new ParametersWithRandom(new ParametersWithIV(key, iv), random));
+
+ return cipher.DoFinal(si, 0, si.Length - 2);
+ }
- this.sessionInfo = c.DoFinal(si, 0, si.Length - 2);
- }
+ private byte[] EncryptSessionInfoForVersion6(byte[] si, SecureRandom random)
+ {
+ byte[] aadata = SymmetricKeyEncSessionPacket.CreateAAData(skeskVersion, encAlgorithm, aeadAlgorithm);
+
+ // key-encryption key derivation
+ var hkdfParams = new HkdfParameters(key.GetKey(), Array.Empty(), aadata);
+ var hkdfGen = new HkdfBytesGenerator(PgpUtilities.CreateDigest(HashAlgorithmTag.Sha256));
+ hkdfGen.Init(hkdfParams);
+ var hkdfOutput = new byte[PgpUtilities.GetKeySizeInOctets(encAlgorithm)];
+ hkdfGen.GenerateBytes(hkdfOutput, 0, hkdfOutput.Length);
+
+ BufferedAeadBlockCipher cipher = AeadUtils.CreateAeadCipher(encAlgorithm, aeadAlgorithm);
+
+ aeadIv = new byte[AeadUtils.GetIVLength(aeadAlgorithm)];
+ random.NextBytes(aeadIv);
+
+ var aeadParams = new AeadParameters(
+ new KeyParameter(hkdfOutput),
+ 8 * AeadUtils.GetAuthTagLength(aeadAlgorithm),
+ aeadIv,
+ aadata);
+
+ cipher.Init(true, aeadParams);
+ byte[] keyBytes = cipher.DoFinal(si, 0, si.Length-2);
+
+ return keyBytes;
+ }
+
+ public override void AddSessionInfo(
+ byte[] si,
+ SecureRandom random)
+ {
+ if (skeskVersion == SymmetricKeyEncSessionPacket.Version4)
+ {
+ this.sessionInfo = EncryptSessionInfoForVersion4(si, random);
+ }
+ else if (skeskVersion == SymmetricKeyEncSessionPacket.Version6)
+ {
+ this.sessionInfo = EncryptSessionInfoForVersion6(si, random);
+ }
+ }
public override void Encode(BcpgOutputStream pOut)
{
- SymmetricKeyEncSessionPacket pk = new SymmetricKeyEncSessionPacket(
- encAlgorithm, s2k, sessionInfo);
+ SymmetricKeyEncSessionPacket pk;
+ if (skeskVersion == SymmetricKeyEncSessionPacket.Version6)
+ {
+ pk = new SymmetricKeyEncSessionPacket(
+ encAlgorithm, aeadAlgorithm, aeadIv, s2k, sessionInfo);
+ }
+ else
+ {
+ pk = new SymmetricKeyEncSessionPacket(encAlgorithm, s2k, sessionInfo);
+ }
pOut.WritePacket(pk);
}
@@ -85,11 +147,14 @@ private class PubMethod
internal PgpPublicKey pubKey;
internal bool sessionKeyObfuscation;
internal byte[][] data;
+ private readonly int pkeskVersion;
- internal PubMethod(PgpPublicKey pubKey, bool sessionKeyObfuscation)
+ internal PubMethod(PgpPublicKey pubKey, bool sessionKeyObfuscation, int pkeskVersion)
+ : base(PacketTag.PublicKeyEncryptedSession)
{
this.pubKey = pubKey;
this.sessionKeyObfuscation = sessionKeyObfuscation;
+ this.pkeskVersion = pkeskVersion;
}
public override void AddSessionInfo(
@@ -105,6 +170,86 @@ private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random)
{
var cryptoPublicKey = pubKey.GetKey();
+ if (pubKey.Algorithm == PublicKeyAlgorithmTag.X25519 || pubKey.Algorithm == PublicKeyAlgorithmTag.X448)
+ {
+ IAsymmetricCipherKeyPairGenerator kpGen;
+ IRawAgreement agreement;
+ AsymmetricCipherKeyPair ephemeral;
+ byte[] ephPubEncoding;
+ IDigest digestForHkdf;
+ byte[] hkdfInfo;
+ SymmetricKeyAlgorithmTag wrappingAlgo;
+
+ if (pubKey.Algorithm == PublicKeyAlgorithmTag.X25519)
+ {
+ agreement = new X25519Agreement();
+ kpGen = new X25519KeyPairGenerator();
+ kpGen.Init(new X25519KeyGenerationParameters(random));
+ digestForHkdf = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha256);
+ hkdfInfo = Encoding.ASCII.GetBytes("OpenPGP X25519");
+ wrappingAlgo = SymmetricKeyAlgorithmTag.Aes128;
+ ephemeral = kpGen.GenerateKeyPair();
+ ephPubEncoding = new byte[X25519PublicKeyParameters.KeySize];
+ ((X25519PublicKeyParameters)ephemeral.Public).Encode(ephPubEncoding, 0);
+ }
+ else
+ {
+ // X448
+ agreement = new X448Agreement();
+ kpGen = new X448KeyPairGenerator();
+ kpGen.Init(new X448KeyGenerationParameters(random));
+ digestForHkdf = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha512);
+ hkdfInfo = Encoding.ASCII.GetBytes("OpenPGP X448");
+ wrappingAlgo = SymmetricKeyAlgorithmTag.Aes256;
+ ephemeral = kpGen.GenerateKeyPair();
+ ephPubEncoding = new byte[X448PublicKeyParameters.KeySize];
+ ((X448PublicKeyParameters)ephemeral.Public).Encode(ephPubEncoding, 0);
+ }
+
+ agreement.Init(ephemeral.Private);
+ byte[] sharedSecret = new byte[agreement.AgreementSize];
+ agreement.CalculateAgreement(cryptoPublicKey, sharedSecret, 0);
+
+ byte[] pubKeyMaterial = ((OctetArrayBcpgKey)pubKey.PublicKeyPacket.Key).GetKey();
+ byte[] ikm = Arrays.ConcatenateAll(ephPubEncoding, pubKeyMaterial, sharedSecret);
+ byte[] hkdfSalt = Array.Empty();
+ var hkdfParams = new HkdfParameters(ikm, hkdfSalt, hkdfInfo);
+ var hkdfGen = new HkdfBytesGenerator(digestForHkdf);
+ hkdfGen.Init(hkdfParams);
+ var hkdfOutput = new byte[PgpUtilities.GetKeySizeInOctets(wrappingAlgo)];
+ hkdfGen.GenerateBytes(hkdfOutput, 0, hkdfOutput.Length);
+
+ KeyParameter kek = ParameterUtilities.CreateKeyParameter("AES", hkdfOutput);
+ var wrapper = PgpUtilities.CreateWrapper(wrappingAlgo);
+ wrapper.Init(true, kek);
+
+ int offset = 0;
+ int length = sessionInfo.Length - 2; // no checksum for X25519 and X448 keys
+ // for X25519 and X448 keys the SymmetricKeyAlgorithmTag, when present (V3 PKESK)
+ // is not encrypted, is prepended to the ESK in plaintext
+ if (pkeskVersion == PublicKeyEncSessionPacket.Version3)
+ {
+ offset = 1;
+ length--;
+ }
+ var keyBytes = wrapper.Wrap(sessionInfo, offset, length);
+
+ byte[] esk;
+ using (var ms = new MemoryStream())
+ {
+ ms.Write(ephPubEncoding, 0, ephPubEncoding.Length);
+ if (pkeskVersion == PublicKeyEncSessionPacket.Version3)
+ {
+ // Unencrypted SymmetricKeyAlgorithmTag (V3 PKESK only)
+ ms.WriteByte(sessionInfo[0]);
+ }
+ ms.Write(keyBytes, 0, keyBytes.Length);
+ esk = ms.ToArray();
+ }
+
+ return esk;
+ }
+
if (pubKey.Algorithm != PublicKeyAlgorithmTag.ECDH)
{
IBufferedCipher c;
@@ -268,6 +413,16 @@ private byte[][] ProcessSessionInfo(byte[] encryptedSessionInfo)
case PublicKeyAlgorithmTag.ECDH:
data = new byte[1][]{ encryptedSessionInfo };
break;
+ case PublicKeyAlgorithmTag.X25519:
+ case PublicKeyAlgorithmTag.X448:
+ int ephemeralKeyLen = pubKey.Algorithm == PublicKeyAlgorithmTag.X25519 ? 32 : 56;
+ byte[] ephemeralKey = new byte[ephemeralKeyLen];
+ byte[] encryptedSessionKey = new byte[encryptedSessionInfo.Length - ephemeralKeyLen];
+ Array.Copy(encryptedSessionInfo, 0, ephemeralKey, 0, ephemeralKeyLen);
+ Array.Copy(encryptedSessionInfo, ephemeralKeyLen, encryptedSessionKey, 0, encryptedSessionKey.Length);
+
+ data = new byte[][] { ephemeralKey, encryptedSessionKey };
+ break;
default:
throw new PgpException("unknown asymmetric algorithm: " + pubKey.Algorithm);
}
@@ -289,7 +444,16 @@ private byte[] ConvertToEncodedMpi(byte[] encryptedSessionInfo)
public override void Encode(BcpgOutputStream pOut)
{
- PublicKeyEncSessionPacket pk = new PublicKeyEncSessionPacket(pubKey.KeyId, pubKey.Algorithm, data);
+ PublicKeyEncSessionPacket pk;
+
+ if (pkeskVersion == PublicKeyEncSessionPacket.Version6)
+ {
+ pk = new PublicKeyEncSessionPacket(pubKey.Version, pubKey.GetFingerprint(), pubKey.Algorithm, data);
+ }
+ else
+ {
+ pk = new PublicKeyEncSessionPacket(pubKey.KeyId, pubKey.Algorithm, data);
+ }
pOut.WritePacket(pk);
}
@@ -297,22 +461,25 @@ public override void Encode(BcpgOutputStream pOut)
private readonly List methods = new List();
private readonly SymmetricKeyAlgorithmTag defAlgorithm;
+ private readonly AeadAlgorithmTag defAeadAlgorithm;
private readonly SecureRandom rand;
- public PgpEncryptedDataGenerator(
+ private readonly int skeskVersion;
+ private readonly int pkeskVersion;
+ private readonly int seipdVersion;
+ private readonly byte chunkSizeOctet = 6; // 1 << (chunkSize + 6) = 4096
+
+ public PgpEncryptedDataGenerator(
SymmetricKeyAlgorithmTag encAlgorithm)
+ :this(encAlgorithm, CryptoServicesRegistrar.GetSecureRandom(), false, false)
{
- this.defAlgorithm = encAlgorithm;
- this.rand = CryptoServicesRegistrar.GetSecureRandom();
}
public PgpEncryptedDataGenerator(
SymmetricKeyAlgorithmTag encAlgorithm,
bool withIntegrityPacket)
- {
- this.defAlgorithm = encAlgorithm;
- this.withIntegrityPacket = withIntegrityPacket;
- this.rand = CryptoServicesRegistrar.GetSecureRandom();
+ : this(encAlgorithm, CryptoServicesRegistrar.GetSecureRandom(), false, withIntegrityPacket)
+ {
}
/// Existing SecureRandom constructor.
@@ -321,12 +488,8 @@ public PgpEncryptedDataGenerator(
public PgpEncryptedDataGenerator(
SymmetricKeyAlgorithmTag encAlgorithm,
SecureRandom random)
+ : this(encAlgorithm, random, false, false)
{
- if (random == null)
- throw new ArgumentNullException(nameof(random));
-
- this.defAlgorithm = encAlgorithm;
- this.rand = random;
}
/// Creates a cipher stream which will have an integrity packet associated with it.
@@ -334,13 +497,8 @@ public PgpEncryptedDataGenerator(
SymmetricKeyAlgorithmTag encAlgorithm,
bool withIntegrityPacket,
SecureRandom random)
+ :this(encAlgorithm, random, false, withIntegrityPacket)
{
- if (random == null)
- throw new ArgumentNullException(nameof(random));
-
- this.defAlgorithm = encAlgorithm;
- this.rand = random;
- this.withIntegrityPacket = withIntegrityPacket;
}
/// Base constructor.
@@ -351,13 +509,57 @@ public PgpEncryptedDataGenerator(
SymmetricKeyAlgorithmTag encAlgorithm,
SecureRandom random,
bool oldFormat)
+ :this (encAlgorithm, random, oldFormat, false)
{
- if (random == null)
- throw new ArgumentNullException(nameof(random));
+ }
+ private PgpEncryptedDataGenerator(
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ SecureRandom random,
+ bool oldFormat,
+ bool withIntegrityPacket)
+ {
+ this.rand = random ?? throw new ArgumentNullException(nameof(random));
this.defAlgorithm = encAlgorithm;
- this.rand = random;
this.oldFormat = oldFormat;
+ this.withIntegrityPacket = withIntegrityPacket;
+
+ skeskVersion = SymmetricKeyEncSessionPacket.Version4;
+ pkeskVersion = PublicKeyEncSessionPacket.Version3;
+ seipdVersion = SymmetricEncIntegrityPacket.Version1;
+ }
+
+
+ public PgpEncryptedDataGenerator(
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ AeadAlgorithmTag aeadAlgorithm)
+ : this(encAlgorithm, aeadAlgorithm, CryptoServicesRegistrar.GetSecureRandom(), false)
+ {
+ }
+
+ public PgpEncryptedDataGenerator(
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ AeadAlgorithmTag aeadAlgorithm,
+ SecureRandom random)
+ : this(encAlgorithm, aeadAlgorithm, random, false)
+ {
+ }
+
+ public PgpEncryptedDataGenerator(
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ AeadAlgorithmTag aeadAlgorithm,
+ SecureRandom random,
+ bool oldFormat)
+ {
+ this.rand = random ?? throw new ArgumentNullException(nameof(random));
+ this.defAlgorithm = encAlgorithm;
+ this.defAeadAlgorithm = aeadAlgorithm;
+ this.oldFormat = oldFormat;
+ this.withIntegrityPacket = true;
+
+ skeskVersion = SymmetricKeyEncSessionPacket.Version6;
+ pkeskVersion = PublicKeyEncSessionPacket.Version6;
+ seipdVersion = SymmetricEncIntegrityPacket.Version2;
}
/// Add a PBE encryption method to the encrypted object.
@@ -392,7 +594,49 @@ internal void DoAddMethod(byte[] rawPassPhrase, bool clearPassPhrase, HashAlgori
{
S2k s2k = PgpUtilities.GenerateS2k(s2kDigest, 0x60, rand);
- methods.Add(new PbeMethod(defAlgorithm, s2k, PgpUtilities.DoMakeKeyFromPassPhrase(defAlgorithm, s2k, rawPassPhrase, clearPassPhrase)));
+ methods.Add(new PbeMethod(defAlgorithm, defAeadAlgorithm, s2k, PgpUtilities.DoMakeKeyFromPassPhrase(defAlgorithm, s2k, rawPassPhrase, clearPassPhrase), skeskVersion));
+ }
+
+ /// Add a PBE encryption method to the encrypted object.
+ ///
+ /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is
+ /// the historical behaviour of the library (1.7 and earlier).
+ ///
+ public void AddMethod(char[] passPhrase, S2k.Argon2Parameters argon2Parameters)
+ {
+ DoAddMethod(PgpUtilities.EncodePassPhrase(passPhrase, false), true, argon2Parameters);
+ }
+
+ /// Add a PBE encryption method to the encrypted object.
+ ///
+ /// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes).
+ ///
+ public void AddMethodUtf8(char[] passPhrase, S2k.Argon2Parameters argon2Parameters)
+ {
+ DoAddMethod(PgpUtilities.EncodePassPhrase(passPhrase, true), true, argon2Parameters);
+ }
+
+ /// Add a PBE encryption method to the encrypted object.
+ ///
+ /// Allows the caller to handle the encoding of the passphrase to bytes.
+ ///
+ public void AddMethodRaw(byte[] rawPassPhrase, S2k.Argon2Parameters argon2Parameters)
+ {
+ DoAddMethod(rawPassPhrase, false, argon2Parameters);
+ }
+
+ internal void DoAddMethod(byte[] rawPassPhrase, bool clearPassPhrase, S2k.Argon2Parameters argon2Parameters)
+ {
+ S2k s2k = new S2k(argon2Parameters);
+
+ methods.Add(
+ new PbeMethod(
+ defAlgorithm,
+ defAeadAlgorithm,
+ s2k,
+ PgpUtilities.DoMakeKeyFromPassPhrase(defAlgorithm, s2k, rawPassPhrase, clearPassPhrase),
+ skeskVersion
+ ));
}
/// Add a public key encrypted session key to the encrypted object.
@@ -408,7 +652,13 @@ public void AddMethod(PgpPublicKey key, bool sessionKeyObfuscation)
throw new ArgumentException("passed in key not an encryption key!");
}
- methods.Add(new PubMethod(key, sessionKeyObfuscation));
+ if (pkeskVersion == PublicKeyEncSessionPacket.Version6
+ && (key.Algorithm == PublicKeyAlgorithmTag.ElGamalEncrypt || key.Algorithm == PublicKeyAlgorithmTag.ElGamalGeneral))
+ {
+ throw new PgpException("cannot generate ElGamal v6 PKESK (see https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-fields-fo)");
+ }
+
+ methods.Add(new PubMethod(key, sessionKeyObfuscation, pkeskVersion));
}
private void AddCheckSum(
@@ -431,9 +681,22 @@ private void AddCheckSum(
private byte[] CreateSessionInfo(SymmetricKeyAlgorithmTag algorithm, KeyParameter key)
{
int keyLength = key.KeyLength;
- byte[] sessionInfo = new byte[keyLength + 3];
- sessionInfo[0] = (byte)algorithm;
- key.CopyTo(sessionInfo, 1, keyLength);
+ int infoLen = keyLength + 2;
+ int offset = 0;
+
+ if (seipdVersion == SymmetricEncIntegrityPacket.Version1)
+ {
+ infoLen++;
+ offset = 1;
+ }
+
+ byte[] sessionInfo = new byte[infoLen];
+
+ if (seipdVersion == SymmetricEncIntegrityPacket.Version1)
+ {
+ sessionInfo[0] = (byte)algorithm;
+ }
+ key.CopyTo(sessionInfo, offset, keyLength);
AddCheckSum(sessionInfo);
return sessionInfo;
}
@@ -454,7 +717,7 @@ private Stream Open(
long length,
byte[] buffer)
{
- if (cOut != null)
+ if (cOut != null || aeadOut != null)
throw new InvalidOperationException("generator already in open state");
if (methods.Count == 0)
throw new InvalidOperationException("No encryption methods specified");
@@ -467,29 +730,31 @@ private Stream Open(
if (methods.Count == 1)
{
- if (methods[0] is PbeMethod pbeMethod)
+ if (methods[0] is PbeMethod pbeMethod && skeskVersion == SymmetricKeyEncSessionPacket.Version4)
{
- key = pbeMethod.GetKey();
+ // For V4 SKESK, the encrypted session key is optional. If not present, the session key
+ // is derived directly with the S2K algorithm applied to the passphrase
+ key = pbeMethod.GetKey();
}
- else if (methods[0] is PubMethod pubMethod)
+ //else if (methods[0] is PubMethod pubMethod)
+ else
{
key = PgpUtilities.MakeRandomKey(defAlgorithm, rand);
-
byte[] sessionInfo = CreateSessionInfo(defAlgorithm, key);
try
{
- pubMethod.AddSessionInfo(sessionInfo, rand);
+ methods[0].AddSessionInfo(sessionInfo, rand);
}
catch (Exception e)
{
throw new PgpException("exception encrypting session key", e);
}
}
- else
- {
- throw new InvalidOperationException();
- }
+ //else
+ //{
+ // throw new InvalidOperationException();
+ //}
pOut.WritePacket(methods[0]);
}
@@ -513,6 +778,63 @@ private Stream Open(
}
}
+
+ if (seipdVersion == SymmetricEncIntegrityPacket.Version2)
+ {
+ if (buffer == null)
+ {
+ int chunkSize = 1 << (chunkSizeOctet + 6);
+ long chunks = ((length + chunkSize - 1) / chunkSize);
+
+ long outputLength = length
+ + 1 // version
+ + 1 // algo ID
+ + 1 // AEAD algo ID
+ + 1 // chunk size octet
+ + 32 // salt
+ + AeadUtils.GetAuthTagLength(defAeadAlgorithm) * (chunks + 1); // one auth tag for each chunk plus final tag
+
+ pOut = new BcpgOutputStream(outStr, PacketTag.SymmetricEncryptedIntegrityProtected, outputLength);
+ }
+ else
+ {
+ pOut = new BcpgOutputStream(outStr, PacketTag.SymmetricEncryptedIntegrityProtected, buffer);
+ }
+
+ pOut.WriteByte(SymmetricEncIntegrityPacket.Version2);
+ pOut.WriteByte((byte)defAlgorithm);
+ pOut.WriteByte((byte)defAeadAlgorithm);
+ pOut.WriteByte((byte)chunkSizeOctet);
+
+ byte[] salt = new byte[32];
+ rand.NextBytes(salt);
+ pOut.Write(salt);
+
+ var cipher = AeadUtils.CreateAeadCipher(defAlgorithm, defAeadAlgorithm);
+ byte[] aadata = SymmetricEncIntegrityPacket.CreateAAData(SymmetricEncIntegrityPacket.Version2, defAlgorithm, defAeadAlgorithm, chunkSizeOctet);
+
+ AeadUtils.DeriveAeadMessageKeyAndIv(
+ key,
+ defAlgorithm,
+ defAeadAlgorithm,
+ salt,
+ aadata,
+ out var messageKey,
+ out var iv);
+
+ aeadOut = new AeadOutputStream(
+ pOut,
+ cipher,
+ messageKey,
+ iv,
+ defAlgorithm,
+ defAeadAlgorithm,
+ chunkSizeOctet);
+
+ return new WrappedGeneratorStream(this, aeadOut);
+
+ }
+
string cName = PgpUtilities.GetSymmetricCipherName(defAlgorithm);
if (cName == null)
throw new PgpException("null cipher specified");
@@ -627,6 +949,14 @@ public Stream Open(
[Obsolete("Dispose any opened Stream directly")]
public void Close()
{
+ if(aeadOut != null)
+ {
+ aeadOut.Close();
+ pOut.Finish();
+
+ aeadOut = null;
+ pOut = null;
+ }
if (cOut != null)
{
// TODO Should this all be under the try/catch block?
diff --git a/crypto/src/openpgp/PgpKeyPair.cs b/crypto/src/openpgp/PgpKeyPair.cs
index 9cf78fa6fb..bfa2e44c21 100644
--- a/crypto/src/openpgp/PgpKeyPair.cs
+++ b/crypto/src/openpgp/PgpKeyPair.cs
@@ -27,19 +27,38 @@ public PgpKeyPair(
{
}
- public PgpKeyPair(
+ public PgpKeyPair(
+ int version,
+ PublicKeyAlgorithmTag algorithm,
+ AsymmetricCipherKeyPair keyPair,
+ DateTime time)
+ : this(version, algorithm, keyPair.Public, keyPair.Private, time)
+ {
+ }
+
+ public PgpKeyPair(
PublicKeyAlgorithmTag algorithm,
AsymmetricKeyParameter pubKey,
AsymmetricKeyParameter privKey,
DateTime time)
+ :this(PublicKeyPacket.DefaultVersion, algorithm, pubKey, privKey, time)
+ {
+ }
+
+ public PgpKeyPair(
+ int version,
+ PublicKeyAlgorithmTag algorithm,
+ AsymmetricKeyParameter pubKey,
+ AsymmetricKeyParameter privKey,
+ DateTime time)
{
- this.pub = new PgpPublicKey(algorithm, pubKey, time);
- this.priv = new PgpPrivateKey(pub.KeyId, pub.PublicKeyPacket, privKey);
+ this.pub = new PgpPublicKey(version, algorithm, pubKey, time);
+ this.priv = new PgpPrivateKey(pub, privKey);
}
- /// Create a key pair from a PgpPrivateKey and a PgpPublicKey.
- /// The public key.
- /// The private key.
+ /// Create a key pair from a PgpPrivateKey and a PgpPublicKey.
+ /// The public key.
+ /// The private key.
public PgpKeyPair(
PgpPublicKey pub,
PgpPrivateKey priv)
diff --git a/crypto/src/openpgp/PgpKeyRingGenerator.cs b/crypto/src/openpgp/PgpKeyRingGenerator.cs
index a04ebc7dfe..6ef7af816f 100644
--- a/crypto/src/openpgp/PgpKeyRingGenerator.cs
+++ b/crypto/src/openpgp/PgpKeyRingGenerator.cs
@@ -1,15 +1,13 @@
+using Org.BouncyCastle.Security;
using System;
using System.Collections.Generic;
-using Org.BouncyCastle.Security;
-using Org.BouncyCastle.Utilities;
-
namespace Org.BouncyCastle.Bcpg.OpenPgp
{
- ///
- /// Generator for a PGP master and subkey ring.
- /// This class will generate both the secret and public key rings
- ///
+ ///
+ /// Generator for a PGP master and subkey ring.
+ /// This class will generate both the secret and public key rings
+ ///
public class PgpKeyRingGenerator
{
private IList keys = new List();
@@ -274,7 +272,8 @@ public void AddSubKey(
PgpSignatureSubpacketVector hashedPackets,
PgpSignatureSubpacketVector unhashedPackets)
{
- AddSubKey(keyPair, hashedPackets, unhashedPackets, HashAlgorithmTag.Sha1);
+ AddSubKey(keyPair, hashedPackets, unhashedPackets,
+ keyPair.PublicKey.Version > PublicKeyPacket.Version4 ? HashAlgorithmTag.Sha256 : HashAlgorithmTag.Sha1);
}
///
@@ -300,7 +299,7 @@ public void AddSubKey(
//
// Generate the certification
//
- sGen.InitSign(PgpSignature.SubkeyBinding, masterKey.PrivateKey);
+ sGen.InitSign(PgpSignature.SubkeyBinding, masterKey.PrivateKey, rand);
sGen.SetHashedSubpackets(hashedPackets);
sGen.SetUnhashedSubpackets(unhashedPackets);
@@ -346,12 +345,12 @@ public void AddSubKey(
//
// Generate the certification
//
- sGen.InitSign(PgpSignature.SubkeyBinding, masterKey.PrivateKey);
+ sGen.InitSign(PgpSignature.SubkeyBinding, masterKey.PrivateKey, rand);
// add primary key binding sub packet
PgpSignatureGenerator pGen = new PgpSignatureGenerator(keyPair.PublicKey.Algorithm, primaryKeyBindingHashAlgorithm);
- pGen.InitSign(PgpSignature.PrimaryKeyBinding, keyPair.PrivateKey);
+ pGen.InitSign(PgpSignature.PrimaryKeyBinding, keyPair.PrivateKey, rand);
PgpSignatureSubpacketGenerator spGen = new PgpSignatureSubpacketGenerator(hashedPackets);
@@ -399,7 +398,7 @@ public PgpPublicKeyRing GeneratePublicKeyRing()
pgpSecretKey = enumerator.Current;
PgpPublicKey k = new PgpPublicKey(pgpSecretKey.PublicKey);
- k.publicPk = new PublicSubkeyPacket(k.Algorithm, k.CreationTime, k.publicPk.Key);
+ k.publicPk = new PublicSubkeyPacket(k.Version, k.Algorithm, k.CreationTime, k.publicPk.Key);
pubKeys.Add(k);
}
diff --git a/crypto/src/openpgp/PgpLiteralData.cs b/crypto/src/openpgp/PgpLiteralData.cs
index 92fafe657a..f9abb5ed48 100644
--- a/crypto/src/openpgp/PgpLiteralData.cs
+++ b/crypto/src/openpgp/PgpLiteralData.cs
@@ -62,5 +62,43 @@ public Stream GetDataStream()
{
return GetInputStream();
}
+
+ ///
+ /// Additional metadata for v5 signatures
+ /// https://www.ietf.org/archive/id/draft-ietf-openpgp-rfc4880bis-10.html#name-computing-signatures
+ /// Only for document signatures (type 0x00 or 0x01) the following three data items are hashed:
+ /// * the one-octet content format,
+ /// * the file name as a string (one octet length, followed by the file name)
+ /// * a four-octet number that indicates a date,
+ /// The three data items hashed for document signatures need to mirror the values of the
+ /// Literal Data packet.
+ /// For detached and cleartext signatures 6 zero bytes are hashed instead.
+ ///
+ /// Signature version
+ ///
+ public byte[] GetMetadata(int sigVersion)
+ {
+ // only v5 signatures requires additional metadata
+ if (sigVersion != SignaturePacket.Version5)
+ {
+ return Array.Empty();
+ }
+
+ using (var ms = new MemoryStream())
+ {
+ byte[] rawFileName = data.GetRawFileName();
+ long modTime = data.ModificationTime / 1000;
+ ms.WriteByte((byte)Format);
+ ms.WriteByte((byte)rawFileName.Length);
+ ms.Write(rawFileName, 0, rawFileName.Length);
+
+ ms.WriteByte((byte)(modTime >> 24));
+ ms.WriteByte((byte)(modTime >> 16));
+ ms.WriteByte((byte)(modTime >> 8));
+ ms.WriteByte((byte)modTime);
+
+ return ms.ToArray();
+ }
+ }
}
}
diff --git a/crypto/src/openpgp/PgpObjectFactory.cs b/crypto/src/openpgp/PgpObjectFactory.cs
index 068b851542..3dabd5e5ab 100644
--- a/crypto/src/openpgp/PgpObjectFactory.cs
+++ b/crypto/src/openpgp/PgpObjectFactory.cs
@@ -101,6 +101,8 @@ public PgpObject NextPgpObject()
}
case PacketTag.Marker:
return new PgpMarker(bcpgIn);
+ case PacketTag.Padding:
+ return new PgpPadding(bcpgIn);
case PacketTag.Experimental1:
case PacketTag.Experimental2:
case PacketTag.Experimental3:
diff --git a/crypto/src/openpgp/PgpOnePassSignature.cs b/crypto/src/openpgp/PgpOnePassSignature.cs
index c14e72bf77..c7505b072b 100644
--- a/crypto/src/openpgp/PgpOnePassSignature.cs
+++ b/crypto/src/openpgp/PgpOnePassSignature.cs
@@ -1,14 +1,11 @@
-using System;
-using System.IO;
-
using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
-using Org.BouncyCastle.Utilities;
+using System;
+using System.IO;
namespace Org.BouncyCastle.Bcpg.OpenPgp
{
- /// A one pass signature object.
+ /// A one pass signature object.
public class PgpOnePassSignature
{
private static OnePassSignaturePacket Cast(Packet packet)
@@ -55,6 +52,11 @@ public void InitVerify(PgpPublicKey pubKey)
try
{
sig.Init(false, key);
+ if (sigPack.Version == OnePassSignaturePacket.Version6)
+ {
+ byte[] salt = sigPack.GetSignatureSalt();
+ sig.BlockUpdate(salt, 0, salt.Length);
+ }
}
catch (InvalidKeyException e)
{
@@ -147,7 +149,23 @@ public void Update(ReadOnlySpan input)
/// Verify the calculated signature against the passed in PgpSignature.
public bool Verify(PgpSignature pgpSig)
{
- byte[] trailer = pgpSig.GetSignatureTrailer();
+ return Verify(pgpSig, Array.Empty());
+ }
+
+ public bool Verify(PgpSignature pgpSig, byte[] additionalMetadata)
+ {
+ // the versions of the Signature and the One-Pass Signature must be aligned as specified in
+ // https://www.rfc-editor.org/rfc/rfc9580#signed-message-versions
+ if (pgpSig.Version == SignaturePacket.Version6 && sigPack.Version != OnePassSignaturePacket.Version6)
+ {
+ return false;
+ }
+ if (pgpSig.Version < SignaturePacket.Version6 && sigPack.Version != OnePassSignaturePacket.Version3)
+ {
+ return false;
+ }
+ // Additional metadata for v5 signatures
+ byte[] trailer = pgpSig.GetSignatureTrailer(additionalMetadata);
sig.BlockUpdate(trailer, 0, trailer.Length);
@@ -159,7 +177,12 @@ public long KeyId
get { return sigPack.KeyId; }
}
- public int SignatureType
+ public int Version
+ {
+ get { return sigPack.Version; }
+ }
+
+ public int SignatureType
{
get { return sigPack.SignatureType; }
}
@@ -174,7 +197,17 @@ public PublicKeyAlgorithmTag KeyAlgorithm
get { return sigPack.KeyAlgorithm; }
}
- public byte[] GetEncoded()
+ public byte[] GetSignatureSalt()
+ {
+ return sigPack.GetSignatureSalt();
+ }
+
+ public byte[] GetFingerprint()
+ {
+ return sigPack.GetFingerprint();
+ }
+
+ public byte[] GetEncoded()
{
var bOut = new MemoryStream();
diff --git a/crypto/src/openpgp/PgpPadding.cs b/crypto/src/openpgp/PgpPadding.cs
new file mode 100644
index 0000000000..0fd6019299
--- /dev/null
+++ b/crypto/src/openpgp/PgpPadding.cs
@@ -0,0 +1,21 @@
+using System.IO;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+ public class PgpPadding
+ : PgpObject
+ {
+ private readonly PaddingPacket data;
+
+ public PgpPadding(BcpgInputStream bcpgInput)
+ {
+ Packet packet = bcpgInput.ReadPacket();
+ if (!(packet is PaddingPacket paddingPacket))
+ throw new IOException("unexpected packet in stream: " + packet);
+
+ data = paddingPacket;
+ }
+
+ public byte[] GetPadding() => data.GetPadding();
+ }
+}
\ No newline at end of file
diff --git a/crypto/src/openpgp/PgpPbeEncryptedData.cs b/crypto/src/openpgp/PgpPbeEncryptedData.cs
index 1c3432d892..c4bca5b20c 100644
--- a/crypto/src/openpgp/PgpPbeEncryptedData.cs
+++ b/crypto/src/openpgp/PgpPbeEncryptedData.cs
@@ -1,15 +1,15 @@
-using System;
-using System.IO;
-
using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.IO;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities.IO;
+using System;
+using System.IO;
namespace Org.BouncyCastle.Bcpg.OpenPgp
{
- /// A password based encryption object.
+ /// A password based encryption object.
public class PgpPbeEncryptedData
: PgpEncryptedData
{
@@ -21,10 +21,63 @@ internal PgpPbeEncryptedData(
: base(encData)
{
this.keyData = keyData;
- }
+ EnforceConstraints();
+ }
- /// Return the raw input stream for the data stream.
- public override Stream GetInputStream()
+ private void EnforceConstraints()
+ {
+ switch (keyData.Version)
+ {
+ case SymmetricKeyEncSessionPacket.Version4:
+ // https://www.rfc-editor.org/rfc/rfc9580#name-version-4-symmetric-key-enc
+ // A version 4 SKESK packet precedes a version 1 SEIPD packet. In historic data, it is sometimes found
+ // preceding a deprecated SED packet. A v4 SKESK packet MUST NOT precede a v2 SEIPD packet.
+ if (encData is SymmetricEncDataPacket)
+ {
+ return;
+ }
+
+ if (encData is SymmetricEncIntegrityPacket seipd1)
+ {
+ if (seipd1.Version == SymmetricEncIntegrityPacket.Version1)
+ {
+ return;
+ }
+
+ // V2 SEIPD cannot be preceded by V4 SKESK
+ throw new PgpException($"Version 4 SKESK cannot precede SEIPD of version {seipd1.Version}");
+ }
+ break;
+
+ case SymmetricKeyEncSessionPacket.Version5:
+ // https://www.ietf.org/archive/id/draft-koch-openpgp-2015-rfc4880bis-01.html does not state any constraints
+ break;
+
+ case SymmetricKeyEncSessionPacket.Version6:
+ // https://www.rfc-editor.org/rfc/rfc9580#name-version-6-symmetric-key-enc
+ // A version 6 SKESK packet precedes a version 2 SEIPD packet. A v6 SKESK packet MUST NOT precede a v1 SEIPD
+ // packet or a deprecated Symmetrically Encrypted Data.
+ if (encData is SymmetricEncDataPacket)
+ {
+ throw new PgpException("Version 6 SKESK MUST NOT precede a deprecated SED packet.");
+ }
+
+ if (encData is SymmetricEncIntegrityPacket seipd2)
+ {
+ if (seipd2.Version == SymmetricEncIntegrityPacket.Version2)
+ {
+ return;
+ }
+ throw new PgpException($"Version 6 PKESK cannot precede SEIPD of version {seipd2.Version}");
+ }
+ break;
+ default:
+ throw new UnsupportedPacketVersionException($"Unsupported PGP secret key encrypted session key packet version encountered: {keyData.Version}");
+ }
+ }
+
+ /// Return the raw input stream for the data stream.
+ public override Stream GetInputStream()
{
return encData.GetInputStream();
}
@@ -57,7 +110,7 @@ public Stream GetDataStreamRaw(byte[] rawPassPhrase)
return DoGetDataStream(rawPassPhrase, false);
}
- internal Stream DoGetDataStream(byte[] rawPassPhrase, bool clearPassPhrase)
+ private Stream DoGetDataStreamVersion1(byte[] rawPassPhrase, bool clearPassPhrase)
{
try
{
@@ -130,7 +183,6 @@ internal Stream DoGetDataStream(byte[] rawPassPhrase, bool clearPassPhrase)
throw new PgpDataValidationException("quick check failed.");
}
-
return encStream;
}
catch (PgpException)
@@ -143,17 +195,96 @@ internal Stream DoGetDataStream(byte[] rawPassPhrase, bool clearPassPhrase)
}
}
- private IBufferedCipher CreateStreamCipher(
+ private static KeyParameter DeriveVersion6SessionKey(SymmetricKeyEncSessionPacket keyData, byte[] rawPassPhrase, bool clearPassPhrase)
+ {
+ var keyAlgorithm = keyData.EncAlgorithm;
+ var aeadAlgo = keyData.AeadAlgorithm;
+ var aeadIV = keyData.GetAeadIV();
+ var hkdfInfo = keyData.GetAAData();
+ var secKeyData = keyData.GetSecKeyData();
+
+ var keyAlgoName = PgpUtilities.GetSymmetricCipherName(keyAlgorithm);
+ var aeadAlgoName = AeadUtils.GetAeadAlgorithmName(aeadAlgo);
+ var keyCipher = CipherUtilities.GetCipher($"{keyAlgoName}/{aeadAlgoName}/NoPadding");
+
+ var key = PgpUtilities.DoMakeKeyFromPassPhrase(keyAlgorithm, keyData.S2k, rawPassPhrase, clearPassPhrase);
+
+ var hkdfParams = new HkdfParameters(key.GetKey(), Array.Empty(), hkdfInfo);
+ var hkdfGen = new HkdfBytesGenerator(PgpUtilities.CreateDigest(HashAlgorithmTag.Sha256));
+ hkdfGen.Init(hkdfParams);
+ var hkdfOutput = new byte[PgpUtilities.GetKeySizeInOctets(keyAlgorithm)];
+ hkdfGen.GenerateBytes(hkdfOutput, 0, hkdfOutput.Length);
+
+ var aeadParams = new AeadParameters(
+ new KeyParameter(hkdfOutput),
+ 8 * AeadUtils.GetAuthTagLength(aeadAlgo),
+ aeadIV,
+ hkdfInfo);
+
+ keyCipher.Init(false, aeadParams);
+ byte[] keyBytes = keyCipher.DoFinal(secKeyData);
+
+ return ParameterUtilities.CreateKeyParameter(
+ PgpUtilities.GetSymmetricCipherName(keyAlgorithm),
+ keyBytes, 0, keyBytes.Length);
+ }
+
+ private Stream DoGetDataStreamVersion2(SymmetricEncIntegrityPacket seipd, byte[] rawPassPhrase, bool clearPassPhrase)
+ {
+ try
+ {
+ KeyParameter sessionKey = DeriveVersion6SessionKey(keyData, rawPassPhrase, clearPassPhrase);
+
+ var aadata = seipd.GetAAData();
+ var salt = seipd.GetSalt();
+ AeadUtils.DeriveAeadMessageKeyAndIv(sessionKey, seipd.CipherAlgorithm, seipd.AeadAlgorithm, salt, aadata, out var messageKey, out var iv);
+ var cipher = AeadUtils.CreateAeadCipher(seipd.CipherAlgorithm, seipd.AeadAlgorithm);
+
+ var aeadStream = new AeadInputStream(
+ encData.GetInputStream(),
+ cipher,
+ messageKey,
+ iv,
+ seipd.AeadAlgorithm,
+ seipd.ChunkSize,
+ aadata);
+
+ encStream = BcpgInputStream.Wrap(aeadStream);
+ return encStream;
+ }
+ catch (PgpException)
+ {
+ throw;
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("Exception creating cipher", e);
+ }
+ }
+
+ internal Stream DoGetDataStream(byte[] rawPassPhrase, bool clearPassPhrase)
+ {
+ if (encData is SymmetricEncIntegrityPacket seipd && seipd.Version == SymmetricEncIntegrityPacket.Version2)
+ {
+ return DoGetDataStreamVersion2(seipd, rawPassPhrase, clearPassPhrase);
+ }
+ else
+ {
+ return DoGetDataStreamVersion1(rawPassPhrase, clearPassPhrase);
+ }
+ }
+
+ private IBufferedCipher CreateStreamCipher(
SymmetricKeyAlgorithmTag keyAlgorithm)
{
string mode = (encData is SymmetricEncIntegrityPacket)
? "CFB"
: "OpenPGPCFB";
- string cName = PgpUtilities.GetSymmetricCipherName(keyAlgorithm)
- + "/" + mode + "/NoPadding";
+ 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
index 61487a5b25..57b3bdca61 100644
--- a/crypto/src/openpgp/PgpPrivateKey.cs
+++ b/crypto/src/openpgp/PgpPrivateKey.cs
@@ -1,16 +1,45 @@
using System;
using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Utilities;
namespace Org.BouncyCastle.Bcpg.OpenPgp
{
/// General class to contain a private key for use with other OpenPGP objects.
public class PgpPrivateKey
{
+ private readonly int version;
private readonly long keyID;
+ private readonly byte[] fingerprint;
private readonly PublicKeyPacket publicKeyPacket;
private readonly AsymmetricKeyParameter privateKey;
+ ///
+ /// Create a PgpPrivateKey from associated public key, and a regular private key.
+ ///
+ /// the corresponding public key
+ /// the private key data packet to be associated with this private key.
+ public PgpPrivateKey(PgpPublicKey pubKey, AsymmetricKeyParameter privateKey)
+ :this(pubKey.KeyId, pubKey.GetFingerprint(), pubKey.PublicKeyPacket, privateKey)
+ {
+ }
+
+ private PgpPrivateKey(
+ long keyID,
+ byte[] fingerprint,
+ PublicKeyPacket publicKeyPacket,
+ AsymmetricKeyParameter privateKey)
+ {
+ if (!privateKey.IsPrivate)
+ throw new ArgumentException("Expected a private key", nameof(privateKey));
+
+ this.version = publicKeyPacket.Version;
+ this.keyID = keyID;
+ this.fingerprint = fingerprint;
+ this.publicKeyPacket = publicKeyPacket;
+ this.privateKey = privateKey;
+ }
+
///
/// Create a PgpPrivateKey from a keyID, the associated public data packet, and a regular private key.
///
@@ -21,13 +50,8 @@ public PgpPrivateKey(
long keyID,
PublicKeyPacket publicKeyPacket,
AsymmetricKeyParameter privateKey)
+ :this(keyID, null, publicKeyPacket, privateKey)
{
- if (!privateKey.IsPrivate)
- throw new ArgumentException("Expected a private key", "privateKey");
-
- this.keyID = keyID;
- this.publicKeyPacket = publicKeyPacket;
- this.privateKey = privateKey;
}
/// The keyId associated with the contained private key.
@@ -36,6 +60,21 @@ public long KeyId
get { return keyID; }
}
+ ///
+ /// The version of the contained private key.
+ ///
+ public int Version {
+ get { return version; }
+ }
+
+ ///
+ /// The Fingerprint associated with the contained private key.
+ ///
+ public byte[] GetFingerprint()
+ {
+ return Arrays.Clone(fingerprint);
+ }
+
/// The public key packet associated with this private key, if available.
public PublicKeyPacket PublicKeyPacket
{
diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs
index 7ee57d4366..1e0a92c231 100644
--- a/crypto/src/openpgp/PgpPublicKey.cs
+++ b/crypto/src/openpgp/PgpPublicKey.cs
@@ -25,6 +25,41 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
public class PgpPublicKey
: PgpObject
{
+ private const byte v4FingerprintPreamble = 0x99;
+ private const byte v5FingerprintPreamble = 0x9A;
+ private const byte v6FingerprintPreamble = 0x9B;
+
+ internal static byte FingerprintPreamble(int version)
+ {
+ switch (version)
+ {
+ case PublicKeyPacket.Version4:
+ return v4FingerprintPreamble;
+ case PublicKeyPacket.Version5:
+ return v5FingerprintPreamble;
+ case PublicKeyPacket.Version6:
+ return v6FingerprintPreamble;
+ default:
+ throw new PgpException($"unsupported OpenPGP key packet version: {version}");
+ }
+ }
+ private static IDigest CreateDigestForFingerprint(int version)
+ {
+ switch (version)
+ {
+ case PublicKeyPacket.Version2:
+ case PublicKeyPacket.Version3:
+ return PgpUtilities.CreateDigest(HashAlgorithmTag.MD5);
+ case PublicKeyPacket.Version4:
+ return PgpUtilities.CreateDigest(HashAlgorithmTag.Sha1);
+ case PublicKeyPacket.Version5:
+ case PublicKeyPacket.Version6:
+ return PgpUtilities.CreateDigest(HashAlgorithmTag.Sha256);
+ default:
+ throw new PgpException($"unsupported OpenPGP key packet version: {version}");
+ }
+ }
+
// We default to these as they are specified as mandatory in RFC 6631.
private static readonly PgpKdfParameters DefaultKdfParameters = new PgpKdfParameters(HashAlgorithmTag.Sha256,
SymmetricKeyAlgorithmTag.Aes128);
@@ -32,16 +67,14 @@ public class PgpPublicKey
public static byte[] CalculateFingerprint(PublicKeyPacket publicPk)
{
IBcpgKey key = publicPk.Key;
- IDigest digest;
+ IDigest digest = CreateDigestForFingerprint(publicPk.Version);
- if (publicPk.Version <= 3)
+ if (publicPk.Version <= PublicKeyPacket.Version3)
{
RsaPublicBcpgKey rK = (RsaPublicBcpgKey)key;
try
{
- digest = PgpUtilities.CreateDigest(HashAlgorithmTag.MD5);
-
UpdateDigest(digest, rK.Modulus);
UpdateDigest(digest, rK.PublicExponent);
}
@@ -54,13 +87,27 @@ public static byte[] CalculateFingerprint(PublicKeyPacket publicPk)
{
try
{
+ digest.Update(FingerprintPreamble(publicPk.Version));
+
byte[] kBytes = publicPk.GetEncodedContents();
- digest = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha1);
+ if (publicPk.Version == PublicKeyPacket.Version4)
+ {
+ digest.Update((byte)(kBytes.Length >> 8));
+ digest.Update((byte)kBytes.Length);
+ }
+ else if (publicPk.Version == PublicKeyPacket.Version5 || publicPk.Version == PublicKeyPacket.Version6)
+ {
+ digest.Update((byte)(kBytes.Length >> 24));
+ digest.Update((byte)(kBytes.Length >> 16));
+ digest.Update((byte)(kBytes.Length >> 8));
+ digest.Update((byte)kBytes.Length);
+ }
+ else
+ {
+ throw new PgpException($"unsupported OpenPGP key packet version: {publicPk.Version}");
+ }
- digest.Update(0x99);
- digest.Update((byte)(kBytes.Length >> 8));
- digest.Update((byte)kBytes.Length);
digest.BlockUpdate(kBytes, 0, kBytes.Length);
}
catch (Exception e)
@@ -106,7 +153,7 @@ private void Init()
this.fingerprint = CalculateFingerprint(publicPk);
- if (publicPk.Version <= 3)
+ if (publicPk.Version <= PublicKeyPacket.Version3)
{
RsaPublicBcpgKey rK = (RsaPublicBcpgKey) key;
@@ -115,7 +162,14 @@ private void Init()
}
else
{
- this.keyId = (long)Pack.BE_To_UInt64(fingerprint, fingerprint.Length - 8);
+ if (publicPk.Version == PublicKeyPacket.Version4)
+ {
+ this.keyId = (long)Pack.BE_To_UInt64(fingerprint, fingerprint.Length - 8);
+ }
+ else
+ {
+ this.keyId = (long)Pack.BE_To_UInt64(fingerprint);
+ }
if (key is RsaPublicBcpgKey)
{
@@ -163,9 +217,23 @@ private void Init()
this.keyStrength = -1; // unknown
}
}
+ else if (key is Ed25519PublicBcpgKey || key is X25519PublicBcpgKey)
+ {
+ this.keyStrength = 256;
+ }
+ else if (key is Ed448PublicBcpgKey || key is X448PublicBcpgKey)
+ {
+ this.keyStrength = 448;
+ }
}
}
+
+ public PgpPublicKey(PublicKeyAlgorithmTag algorithm, AsymmetricKeyParameter pubKey, DateTime time)
+ :this(PublicKeyPacket.DefaultVersion, algorithm, pubKey, time)
+ {
+ }
+
///
/// Create a PgpPublicKey from the passed in lightweight one.
///
@@ -178,7 +246,7 @@ private void Init()
/// Date of creation.
/// If pubKey is not public.
/// On key creation problem.
- public PgpPublicKey(PublicKeyAlgorithmTag algorithm, AsymmetricKeyParameter pubKey, DateTime time)
+ public PgpPublicKey(int version, PublicKeyAlgorithmTag algorithm, AsymmetricKeyParameter pubKey, DateTime time)
{
if (pubKey.IsPrivate)
throw new ArgumentException("Expected a public key", nameof(pubKey));
@@ -218,44 +286,72 @@ public PgpPublicKey(PublicKeyAlgorithmTag algorithm, AsymmetricKeyParameter pubK
}
else if (pubKey is Ed25519PublicKeyParameters ed25519PubKey)
{
- byte[] pointEnc = new byte[1 + Ed25519PublicKeyParameters.KeySize];
- pointEnc[0] = 0x40;
- ed25519PubKey.Encode(pointEnc, 1);
- bcpgKey = new EdDsaPublicBcpgKey(GnuObjectIdentifiers.Ed25519, new BigInteger(1, pointEnc));
+ if (algorithm == PublicKeyAlgorithmTag.Ed25519)
+ {
+ bcpgKey = new Ed25519PublicBcpgKey(ed25519PubKey.GetEncoded());
+ }
+ else
+ {
+ byte[] pointEnc = new byte[1 + Ed25519PublicKeyParameters.KeySize];
+ pointEnc[0] = 0x40;
+ ed25519PubKey.Encode(pointEnc, 1);
+ bcpgKey = new EdDsaPublicBcpgKey(GnuObjectIdentifiers.Ed25519, new BigInteger(1, pointEnc));
+ }
}
else if (pubKey is Ed448PublicKeyParameters ed448PubKey)
{
- byte[] pointEnc = new byte[Ed448PublicKeyParameters.KeySize];
- ed448PubKey.Encode(pointEnc, 0);
- bcpgKey = new EdDsaPublicBcpgKey(EdECObjectIdentifiers.id_Ed448, new BigInteger(1, pointEnc));
+ if (algorithm == PublicKeyAlgorithmTag.Ed448)
+ {
+ bcpgKey = new Ed448PublicBcpgKey(ed448PubKey.GetEncoded());
+ }
+ else
+ {
+ byte[] pointEnc = new byte[Ed448PublicKeyParameters.KeySize];
+ ed448PubKey.Encode(pointEnc, 0);
+ bcpgKey = new EdDsaPublicBcpgKey(EdECObjectIdentifiers.id_Ed448, new BigInteger(1, pointEnc));
+ }
}
else if (pubKey is X25519PublicKeyParameters x25519PubKey)
{
- byte[] pointEnc = new byte[1 + X25519PublicKeyParameters.KeySize];
- pointEnc[0] = 0x40;
- x25519PubKey.Encode(pointEnc, 1);
+ if (algorithm == PublicKeyAlgorithmTag.X25519)
+ {
+ bcpgKey = new X25519PublicBcpgKey(x25519PubKey.GetEncoded());
+ }
+ else
+ {
+ byte[] pointEnc = new byte[1 + X25519PublicKeyParameters.KeySize];
+ pointEnc[0] = 0x40;
+ x25519PubKey.Encode(pointEnc, 1);
- PgpKdfParameters kdfParams = DefaultKdfParameters;
+ PgpKdfParameters kdfParams = DefaultKdfParameters;
- bcpgKey = new ECDHPublicBcpgKey(CryptlibObjectIdentifiers.curvey25519, new BigInteger(1, pointEnc),
- kdfParams.HashAlgorithm, kdfParams.SymmetricWrapAlgorithm);
+ bcpgKey = new ECDHPublicBcpgKey(CryptlibObjectIdentifiers.curvey25519, new BigInteger(1, pointEnc),
+ kdfParams.HashAlgorithm, kdfParams.SymmetricWrapAlgorithm);
+ }
}
else if (pubKey is X448PublicKeyParameters x448PubKey)
{
- byte[] pointEnc = new byte[X448PublicKeyParameters.KeySize];
- x448PubKey.Encode(pointEnc, 0);
+ if (algorithm == PublicKeyAlgorithmTag.X448)
+ {
+ bcpgKey = new X448PublicBcpgKey(x448PubKey.GetEncoded());
+ }
+ else
+ {
+ byte[] pointEnc = new byte[X448PublicKeyParameters.KeySize];
+ x448PubKey.Encode(pointEnc, 0);
- PgpKdfParameters kdfParams = DefaultKdfParameters;
+ PgpKdfParameters kdfParams = DefaultKdfParameters;
- bcpgKey = new ECDHPublicBcpgKey(EdECObjectIdentifiers.id_X448, new BigInteger(1, pointEnc),
- kdfParams.HashAlgorithm, kdfParams.SymmetricWrapAlgorithm);
+ bcpgKey = new ECDHPublicBcpgKey(EdECObjectIdentifiers.id_X448, new BigInteger(1, pointEnc),
+ kdfParams.HashAlgorithm, kdfParams.SymmetricWrapAlgorithm);
+ }
}
else
{
throw new PgpException("unknown key class");
}
- this.publicPk = new PublicKeyPacket(algorithm, time, bcpgKey);
+ this.publicPk = new PublicKeyPacket(version, algorithm, time, bcpgKey);
this.ids = new List();
this.idSigs = new List>();
@@ -401,7 +497,7 @@ public byte[] GetTrustData()
/// The number of valid seconds from creation time - zero means no expiry.
public long GetValidSeconds()
{
- if (publicPk.Version <= 3)
+ if (publicPk.Version <= PublicKeyPacket.Version3)
{
return (long)publicPk.ValidDays * (24 * 60 * 60);
}
@@ -480,7 +576,7 @@ public long KeyId
/// The fingerprint of the public key
public byte[] GetFingerprint()
{
- return (byte[]) fingerprint.Clone();
+ return Arrays.Clone(fingerprint);
}
public bool HasFingerprint(byte[] fingerprint)
@@ -509,6 +605,8 @@ public bool IsEncryptionKey
case PublicKeyAlgorithmTag.ElGamalGeneral:
case PublicKeyAlgorithmTag.RsaEncrypt:
case PublicKeyAlgorithmTag.RsaGeneral:
+ case PublicKeyAlgorithmTag.X25519:
+ case PublicKeyAlgorithmTag.X448:
return true;
default:
return false;
@@ -641,8 +739,25 @@ public AsymmetricKeyParameter GetKey()
case PublicKeyAlgorithmTag.ElGamalGeneral:
ElGamalPublicBcpgKey elK = (ElGamalPublicBcpgKey)publicPk.Key;
return new ElGamalPublicKeyParameters(elK.Y, new ElGamalParameters(elK.P, elK.G));
+
+ case PublicKeyAlgorithmTag.Ed25519:
+ Ed25519PublicBcpgKey ed25519key = (Ed25519PublicBcpgKey)publicPk.Key;
+ return new Ed25519PublicKeyParameters(ed25519key.GetKey());
+
+ case PublicKeyAlgorithmTag.X25519:
+ X25519PublicBcpgKey x25519key = (X25519PublicBcpgKey)publicPk.Key;
+ return new X25519PublicKeyParameters(x25519key.GetKey());
+
+ case PublicKeyAlgorithmTag.Ed448:
+ Ed448PublicBcpgKey ed448key = (Ed448PublicBcpgKey)publicPk.Key;
+ return new Ed448PublicKeyParameters(ed448key.GetKey());
+
+ case PublicKeyAlgorithmTag.X448:
+ X448PublicBcpgKey x448key = (X448PublicBcpgKey)publicPk.Key;
+ return new X448PublicKeyParameters(x448key.GetKey());
+
default:
- throw new PgpException("unknown public key algorithm encountered");
+ throw new PgpException("unknown public key algorithm encountered");
}
}
catch (PgpException)
@@ -1312,4 +1427,4 @@ public static PgpPublicKey Join(PgpPublicKey key, PgpPublicKey copy, bool joinTr
return merged;
}
}
-}
+}
\ No newline at end of file
diff --git a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
index 6459732156..402351bdaf 100644
--- a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
+++ b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
@@ -1,8 +1,10 @@
using System;
using System.IO;
+using System.Text;
using Org.BouncyCastle.Asn1.Cryptlib;
using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.IO;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
@@ -26,9 +28,56 @@ internal PgpPublicKeyEncryptedData(
: base(encData)
{
this.keyData = keyData;
+ EnforceConstraints();
}
- private static IBufferedCipher GetKeyCipher(
+ private void EnforceConstraints()
+ {
+ switch (keyData.Version)
+ {
+ case PublicKeyEncSessionPacket.Version3:
+ // https://www.rfc-editor.org/rfc/rfc9580#name-version-3-public-key-encryp
+ // A version 3 PKESK packet precedes a version 1 SEIPD packet. In historic data, it is sometimes
+ // found preceding a deprecated SED packet.
+ // A V3 PKESK packet MUST NOT precede a V2 SEIPD packet.
+ if (encData is SymmetricEncDataPacket)
+ {
+ return;
+ }
+ if (encData is SymmetricEncIntegrityPacket seipd1)
+ {
+ if (seipd1.Version == SymmetricEncIntegrityPacket.Version1)
+ {
+ return;
+ }
+ throw new ArgumentException($"Version 3 PKESK cannot precede SEIPD of version {seipd1.Version}");
+ }
+ break;
+
+ case PublicKeyEncSessionPacket.Version6:
+ // https://www.rfc-editor.org/rfc/rfc9580#name-version-6-public-key-encryp
+ //A version 6 PKESK packet precedes a version 2 SEIPD packet.
+ //A V6 PKESK packet MUST NOT precede a V1 SEIPD packet or a deprecated SED packet.
+ if (encData is SymmetricEncDataPacket)
+ {
+ throw new ArgumentException("Version 6 PKESK MUST NOT precede a deprecated SED packet.");
+ }
+
+ if (encData is SymmetricEncIntegrityPacket seipd2)
+ {
+ if (seipd2.Version == SymmetricEncIntegrityPacket.Version2)
+ {
+ return;
+ }
+ throw new ArgumentException($"Version 6 PKESK cannot precede SEIPD of version {seipd2.Version}");
+ }
+ break;
+ default:
+ throw new UnsupportedPacketVersionException($"Unsupported PGP public key encrypted session key packet version encountered: {keyData.Version}");
+ }
+ }
+
+ private static IBufferedCipher GetKeyCipher(
PublicKeyAlgorithmTag algorithm)
{
try
@@ -58,9 +107,14 @@ private static IBufferedCipher GetKeyCipher(
private bool ConfirmCheckSum(
byte[] sessionInfo)
{
- int check = 0;
+ // for X25519 and X448 no checksum or padding are appended to the session key before key wrapping
+ if (keyData.Algorithm == PublicKeyAlgorithmTag.X25519 || keyData.Algorithm == PublicKeyAlgorithmTag.X448)
+ {
+ return true;
+ }
- for (int i = 1; i != sessionInfo.Length - 2; i++)
+ int check = 0;
+ for (int i = 1; i != sessionInfo.Length - 2; i++)
{
check += sessionInfo[i] & 0xff;
}
@@ -75,16 +129,82 @@ public long KeyId
get { return keyData.KeyId; }
}
- ///
- /// Return the algorithm code for the symmetric algorithm used to encrypt the data.
- ///
- public SymmetricKeyAlgorithmTag GetSymmetricAlgorithm(
- PgpPrivateKey privKey)
- {
- byte[] sessionData = RecoverSessionData(privKey);
+ /// The key fingerprint for the key used to encrypt the data (v6 only).
+ public byte[] GetKeyFingerprint()
+ {
+ return keyData.GetKeyFingerprint();
+ }
- return (SymmetricKeyAlgorithmTag)sessionData[0];
- }
+ ///
+ /// Return the algorithm code for the symmetric algorithm used to encrypt the data.
+ ///
+ public SymmetricKeyAlgorithmTag GetSymmetricAlgorithm(
+ PgpPrivateKey privKey)
+ {
+ if (keyData.Version == PublicKeyEncSessionPacket.Version3)
+ {
+ // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-fields-for-
+ // In V3 PKESK, the symmetric algorithm Id
+ // * with X25519 and X448 is not encrypted, it's prepended in plaintext
+ // to the encrypted session key.
+ // * with other algorithms, it's is encrypted with the session key
+
+ if (keyData.Algorithm == PublicKeyAlgorithmTag.X25519 || keyData.Algorithm == PublicKeyAlgorithmTag.X448)
+ {
+ byte[][] secKeyData = keyData.GetEncSessionKey();
+ return (SymmetricKeyAlgorithmTag)secKeyData[1][0];
+ }
+ else
+ {
+ byte[] sessionData = RecoverSessionData(privKey);
+
+ return (SymmetricKeyAlgorithmTag)sessionData[0];
+ }
+ }
+ else if (keyData.Version == PublicKeyEncSessionPacket.Version6)
+ {
+ // V6 PKESK stores the cipher algorithm in the V2 SEIPD packet fields.
+ return ((SymmetricEncIntegrityPacket)encData).CipherAlgorithm;
+ }
+ else
+ {
+ throw new UnsupportedPacketVersionException($"Unsupported PGP public key encrypted session key packet version encountered: {keyData.Version}");
+ }
+ }
+
+ private Stream GetDataStreamSeipdVersion2(byte[] sessionData, SymmetricEncIntegrityPacket seipd)
+ {
+ var encAlgo = seipd.CipherAlgorithm;
+ var aeadAlgo = seipd.AeadAlgorithm;
+ var aadata = seipd.GetAAData();
+ var salt = seipd.GetSalt();
+
+ // no checksum and padding for X25519 and X448
+ int length = sessionData.Length;
+ if (keyData.Algorithm != PublicKeyAlgorithmTag.X25519 && keyData.Algorithm != PublicKeyAlgorithmTag.X448)
+ {
+ length -= 2;
+ }
+
+ var sessionKey = ParameterUtilities.CreateKeyParameter(
+ PgpUtilities.GetSymmetricCipherName(encAlgo),
+ sessionData, 0, length);
+
+ AeadUtils.DeriveAeadMessageKeyAndIv(sessionKey, encAlgo, aeadAlgo, salt, aadata, out var messageKey, out var iv);
+ var cipher = AeadUtils.CreateAeadCipher(seipd.CipherAlgorithm, seipd.AeadAlgorithm);
+
+ var aeadStream = new AeadInputStream(
+ encData.GetInputStream(),
+ cipher,
+ messageKey,
+ iv,
+ aeadAlgo,
+ seipd.ChunkSize,
+ aadata);
+
+ encStream = BcpgInputStream.Wrap(aeadStream);
+ return encStream;
+ }
/// Return the decrypted data stream for the packet.
public Stream GetDataStream(
@@ -95,7 +215,35 @@ public Stream GetDataStream(
if (!ConfirmCheckSum(sessionData))
throw new PgpKeyValidationException("key checksum failed");
- SymmetricKeyAlgorithmTag symmAlg = (SymmetricKeyAlgorithmTag)sessionData[0];
+ if (keyData.Version == PublicKeyEncSessionPacket.Version6)
+ {
+ // V6 PKESK + V2 SEIPD
+ try
+ {
+ return GetDataStreamSeipdVersion2(sessionData, (SymmetricEncIntegrityPacket)encData);
+ }
+ catch (PgpException)
+ {
+ throw;
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("Exception starting decryption", e);
+ }
+ }
+
+ SymmetricKeyAlgorithmTag symmAlg;
+ if (keyData.Algorithm == PublicKeyAlgorithmTag.X25519 || keyData.Algorithm == PublicKeyAlgorithmTag.X448)
+ {
+ // with X25519 and X448 is not encrypted, the symmetric algorithm Id is
+ // prepended in plaintext to the encrypted session key.
+ byte[][] secKeyData = keyData.GetEncSessionKey();
+ symmAlg = (SymmetricKeyAlgorithmTag)secKeyData[1][0];
+ }
+ else
+ {
+ symmAlg = (SymmetricKeyAlgorithmTag)sessionData[0];
+ }
if (symmAlg == SymmetricKeyAlgorithmTag.Null)
return encData.GetInputStream();
@@ -127,8 +275,17 @@ public Stream GetDataStream(
try
{
- KeyParameter key = ParameterUtilities.CreateKeyParameter(
- cipherName, sessionData, 1, sessionData.Length - 3);
+ // no checksum and padding for X25519 and X448
+ int offset = 0;
+ int length = sessionData.Length;
+ if (keyData.Algorithm != PublicKeyAlgorithmTag.X25519 && keyData.Algorithm != PublicKeyAlgorithmTag.X448)
+ {
+ offset = 1;
+ length -= 3;
+ }
+
+ KeyParameter key = ParameterUtilities.CreateKeyParameter(
+ cipherName, sessionData, offset, length);
byte[] iv = new byte[cipher.GetBlockSize()];
@@ -189,43 +346,102 @@ private byte[] RecoverSessionData(PgpPrivateKey privKey)
{
byte[][] secKeyData = keyData.GetEncSessionKey();
+ if (keyData.Algorithm == PublicKeyAlgorithmTag.X25519 || keyData.Algorithm == PublicKeyAlgorithmTag.X448)
+ {
+ // See sect. 5.1.6. and 5.1.7 of RFC 9580 for the description of
+ // the key derivation algorithm for X25519 and X448
+ // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-fields-for-
+ // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-fields-for-x
+ byte[] eph = secKeyData[0];
+ byte[] esk = secKeyData[1];
+
+ IRawAgreement agreement;
+ IDigest digestForHkdf;
+ byte[] hkdfInfo;
+ AsymmetricKeyParameter ephPubkey;
+ SymmetricKeyAlgorithmTag wrappingAlgo;
+
+ if (keyData.Algorithm == PublicKeyAlgorithmTag.X25519)
+ {
+ agreement = new X25519Agreement();
+ ephPubkey = new X25519PublicKeyParameters(eph);
+ digestForHkdf = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha256);
+ hkdfInfo = Encoding.ASCII.GetBytes("OpenPGP X25519");
+ wrappingAlgo = SymmetricKeyAlgorithmTag.Aes128;
+ }
+ else
+ {
+ agreement = new X448Agreement();
+ ephPubkey = new X448PublicKeyParameters(eph);
+ digestForHkdf = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha512);
+ hkdfInfo = Encoding.ASCII.GetBytes("OpenPGP X448");
+ wrappingAlgo = SymmetricKeyAlgorithmTag.Aes256;
+ }
+
+ agreement.Init(privKey.Key);
+ byte[] sharedSecret = new byte[agreement.AgreementSize];
+ agreement.CalculateAgreement(ephPubkey, sharedSecret, 0);
+
+ byte[] pubKeyMaterial = ((OctetArrayBcpgKey)privKey.PublicKeyPacket.Key).GetKey();
+ byte[] ikm = Arrays.ConcatenateAll(eph, pubKeyMaterial, sharedSecret);
+ byte[] hkdfSalt = Array.Empty();
+ var hkdfParams = new HkdfParameters(ikm, hkdfSalt, hkdfInfo);
+ var hkdfGen = new HkdfBytesGenerator(digestForHkdf);
+ hkdfGen.Init(hkdfParams);
+ var hkdfOutput = new byte[PgpUtilities.GetKeySizeInOctets(wrappingAlgo)];
+ hkdfGen.GenerateBytes(hkdfOutput, 0, hkdfOutput.Length);
+
+ KeyParameter kek = ParameterUtilities.CreateKeyParameter("AES", hkdfOutput);
+ var wrapper = PgpUtilities.CreateWrapper(wrappingAlgo);
+ wrapper.Init(false, kek);
+ int offset = 0;
+ int length = esk.Length;
+ if (keyData.Version == PublicKeyEncSessionPacket.Version3)
+ {
+ offset = 1;
+ length--;
+ }
+ var keyBytes = wrapper.Unwrap(esk, offset, length);
+ return keyBytes;
+ }
+
if (keyData.Algorithm != PublicKeyAlgorithmTag.ECDH)
{
IBufferedCipher cipher = GetKeyCipher(keyData.Algorithm);
try
- {
+ {
cipher.Init(false, privKey.Key);
- }
- catch (InvalidKeyException e)
- {
- throw new PgpException("error setting asymmetric cipher", e);
- }
+ }
+ catch (InvalidKeyException e)
+ {
+ throw new PgpException("error setting asymmetric cipher", e);
+ }
if (keyData.Algorithm == PublicKeyAlgorithmTag.RsaEncrypt
- || keyData.Algorithm == PublicKeyAlgorithmTag.RsaGeneral)
- {
+ || keyData.Algorithm == PublicKeyAlgorithmTag.RsaGeneral)
+ {
byte[] bi = secKeyData[0];
cipher.ProcessBytes(bi, 2, bi.Length - 2);
- }
- else
- {
- ElGamalPrivateKeyParameters k = (ElGamalPrivateKeyParameters)privKey.Key;
- int size = (k.Parameters.P.BitLength + 7) / 8;
+ }
+ else
+ {
+ ElGamalPrivateKeyParameters k = (ElGamalPrivateKeyParameters)privKey.Key;
+ int size = (k.Parameters.P.BitLength + 7) / 8;
ProcessEncodedMpi(cipher, size, secKeyData[0]);
ProcessEncodedMpi(cipher, size, secKeyData[1]);
- }
+ }
try
- {
+ {
return cipher.DoFinal();
- }
- catch (Exception e)
- {
- throw new PgpException("exception decrypting secret key", e);
- }
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("exception decrypting secret key", e);
+ }
}
ECDHPublicBcpgKey ecPubKey = (ECDHPublicBcpgKey)privKey.PublicKeyPacket.Key;
diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs
index 184621b5c1..57b3609488 100644
--- a/crypto/src/openpgp/PgpSecretKey.cs
+++ b/crypto/src/openpgp/PgpSecretKey.cs
@@ -10,6 +10,7 @@
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Math.EC.Rfc8032;
@@ -25,6 +26,8 @@ public class PgpSecretKey
private readonly SecretKeyPacket secret;
private readonly PgpPublicKey pub;
+ #region "Internal constructors"
+
internal PgpSecretKey(
SecretKeyPacket secret,
PgpPublicKey pub)
@@ -33,13 +36,29 @@ internal PgpSecretKey(
this.pub = pub;
}
+ internal PgpSecretKey(
+ PgpPrivateKey privKey,
+ PgpPublicKey pubKey,
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ byte[] rawPassPhrase,
+ bool clearPassPhrase,
+ bool useSha1,
+ SecureRandom rand,
+ bool isMasterKey)
+ :this(privKey, pubKey, encAlgorithm, 0, rawPassPhrase, clearPassPhrase,
+ useSha1 ? SecretKeyPacket.UsageSha1 : SecretKeyPacket.UsageChecksum,
+ rand, isMasterKey)
+ {
+ }
+
internal PgpSecretKey(
PgpPrivateKey privKey,
PgpPublicKey pubKey,
SymmetricKeyAlgorithmTag encAlgorithm,
+ AeadAlgorithmTag aeadAlgorithm,
byte[] rawPassPhrase,
bool clearPassPhrase,
- bool useSha1,
+ int s2kUsage,
SecureRandom rand,
bool isMasterKey)
{
@@ -110,31 +129,75 @@ internal PgpSecretKey(
ElGamalPrivateKeyParameters esK = (ElGamalPrivateKeyParameters) privKey.Key;
secKey = new ElGamalSecretBcpgKey(esK.X);
break;
+ case PublicKeyAlgorithmTag.Ed25519:
+ Ed25519PrivateKeyParameters e25519pk = (Ed25519PrivateKeyParameters)privKey.Key;
+ secKey = new Ed25519SecretBcpgKey(e25519pk.GetEncoded());
+ break;
+ case PublicKeyAlgorithmTag.Ed448:
+ Ed448PrivateKeyParameters e448pk = (Ed448PrivateKeyParameters)privKey.Key;
+ secKey = new Ed448SecretBcpgKey(e448pk.GetEncoded());
+ break;
+ case PublicKeyAlgorithmTag.X25519:
+ X25519PrivateKeyParameters x25519pk = (X25519PrivateKeyParameters)privKey.Key;
+ secKey = new X25519SecretBcpgKey(x25519pk.GetEncoded());
+ break;
+ case PublicKeyAlgorithmTag.X448:
+ X448PrivateKeyParameters x448pk = (X448PrivateKeyParameters)privKey.Key;
+ secKey = new X448SecretBcpgKey(x448pk.GetEncoded());
+ break;
default:
throw new PgpException("unknown key class");
}
try
{
- MemoryStream bOut = new MemoryStream();
- BcpgOutputStream pOut = new BcpgOutputStream(bOut);
+ byte[] keyData = secKey.GetEncoded();
- pOut.WriteObject(secKey);
+ if (encAlgorithm == SymmetricKeyAlgorithmTag.Null)
+ {
+ s2kUsage = SecretKeyPacket.UsageNone;
+ }
+
+ // RFC 4880 § 5.5.3 + "RFC 4880bis" § 5.5.3 + RFC 9580 § 5.5.3.
+ // v6 keys with UsageNone: No checksum
+ // v5 v6 keys with MalleableCFB: Not allowed
+ // v3 v4 v5 keys with UsageNone: two-octet checksum
+ // v3 v4 keys with MalleableCFB: two-octet checksum
+ // all keys with UsageSha1: Sha1 checksum
+ // all keys with UsageAead: No checksum (use the auth tag of the AEAD algo)
+ if (pub.Version > PublicKeyPacket.Version4 && s2kUsage == SecretKeyPacket.UsageChecksum)
+ {
+ throw new PgpException($"A version {pub.Version} key MUST NOT use MalleableCFB S2K Usage");
+ }
+ else
+ {
+ byte[] checksumData = Array.Empty();
- byte[] keyData = bOut.ToArray();
- byte[] checksumData = Checksum(useSha1, keyData, keyData.Length);
+ if (s2kUsage == SecretKeyPacket.UsageSha1)
+ {
+ checksumData = Checksum(true, keyData, keyData.Length);
+ }
+ else if (s2kUsage == SecretKeyPacket.UsageNone && pub.Version != PublicKeyPacket.Version6)
+ {
+ checksumData = Checksum(false, keyData, keyData.Length);
+ }
+ else if (s2kUsage == SecretKeyPacket.UsageChecksum)
+ {
+ checksumData = Checksum(false, keyData, keyData.Length);
+ }
- keyData = Arrays.Concatenate(keyData, checksumData);
+ keyData = Arrays.Concatenate(keyData, checksumData);
+ }
if (encAlgorithm == SymmetricKeyAlgorithmTag.Null)
{
if (isMasterKey)
{
- this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, null, null, keyData);
+ this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, aeadAlgorithm, s2kUsage, null, null, keyData);
}
else
{
- this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, null, null, keyData);
+ this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, aeadAlgorithm, s2kUsage, null, null, keyData);
}
}
else
@@ -152,17 +215,13 @@ internal PgpSecretKey(
encData = EncryptKeyDataV3(keyData, encAlgorithm, rawPassPhrase, clearPassPhrase, 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);
+ this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, aeadAlgorithm, s2kUsage, s2k, iv, encData);
}
else
{
- this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, s2kUsage, s2k, iv, encData);
+ this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, aeadAlgorithm, s2kUsage, s2k, iv, encData);
}
}
}
@@ -176,6 +235,98 @@ internal PgpSecretKey(
}
}
+
+ internal PgpSecretKey(
+ int certificationLevel,
+ PgpKeyPair keyPair,
+ string id,
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ byte[] rawPassPhrase,
+ bool clearPassPhrase,
+ bool useSha1,
+ PgpSignatureSubpacketVector hashedPackets,
+ PgpSignatureSubpacketVector unhashedPackets,
+ SecureRandom rand)
+ : this(keyPair.PrivateKey, CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets, rand),
+ encAlgorithm, rawPassPhrase, clearPassPhrase, useSha1, rand, true)
+ {
+ }
+
+ internal PgpSecretKey(
+ int certificationLevel,
+ PgpKeyPair keyPair,
+ string id,
+ SymmetricKeyAlgorithmTag encAlgorithm,
+ HashAlgorithmTag hashAlgorithm,
+ byte[] rawPassPhrase,
+ bool clearPassPhrase,
+ bool useSha1,
+ PgpSignatureSubpacketVector hashedPackets,
+ PgpSignatureSubpacketVector unhashedPackets,
+ SecureRandom rand)
+ : this(keyPair.PrivateKey, CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets, hashAlgorithm, rand),
+ encAlgorithm, rawPassPhrase, clearPassPhrase, useSha1, rand, true)
+ {
+ }
+
+ private static PgpPublicKey CertifiedPublicKey(
+ int certificationLevel,
+ PgpKeyPair keyPair,
+ string id,
+ PgpSignatureSubpacketVector hashedPackets,
+ PgpSignatureSubpacketVector unhashedPackets,
+ SecureRandom rand)
+ {
+ return CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets,
+ keyPair.PublicKey.Version > PublicKeyPacket.Version4 ? HashAlgorithmTag.Sha256 : HashAlgorithmTag.Sha1, rand);
+ }
+
+
+ private static PgpPublicKey CertifiedPublicKey(
+ int certificationLevel,
+ PgpKeyPair keyPair,
+ string id,
+ PgpSignatureSubpacketVector hashedPackets,
+ PgpSignatureSubpacketVector unhashedPackets,
+ HashAlgorithmTag hashAlgorithm,
+ SecureRandom rand)
+ {
+ PgpSignatureGenerator sGen;
+ try
+ {
+ sGen = new PgpSignatureGenerator(keyPair.PublicKey.Algorithm, hashAlgorithm);
+ }
+ catch (Exception e)
+ {
+ throw new PgpException("Creating signature generator: " + e.Message, e);
+ }
+
+ // TODO For v6 keys, the User Id should be optional and information about
+ // the primary Public-Key (key flags, key expiration, features, algorithm
+ // preferences, etc.) should be stored in a direct key signature over the
+ // Public-Key instead of in a User ID self-signature.
+
+ //
+ // Generate the certification
+ //
+ sGen.InitSign(certificationLevel, keyPair.PrivateKey, rand);
+
+ 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);
+ }
+ }
+
+ #endregion
+
///
/// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is
/// the historical behaviour of the library (1.7 and earlier).
@@ -232,22 +383,6 @@ public PgpSecretKey(
{
}
- internal PgpSecretKey(
- int certificationLevel,
- PgpKeyPair keyPair,
- string id,
- SymmetricKeyAlgorithmTag encAlgorithm,
- byte[] rawPassPhrase,
- bool clearPassPhrase,
- bool useSha1,
- PgpSignatureSubpacketVector hashedPackets,
- PgpSignatureSubpacketVector unhashedPackets,
- SecureRandom rand)
- : this(keyPair.PrivateKey, CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets),
- encAlgorithm, rawPassPhrase, clearPassPhrase, useSha1, rand, true)
- {
- }
-
///
/// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is
/// the historical behaviour of the library (1.7 and earlier).
@@ -307,97 +442,6 @@ public PgpSecretKey(
{
}
- internal PgpSecretKey(
- int certificationLevel,
- PgpKeyPair keyPair,
- string id,
- SymmetricKeyAlgorithmTag encAlgorithm,
- HashAlgorithmTag hashAlgorithm,
- byte[] rawPassPhrase,
- bool clearPassPhrase,
- bool useSha1,
- PgpSignatureSubpacketVector hashedPackets,
- PgpSignatureSubpacketVector unhashedPackets,
- SecureRandom rand)
- : this(keyPair.PrivateKey, CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets, hashAlgorithm),
- encAlgorithm, rawPassPhrase, clearPassPhrase, useSha1, rand, true)
- {
- }
-
- private static PgpPublicKey CertifiedPublicKey(
- int certificationLevel,
- PgpKeyPair keyPair,
- string id,
- PgpSignatureSubpacketVector hashedPackets,
- PgpSignatureSubpacketVector unhashedPackets)
- {
- PgpSignatureGenerator sGen;
- try
- {
- sGen = new PgpSignatureGenerator(keyPair.PublicKey.Algorithm, HashAlgorithmTag.Sha1);
- }
- catch (Exception e)
- {
- throw new PgpException("Creating signature generator: " + e.Message, e);
- }
-
- //
- // Generate the certification
- //
- sGen.InitSign(certificationLevel, keyPair.PrivateKey);
-
- sGen.SetHashedSubpackets(hashedPackets);
- sGen.SetUnhashedSubpackets(unhashedPackets);
-
- try
- {
- PgpSignature certification = sGen.GenerateCertification(id, keyPair.PublicKey);
- return PgpPublicKey.AddCertification(keyPair.PublicKey, id, certification);
- }
- catch (Exception e)
- {
- throw new PgpException("Exception doing certification: " + e.Message, e);
- }
- }
-
-
- private static PgpPublicKey CertifiedPublicKey(
- int certificationLevel,
- PgpKeyPair keyPair,
- string id,
- PgpSignatureSubpacketVector hashedPackets,
- PgpSignatureSubpacketVector unhashedPackets,
- HashAlgorithmTag hashAlgorithm)
- {
- PgpSignatureGenerator sGen;
- try
- {
- sGen = new PgpSignatureGenerator(keyPair.PublicKey.Algorithm, hashAlgorithm);
- }
- catch (Exception e)
- {
- throw new PgpException("Creating signature generator: " + e.Message, e);
- }
-
- //
- // Generate the certification
- //
- sGen.InitSign(certificationLevel, keyPair.PrivateKey);
-
- sGen.SetHashedSubpackets(hashedPackets);
- sGen.SetUnhashedSubpackets(unhashedPackets);
-
- try
- {
- PgpSignature certification = sGen.GenerateCertification(id, keyPair.PublicKey);
- return PgpPublicKey.AddCertification(keyPair.PublicKey, id, certification);
- }
- catch (Exception e)
- {
- throw new PgpException("Exception doing certification: " + e.Message, e);
- }
- }
-
public PgpSecretKey(
int certificationLevel,
PublicKeyAlgorithmTag algorithm,
@@ -455,6 +499,8 @@ public bool IsSigningKey
case PublicKeyAlgorithmTag.ECDsa:
case PublicKeyAlgorithmTag.EdDsa_Legacy:
case PublicKeyAlgorithmTag.ElGamalGeneral:
+ case PublicKeyAlgorithmTag.Ed25519:
+ case PublicKeyAlgorithmTag.Ed448:
return true;
default:
return false;
@@ -485,6 +531,12 @@ public SymmetricKeyAlgorithmTag KeyEncryptionAlgorithm
get { return secret.EncAlgorithm; }
}
+ /// The AEAD algorithm the key is encrypted with.
+ public AeadAlgorithmTag KeyEncryptionAeadAlgorithm
+ {
+ get { return secret.AeadAlgorithm; }
+ }
+
/// The key ID of the public key associated with this key.
public long KeyId
{
@@ -545,7 +597,35 @@ private byte[] ExtractKeyData(byte[] rawPassPhrase, bool clearPassPhrase)
byte[] iv = secret.GetIV();
byte[] data;
- if (secret.PublicKeyPacket.Version >= 4)
+ if (secret.S2kUsage == SecretKeyPacket.UsageAead)
+ {
+ var aeadAlgorithm = secret.AeadAlgorithm;
+ var hkdfInfo = secret.GetAAData();
+ var hkdfParams = new HkdfParameters(key.GetKey(), Array.Empty(), hkdfInfo);
+ var hkdfGen = new HkdfBytesGenerator(PgpUtilities.CreateDigest(HashAlgorithmTag.Sha256));
+ hkdfGen.Init(hkdfParams);
+ var hkdfOutput = new byte[PgpUtilities.GetKeySizeInOctets(encAlgorithm)];
+ hkdfGen.GenerateBytes(hkdfOutput, 0, hkdfOutput.Length);
+
+ var encodedPubkeyContents = secret.PublicKeyPacket.GetEncodedContents();
+ byte[] aadata = new byte[encodedPubkeyContents.Length + 1];
+ aadata[0] = (byte)(0xC0 | (byte)secret.Tag);
+ Array.Copy(encodedPubkeyContents, 0, aadata, 1, encodedPubkeyContents.Length);
+
+ var encAlgoName = PgpUtilities.GetSymmetricCipherName(encAlgorithm);
+ var aeadAlgoName = AeadUtils.GetAeadAlgorithmName(aeadAlgorithm);
+ var cipher = CipherUtilities.GetCipher($"{encAlgoName}/{aeadAlgoName}/NoPadding");
+
+ var aeadParams = new AeadParameters(
+ new KeyParameter(hkdfOutput),
+ 8 * AeadUtils.GetAuthTagLength(aeadAlgorithm),
+ iv,
+ aadata);
+
+ cipher.Init(false, aeadParams);
+ data = cipher.DoFinal(encData);
+ }
+ else if (secret.PublicKeyPacket.Version >= PublicKeyPacket.Version4)
{
data = RecoverKeyData(encAlgorithm, "/CFB/NoPadding", key, iv, encData, 0, encData.Length);
@@ -777,11 +857,33 @@ internal PgpPrivateKey DoExtractPrivateKey(byte[] rawPassPhrase, bool clearPassP
ElGamalParameters elParams = new ElGamalParameters(elPub.P, elPub.G);
privateKey = new ElGamalPrivateKeyParameters(elPriv.X, elParams);
break;
+
+
+ case PublicKeyAlgorithmTag.Ed25519:
+ Ed25519SecretBcpgKey ed25519key = new Ed25519SecretBcpgKey(bcpgIn);
+ privateKey = new Ed25519PrivateKeyParameters(ed25519key.GetKey());
+ break;
+
+ case PublicKeyAlgorithmTag.X25519:
+ X25519SecretBcpgKey x25519key = new X25519SecretBcpgKey(bcpgIn);
+ privateKey = new X25519PrivateKeyParameters(x25519key.GetKey());
+ break;
+
+ case PublicKeyAlgorithmTag.Ed448:
+ Ed448SecretBcpgKey ed448key = new Ed448SecretBcpgKey(bcpgIn);
+ privateKey = new Ed448PrivateKeyParameters(ed448key.GetKey());
+ break;
+
+ case PublicKeyAlgorithmTag.X448:
+ X448SecretBcpgKey x448key = new X448SecretBcpgKey(bcpgIn);
+ privateKey = new X448PrivateKeyParameters(x448key.GetKey());
+ break;
+
default:
throw new PgpException("unknown public key algorithm encountered");
}
- return new PgpPrivateKey(KeyId, pubPk, privateKey);
+ return new PgpPrivateKey(pub, privateKey);
}
catch (PgpException)
{
@@ -978,7 +1080,14 @@ internal static PgpSecretKey DoCopyWithNewPassword(
if (newEncAlgorithm == SymmetricKeyAlgorithmTag.Null)
{
s2kUsage = SecretKeyPacket.UsageNone;
- if (key.secret.S2kUsage == SecretKeyPacket.UsageSha1) // SHA-1 hash, need to rewrite Checksum
+
+ // v6 keys with UsageNone don't use checksums
+ if (key.PublicKey.Version == PublicKeyPacket.Version6)
+ {
+ keyData = new byte[rawKeyData.Length - 2];
+ Array.Copy(rawKeyData, 0, keyData, 0, keyData.Length - 2);
+ }
+ else if (key.secret.S2kUsage == SecretKeyPacket.UsageSha1) // SHA-1 hash, need to rewrite Checksum
{
keyData = new byte[rawKeyData.Length - 18];
@@ -998,13 +1107,34 @@ internal static PgpSecretKey DoCopyWithNewPassword(
{
if (s2kUsage == SecretKeyPacket.UsageNone)
{
- s2kUsage = SecretKeyPacket.UsageChecksum;
+ if (key.PublicKey.Version == PublicKeyPacket.Version6)
+ {
+ // v6 keys with UsageNone don't use checksums
+ }
+ else if (key.PublicKey.Version == PublicKeyPacket.Version3)
+ {
+ // for backward compatibility with legacy v3 keys use the deprecated UsageChecksum (MalleableCFB)
+ s2kUsage = SecretKeyPacket.UsageChecksum;
+ }
+ else
+ {
+ // rewrite Checksum using SHA-1 instead of the deprecated UsageChecksum (MalleableCFB)
+ s2kUsage = SecretKeyPacket.UsageSha1;
+ byte[] check = Checksum(true, rawKeyData, rawKeyData.Length - 2);
+ byte[] newKeyData = new byte[rawKeyData.Length - 2 + 20];
+ Array.Copy(rawKeyData, 0, newKeyData, 0, rawKeyData.Length - 2);
+ Array.Copy(check, 0, newKeyData, rawKeyData.Length - 2, check.Length);
+
+ rawKeyData = newKeyData;
+ }
+
}
try
{
- if (pubKeyPacket.Version >= 4)
+ if (pubKeyPacket.Version >= PublicKeyPacket.Version4)
{
+ // TODO for v6 use AEAD when implemented
keyData = EncryptKeyDataV4(rawKeyData, newEncAlgorithm, HashAlgorithmTag.Sha1, rawNewPassPhrase, clearPassPhrase, rand, out s2k, out iv);
}
else
diff --git a/crypto/src/openpgp/PgpSignature.cs b/crypto/src/openpgp/PgpSignature.cs
index d6ffc0f747..b543bb9d2e 100644
--- a/crypto/src/openpgp/PgpSignature.cs
+++ b/crypto/src/openpgp/PgpSignature.cs
@@ -109,6 +109,12 @@ public void InitVerify(PgpPublicKey pubKey)
try
{
sig.Init(false, key);
+
+ if (Version == SignaturePacket.Version6)
+ {
+ byte[] salt = GetSignatureSalt();
+ sig.BlockUpdate(salt, 0, salt.Length);
+ }
}
catch (InvalidKeyException e)
{
@@ -207,7 +213,17 @@ public bool Verify()
return sig.VerifySignature(GetSignature());
}
- private void UpdateWithIdData(int header, byte[] idBytes)
+ public bool Verify(byte[] additionalMetadata)
+ {
+ // Additional metadata for v5 signatures
+ byte[] trailer = sigPck.GetSignatureTrailer(additionalMetadata);
+
+ sig.BlockUpdate(trailer, 0, trailer.Length);
+
+ return sig.VerifySignature(GetSignature());
+ }
+
+ private void UpdateWithIdData(int header, byte[] idBytes)
{
this.Update(
(byte) header,
@@ -222,10 +238,25 @@ private void UpdateWithPublicKey(PgpPublicKey key)
{
byte[] keyBytes = GetEncodedPublicKey(key);
- this.Update(
- (byte) 0x99,
- (byte)(keyBytes.Length >> 8),
- (byte)(keyBytes.Length));
+ this.Update(PgpPublicKey.FingerprintPreamble(Version));
+
+ switch (Version)
+ {
+ case SignaturePacket.Version4:
+ this.Update(
+ (byte)(keyBytes.Length >> 8),
+ (byte)(keyBytes.Length));
+ break;
+ case SignaturePacket.Version5:
+ case SignaturePacket.Version6:
+ this.Update(
+ (byte)(keyBytes.Length >> 24),
+ (byte)(keyBytes.Length >> 16),
+ (byte)(keyBytes.Length >> 8),
+ (byte)(keyBytes.Length));
+ break;
+ }
+
this.Update(keyBytes);
}
@@ -300,7 +331,8 @@ public bool VerifyCertification(
PgpPublicKey pubKey)
{
if (SignatureType != KeyRevocation
- && SignatureType != SubkeyRevocation)
+ && SignatureType != SubkeyRevocation
+ && SignatureType != DirectKey)
{
throw new InvalidOperationException("signature is not a key signature");
}
@@ -321,7 +353,12 @@ public long KeyId
get { return sigPck.KeyId; }
}
- /// The creation time of this signature.
+ public byte[] GetIssuerFingerprint()
+ {
+ return sigPck.GetIssuerFingerprint();
+ }
+
+ /// The creation time of this signature.
public DateTime CreationTime
{
get { return DateTimeUtilities.UnixMsToDateTime(sigPck.CreationTime); }
@@ -332,10 +369,21 @@ public byte[] GetSignatureTrailer()
return sigPck.GetSignatureTrailer();
}
- ///
- /// Return true if the signature has either hashed or unhashed subpackets.
- ///
- public bool HasSubpackets
+ public byte[] GetSignatureTrailer(byte[] additionalMetadata)
+ {
+ // Additional metadata for v5 signatures
+ return sigPck.GetSignatureTrailer(additionalMetadata);
+ }
+
+ public byte[] GetSignatureSalt()
+ {
+ return sigPck.GetSignatureSalt();
+ }
+
+ ///
+ /// Return true if the signature has either hashed or unhashed subpackets.
+ ///
+ public bool HasSubpackets
{
get
{
@@ -514,8 +562,13 @@ public static PgpSignature Join(PgpSignature sig1, PgpSignature sig2)
}
SignatureSubpacket[] unhashed = merged.ToArray();
- return new PgpSignature(
- new SignaturePacket(
+
+ SignaturePacket sigpkt;
+
+ if (sig1.KeyAlgorithm == PublicKeyAlgorithmTag.Ed25519 || sig1.KeyAlgorithm == PublicKeyAlgorithmTag.Ed448)
+ {
+ sigpkt = new SignaturePacket(
+ sig1.Version,
sig1.SignatureType,
sig1.KeyId,
sig1.KeyAlgorithm,
@@ -523,9 +576,28 @@ public static PgpSignature Join(PgpSignature sig1, PgpSignature sig2)
sig1.GetHashedSubPackets().ToSubpacketArray(),
unhashed,
sig1.GetDigestPrefix(),
- sig1.sigPck.GetSignature()
- )
- );
+ sig1.GetSignatureSalt(),
+ sig1.GetIssuerFingerprint(),
+ sig1.sigPck.GetSignatureBytes());
+ }
+ else
+ {
+ sigpkt = new SignaturePacket(
+ sig1.Version,
+ sig1.SignatureType,
+ sig1.KeyId,
+ sig1.KeyAlgorithm,
+ sig1.HashAlgorithm,
+ sig1.GetHashedSubPackets().ToSubpacketArray(),
+ unhashed,
+ sig1.GetDigestPrefix(),
+ sig1.GetSignatureSalt(),
+ sig1.GetIssuerFingerprint(),
+ sig1.sigPck.GetSignature());
+ }
+
+
+ return new PgpSignature(sigpkt);
}
}
}
diff --git a/crypto/src/openpgp/PgpSignatureGenerator.cs b/crypto/src/openpgp/PgpSignatureGenerator.cs
index 7ff771997a..b5c71ff940 100644
--- a/crypto/src/openpgp/PgpSignatureGenerator.cs
+++ b/crypto/src/openpgp/PgpSignatureGenerator.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel;
using System.IO;
using Org.BouncyCastle.Bcpg.Sig;
@@ -14,17 +15,21 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
/// Generator for PGP signatures.
public class PgpSignatureGenerator
{
- private static readonly SignatureSubpacket[] EmptySignatureSubpackets = new SignatureSubpacket[0];
+ private static readonly SignatureSubpacket[] EmptySignatureSubpackets = Array.Empty();
private readonly PublicKeyAlgorithmTag keyAlgorithm;
private readonly HashAlgorithmTag hashAlgorithm;
+ private int version;
+
private PgpPrivateKey privKey;
private ISigner sig;
- private IDigest dig;
+ private readonly IDigest dig;
private int signatureType;
private byte lastb;
+ private byte[] salt;
+
private SignatureSubpacket[] unhashed = EmptySignatureSubpackets;
private SignatureSubpacket[] hashed = EmptySignatureSubpackets;
@@ -45,13 +50,24 @@ public void InitSign(int sigType, PgpPrivateKey privKey)
InitSign(sigType, privKey, null);
}
- /// Initialise the generator for signing.
- public void InitSign(int sigType, PgpPrivateKey privKey, SecureRandom random)
+ /// Initialise the generator for signing.
+ public void InitSign(int sigType, PgpPrivateKey privKey, SecureRandom random)
{
+ // https://www.rfc-editor.org/rfc/rfc9580#name-signature-packet-type-id-2
+ // An implementation MUST generate a version 6 signature when signing with a version 6 key.
+ // An implementation MUST generate a version 4 signature when signing with a version 4 key.
+ this.version = privKey.Version;
this.privKey = privKey;
this.signatureType = sigType;
- AsymmetricKeyParameter key = privKey.Key;
+ // https://www.rfc-editor.org/rfc/rfc9580#name-hash-algorithms
+ // avoid V6 signatures with weak hash algorithms
+ if (this.version > SignaturePacket.Version4 && (hashAlgorithm == HashAlgorithmTag.MD5 || hashAlgorithm == HashAlgorithmTag.Sha1 || hashAlgorithm == HashAlgorithmTag.RipeMD160))
+ {
+ throw new PgpException("avoid V6 signatures with weak hash algorithms");
+ }
+
+ AsymmetricKeyParameter key = privKey.Key;
this.sig = PgpUtilities.CreateSigner(keyAlgorithm, hashAlgorithm, key);
@@ -60,7 +76,9 @@ public void InitSign(int sigType, PgpPrivateKey privKey, SecureRandom random)
ICipherParameters cp = key;
// TODO Ask SignerUtilities whether random is permitted?
- if (keyAlgorithm == PublicKeyAlgorithmTag.EdDsa_Legacy)
+ if (keyAlgorithm == PublicKeyAlgorithmTag.EdDsa_Legacy
+ || keyAlgorithm == PublicKeyAlgorithmTag.Ed25519
+ || keyAlgorithm == PublicKeyAlgorithmTag.Ed448)
{
// EdDSA signers don't expect a SecureRandom
}
@@ -70,6 +88,19 @@ public void InitSign(int sigType, PgpPrivateKey privKey, SecureRandom random)
}
sig.Init(true, cp);
+
+ // salt for v6 signatures
+ if(version == SignaturePacket.Version6)
+ {
+ if (random is null)
+ {
+ throw new ArgumentNullException(nameof(random), "v6 signatures requires a SecureRandom() for salt generation");
+ }
+ int saltSize = PgpUtilities.GetSaltSize(hashAlgorithm);
+ salt = new byte[saltSize];
+ random.NextBytes(salt);
+ sig.BlockUpdate(salt, 0, salt.Length);
+ }
}
catch (InvalidKeyException e)
{
@@ -190,9 +221,23 @@ public void SetUnhashedSubpackets(
public PgpOnePassSignature GenerateOnePassVersion(
bool isNested)
{
- return new PgpOnePassSignature(
- new OnePassSignaturePacket(
- signatureType, hashAlgorithm, keyAlgorithm, privKey.KeyId, isNested));
+ OnePassSignaturePacket opsPkt;
+
+ // the versions of the Signature and the One-Pass Signature must be aligned as specified in
+ // https://www.rfc-editor.org/rfc/rfc9580#signed-message-versions
+ if (version == SignaturePacket.Version6)
+ {
+ opsPkt = new OnePassSignaturePacket(
+ signatureType, hashAlgorithm, keyAlgorithm, salt, privKey.GetFingerprint(), isNested);
+
+ }
+ else
+ {
+ opsPkt = new OnePassSignaturePacket(
+ signatureType, hashAlgorithm, keyAlgorithm, privKey.KeyId, isNested);
+ }
+
+ return new PgpOnePassSignature(opsPkt);
}
/// Return a signature object containing the current signature state.
@@ -205,36 +250,65 @@ public PgpSignature Generate()
hPkts = InsertSubpacket(hPkts, new SignatureCreationTime(false, DateTime.UtcNow));
}
- if (!IsPacketPresent(hashed, SignatureSubpacketTag.IssuerKeyId)
- && !IsPacketPresent(unhashed, SignatureSubpacketTag.IssuerKeyId))
- {
- unhPkts = InsertSubpacket(unhPkts, new IssuerKeyId(false, privKey.KeyId));
- }
+ // https://www.rfc-editor.org/rfc/rfc9580#name-issuer-key-id
+ // https://www.rfc-editor.org/rfc/rfc9580#issuer-fingerprint-subpacket
+ bool containsIssuerKeyId = IsPacketPresent(hashed, SignatureSubpacketTag.IssuerKeyId) || IsPacketPresent(unhashed, SignatureSubpacketTag.IssuerKeyId);
+ bool containsIssuerKeyFpr = IsPacketPresent(hashed, SignatureSubpacketTag.IssuerFingerprint) || IsPacketPresent(unhashed, SignatureSubpacketTag.IssuerFingerprint);
+ switch (version)
+ {
+ case SignaturePacket.Version4:
+ // TODO enforce constraint: if a v4 signature contains both IssuerKeyId and IssuerFingerprint
+ // subpackets, then the KeyId MUST match the low 64 bits of the fingerprint.
+ if (!containsIssuerKeyId)
+ {
+ unhPkts = InsertSubpacket(unhPkts, new IssuerKeyId(false, privKey.KeyId));
+ }
+ break;
+ case SignaturePacket.Version6:
+ // V6 signatures MUST NOT include an IssuerKeyId subpacket and SHOULD include an IssuerFingerprint subpacket
+ if (containsIssuerKeyId)
+ {
+ // TODO enforce constraint: v6 signatures MUST NOT include an IssuerKeyId subpacket
+ }
+ if (!containsIssuerKeyFpr)
+ {
+ unhPkts = InsertSubpacket(unhPkts, new IssuerFingerprint(false, privKey.Version, privKey.GetFingerprint()));
+ }
+ break;
+ }
- int version = 4;
byte[] hData;
try
{
- MemoryStream hOut = new MemoryStream();
-
- for (int i = 0; i != hPkts.Length; i++)
+ using (MemoryStream hOut = new MemoryStream())
{
- hPkts[i].Encode(hOut);
- }
-
- byte[] data = hOut.ToArray();
-
- MemoryStream sOut = new MemoryStream(data.Length + 6);
- sOut.WriteByte((byte)version);
- sOut.WriteByte((byte)signatureType);
- sOut.WriteByte((byte)keyAlgorithm);
- sOut.WriteByte((byte)hashAlgorithm);
- sOut.WriteByte((byte)(data.Length >> 8));
- sOut.WriteByte((byte)data.Length);
- sOut.Write(data, 0, data.Length);
- hData = sOut.ToArray();
+ for (int i = 0; i != hPkts.Length; i++)
+ {
+ hPkts[i].Encode(hOut);
+ }
+
+ byte[] data = hOut.ToArray();
+
+ using (MemoryStream sOut = new MemoryStream(data.Length + 6))
+ {
+ sOut.WriteByte((byte)version);
+ sOut.WriteByte((byte)signatureType);
+ sOut.WriteByte((byte)keyAlgorithm);
+ sOut.WriteByte((byte)hashAlgorithm);
+ if (version == SignaturePacket.Version6)
+ {
+ sOut.WriteByte((byte)(data.Length >> 24));
+ sOut.WriteByte((byte)(data.Length >> 16));
+ }
+ sOut.WriteByte((byte)(data.Length >> 8));
+ sOut.WriteByte((byte)data.Length);
+ sOut.Write(data, 0, data.Length);
+
+ hData = sOut.ToArray();
+ }
+ }
}
catch (IOException e)
{
@@ -261,41 +335,75 @@ public PgpSignature Generate()
byte[] digest = DigestUtilities.DoFinal(dig);
byte[] fingerPrint = new byte[2]{ digest[0], digest[1] };
- MPInteger[] sigValues;
- if (keyAlgorithm == PublicKeyAlgorithmTag.EdDsa_Legacy)
- {
- int sigLen = sigBytes.Length;
- if (sigLen == Ed25519.SignatureSize)
+ SignaturePacket sigPkt;
+
+ if (keyAlgorithm == PublicKeyAlgorithmTag.Ed25519 || keyAlgorithm == PublicKeyAlgorithmTag.Ed448)
+ {
+ sigPkt = new SignaturePacket(
+ version,
+ signatureType,
+ privKey.KeyId,
+ keyAlgorithm,
+ hashAlgorithm,
+ hPkts,
+ unhPkts,
+ fingerPrint,
+ version == SignaturePacket.Version6 ? salt : null,
+ privKey.GetFingerprint(),
+ sigBytes);
+ }
+ else
+ {
+ MPInteger[] sigValues;
+ if (keyAlgorithm == PublicKeyAlgorithmTag.EdDsa_Legacy)
{
- sigValues = new MPInteger[2]{
- new MPInteger(new BigInteger(1, sigBytes, 0, 32)),
- new MPInteger(new BigInteger(1, sigBytes, 32, 32))
- };
+ int sigLen = sigBytes.Length;
+
+ if (sigLen == Ed25519.SignatureSize)
+ {
+ sigValues = new MPInteger[2]
+ {
+ new MPInteger(new BigInteger(1, sigBytes, 0, 32)),
+ new MPInteger(new BigInteger(1, sigBytes, 32, 32))
+ };
+ }
+ else if (sigLen == Ed448.SignatureSize)
+ {
+ sigValues = new MPInteger[2]
+ {
+ new MPInteger(new BigInteger(1, Arrays.Prepend(sigBytes, 0x40))),
+ new MPInteger(BigInteger.Zero)
+ };
+ }
+ else
+ {
+ throw new InvalidOperationException();
+ }
}
- else if (sigLen == Ed448.SignatureSize)
- {
- sigValues = new MPInteger[2]{
- new MPInteger(new BigInteger(1, Arrays.Prepend(sigBytes, 0x40))),
- new MPInteger(BigInteger.Zero)
- };
- }
- else
+ else if (keyAlgorithm == PublicKeyAlgorithmTag.RsaSign || keyAlgorithm == PublicKeyAlgorithmTag.RsaGeneral)
{
- throw new InvalidOperationException();
+ sigValues = PgpUtilities.RsaSigToMpi(sigBytes);
}
- }
- else if (keyAlgorithm == PublicKeyAlgorithmTag.RsaSign || keyAlgorithm == PublicKeyAlgorithmTag.RsaGeneral)
- {
- sigValues = PgpUtilities.RsaSigToMpi(sigBytes);
- }
- else
- {
- sigValues = PgpUtilities.DsaSigToMpi(sigBytes);
+ else
+ {
+ sigValues = PgpUtilities.DsaSigToMpi(sigBytes);
+ }
+
+ sigPkt = new SignaturePacket(
+ version,
+ signatureType,
+ privKey.KeyId,
+ keyAlgorithm,
+ hashAlgorithm,
+ hPkts,
+ unhPkts,
+ fingerPrint,
+ version == SignaturePacket.Version6 ? salt : null,
+ privKey.GetFingerprint(),
+ sigValues);
}
- return new PgpSignature(
- new SignaturePacket(signatureType, privKey.KeyId, keyAlgorithm,
- hashAlgorithm, hPkts, unhPkts, fingerPrint, sigValues));
+ return new PgpSignature(sigPkt);
}
/// Generate a certification for the passed in ID and key.
@@ -387,7 +495,7 @@ private static bool IsPacketPresent(SignatureSubpacket[] packets, SignatureSubpa
return false;
}
- private static SignatureSubpacket[] InsertSubpacket(SignatureSubpacket[] packets, SignatureSubpacket subpacket)
+ private static SignatureSubpacket[] InsertSubpacket(SignatureSubpacket[] packets, SignatureSubpacket subpacket)
{
return Arrays.Prepend(packets, subpacket);
}
@@ -408,11 +516,26 @@ private void UpdateWithPublicKey(
{
byte[] keyBytes = GetEncodedPublicKey(key);
- Update(
- 0x99,
- (byte)(keyBytes.Length >> 8),
- (byte)(keyBytes.Length));
- Update(keyBytes);
- }
+ this.Update(PgpPublicKey.FingerprintPreamble(version));
+
+ switch (version)
+ {
+ case SignaturePacket.Version4:
+ this.Update(
+ (byte)(keyBytes.Length >> 8),
+ (byte)(keyBytes.Length));
+ break;
+ case SignaturePacket.Version5:
+ case SignaturePacket.Version6:
+ this.Update(
+ (byte)(keyBytes.Length >> 24),
+ (byte)(keyBytes.Length >> 16),
+ (byte)(keyBytes.Length >> 8),
+ (byte)(keyBytes.Length));
+ break;
+ }
+
+ this.Update(keyBytes);
+ }
}
}
diff --git a/crypto/src/openpgp/PgpUtilities.cs b/crypto/src/openpgp/PgpUtilities.cs
index fa04f5f463..d1f5b7173f 100644
--- a/crypto/src/openpgp/PgpUtilities.cs
+++ b/crypto/src/openpgp/PgpUtilities.cs
@@ -8,6 +8,7 @@
using Org.BouncyCastle.Asn1.Sec;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Signers;
using Org.BouncyCastle.Math;
@@ -26,18 +27,22 @@ public sealed class PgpUtilities
private static IDictionary CreateNameToHashID()
{
- var d = new Dictionary(StringComparer.OrdinalIgnoreCase);
- d.Add("sha1", HashAlgorithmTag.Sha1);
- d.Add("sha224", HashAlgorithmTag.Sha224);
- d.Add("sha256", HashAlgorithmTag.Sha256);
- d.Add("sha384", HashAlgorithmTag.Sha384);
- d.Add("sha512", HashAlgorithmTag.Sha512);
- d.Add("ripemd160", HashAlgorithmTag.RipeMD160);
- d.Add("rmd160", HashAlgorithmTag.RipeMD160);
- d.Add("md2", HashAlgorithmTag.MD2);
- d.Add("tiger", HashAlgorithmTag.Tiger192);
- d.Add("haval", HashAlgorithmTag.Haval5pass160);
- d.Add("md5", HashAlgorithmTag.MD5);
+ var d = new Dictionary(StringComparer.OrdinalIgnoreCase)
+ {
+ { "sha1", HashAlgorithmTag.Sha1 },
+ { "sha224", HashAlgorithmTag.Sha224 },
+ { "sha256", HashAlgorithmTag.Sha256 },
+ { "sha384", HashAlgorithmTag.Sha384 },
+ { "sha512", HashAlgorithmTag.Sha512 },
+ { "ripemd160", HashAlgorithmTag.RipeMD160 },
+ { "rmd160", HashAlgorithmTag.RipeMD160 },
+ { "md2", HashAlgorithmTag.MD2 },
+ { "tiger", HashAlgorithmTag.Tiger192 },
+ { "haval", HashAlgorithmTag.Haval5pass160 },
+ { "md5", HashAlgorithmTag.MD5 },
+ { "Sha3_256" , HashAlgorithmTag.Sha3_256},
+ { "Sha3_512" , HashAlgorithmTag.Sha3_512},
+ };
return d;
}
@@ -102,11 +107,37 @@ public static string GetDigestName(
return "SHA384";
case HashAlgorithmTag.Sha512:
return "SHA512";
- default:
+ case HashAlgorithmTag.Sha3_256:
+ return "SHA3-256";
+ case HashAlgorithmTag.Sha3_512:
+ return "SHA3-512";
+ default:
throw new PgpException("unknown hash algorithm tag in GetDigestName: " + hashAlgorithm);
}
}
+ ///
+ /// Returns the V6 signature salt size for a hash algorithm.
+ /// https://www.rfc-editor.org/rfc/rfc9580#name-hash-algorithms
+ ///
+ public static int GetSaltSize(HashAlgorithmTag hashAlgorithm)
+ {
+ switch (hashAlgorithm)
+ {
+ case HashAlgorithmTag.Sha256:
+ case HashAlgorithmTag.Sha224:
+ case HashAlgorithmTag.Sha3_256:
+ return 16;
+ case HashAlgorithmTag.Sha384:
+ return 24;
+ case HashAlgorithmTag.Sha512:
+ case HashAlgorithmTag.Sha3_512:
+ return 32;
+ default:
+ return 0;
+ }
+ }
+
public static int GetDigestIDForName(string name)
{
if (NameToHashID.TryGetValue(name, out var hashAlgorithmTag))
@@ -157,11 +188,18 @@ public static string GetSignatureName(
case PublicKeyAlgorithmTag.ElGamalGeneral:
encAlg = "ElGamal";
break;
- default:
+ case PublicKeyAlgorithmTag.Ed25519:
+ encAlg = "Ed25519";
+ break;
+ case PublicKeyAlgorithmTag.Ed448:
+ encAlg = "Ed448";
+ break;
+ default:
throw new PgpException("unknown algorithm tag in signature:" + keyAlgorithm);
}
- return GetDigestName(hashAlgorithm) + "with" + encAlg;
+ string digestName = GetDigestName(hashAlgorithm);
+ return $"{digestName}with{encAlg}";
}
public static string GetSymmetricCipherName(
@@ -235,7 +273,12 @@ public static int GetKeySize(SymmetricKeyAlgorithmTag algorithm)
return keySize;
}
- public static KeyParameter MakeKey(
+ public static int GetKeySizeInOctets(SymmetricKeyAlgorithmTag algorithm)
+ {
+ return (GetKeySize(algorithm) + 7) / 8;
+ }
+
+ public static KeyParameter MakeKey(
SymmetricKeyAlgorithmTag algorithm,
byte[] keyBytes)
{
@@ -297,7 +340,24 @@ internal static KeyParameter DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag al
int generatedBytes = 0;
int loopCount = 0;
- while (generatedBytes < keyBytes.Length)
+ if (s2k != null && s2k.Type == S2k.Argon2)
+ {
+ Argon2Parameters.Builder builder =
+ new Argon2Parameters.Builder(Argon2Parameters.Argon2id)
+ .WithVersion(Argon2Parameters.Version13)
+ .WithIterations(s2k.Passes)
+ .WithMemoryPowOfTwo(s2k.MemorySizeExponent)
+ .WithParallelism(s2k.Parallelism)
+ .WithSalt(s2k.GetIV());
+
+ Argon2BytesGenerator gen = new Argon2BytesGenerator();
+
+ gen.Init(builder.Build());
+ gen.GenerateBytes(rawPassPhrase, keyBytes, 0, keyBytes.Length);
+ return MakeKey(algorithm, keyBytes);
+ }
+
+ while (generatedBytes < keyBytes.Length)
{
IDigest digest;
if (s2k != null)
@@ -547,6 +607,8 @@ internal static ISigner CreateSigner(PublicKeyAlgorithmTag publicKeyAlgorithm, H
switch (publicKeyAlgorithm)
{
case PublicKeyAlgorithmTag.EdDsa_Legacy:
+ case PublicKeyAlgorithmTag.Ed25519:
+ case PublicKeyAlgorithmTag.Ed448:
{
ISigner signer;
if (key is Ed25519PrivateKeyParameters || key is Ed25519PublicKeyParameters)
diff --git a/crypto/test/data/openpgp/big-pkesk-aead-msg.asc b/crypto/test/data/openpgp/big-pkesk-aead-msg.asc
new file mode 100644
index 0000000000..7380c636e8
--- /dev/null
+++ b/crypto/test/data/openpgp/big-pkesk-aead-msg.asc
@@ -0,0 +1,57 @@
+-----BEGIN PGP MESSAGE-----
+Comment: V6 PKESK + V2 SEIPD AEAD encrypted message that spans
+Comment: over 4 chunks (chunk size 512 octets)
+Comment: 2000 octets of /dev/zero encrypted with sample V6
+Comment: certificate from RFC 9580 Appendix A.3
+Comment: generated with gosop 2.0.0-alpha
+Comment: Session key CFB73D46CF7C13B7535227BEDB5B2D8B4023C5B...
+Comment: Session key ...58289D19CF2C33B0DB388B0B6
+
+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
+-----END PGP MESSAGE-----
\ No newline at end of file
diff --git a/crypto/test/data/openpgp/big-skesk-aead-msg.asc b/crypto/test/data/openpgp/big-skesk-aead-msg.asc
new file mode 100644
index 0000000000..5a00b879c9
--- /dev/null
+++ b/crypto/test/data/openpgp/big-skesk-aead-msg.asc
@@ -0,0 +1,57 @@
+-----BEGIN PGP MESSAGE-----
+Comment: V6 SKESK + V2 SEIPD AEAD encrypted message that spans
+Comment: over 4 chunks (chunk size 512 octets)
+Comment: 2000 octets of /dev/zero encrypted with password
+Comment: "password" using Argon2 and AES-256 in OCB mode
+Comment: generated with gosop 2.0.0-alpha
+Comment: Session key A96F671431CEB0F859CFC653976417CCC41...
+Comment: Session key ...26BC0F93C30C6E5F0073E0B91E65A
+
+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
+sKZt+8/TOYGzbCH/2g==
+-----END PGP MESSAGE-----
\ No newline at end of file
diff --git a/crypto/test/src/openpgp/test/IgnoreMarkerPacketInCertificatesTest.cs b/crypto/test/src/openpgp/test/IgnoreMarkerPacketInCertificatesTest.cs
index 71196edaef..de1ce16523 100644
--- a/crypto/test/src/openpgp/test/IgnoreMarkerPacketInCertificatesTest.cs
+++ b/crypto/test/src/openpgp/test/IgnoreMarkerPacketInCertificatesTest.cs
@@ -58,15 +58,66 @@ public class IgnoreMarkerPacketInCertificatesTest
"=6mfA\n" +
"-----END PGP PUBLIC KEY BLOCK-----";
- public override string Name
+ // [PUBLIC KEY, MARKER, PADDING, USER-ID, SIGNATURE, SUBKEY, SIGNATURE]
+ private static readonly string CERT_WITH_MARKER_AND_PADDING = "" +
+ "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
+ "\n" +
+ "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\n" +
+ "7i5tWuPkqOFfY9U0Ql3ddlHNpdkTZoAx\n" +
+ "=kgFK\n" +
+ "-----END PGP PUBLIC KEY BLOCK-----";
+
+
+ public override string Name
{
get { return "IgnoreMarkerPacketInCertificatesTest"; }
}
public override void PerformTest()
+ {
+ PerformTestMarker(CERT_WITH_MARKER);
+ PerformTestMarker(CERT_WITH_MARKER_AND_PADDING);
+ }
+
+ private void PerformTestMarker(string cert)
{
ArmoredInputStream armorIn = new ArmoredInputStream(
- new MemoryStream(Encoding.UTF8.GetBytes(CERT_WITH_MARKER), false));
+ new MemoryStream(Encoding.UTF8.GetBytes(cert), false));
PgpObjectFactory objectFactory = new PgpObjectFactory(armorIn);
PgpPublicKeyRing certificate = (PgpPublicKeyRing)objectFactory.NextPgpObject();
@@ -86,7 +137,7 @@ public override void PerformTest()
IsEquals(1, Count(signatures));
}
- [Test]
+ [Test]
public void TestFunction()
{
string resultText = Perform().ToString();
diff --git a/crypto/test/src/openpgp/test/PGPArmoredTest.cs b/crypto/test/src/openpgp/test/PGPArmoredTest.cs
index 9fe5da6edd..2cacf1366d 100644
--- a/crypto/test/src/openpgp/test/PGPArmoredTest.cs
+++ b/crypto/test/src/openpgp/test/PGPArmoredTest.cs
@@ -124,6 +124,52 @@ private void blankLineTest()
}
}
+ private void VersionIsOptionalTest()
+ {
+ using (MemoryStream bOut = new MemoryStream())
+ {
+ using (ArmoredOutputStream aOut = new ArmoredOutputStream(bOut, addVersionHeader: false))
+ {
+ aOut.Write(sample, 0, sample.Length);
+ }
+
+ bOut.Position = 0;
+ using (var reader = new StreamReader(bOut))
+ {
+ string line;
+ while ((line = reader.ReadLine()) != null)
+ {
+ FailIf("Unexpected version armor header", line.StartsWith("Version:"));
+ }
+ }
+ }
+
+ using (MemoryStream bOut = new MemoryStream())
+ {
+ using (ArmoredOutputStream aOut = new ArmoredOutputStream(bOut, addVersionHeader: true))
+ {
+ aOut.Write(sample, 0, sample.Length);
+ }
+
+ bOut.Position = 0;
+ using (var reader = new StreamReader(bOut))
+ {
+ bool versionIsPresent = false;
+ string line;
+ while ((line = reader.ReadLine()) != null)
+ {
+ if (line.StartsWith("Version:"))
+ {
+ versionIsPresent = true;
+ break;
+ }
+ }
+
+ IsTrue("Version armored header expected", versionIsPresent);
+ }
+ }
+ }
+
private void repeatHeaderTest()
{
MemoryStream bOut = new MemoryStream();
@@ -288,6 +334,7 @@ public override void PerformTest()
blankLineTest();
pgpUtilTest();
repeatHeaderTest();
+ VersionIsOptionalTest();
}
public override string Name
diff --git a/crypto/test/src/openpgp/test/PGPClearSignedSignatureTest.cs b/crypto/test/src/openpgp/test/PGPClearSignedSignatureTest.cs
index a6f03c06f4..e41c1f19cf 100644
--- a/crypto/test/src/openpgp/test/PGPClearSignedSignatureTest.cs
+++ b/crypto/test/src/openpgp/test/PGPClearSignedSignatureTest.cs
@@ -164,23 +164,26 @@ public override string Name
private void messageTest(
string message,
- string type)
+ string type,
+ bool ignoreHeaders = false)
{
ArmoredInputStream aIn = new ArmoredInputStream(
new MemoryStream(Encoding.ASCII.GetBytes(message)));
- string[] headers = aIn.GetArmorHeaders();
-
- if (headers == null || headers.Length != 1)
+ if (!ignoreHeaders)
{
- Fail("wrong number of headers found");
- }
+ string[] headers = aIn.GetArmorHeaders();
- if (!"Hash: SHA256".Equals(headers[0]))
- {
- Fail("header value wrong: " + headers[0]);
- }
+ if (headers == null || headers.Length != 1)
+ {
+ Fail("wrong number of headers found");
+ }
+ if (!"Hash: SHA256".Equals(headers[0]))
+ {
+ Fail("header value wrong: " + headers[0]);
+ }
+ }
//
// read the input, making sure we ingore the last newline.
//
@@ -254,7 +257,8 @@ private PgpSecretKey ReadSecretKey(
private void generateTest(
string message,
- string type)
+ string type,
+ bool includeHashHeader = true)
{
PgpSecretKey pgpSecKey = ReadSecretKey(new MemoryStream(secretKey));
PgpPrivateKey pgpPrivKey = pgpSecKey.ExtractPrivateKey("".ToCharArray());
@@ -273,7 +277,14 @@ private void generateTest(
ArmoredOutputStream aOut = new ArmoredOutputStream(bOut);
MemoryStream bIn = new MemoryStream(Encoding.ASCII.GetBytes(message), false);
- aOut.BeginClearText(HashAlgorithmTag.Sha256);
+ if (includeHashHeader)
+ {
+ aOut.BeginClearText(HashAlgorithmTag.Sha256);
+ }
+ else
+ {
+ aOut.BeginClearText();
+ }
//
// note the last \n m_in the file is ignored
@@ -306,7 +317,7 @@ private void generateTest(
aOut.Close();
byte[] bs = bOut.ToArray();
- messageTest(Encoding.ASCII.GetString(bs, 0, bs.Length), type);
+ messageTest(Encoding.ASCII.GetString(bs, 0, bs.Length), type, !includeHashHeader);
}
private static int ReadInputLine(
@@ -424,7 +435,11 @@ public override void PerformTest()
generateTest(nlOnlyMessage, "\\r");
generateTest(crOnlyMessage, "\\n");
generateTest(crNlMessage, "\\r\\n");
- }
+
+ generateTest(nlOnlyMessage, "\\r", includeHashHeader: false);
+ generateTest(crOnlyMessage, "\\n", includeHashHeader: false);
+ generateTest(crNlMessage, "\\r\\n", includeHashHeader: false);
+ }
[Test]
public void TestFunction()
diff --git a/crypto/test/src/openpgp/test/PaddingPacketTest.cs b/crypto/test/src/openpgp/test/PaddingPacketTest.cs
new file mode 100644
index 0000000000..8c607d7b93
--- /dev/null
+++ b/crypto/test/src/openpgp/test/PaddingPacketTest.cs
@@ -0,0 +1,142 @@
+using NUnit.Framework;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.Test;
+using System.IO;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
+{
+ [TestFixture]
+ public class PaddingPacketTest
+ : SimpleTest
+ {
+ public override string Name => "PaddingPacketTest";
+
+ [Test]
+ public void PaddingPacketReadFromStreamTest()
+ {
+ /*
+ * Simple padding packet
+ *
+ * 0xD5 Packet tag (0xC0 | 0x15)
+ * 0x04 Length
+ * 0x01 0x02 0x03 0x04 Padding content
+ */
+ byte[] packet = Hex.Decode("D50401020304");
+ byte[] expected = Hex.Decode("01020304");
+
+ using (Stream input = new MemoryStream(packet))
+ {
+ PgpObjectFactory objectFactory = new PgpObjectFactory(input);
+ PgpObject obj = objectFactory.NextPgpObject();
+
+ IsTrue(obj is PgpPadding);
+
+ byte[] padding = (obj as PgpPadding).GetPadding();
+
+ IsEquals($"unexpected padding length: expected {expected.Length} got {padding.Length}", padding.Length, expected.Length);
+ FailIf($"unexpected padding", !AreEqual(padding, expected));
+ }
+ }
+
+
+ [Test]
+ public void PaddingPacketReadThreePacketsFromStreamTest()
+ {
+ byte[] packet = Hex.Decode("D50401020304D503556677D503AABBCC");
+ byte[][] expected = new byte[][] {
+ Hex.Decode("01020304"),
+ Hex.Decode("556677"),
+ Hex.Decode("AABBCC")
+ };
+
+ using (Stream input = new MemoryStream(packet))
+ {
+ PgpObjectFactory objectFactory = new PgpObjectFactory(input);
+
+ int i = 0;
+ PgpObject obj;
+ while ((obj = objectFactory.NextPgpObject()) != null)
+ {
+ IsTrue(obj is PgpPadding);
+ byte[] padding = (obj as PgpPadding).GetPadding();
+
+ IsEquals($"unexpected padding length: expected {expected[i].Length} got {padding.Length}", padding.Length, expected[i].Length);
+ FailIf($"unexpected padding", !AreEqual(padding, expected[i]));
+ ++i;
+ }
+
+ IsEquals(i, 3);
+ }
+ }
+
+ [Test]
+ public void PaddingPacketEncodeTest()
+ {
+ byte[] encoded = Hex.Decode("D50401020304");
+
+ byte[] padding = Hex.Decode("01020304");
+ PaddingPacket packet = new PaddingPacket(padding);
+
+ using (MemoryStream output = new MemoryStream())
+ {
+ BcpgOutputStream bcOut = new BcpgOutputStream(output);
+ packet.Encode(bcOut);
+ bcOut.Close();
+
+ FailIf("wrong encoding", !AreEqual(output.ToArray(), encoded));
+ }
+ }
+
+ [Test]
+ public void PaddingPacketEncodeThenDecodeTest()
+ {
+ SecureRandom random = new SecureRandom();
+ PaddingPacket packet = new PaddingPacket(32, random);
+
+ using (MemoryStream output = new MemoryStream())
+ {
+ BcpgOutputStream bcOut = new BcpgOutputStream(output);
+ packet.Encode(bcOut);
+ bcOut.Close();
+
+ using (Stream input = new MemoryStream(output.ToArray()))
+ {
+ PgpObjectFactory factory = new PgpObjectFactory(input);
+
+ PgpPadding padding = (PgpPadding)factory.NextPgpObject();
+ IsTrue(Arrays.AreEqual(packet.GetPadding(), padding.GetPadding()));
+ }
+ }
+ }
+
+ [Test]
+ public void KnownPaddingBytesTest()
+ {
+ byte[] known = Strings.ToByteArray("thisIsKnownPadding");
+ PaddingPacket packet = new PaddingPacket(known);
+ IsTrue(Arrays.AreEqual(known, packet.GetPadding()));
+ }
+
+ [Test]
+ public void Random50BytesTest()
+ {
+ int len = 50;
+ SecureRandom random = new SecureRandom();
+ PaddingPacket packet = new PaddingPacket(len, random);
+ IsEquals(len, packet.GetPadding().Length);
+ }
+
+
+ public override void PerformTest()
+ {
+ PaddingPacketReadFromStreamTest();
+ PaddingPacketReadThreePacketsFromStreamTest();
+ PaddingPacketEncodeTest();
+ PaddingPacketEncodeThenDecodeTest();
+ KnownPaddingBytesTest();
+ Random50BytesTest();
+ }
+ }
+}
\ No newline at end of file
diff --git a/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
new file mode 100644
index 0000000000..27606cbe17
--- /dev/null
+++ b/crypto/test/src/openpgp/test/PgpCryptoRefreshTest.cs
@@ -0,0 +1,1348 @@
+using NUnit.Framework;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Generators;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.Test;
+using System;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
+{
+ [TestFixture]
+ public class PgpCryptoRefreshTest
+ : SimpleTest
+ {
+ // https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-4-ed25519leg
+ private readonly byte[] v4Ed25519LegacyPubkeySample = Base64.Decode(
+ "xjMEU/NfCxYJKwYBBAHaRw8BAQdAPwmJlL3ZFu1AUxl5NOSofIBzOhKA1i+AEJku" +
+ "Q+47JAY=");
+
+ // https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-4-ed25519lega
+ private readonly byte[] v4Ed25519LegacySignatureSample = Base64.Decode(
+ "iF4EABYIAAYFAlX5X5UACgkQjP3hIZeWWpr2IgD/VvkMypjiECY3vZg/2xbBMd/S" +
+ "ftgr9N3lYG4NdWrtM2YBANCcT6EVJ/A44PV/IgHYLy6iyQMyZfps60iehUuuYbQE");
+
+ // https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-6-certificat
+ private readonly byte[] v6Certificate = Base64.Decode(
+ "xioGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laPCsQYf" +
+ "GwoAAABCBYJjh3/jAwsJBwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxy" +
+ "KwwfHifBilZwj2Ul7Ce62azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lw" +
+ "gyU2kCcUmKfvBXbAf6rhRYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaE" +
+ "QsiPlR4zxP/TP7mhfVEe7XWPxtnMUMtf15OyA51YBM4qBmOHf+MZAAAAIIaTJINn" +
+ "+eUBXbki+PSAld2nhJh/LVmFsS+60WyvXkQ1wpsGGBsKAAAALAWCY4d/4wKbDCIh" +
+ "BssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce62azJAAAAAAQBIKbpGG2dWTX8" +
+ "j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDEM0g12vYxoWM8Y81W+bHBw805" +
+ "I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUrk0mXubZvyl4GBg==");
+
+ // https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-6-secret-key
+ private readonly byte[] v6UnlockedSecretKey = Base64.Decode(
+ "xUsGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laMAGXKB" +
+ "exK+cH6NX1hs5hNhIB00TrJmosgv3mg1ditlsLfCsQYfGwoAAABCBYJjh3/jAwsJ" +
+ "BwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6" +
+ "2azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lwgyU2kCcUmKfvBXbAf6rh" +
+ "RYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaEQsiPlR4zxP/TP7mhfVEe" +
+ "7XWPxtnMUMtf15OyA51YBMdLBmOHf+MZAAAAIIaTJINn+eUBXbki+PSAld2nhJh/" +
+ "LVmFsS+60WyvXkQ1AE1gCk95TUR3XFeibg/u/tVY6a//1q0NWC1X+yui3O24wpsG" +
+ "GBsKAAAALAWCY4d/4wKbDCIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6" +
+ "2azJAAAAAAQBIKbpGG2dWTX8j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDE" +
+ "M0g12vYxoWM8Y81W+bHBw805I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUr" +
+ "k0mXubZvyl4GBg==");
+
+ // https://www.rfc-editor.org/rfc/rfc9580#name-sample-locked-version-6-sec
+ private readonly byte[] v6LockedSecretKey = Base64.Decode(
+ "xYIGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laP9JgkC" +
+ "FARdb9ccngltHraRe25uHuyuAQQVtKipJ0+r5jL4dacGWSAheCWPpITYiyfyIOPS" +
+ "3gIDyg8f7strd1OB4+LZsUhcIjOMpVHgmiY/IutJkulneoBYwrEGHxsKAAAAQgWC" +
+ "Y4d/4wMLCQcFFQoOCAwCFgACmwMCHgkiIQbLGGxPBgmml+TVLfpscisMHx4nwYpW" +
+ "cI9lJewnutmsyQUnCQIHAgAAAACtKCAQPi19In7A5tfORHHbNr/JcIMlNpAnFJin" +
+ "7wV2wH+q4UWFs7kDsBJ+xP2i8CMEWi7Ha8tPlXGpZR4UruETeh1mhELIj5UeM8T/" +
+ "0z+5oX1RHu11j8bZzFDLX9eTsgOdWATHggZjh3/jGQAAACCGkySDZ/nlAV25Ivj0" +
+ "gJXdp4SYfy1ZhbEvutFsr15ENf0mCQIUBA5hhGgp2oaavg6mFUXcFMwBBBUuE8qf" +
+ "9Ock+xwusd+GAglBr5LVyr/lup3xxQvHXFSjjA2haXfoN6xUGRdDEHI6+uevKjVR" +
+ "v5oAxgu7eJpaXNjCmwYYGwoAAAAsBYJjh3/jApsMIiEGyxhsTwYJppfk1S36bHIr" +
+ "DB8eJ8GKVnCPZSXsJ7rZrMkAAAAABAEgpukYbZ1ZNfyP5WMUzbUnSGpaUSD5t2Ki" +
+ "Nacp8DkBClZRa2c3AMQzSDXa9jGhYzxjzVb5scHDzTkjyRZWRdTq8U6L4da+/+Kt" +
+ "ruh8m7Xo2ehSSFyWRSuTSZe5tm/KXgYG");
+
+ // https://www.rfc-editor.org/rfc/rfc9580#name-sample-cleartext-signed-mes
+ private readonly string v6SampleCleartextSignedMessage = "What we need from the grocery store:\r\n\r\n- tofu\r\n- vegetables\r\n- noodles\r\n";
+ private readonly byte[] v6SampleCleartextSignedMessageSignature = Base64.Decode(
+ "wpgGARsKAAAAKQWCY5ijYyIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6" +
+ "2azJAAAAAGk2IHZJX1AhiJD39eLuPBgiUU9wUA9VHYblySHkBONKU/usJ9BvuAqo" +
+ "/FvLFuGWMbKAdA+epq7V4HOtAPlBWmU8QOd6aud+aSunHQaaEJ+iTFjP2OMW0KBr" +
+ "NK2ay45cX1IVAQ==");
+
+ // https://www.rfc-editor.org/rfc/rfc9580#name-sample-inline-signed-messag
+ private readonly byte[] v6SampleInlineSignedMessage = Base64.Decode(
+ "xEYGAQobIHZJX1AhiJD39eLuPBgiUU9wUA9VHYblySHkBONKU/usyxhsTwYJppfk" +
+ "1S36bHIrDB8eJ8GKVnCPZSXsJ7rZrMkBy0p1AAAAAABXaGF0IHdlIG5lZWQgZnJv" +
+ "bSB0aGUgZ3JvY2VyeSBzdG9yZToKCi0gdG9mdQotIHZlZ2V0YWJsZXMKLSBub29k" +
+ "bGVzCsKYBgEbCgAAACkFgmOYo2MiIQbLGGxPBgmml+TVLfpscisMHx4nwYpWcI9l" +
+ "JewnutmsyQAAAABpNiB2SV9QIYiQ9/Xi7jwYIlFPcFAPVR2G5ckh5ATjSlP7rCfQ" +
+ "b7gKqPxbyxbhljGygHQPnqau1eBzrQD5QVplPEDnemrnfmkrpx0GmhCfokxYz9jj" +
+ "FtCgazStmsuOXF9SFQE=");
+
+ // Sample AEAD encryption and decryption - V6 SKESK + V2 SEIPD
+ // https://www.rfc-editor.org/rfc/rfc9580#name-sample-aead-eax-encryption-
+ // encrypts the cleartext string Hello, world! with the passphrase password, S2K type iterated+salted,
+ // using AES-128 with AEAD-EAX encryption.
+ private readonly byte[] v6skesk_aes128_eax = Base64.Decode(
+ "w0AGHgcBCwMIpa5XnR/F2Cv/aSJPkZmTs1Bvo7WaanPP+MXvxfQcV/tU4cImgV14" +
+ "KPX5LEVOtl6+AKtZhsaObnxV0mkCBwEGn/kOOzIZZPOkKRPI3MZhkyUBUifvt+rq" +
+ "pJ8EwuZ0F11KPSJu1q/LnKmsEiwUcOEcY9TAqyQcapOK1Iv5mlqZuQu6gyXeYQR1" +
+ "QCWKt5Wala0FHdqW6xVDHf719eIlXKeCYVRuM5o=");
+
+ // https://www.rfc-editor.org/rfc/rfc9580#name-sample-aead-ocb-encryption-
+ // encrypts the cleartext string Hello, world! with the passphrase password, S2K type iterated+salted,
+ // using AES-128 with AEAD-OCB encryption.
+ private readonly byte[] v6skesk_aes128_ocb = Base64.Decode(
+ "wz8GHQcCCwMIVqKY0vXjZFP/z8xcEWZO2520JZDX3EawckG2EsOBLP/76gDyNHsl" +
+ "ZBEj+IeuYNT9YU4IN9gZ02zSaQIHAgYgpmH3MfyaMDK1YjMmAn46XY21dI6+/wsM" +
+ "WRDQns3WQf+f04VidYA1vEl1TOG/P/+n2tCjuBBPUTPPQqQQCoPu9MobSAGohGv0" +
+ "K82nyM6dZeIS8wHLzZj9yt5pSod61CRzI/boVw==");
+
+ // https://www.rfc-editor.org/rfc/rfc9580#name-sample-aead-gcm-encryption-
+ // encrypts the cleartext string Hello, world! with the passphrase password, S2K type iterated+salted,
+ // using AES-128 with AEAD-GCM encryption.
+ private readonly byte[] v6skesk_aes128_gcm = Base64.Decode(
+ "wzwGGgcDCwMI6dOXhbIHAAj/tC58SD70iERXyzcmubPbn/d25fTZpAlS4kRymIUa" +
+ "v/91Jt8t1VRBdXmneZ/SaQIHAwb8uUSQvLmLvcnRBsYJAmaUD3LontwhtVlrFXax" +
+ "Ae0Pn/xvxtZbv9JNzQeQlm5tHoWjAFN4TLHYtqBpnvEhVaeyrWJYUxtXZR/Xd3kS" +
+ "+pXjXZtAIW9ppMJI2yj/QzHxYykHOZ5v+Q==");
+
+ // https://www.rfc-editor.org/rfc/rfc9580#name-sample-messages-encrypted-u
+ // V4 SKESK + V1 SEIPD using Argon2 (t=1, p=4, m=21) with AES-128/192/256,
+ // cleartext string "Hello, world!", passphrase "password"
+ private readonly byte[] v4skesk_argon2_aes128 = Base64.Decode(
+ "wycEBwScUvg8J/leUNU1RA7N/zE2AQQVnlL8rSLPP5VlQsunlO+ECxHSPgGYGKY+" +
+ "YJz4u6F+DDlDBOr5NRQXt/KJIf4m4mOlKyC/uqLbpnLJZMnTq3o79GxBTdIdOzhH" +
+ "XfA3pqV4mTzF");
+
+ private readonly byte[] v4skesk_argon2_aes192 = Base64.Decode(
+ "wy8ECAThTKxHFTRZGKli3KNH4UP4AQQVhzLJ2va3FG8/pmpIPd/H/mdoVS5VBLLw" +
+ "F9I+AdJ1Sw56PRYiKZjCvHg+2bnq02s33AJJoyBexBI4QKATFRkyez2gldJldRys" +
+ "LVg77Mwwfgl2n/d572WciAM=");
+
+ private readonly byte[] v4skesk_argon2_aes256 = Base64.Decode(
+ "wzcECQS4eJUgIG/3mcaILEJFpmJ8AQQVnZ9l7KtagdClm9UaQ/Z6M/5roklSGpGu" +
+ "623YmaXezGj80j4B+Ku1sgTdJo87X1Wrup7l0wJypZls21Uwd67m9koF60eefH/K" +
+ "95D1usliXOEm8ayQJQmZrjf6K6v9PWwqMQ==");
+
+ // V6 SKESK + V2 SEIPD using Argon2 with AES-256 in OCB mode
+ // cleartext string "Hello, world!", passphrase "password"
+ // Session key 9DC22B5D8DFCED080C881885335E5A1A7E1215F17BBEC0B485655A308BE3D934
+ // generated with gosop 2.0.0-alpha
+ private readonly byte[] v6skesk_argon2_aes256_ocb = Base64.Decode(
+ "w1gGJgkCFARXue/MBMPDOPspqjeXOAwCAwQQdUzaSpJVUWXsrfYfYX6Bu+PWWSv5" +
+ "v6yNbe7XcntA8BuivOCuH6FU3Mt0UJPZRO9/fRjiEGTuwg6Q7ar/gZ/N0lkCCQIM" +
+ "Zjd4SG7Tv4RJHeycolKmqSHDoK5XlOsA7vlw50nKuRjDyRfsPOFDfHz8hR/z7D1i" +
+ "HST68tjRCRmwqeqVgusCmBlXrXzYTkPXGtmZl2+EYazSACQFVg==");
+
+ // https://www.rfc-editor.org/rfc/rfc9580#name-sample-x25519-aead-ocb-encr
+ // encrypts the cleartext string "Hello, world!" for the sample certificate v6Certificate
+ // V6 PKESK + V2 SEIPD X25519 AES-128 OCB
+ private readonly byte[] v6pkesk_v2seipd_aes128_ocb = Base64.Decode(
+ "wV0GIQYSyD8ecG9jCP4VGkF3Q6HwM3kOk+mXhIjR2zeNqZMIhRmHzxjV8bU/gXzO" +
+ "WgBM85PMiVi93AZfJfhK9QmxfdNnZBjeo1VDeVZheQHgaVf7yopqR6W1FT6NOrfS" +
+ "aQIHAgZhZBZTW+CwcW1g4FKlbExAf56zaw76/prQoN+bAzxpohup69LA7JW/Vp0l" +
+ "yZnuSj3hcFj0DfqLTGgr4/u717J+sPWbtQBfgMfG9AOIwwrUBqsFE9zW+f1zdlYo" +
+ "bhF30A+IitsxxA==");
+
+ // from the "OpenPGP interoperability test suite"
+ // https://tests.sequoia-pgp.org/#Encrypt-Decrypt_roundtrip_with_minimal_key_from_RFC9760
+ // encrypts the cleartext string "Hello World :)" for the sample certificate v6Certificate
+ // V3 PKESK + V1 SEIPD X25519 AES-256
+ private readonly byte[] v3pkesk_v1seipd_aes256 = Base64.Decode(
+ "wVQDEsg/HnBvYwgZEFZQspuRTLEGqQ4oEX+0ap/cDogTvDbh+Fu5K6O7ZCkpCZu4" +
+ "g6JfGwkmmqn6Ekff2LPS+jcsgz4S3y+90y7zg+bw6jgy81vJYZLSPwHPmq3ld0oV" +
+ "codBvOUSOAvARPpDCHvAOyMT+ZmYEbbQK/ahc3P6HGDArsfcAETvsIHBE8U45o4g" +
+ "poZLYyxi0A==");
+
+ private readonly char[] emptyPassphrase = Array.Empty();
+
+ #region "Helpers"
+ private void SignVerifyRoundtrip(PgpSecretKey signingKey, char[] passphrase)
+ {
+ byte[] data = Encoding.UTF8.GetBytes("OpenPGP");
+ byte[] wrongData = Encoding.UTF8.GetBytes("OpePGP");
+
+ PgpSignatureGenerator sigGen = new PgpSignatureGenerator(signingKey.PublicKey.Algorithm, HashAlgorithmTag.Sha512);
+ PgpSignatureSubpacketGenerator spkGen = new PgpSignatureSubpacketGenerator();
+ PgpPrivateKey privKey = signingKey.ExtractPrivateKey(passphrase);
+ spkGen.SetIssuerFingerprint(false, signingKey);
+ sigGen.InitSign(PgpSignature.CanonicalTextDocument, privKey, new SecureRandom());
+ sigGen.Update(data);
+ sigGen.SetHashedSubpackets(spkGen.Generate());
+ PgpSignature signature = sigGen.Generate();
+
+ AreEqual(signature.GetIssuerFingerprint(), signingKey.GetFingerprint());
+
+ VerifySignature(signature, data, signingKey.PublicKey);
+ VerifySignature(signature, wrongData, signingKey.PublicKey, shouldFail: true);
+
+ byte[] encodedSignature = signature.GetEncoded();
+ VerifyEncodedSignature(encodedSignature, data, signingKey.PublicKey);
+ VerifyEncodedSignature(encodedSignature, wrongData, signingKey.PublicKey, shouldFail: true);
+ }
+
+ private void VerifyInlineSignature(byte[] message, PgpPublicKey signer, bool shouldFail = false)
+ {
+ byte[] data;
+ PgpObjectFactory factory = new PgpObjectFactory(message);
+
+ PgpOnePassSignatureList p1 = factory.NextPgpObject() as PgpOnePassSignatureList;
+ PgpOnePassSignature ops = p1[0];
+
+ PgpLiteralData p2 = factory.NextPgpObject() as PgpLiteralData;
+ Stream dIn = p2.GetInputStream();
+
+ ops.InitVerify(signer);
+
+ using (MemoryStream ms = new MemoryStream())
+ {
+ byte[] buffer = new byte[30];
+ int bytesRead;
+ while ((bytesRead = dIn.Read(buffer, 0, buffer.Length)) > 0)
+ {
+ ops.Update(buffer, 0, bytesRead);
+ ms.Write(buffer, 0, bytesRead);
+ }
+
+ data = ms.ToArray();
+ }
+ PgpSignatureList p3 = factory.NextPgpObject() as PgpSignatureList;
+ PgpSignature sig = p3[0];
+
+ bool result = ops.Verify(sig) != shouldFail;
+ IsTrue("signature test failed", result);
+
+ VerifySignature(sig, data, signer, shouldFail);
+ }
+
+ private void VerifySignature(PgpSignature signature, byte[] data, PgpPublicKey signer, bool shouldFail = false)
+ {
+ IsEquals(signature.KeyAlgorithm, signer.Algorithm);
+ // the version of the signature is bound to the version of the signing key
+ IsEquals(signature.Version, signer.Version);
+
+ if (signature.KeyId != 0)
+ {
+ IsEquals(signature.KeyId, signer.KeyId);
+ }
+ byte[] issuerFpt = signature.GetIssuerFingerprint();
+ if (issuerFpt != null)
+ {
+ IsTrue(AreEqual(issuerFpt, signer.GetFingerprint()));
+ }
+
+ signature.InitVerify(signer);
+ signature.Update(data);
+
+ bool result = signature.Verify() != shouldFail;
+ IsTrue("signature test failed", result);
+ }
+
+ private void VerifyEncodedSignature(byte[] sigPacket, byte[] data, PgpPublicKey signer, bool shouldFail = false)
+ {
+ PgpObjectFactory factory = new PgpObjectFactory(sigPacket);
+ PgpSignatureList sigList = factory.NextPgpObject() as PgpSignatureList;
+ PgpSignature signature = sigList[0];
+
+ VerifySignature(signature, data, signer, shouldFail);
+ }
+
+ private static PgpEncryptedDataGenerator CreatePgpEncryptedDataGenerator(bool useAead)
+ {
+ if (useAead)
+ {
+ return new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Aes256, AeadAlgorithmTag.Ocb, new SecureRandom());
+ }
+ else
+ {
+ return new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Aes256, true, new SecureRandom());
+ }
+ }
+
+ private static byte[] EncryptPlaintext(PgpEncryptedDataGenerator encDataGen, byte[] plaintext, bool useBuffer)
+ {
+ byte[] enc;
+
+ if (useBuffer)
+ {
+ byte[] buffer = new byte[1024];
+ using (MemoryStream ms = new MemoryStream())
+ {
+ using (Stream cOut = encDataGen.Open(ms, buffer))
+ {
+ using (BcpgOutputStream bcOut = new BcpgOutputStream(cOut, newFormatOnly: true))
+ {
+ PgpLiteralDataGenerator literalDataGen = new PgpLiteralDataGenerator();
+ DateTime modificationTime = DateTime.UtcNow;
+
+ using (Stream lOut = literalDataGen.Open(
+ new UncloseableStream(bcOut),
+ PgpLiteralData.Utf8,
+ PgpLiteralData.Console,
+ plaintext.Length,
+ modificationTime))
+ {
+ lOut.Write(plaintext, 0, plaintext.Length);
+ }
+ }
+ }
+ enc = ms.ToArray();
+ }
+ }
+ else
+ {
+ byte[] literal;
+ using (MemoryStream ms = new MemoryStream())
+ {
+ PgpLiteralDataGenerator literalDataGen = new PgpLiteralDataGenerator();
+ DateTime modificationTime = DateTime.UtcNow;
+
+ using (Stream lOut = literalDataGen.Open(
+ new UncloseableStream(ms),
+ PgpLiteralData.Utf8,
+ PgpLiteralData.Console,
+ plaintext.Length,
+ modificationTime))
+ {
+ lOut.Write(plaintext, 0, plaintext.Length);
+ }
+ literal = ms.ToArray();
+ }
+ using (MemoryStream ms = new MemoryStream())
+ {
+ using (Stream cOut = encDataGen.Open(ms, literal.Length))
+ {
+ cOut.Write(literal, 0, literal.Length);
+ }
+ enc = ms.ToArray();
+ }
+ }
+
+ return enc;
+ }
+
+ private void SymmetricEncryptDecryptRoundtrip(byte[] plaintext, bool useAead, bool useBuffer, byte[] rawPassword)
+ {
+ // encrypt
+ PgpEncryptedDataGenerator encDataGen = CreatePgpEncryptedDataGenerator(useAead);
+ encDataGen.AddMethodRaw(rawPassword, S2k.Argon2Parameters.MemoryConstrainedParameters());
+ byte[] enc = EncryptPlaintext(encDataGen, plaintext, useBuffer);
+
+ // decrypt
+ PgpObjectFactory factory = new PgpObjectFactory(enc);
+ PgpEncryptedDataList encDataList = factory.NextPgpObject() as PgpEncryptedDataList;
+ FailIf("invalid PgpEncryptedDataList", encDataList is null);
+
+ PgpPbeEncryptedData encData = encDataList[0] as PgpPbeEncryptedData;
+ FailIf("invalid PgpPbeEncryptedData", encData is null);
+
+ using (Stream stream = encData.GetDataStreamRaw(rawPassword))
+ {
+ factory = new PgpObjectFactory(stream);
+ PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+ using (MemoryStream ms = new MemoryStream())
+ {
+ lit.GetDataStream().CopyTo(ms);
+ byte[] decrypted = ms.ToArray();
+ IsTrue(Arrays.AreEqual(plaintext, decrypted));
+ }
+ }
+ }
+
+ private void PubkeyEncryptDecryptRoundtrip(byte[] plaintext, bool useAead,bool useBuffer, PgpPublicKey pubKey, PgpPrivateKey privKey)
+ {
+ // encrypt
+ PgpEncryptedDataGenerator encDataGen = CreatePgpEncryptedDataGenerator(useAead);
+ encDataGen.AddMethod(pubKey);
+ byte[] enc = EncryptPlaintext(encDataGen, plaintext, useBuffer);
+
+ // decrypt
+ PgpObjectFactory factory = new PgpObjectFactory(enc);
+ PgpEncryptedDataList encDataList = factory.NextPgpObject() as PgpEncryptedDataList;
+ FailIf("invalid PgpEncryptedDataList", encDataList is null);
+
+ PgpPublicKeyEncryptedData encData = encDataList[0] as PgpPublicKeyEncryptedData;
+ FailIf("invalid PgpPublicKeyEncryptedData", encData is null);
+
+ using (Stream stream = encData.GetDataStream(privKey))
+ {
+ factory = new PgpObjectFactory(stream);
+ PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+ using (MemoryStream ms = new MemoryStream())
+ {
+ lit.GetDataStream().CopyTo(ms);
+ byte[] decrypted = ms.ToArray();
+ IsTrue(Arrays.AreEqual(plaintext, decrypted));
+ }
+ }
+ }
+ #endregion
+
+ [Test]
+ public void Version4Ed25519LegacyPubkeySampleTest()
+ {
+ // https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-4-ed25519leg
+ PgpPublicKeyRing pubRing = new PgpPublicKeyRing(v4Ed25519LegacyPubkeySample);
+ PgpPublicKey pubKey = pubRing.GetPublicKey();
+
+ IsEquals(pubKey.Algorithm, PublicKeyAlgorithmTag.EdDsa_Legacy);
+ IsEquals(pubKey.CreationTime.ToString("yyyyMMddHHmmss"), "20140819142827");
+ IsEquals(pubKey.BitStrength, 256);
+ byte[] expectedFingerprint = Hex.Decode("C959BDBAFA32A2F89A153B678CFDE12197965A9A");
+ IsEquals((ulong)pubKey.KeyId, 0x8CFDE12197965A9A);
+ IsTrue("wrong fingerprint", AreEqual(pubKey.GetFingerprint(), expectedFingerprint));
+ }
+
+ [Test]
+ public void Version4Ed25519LegacyCreateTest()
+ {
+ // create a v4 EdDsa_Legacy Pubkey with the same key material and creation datetime as the test vector
+ // https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-4-ed25519leg
+ // then check KeyId/Fingerprint
+ var key = new Ed25519PublicKeyParameters(Hex.Decode("3f098994bdd916ed4053197934e4a87c80733a1280d62f8010992e43ee3b2406"));
+ var pubKey = new PgpPublicKey(PublicKeyAlgorithmTag.EdDsa_Legacy, key, DateTime.Parse("2014-08-19 14:28:27Z"));
+ IsEquals(pubKey.Algorithm, PublicKeyAlgorithmTag.EdDsa_Legacy);
+ IsEquals(pubKey.CreationTime.ToString("yyyyMMddHHmmss"), "20140819142827");
+
+ byte[] expectedFingerprint = Hex.Decode("C959BDBAFA32A2F89A153B678CFDE12197965A9A");
+ IsEquals((ulong)pubKey.KeyId, 0x8CFDE12197965A9A);
+ IsTrue("wrong fingerprint", AreEqual(pubKey.GetFingerprint(), expectedFingerprint));
+ }
+
+ [Test]
+ public void Version4Ed25519LegacySignatureSampleTest()
+ {
+ // https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-4-ed25519lega
+ PgpPublicKeyRing pubRing = new PgpPublicKeyRing(v4Ed25519LegacyPubkeySample);
+ PgpPublicKey pubKey = pubRing.GetPublicKey();
+
+ PgpObjectFactory factory = new PgpObjectFactory(v4Ed25519LegacySignatureSample);
+ PgpSignatureList sigList = factory.NextPgpObject() as PgpSignatureList;
+ PgpSignature signature = sigList[0];
+
+ IsEquals(signature.KeyId, pubKey.KeyId);
+ IsEquals(signature.KeyAlgorithm, PublicKeyAlgorithmTag.EdDsa_Legacy);
+ IsEquals(signature.HashAlgorithm, HashAlgorithmTag.Sha256);
+ IsEquals(signature.CreationTime.ToString("yyyyMMddHHmmss"), "20150916122453");
+
+ byte[] data = Encoding.UTF8.GetBytes("OpenPGP");
+ VerifySignature(signature, data, pubKey);
+
+ // test with wrong data, verification should fail
+ data = Encoding.UTF8.GetBytes("OpePGP");
+ VerifySignature(signature, data, pubKey, shouldFail: true);
+ }
+
+ [Test]
+ public void Version6CertificateParsingTest()
+ {
+ /*
+ * https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-6-certificat
+ * A Transferable Public Key consisting of:
+ * A v6 Ed25519 Public-Key packet
+ * A v6 direct key self-signature
+ * A v6 X25519 Public-Subkey packet
+ * A v6 subkey binding signature
+ */
+ PgpPublicKeyRing pubRing = new PgpPublicKeyRing(v6Certificate);
+ PgpPublicKey[] publicKeys = pubRing.GetPublicKeys().ToArray();
+ IsEquals("wrong number of public keys", publicKeys.Length, 2);
+
+ // master key
+ PgpPublicKey masterKey = publicKeys[0];
+ FailIf("wrong detection of master key", !masterKey.IsMasterKey);
+ IsEquals(masterKey.Algorithm, PublicKeyAlgorithmTag.Ed25519);
+ IsEquals(masterKey.CreationTime.ToString("yyyyMMddHHmmss"), "20221130160803");
+ byte[] expectedFingerprint = Hex.Decode("CB186C4F0609A697E4D52DFA6C722B0C1F1E27C18A56708F6525EC27BAD9ACC9");
+ IsEquals((ulong)masterKey.KeyId, 0xCB186C4F0609A697);
+ IsTrue("wrong master key fingerprint", AreEqual(masterKey.GetFingerprint(), expectedFingerprint));
+
+ // Verify direct key self-signature
+ PgpSignature selfSig = masterKey.GetSignatures().First();
+ IsTrue(selfSig.SignatureType == PgpSignature.DirectKey);
+ selfSig.InitVerify(masterKey);
+ FailIf("self signature verification failed", !selfSig.VerifyCertification(masterKey));
+
+ // subkey
+ PgpPublicKey subKey = publicKeys[1];
+ FailIf("wrong detection of encryption subkey", !subKey.IsEncryptionKey);
+ IsEquals(subKey.Algorithm, PublicKeyAlgorithmTag.X25519);
+ expectedFingerprint = Hex.Decode("12C83F1E706F6308FE151A417743A1F033790E93E9978488D1DB378DA9930885");
+ IsEquals(subKey.KeyId, 0x12C83F1E706F6308);
+ IsTrue("wrong sub key fingerprint", AreEqual(subKey.GetFingerprint(), expectedFingerprint));
+
+ // Verify subkey binding signature
+ PgpSignature bindingSig = subKey.GetSignatures().First();
+ IsTrue(bindingSig.SignatureType == PgpSignature.SubkeyBinding);
+ bindingSig.InitVerify(masterKey);
+ FailIf("subkey binding signature verification failed", !bindingSig.VerifyCertification(masterKey, subKey));
+
+ // Encode test
+ using (MemoryStream ms = new MemoryStream())
+ {
+ using (BcpgOutputStream bs = new BcpgOutputStream(ms, newFormatOnly: true))
+ {
+ pubRing.Encode(bs);
+ }
+
+ byte[] encoded = ms.ToArray();
+ IsTrue(AreEqual(encoded, v6Certificate));
+ }
+ }
+
+ [Test]
+ public void Version6PublicKeyCreationTest()
+ {
+ /*
+ * Create a v6 Ed25519 pubkey with the same key material and creation datetime as the test vector
+ * https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-6-certificat
+ * then check the fingerprint and verify a signature
+ */
+ byte[] keyMaterial = Hex.Decode("f94da7bb48d60a61e567706a6587d0331999bb9d891a08242ead84543df895a3");
+ var key = new Ed25519PublicKeyParameters(keyMaterial);
+ var pubKey = new PgpPublicKey(PublicKeyPacket.Version6, PublicKeyAlgorithmTag.Ed25519, key, DateTime.Parse("2022-11-30 16:08:03Z"));
+
+ IsEquals(pubKey.Algorithm, PublicKeyAlgorithmTag.Ed25519);
+ IsEquals(pubKey.CreationTime.ToString("yyyyMMddHHmmss"), "20221130160803");
+ byte[] expectedFingerprint = Hex.Decode("CB186C4F0609A697E4D52DFA6C722B0C1F1E27C18A56708F6525EC27BAD9ACC9");
+ IsEquals((ulong)pubKey.KeyId, 0xCB186C4F0609A697);
+ IsTrue("wrong master key fingerprint", AreEqual(pubKey.GetFingerprint(), expectedFingerprint));
+
+ VerifyEncodedSignature(
+ v6SampleCleartextSignedMessageSignature,
+ Encoding.UTF8.GetBytes(v6SampleCleartextSignedMessage),
+ pubKey);
+
+ VerifyEncodedSignature(
+ v6SampleCleartextSignedMessageSignature,
+ Encoding.UTF8.GetBytes("wrongdata"),
+ pubKey,
+ shouldFail: true);
+ }
+
+ [Test]
+ public void Version6UnlockedSecretKeyParsingTest()
+ {
+ /*
+ * https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-6-certificat
+ * A Transferable Secret Key consisting of:
+ * A v6 Ed25519 Secret-Key packet
+ * A v6 direct key self-signature
+ * A v6 X25519 Secret-Subkey packet
+ * A v6 subkey binding signature
+ */
+
+ PgpSecretKeyRing secretKeyRing = new PgpSecretKeyRing(v6UnlockedSecretKey);
+ PgpSecretKey[] secretKeys = secretKeyRing.GetSecretKeys().ToArray();
+ IsEquals("wrong number of secret keys", secretKeys.Length, 2);
+
+ // signing key
+ PgpSecretKey signingKey = secretKeys[0];
+ IsEquals(signingKey.PublicKey.Algorithm, PublicKeyAlgorithmTag.Ed25519);
+ IsEquals((ulong)signingKey.PublicKey.KeyId, 0xCB186C4F0609A697);
+
+ SignVerifyRoundtrip(signingKey, emptyPassphrase);
+
+ // encryption key
+ PgpSecretKey encryptionKey = secretKeys[1];
+ IsEquals(encryptionKey.PublicKey.Algorithm, PublicKeyAlgorithmTag.X25519);
+ IsEquals(encryptionKey.PublicKey.KeyId, 0x12C83F1E706F6308);
+
+ // Encode-Decode roundtrip
+ using (MemoryStream ms = new MemoryStream())
+ {
+ using (BcpgOutputStream bs = new BcpgOutputStream(ms, newFormatOnly: true))
+ {
+ secretKeyRing.Encode(bs);
+ }
+
+ byte[] encoded = ms.ToArray();
+ IsTrue(AreEqual(encoded, v6UnlockedSecretKey));
+ }
+
+ // generate and verify a v6 userid self-cert
+ string userId = "Alice ";
+ string wrongUserId = "Bob ";
+ PgpSignatureGenerator sigGen = new PgpSignatureGenerator(signingKey.PublicKey.Algorithm, HashAlgorithmTag.Sha512);
+ PgpPrivateKey privKey = signingKey.ExtractPrivateKey(emptyPassphrase);
+ sigGen.InitSign(PgpSignature.PositiveCertification, privKey, new SecureRandom());
+ PgpSignature signature = sigGen.GenerateCertification(userId, signingKey.PublicKey);
+ signature.InitVerify(signingKey.PublicKey);
+ if (!signature.VerifyCertification(userId, signingKey.PublicKey))
+ {
+ Fail("self-cert verification failed.");
+ }
+ signature.InitVerify(signingKey.PublicKey);
+ if (signature.VerifyCertification(wrongUserId, signingKey.PublicKey))
+ {
+ Fail("self-cert verification failed.");
+ }
+ PgpPublicKey key = PgpPublicKey.AddCertification(signingKey.PublicKey, userId, signature);
+ byte[] keyEnc = key.GetEncoded();
+ PgpPublicKeyRing tmpRing = new PgpPublicKeyRing(keyEnc);
+ key = tmpRing.GetPublicKey();
+ IsTrue(key.GetUserIds().Contains(userId));
+
+ // generate and verify a v6 cert revocation
+ sigGen.InitSign(PgpSignature.KeyRevocation, privKey, new SecureRandom());
+ signature = sigGen.GenerateCertification(signingKey.PublicKey);
+ signature.InitVerify(signingKey.PublicKey);
+ if (!signature.VerifyCertification(signingKey.PublicKey))
+ {
+ Fail("revocation verification failed.");
+ }
+ key = PgpPublicKey.AddCertification(signingKey.PublicKey, signature);
+ keyEnc = key.GetEncoded();
+ tmpRing = new PgpPublicKeyRing(keyEnc);
+ key = tmpRing.GetPublicKey();
+ IsTrue(key.IsRevoked());
+ }
+
+ [Test]
+ public void Version6Ed25519KeyPairCreationTest()
+ {
+ /*
+ * Create a v6 Ed25519 keypair with the same key material and creation datetime as the test vector
+ * https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-6-certificat
+ * then check the fingerprint and perform encode-decode, sign-verify, encrypt-decrypt roundtrips
+ */
+ byte[] keyMaterial = Hex.Decode("1972817b12be707e8d5f586ce61361201d344eb266a2c82fde6835762b65b0b7");
+ Ed25519PrivateKeyParameters seckey = new Ed25519PrivateKeyParameters(keyMaterial);
+ Ed25519PublicKeyParameters pubkey = seckey.GeneratePublicKey();
+ PgpKeyPair keypair = new PgpKeyPair(PublicKeyPacket.Version6, PublicKeyAlgorithmTag.Ed25519, pubkey, seckey, DateTime.Parse("2022-11-30 16:08:03Z"));
+
+ IsEquals(keypair.PublicKey.Algorithm, PublicKeyAlgorithmTag.Ed25519);
+ IsEquals(keypair.PublicKey.CreationTime.ToString("yyyyMMddHHmmss"), "20221130160803");
+ IsEquals(keypair.PublicKey.BitStrength, 256);
+ byte[] expectedFingerprint = Hex.Decode("CB186C4F0609A697E4D52DFA6C722B0C1F1E27C18A56708F6525EC27BAD9ACC9");
+ IsEquals((ulong)keypair.KeyId, 0xCB186C4F0609A697);
+ IsTrue("wrong master key fingerprint", AreEqual(keypair.PublicKey.GetFingerprint(), expectedFingerprint));
+
+
+ VerifyEncodedSignature(
+ v6SampleCleartextSignedMessageSignature,
+ Encoding.UTF8.GetBytes(v6SampleCleartextSignedMessage),
+ keypair.PublicKey);
+
+ VerifyEncodedSignature(
+ v6SampleCleartextSignedMessageSignature,
+ Encoding.UTF8.GetBytes("wrongdata"),
+ keypair.PublicKey,
+ shouldFail: true);
+
+ // Encode-Decode roundtrip
+ SecureRandom rand = new SecureRandom();
+
+ PgpSignatureSubpacketGenerator spgen = new PgpSignatureSubpacketGenerator();
+ spgen.SetPreferredHashAlgorithms(true, new int[] { (int)HashAlgorithmTag.Sha512, (int)HashAlgorithmTag.Sha256 });
+ spgen.SetPreferredSymmetricAlgorithms(true, new int[] { (int)SymmetricKeyAlgorithmTag.Aes256, (int)SymmetricKeyAlgorithmTag.Aes128 });
+ PgpSignatureSubpacketVector hashed = spgen.Generate();
+
+ string uid = "Alice ";
+ PgpKeyRingGenerator keyRingGen = new PgpKeyRingGenerator(
+ PgpSignature.PositiveCertification,
+ keypair,
+ uid,
+ SymmetricKeyAlgorithmTag.Null,
+ Array.Empty(),
+ false,
+ hashed,
+ null,
+ rand);
+
+ // add an encryption subkey
+ X25519KeyPairGenerator x25519gen = new X25519KeyPairGenerator();
+ x25519gen.Init(new X25519KeyGenerationParameters(rand));
+ AsymmetricCipherKeyPair x25519kp = x25519gen.GenerateKeyPair();
+ keypair = new PgpKeyPair(PublicKeyPacket.Version6, PublicKeyAlgorithmTag.X25519, x25519kp, DateTime.Parse("2022-11-30 16:08:03Z"));
+ keyRingGen.AddSubKey(keypair);
+
+ PgpSecretKeyRing secring = keyRingGen.GenerateSecretKeyRing();
+
+ byte[] encodedsecring = secring.GetEncoded();
+ // expected length of v6 unencrypted Ed25519 secret key packet: 75 octets
+ FailIf("unexpected packet length", encodedsecring[1] != 75);
+
+ PgpSecretKeyRing decodedsecring = new PgpSecretKeyRing(encodedsecring);
+
+ PgpPublicKey pgppubkey = decodedsecring.GetPublicKey();
+ PgpSecretKey pgpseckey = decodedsecring.GetSecretKey();
+ IsEquals(pgppubkey.Algorithm, PublicKeyAlgorithmTag.Ed25519);
+ IsEquals(pgppubkey.CreationTime.ToString("yyyyMMddHHmmss"), "20221130160803");
+ IsEquals((ulong)pgppubkey.KeyId, 0xCB186C4F0609A697);
+ IsTrue("wrong master key fingerprint", AreEqual(pgppubkey.GetFingerprint(), expectedFingerprint));
+ IsTrue(pgppubkey.GetUserIds().Contains(uid));
+
+ // verify selfsig
+ PgpSignature signature = pgppubkey.GetSignaturesForId(uid).ToArray()[0];
+ IsEquals(signature.Version, SignaturePacket.Version6);
+ IsEquals(signature.SignatureType, PgpSignature.PositiveCertification);
+ signature.InitVerify(pgppubkey);
+ IsTrue(signature.VerifyCertification(uid, pgppubkey));
+ IsTrue(!signature.VerifyCertification("Bob ", pgppubkey));
+
+ // verify subkey
+ PgpSecretKey subKey = decodedsecring.GetSecretKeys().ToArray()[1];
+ IsEquals(subKey.PublicKey.Algorithm, PublicKeyAlgorithmTag.X25519);
+
+ // Verify subkey binding signature
+ PgpSignature bindingSig = subKey.PublicKey.GetSignatures().First();
+ IsTrue(bindingSig.SignatureType == PgpSignature.SubkeyBinding);
+ bindingSig.InitVerify(pgppubkey);
+ IsTrue("subkey binding signature verification failed", bindingSig.VerifyCertification(pgppubkey, subKey.PublicKey));
+
+ // Sign-Verify roundtrip
+ SignVerifyRoundtrip(pgpseckey, emptyPassphrase);
+
+ // encrypt-decrypt roundtrip
+ // V3 PKESK + V1 SEIPD
+ PubkeyEncryptDecryptRoundtrip(
+ Encoding.UTF8.GetBytes("Hello, World!"),
+ false,
+ false,
+ subKey.PublicKey,
+ subKey.ExtractPrivateKey(emptyPassphrase));
+
+ // V6 PKESK + V2 SEIPD
+ PubkeyEncryptDecryptRoundtrip(
+ Encoding.UTF8.GetBytes("Hello, World!"),
+ true,
+ false,
+ subKey.PublicKey,
+ subKey.ExtractPrivateKey(emptyPassphrase));
+
+ PgpPublicKeyRing pubring = keyRingGen.GeneratePublicKeyRing();
+ PgpPublicKey[] keys = pubring.GetPublicKeys().ToArray();
+ IsEquals(keys[0].Version, PublicKeyPacket.Version6);
+ IsEquals(keys[0].Algorithm, PublicKeyAlgorithmTag.Ed25519);
+ IsTrue("wrong master key fingerprint", AreEqual(keys[0].GetFingerprint(), expectedFingerprint));
+ IsEquals(keys[1].Version, PublicKeyPacket.Version6);
+ IsEquals(keys[1].Algorithm, PublicKeyAlgorithmTag.X25519);
+ }
+
+ [Test]
+ public void Version6Ed448KeyPairCreationTest()
+ {
+ /*
+ * Create a v6 Ed448 keypair, then perform encode-decode, sign-verify, encrypt-decrypt roundtrips
+ */
+ SecureRandom rand = new SecureRandom();
+ DateTime now = DateTime.UtcNow;
+
+ Ed448KeyPairGenerator ed448gen = new Ed448KeyPairGenerator();
+ ed448gen.Init(new Ed448KeyGenerationParameters(rand));
+ AsymmetricCipherKeyPair kp = ed448gen.GenerateKeyPair();
+
+ PgpKeyPair keypair = new PgpKeyPair(PublicKeyPacket.Version6, PublicKeyAlgorithmTag.Ed448, kp, now);
+ IsEquals(keypair.PublicKey.Algorithm, PublicKeyAlgorithmTag.Ed448);
+ IsEquals(keypair.PublicKey.CreationTime.ToString("yyyyMMddHHmmss"), now.ToString("yyyyMMddHHmmss"));
+ IsEquals(keypair.PublicKey.BitStrength, 448);
+ long keyId = keypair.PublicKey.KeyId;
+ byte[] fpr = keypair.PublicKey.GetFingerprint();
+ IsEquals(fpr.Length, 32);
+
+ // encode-decode roundtrip
+ string uid = "Alice ";
+ PgpKeyRingGenerator keyRingGen = new PgpKeyRingGenerator(
+ PgpSignature.PositiveCertification,
+ keypair,
+ uid,
+ SymmetricKeyAlgorithmTag.Null,
+ Array.Empty(),
+ false,
+ null,
+ null,
+ rand);
+
+ // add an encryption subkey
+ X448KeyPairGenerator x448gen = new X448KeyPairGenerator();
+ x448gen.Init(new X448KeyGenerationParameters(rand));
+ AsymmetricCipherKeyPair x448kp = x448gen.GenerateKeyPair();
+ keypair = new PgpKeyPair(PublicKeyPacket.Version6, PublicKeyAlgorithmTag.X448, x448kp, now);
+ keyRingGen.AddSubKey(keypair);
+
+ PgpSecretKeyRing secring = keyRingGen.GenerateSecretKeyRing();
+
+ byte[] encodedsecring = secring.GetEncoded();
+ // expected length of v6 unencrypted Ed448 secret key packet: 125 octets
+ FailIf("unexpected packet length", encodedsecring[1] != 125);
+
+ PgpSecretKeyRing decodedsecring = new PgpSecretKeyRing(encodedsecring);
+
+ PgpPublicKey pgppubkey = decodedsecring.GetPublicKey();
+ PgpSecretKey pgpseckey = decodedsecring.GetSecretKey();
+ IsEquals(pgppubkey.Algorithm, PublicKeyAlgorithmTag.Ed448);
+ IsEquals(pgppubkey.CreationTime.ToString("yyyyMMddHHmmss"), now.ToString("yyyyMMddHHmmss"));
+ IsEquals(pgppubkey.KeyId, keyId);
+ IsTrue("wrong master key fingerprint", AreEqual(pgppubkey.GetFingerprint(), fpr));
+ IsTrue(pgppubkey.GetUserIds().Contains(uid));
+
+ // verify selfsig
+ PgpSignature signature = pgppubkey.GetSignaturesForId(uid).ToArray()[0];
+ IsEquals(signature.Version, SignaturePacket.Version6);
+ IsEquals(signature.SignatureType, PgpSignature.PositiveCertification);
+ signature.InitVerify(pgppubkey);
+ IsTrue(signature.VerifyCertification(uid, pgppubkey));
+ IsTrue(!signature.VerifyCertification("Bob ", pgppubkey));
+
+ // verify subkey
+ PgpSecretKey subKey = decodedsecring.GetSecretKeys().ToArray()[1];
+ IsEquals(subKey.PublicKey.Algorithm, PublicKeyAlgorithmTag.X448);
+
+ // Verify subkey binding signature
+ PgpSignature bindingSig = subKey.PublicKey.GetSignatures().First();
+ IsTrue(bindingSig.SignatureType == PgpSignature.SubkeyBinding);
+ bindingSig.InitVerify(pgppubkey);
+ IsTrue("subkey binding signature verification failed", bindingSig.VerifyCertification(pgppubkey, subKey.PublicKey));
+
+ // Sign-Verify roundtrip
+ SignVerifyRoundtrip(pgpseckey, emptyPassphrase);
+
+ // Encrypt-Decrypt roundtrip
+ PubkeyEncryptDecryptRoundtrip(
+ Encoding.UTF8.GetBytes("Hello, World!"),
+ false,
+ false,
+ subKey.PublicKey,
+ subKey.ExtractPrivateKey(emptyPassphrase));
+
+ PgpPublicKeyRing pubring = keyRingGen.GeneratePublicKeyRing();
+ PgpPublicKey[] keys = pubring.GetPublicKeys().ToArray();
+ IsEquals(keys[0].Version, PublicKeyPacket.Version6);
+ IsEquals(keys[0].Algorithm, PublicKeyAlgorithmTag.Ed448);
+ IsTrue("wrong master key fingerprint", AreEqual(keys[0].GetFingerprint(), fpr));
+ IsEquals(keys[1].Version, PublicKeyPacket.Version6);
+ IsEquals(keys[1].Algorithm, PublicKeyAlgorithmTag.X448);
+
+ using (var ms = new MemoryStream())
+ {
+ using (var arms = new ArmoredOutputStream(ms))
+ {
+ decodedsecring.Encode(arms);
+ }
+ string armored = Encoding.ASCII.GetString(ms.ToArray());
+ }
+
+ using (var ms = new MemoryStream())
+ {
+ using (var arms = new ArmoredOutputStream(ms))
+ {
+ pubring.Encode(arms);
+ }
+ string armored = Encoding.ASCII.GetString(ms.ToArray());
+ }
+ }
+
+ [Test]
+ public void Version6LockedSecretKeyParsingTest()
+ {
+ /*
+ * https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-6-certificat
+ * The same secret key as in Version6UnlockedSecretKeyParsingTest, but the secret key
+ * material is locked with a passphrase using AEAD and Argon2.
+ */
+
+ PgpSecretKeyRing secretKeyRing = new PgpSecretKeyRing(v6LockedSecretKey);
+ PgpSecretKey[] secretKeys = secretKeyRing.GetSecretKeys().ToArray();
+ IsEquals("wrong number of secret keys", secretKeys.Length, 2);
+
+ // signing key
+ PgpSecretKey signingKey = secretKeys[0];
+ IsEquals(signingKey.KeyEncryptionAlgorithm, SymmetricKeyAlgorithmTag.Aes256);
+ IsEquals(signingKey.KeyEncryptionAeadAlgorithm, AeadAlgorithmTag.Ocb);
+ IsEquals(signingKey.PublicKey.Algorithm, PublicKeyAlgorithmTag.Ed25519);
+ IsEquals((ulong)signingKey.PublicKey.KeyId, 0xCB186C4F0609A697);
+
+ // try to decrypt with wrong passphrases
+ Assert.Throws(() =>
+ {
+ PgpPrivateKey pk = signingKey.ExtractPrivateKey(emptyPassphrase);
+ });
+ Assert.Throws(() =>
+ {
+ PgpPrivateKey pk = signingKey.ExtractPrivateKey("wrong".ToCharArray());
+ });
+
+ string passphrase = "correct horse battery staple";
+ SignVerifyRoundtrip(signingKey, passphrase.ToCharArray());
+
+ // encryption key
+ PgpSecretKey encryptionKey = secretKeys[1];
+ IsEquals(encryptionKey.KeyEncryptionAlgorithm, SymmetricKeyAlgorithmTag.Aes256);
+ IsEquals(encryptionKey.KeyEncryptionAeadAlgorithm, AeadAlgorithmTag.Ocb);
+ IsEquals(encryptionKey.PublicKey.Algorithm, PublicKeyAlgorithmTag.X25519);
+ IsEquals(encryptionKey.PublicKey.KeyId, 0x12C83F1E706F6308);
+
+ // encrypt-decrypt
+ PubkeyEncryptDecryptRoundtrip(
+ Encoding.UTF8.GetBytes("Hello, World!"),
+ false,
+ false,
+ encryptionKey.PublicKey,
+ encryptionKey.ExtractPrivateKey(passphrase.ToCharArray()));
+
+ // Encode-Decode roundtrip
+ using (MemoryStream ms = new MemoryStream())
+ {
+ using (BcpgOutputStream bs = new BcpgOutputStream(ms, newFormatOnly: true))
+ {
+ secretKeyRing.Encode(bs);
+ }
+
+ byte[] encoded = ms.ToArray();
+ IsTrue(AreEqual(encoded, v6LockedSecretKey));
+ }
+ }
+
+ [Test]
+ public void Version6SampleCleartextSignedMessageVerifySignatureTest()
+ {
+ // https://www.rfc-editor.org/rfc/rfc9580#name-sample-cleartext-signed-mes
+ PgpPublicKeyRing pubRing = new PgpPublicKeyRing(v6Certificate);
+ PgpPublicKey pubKey = pubRing.GetPublicKey();
+
+ VerifyEncodedSignature(
+ v6SampleCleartextSignedMessageSignature,
+ Encoding.UTF8.GetBytes(v6SampleCleartextSignedMessage),
+ pubKey);
+
+ VerifyEncodedSignature(
+ v6SampleCleartextSignedMessageSignature,
+ Encoding.UTF8.GetBytes("wrongdata"),
+ pubKey,
+ shouldFail: true);
+ }
+
+ [Test]
+ public void Version6SampleInlineSignedMessageVerifySignatureTest()
+ {
+ // https://www.rfc-editor.org/rfc/rfc9580#name-sample-inline-signed-messag
+ PgpPublicKeyRing pubRing = new PgpPublicKeyRing(v6Certificate);
+ PgpPublicKey pubKey = pubRing.GetPublicKey();
+
+ VerifyInlineSignature(v6SampleInlineSignedMessage, pubKey);
+ }
+
+ [Test]
+ public void Version6GenerateAndVerifyInlineSignatureTest()
+ {
+ PgpSecretKeyRing secretKeyRing = new PgpSecretKeyRing(v6UnlockedSecretKey);
+ PgpSecretKey signingKey = secretKeyRing.GetSecretKey();
+ PgpPrivateKey privKey = signingKey.ExtractPrivateKey(emptyPassphrase);
+ byte[] data = Encoding.UTF8.GetBytes("OpenPGP\nOpenPGP");
+ byte[] inlineSignatureMessage;
+
+ using (MemoryStream ms = new MemoryStream())
+ {
+ using (BcpgOutputStream bcOut = new BcpgOutputStream(ms, newFormatOnly: true))
+ {
+ PgpSignatureGenerator sGen = new PgpSignatureGenerator(signingKey.PublicKey.Algorithm, HashAlgorithmTag.Sha384);
+ sGen.InitSign(PgpSignature.CanonicalTextDocument, privKey, new SecureRandom());
+ sGen.GenerateOnePassVersion(false).Encode(bcOut);
+
+ PgpLiteralDataGenerator lGen = new PgpLiteralDataGenerator();
+ DateTime modificationTime = DateTime.UtcNow;
+ using (var lOut = lGen.Open(
+ new UncloseableStream(bcOut),
+ PgpLiteralData.Utf8,
+ "_CONSOLE",
+ data.Length,
+ modificationTime))
+ {
+ lOut.Write(data, 0, data.Length);
+ sGen.Update(data);
+ }
+
+ sGen.Generate().Encode(bcOut);
+ }
+
+ inlineSignatureMessage = ms.ToArray();
+ }
+
+ VerifyInlineSignature(inlineSignatureMessage, signingKey.PublicKey);
+ // corrupt data
+ inlineSignatureMessage[88] = 80;
+ VerifyInlineSignature(inlineSignatureMessage, signingKey.PublicKey, shouldFail: true);
+ }
+
+ [Test]
+ public void Version6SkeskVersion2SeipdTest()
+ {
+ // encrypts the cleartext string "Hello, world!" with the passphrase "password",
+ // S2K type iterated+salted, using AES-128 with AEAD encryption.
+ byte[][] messages = new byte[][]
+ {
+ v6skesk_aes128_eax, // from RFC 9580 A.9
+ v6skesk_aes128_ocb, // from RFC 9580 A.10
+ v6skesk_aes128_gcm // from RFC 9580 A.11
+ };
+
+ byte[] plaintext = Encoding.UTF8.GetBytes("Hello, world!");
+ byte[] password = Encoding.UTF8.GetBytes("password");
+
+ for (int i = 0; i < messages.Length; i++)
+ {
+ PgpObjectFactory factory = new PgpObjectFactory(messages[i]);
+ PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+ FailIf("invalid PgpEncryptedDataList", encData is null);
+
+ var encData0 = encData[0] as PgpPbeEncryptedData;
+ FailIf("invalid PgpPbeEncryptedData", encData0 is null);
+
+ using (var stream = encData0.GetDataStreamRaw(password))
+ {
+ factory = new PgpObjectFactory(stream);
+ PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+ using (var ms = new MemoryStream())
+ {
+ lit.GetDataStream().CopyTo(ms);
+ var decrypted = ms.ToArray();
+ IsTrue(Arrays.AreEqual(plaintext, decrypted));
+ }
+ }
+ }
+
+ // wrong passsword
+ byte[] wrongpassword = Encoding.UTF8.GetBytes("wrongpassword");
+ for (int i = 0; i < messages.Length; i++)
+ {
+ PgpObjectFactory factory = new PgpObjectFactory(messages[i]);
+ PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+ var encData0 = encData[0] as PgpPbeEncryptedData;
+ var err = Assert.Throws(() =>
+ {
+ var stream = encData0.GetDataStreamRaw(wrongpassword);
+ });
+ }
+
+ for (int i = 0; i < messages.Length; i++)
+ {
+ // corrupt AEAD nonce
+ var message = Arrays.Clone(messages[i]);
+ message[0x18]--;
+ PgpObjectFactory factory = new PgpObjectFactory(message);
+ PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+ var encData0 = encData[0] as PgpPbeEncryptedData;
+ var err = Assert.Throws(() =>
+ {
+ var stream = encData0.GetDataStreamRaw(password);
+ });
+ }
+
+ for (int i = 0; i < messages.Length; i++)
+ {
+ // corrupt encrypted session key
+ var message = Arrays.Clone(messages[i]);
+ message[0x28]--;
+ PgpObjectFactory factory = new PgpObjectFactory(message);
+ PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+ var encData0 = encData[0] as PgpPbeEncryptedData;
+ var err = Assert.Throws(() =>
+ {
+ var stream = encData0.GetDataStreamRaw(password);
+ });
+ }
+
+ for (int i = 0; i < messages.Length; i++)
+ {
+ // corrupt chunk #0 encrypted data
+ var message = Arrays.Clone(messages[i]);
+ message[message.Length - 35]--;
+ PgpObjectFactory factory = new PgpObjectFactory(message);
+ PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+ var encData0 = encData[0] as PgpPbeEncryptedData;
+ var err = Assert.Throws(() =>
+ {
+ var stream = encData0.GetDataStreamRaw(password);
+ });
+ }
+
+ for (int i = 0; i < messages.Length; i++)
+ {
+ // corrupt chunk #0 authtag
+ var message = Arrays.Clone(messages[i]);
+ message[message.Length - 20]--;
+ PgpObjectFactory factory = new PgpObjectFactory(message);
+ PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+ var encData0 = encData[0] as PgpPbeEncryptedData;
+ var err = Assert.Throws(() =>
+ {
+ var stream = encData0.GetDataStreamRaw(password);
+ });
+ }
+
+ for (int i = 0; i < messages.Length; i++)
+ {
+ // corrupt final authtag
+ var message = Arrays.Clone(messages[i]);
+ message[message.Length-2]--;
+ PgpObjectFactory factory = new PgpObjectFactory(message);
+ PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+ var encData0 = encData[0] as PgpPbeEncryptedData;
+ var err = Assert.Throws(() =>
+ {
+ var stream = encData0.GetDataStreamRaw(password);
+ });
+ }
+
+ /*
+ * V6 SKESK + V2 SEIPD AEAD encrypted message that spans over 4 chunks
+ * (chunk size 512 octets)
+ * 2000 octets of /dev/zero encrypted with password "password" using Argon2
+ * and AES-256 in OCB mode. Generated with gosop 2.0.0-alpha
+ * Session key A96F671431CEB0F859CFC653976417CCC4126BC0F93C30C6E5F0073E0B91E65A
+ */
+ {
+ plaintext = new byte[2000];
+ Arrays.Fill(plaintext, 0);
+
+ Stream message = PgpUtilities.GetDecoderStream(
+ SimpleTest.GetTestDataAsStream("openpgp.big-skesk-aead-msg.asc"));
+
+ PgpObjectFactory factory = new PgpObjectFactory(message);
+ PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+ FailIf("invalid PgpEncryptedDataList", encData is null);
+
+ var encData0 = encData[0] as PgpPbeEncryptedData;
+ FailIf("invalid PgpPbeEncryptedData", encData0 is null);
+
+ using (var stream = encData0.GetDataStreamRaw(password))
+ {
+ factory = new PgpObjectFactory(stream);
+ PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+ using (var ms = new MemoryStream())
+ {
+ lit.GetDataStream().CopyTo(ms);
+ var decrypted = ms.ToArray();
+ IsTrue(Arrays.AreEqual(plaintext, decrypted));
+ }
+ }
+ }
+ }
+
+ [Test]
+ public void SkeskWithArgon2Test()
+ {
+ byte[][] messages = new byte[][]
+ {
+ v4skesk_argon2_aes128, // from RFC 9580 A.12.1
+ v4skesk_argon2_aes192, // from RFC 9580 A.12.2
+ v4skesk_argon2_aes256, // from RFC 9580 A.12.3
+ v6skesk_argon2_aes256_ocb // generated with gosop 2.0.0-alpha
+ };
+
+ byte[] plaintext = Encoding.UTF8.GetBytes("Hello, world!");
+ byte[] password = Encoding.UTF8.GetBytes("password");
+
+ for (int i = 0; i < messages.Length; i++)
+ {
+ PgpObjectFactory factory = new PgpObjectFactory(messages[i]);
+ PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+ FailIf("invalid PgpEncryptedDataList", encData is null);
+
+ var encData0 = encData[0] as PgpPbeEncryptedData;
+ FailIf("invalid PgpPbeEncryptedData", encData0 is null);
+
+ using (var stream = encData0.GetDataStreamRaw(password))
+ {
+ factory = new PgpObjectFactory(stream);
+ PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+ using (var ms = new MemoryStream())
+ {
+ lit.GetDataStream().CopyTo(ms);
+ var decrypted = ms.ToArray();
+ IsTrue(Arrays.AreEqual(plaintext, decrypted));
+ }
+ }
+ }
+
+ // wrong passsword
+ byte[] wrongpassword = Encoding.UTF8.GetBytes("wrongpassword");
+ for (int i = 0; i < messages.Length; i++)
+ {
+ PgpObjectFactory factory = new PgpObjectFactory(messages[i]);
+ PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+ var encData0 = encData[0] as PgpPbeEncryptedData;
+ var err = Assert.Throws(() =>
+ {
+ var stream = encData0.GetDataStreamRaw(wrongpassword);
+ });
+ }
+
+ // encrypt-decrypt roundtrip
+ byte[] largePlaintext = new byte[50000];
+ Arrays.Fill(largePlaintext, 0);
+ // V4 SKESK + V1 SEIPD
+ // Using length in PgpEncryptedDataGenerator.Open
+ SymmetricEncryptDecryptRoundtrip(plaintext, false, false, password);
+ SymmetricEncryptDecryptRoundtrip(largePlaintext, false, false, password);
+ // Using buffer in PgpEncryptedDataGenerator.Open
+ SymmetricEncryptDecryptRoundtrip(plaintext, false, true, password);
+ SymmetricEncryptDecryptRoundtrip(largePlaintext, false, true, password);
+ // AEAD V6 SKESK + V2 SEIPD
+ // Using length
+ SymmetricEncryptDecryptRoundtrip(plaintext, true, false, password);
+ SymmetricEncryptDecryptRoundtrip(largePlaintext, true, false, password);
+ // Using buffer
+ SymmetricEncryptDecryptRoundtrip(plaintext, true, true, password);
+ SymmetricEncryptDecryptRoundtrip(largePlaintext, true, true, password);
+ }
+
+
+ [Test]
+ public void PkeskTest()
+ {
+ PgpSecretKeyRing secretKeyRing = new PgpSecretKeyRing(v6UnlockedSecretKey);
+ PgpSecretKey[] secretKeys = secretKeyRing.GetSecretKeys().ToArray();
+ PgpSecretKey encryptionSubkey = secretKeys[1];
+ PgpPrivateKey privKey = encryptionSubkey.ExtractPrivateKey(emptyPassphrase);
+
+ // V6 PKESK + V2 SEIPD X25519 AES-128 OCB
+ {
+ byte[] plaintext = Encoding.UTF8.GetBytes("Hello, world!");
+ PgpObjectFactory factory = new PgpObjectFactory(v6pkesk_v2seipd_aes128_ocb);
+ PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+ FailIf("invalid PgpEncryptedDataList", encData is null);
+
+ var encData0 = encData[0] as PgpPublicKeyEncryptedData;
+ FailIf("invalid PgpPublicKeyEncryptedData", encData0 is null);
+
+ IsEquals(encryptionSubkey.KeyId, encData0.KeyId);
+ IsTrue(Arrays.AreEqual(encryptionSubkey.GetFingerprint(), encData0.GetKeyFingerprint()));
+ IsEquals(SymmetricKeyAlgorithmTag.Aes128, encData0.GetSymmetricAlgorithm(privKey));
+
+ using (var stream = encData0.GetDataStream(privKey))
+ {
+ factory = new PgpObjectFactory(stream);
+ PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+ using (var ms = new MemoryStream())
+ {
+ lit.GetDataStream().CopyTo(ms);
+ var decrypted = ms.ToArray();
+ IsTrue(Arrays.AreEqual(plaintext, decrypted));
+ }
+ }
+ }
+
+ /*
+ * V6 PKESK + V2 SEIPD AEAD encrypted message that spans over 4 chunks
+ * (chunk size 512 octets)
+ * 2000 octets of /dev/zero encrypted with sample V6 certificate from
+ * RFC 9580 Appendix A.3 and AES-256 in OCB mode.
+ * Generated with gosop 2.0.0-alpha
+ * Session key CFB73D46CF7C13B7535227BEDB5B2D8B4023C5B58289D19CF2C33B0DB388B0B6
+ */
+ {
+ var plaintext = new byte[2000];
+ Arrays.Fill(plaintext, 0);
+
+ Stream message = PgpUtilities.GetDecoderStream(
+ SimpleTest.GetTestDataAsStream("openpgp.big-pkesk-aead-msg.asc"));
+
+ PgpObjectFactory factory = new PgpObjectFactory(message);
+ PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+ FailIf("invalid PgpEncryptedDataList", encData is null);
+
+ var encData0 = encData[0] as PgpPublicKeyEncryptedData;
+ FailIf("invalid PgpPublicKeyEncryptedData", encData0 is null);
+
+ IsEquals(encryptionSubkey.KeyId, encData0.KeyId);
+ IsEquals(SymmetricKeyAlgorithmTag.Aes256, encData0.GetSymmetricAlgorithm(privKey));
+
+ using (var stream = encData0.GetDataStream(privKey))
+ {
+ factory = new PgpObjectFactory(stream);
+ PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+ using (var ms = new MemoryStream())
+ {
+ lit.GetDataStream().CopyTo(ms);
+ var decrypted = ms.ToArray();
+ IsTrue(Arrays.AreEqual(plaintext, decrypted));
+ }
+ }
+ }
+
+ // V3 PKESK + V1 SEIPD X25519 AES-256
+ {
+ byte[] plaintext = Encoding.UTF8.GetBytes("Hello World :)");
+ PgpObjectFactory factory = new PgpObjectFactory(v3pkesk_v1seipd_aes256);
+ PgpEncryptedDataList encData = factory.NextPgpObject() as PgpEncryptedDataList;
+ FailIf("invalid PgpEncryptedDataList", encData is null);
+
+ var encData0 = encData[0] as PgpPublicKeyEncryptedData;
+ FailIf("invalid PgpPublicKeyEncryptedData", encData0 is null);
+
+ IsEquals(encryptionSubkey.KeyId, encData0.KeyId);
+ IsEquals(SymmetricKeyAlgorithmTag.Aes256, encData0.GetSymmetricAlgorithm(privKey));
+
+ using (var stream = encData0.GetDataStream(privKey))
+ {
+ factory = new PgpObjectFactory(stream);
+ PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+ using (var ms = new MemoryStream())
+ {
+ lit.GetDataStream().CopyTo(ms);
+ var decrypted = ms.ToArray();
+ IsTrue(Arrays.AreEqual(plaintext, decrypted));
+ }
+ }
+ }
+
+ // encrypt-decrypt roundtrip
+ {
+ byte[] shortPlaintext = Encoding.UTF8.GetBytes("Hello, world!");
+ byte[] largePlaintext = new byte[50000];
+ Arrays.Fill(largePlaintext, 0);
+
+ PgpPublicKeyRing publicKeyRing = new PgpPublicKeyRing(v6Certificate);
+ PgpPublicKey pubKey = publicKeyRing.GetPublicKeys().First(k => k.IsEncryptionKey);
+
+ // V3 PKESK + V1 SEIPD X25519
+ // Using length in PgpEncryptedDataGenerator.Open
+ PubkeyEncryptDecryptRoundtrip(shortPlaintext, false, false, pubKey, privKey);
+ PubkeyEncryptDecryptRoundtrip(largePlaintext, false, false, pubKey, privKey);
+ // Using buffer in PgpEncryptedDataGenerator.Open
+ PubkeyEncryptDecryptRoundtrip(shortPlaintext, false, true, pubKey, privKey);
+ PubkeyEncryptDecryptRoundtrip(largePlaintext, false, true, pubKey, privKey);
+
+ // V6 PKESK + V2 SEIPD X25519
+ // Using length
+ PubkeyEncryptDecryptRoundtrip(shortPlaintext, true, false, pubKey, privKey);
+ PubkeyEncryptDecryptRoundtrip(largePlaintext, true, false, pubKey, privKey);
+ // Using buffer
+ PubkeyEncryptDecryptRoundtrip(shortPlaintext, true, true, pubKey, privKey);
+ PubkeyEncryptDecryptRoundtrip(largePlaintext, true, true, pubKey, privKey);
+ }
+ }
+
+ public override string Name => "PgpCryptoRefreshTest";
+
+ public override void PerformTest()
+ {
+ Version4Ed25519LegacyPubkeySampleTest();
+ Version4Ed25519LegacySignatureSampleTest();
+ Version4Ed25519LegacyCreateTest();
+ Version6CertificateParsingTest();
+ Version6PublicKeyCreationTest();
+ Version6Ed25519KeyPairCreationTest();
+ Version6Ed448KeyPairCreationTest();
+ Version6UnlockedSecretKeyParsingTest();
+ Version6LockedSecretKeyParsingTest();
+ Version6SampleCleartextSignedMessageVerifySignatureTest();
+ Version6SampleInlineSignedMessageVerifySignatureTest();
+ Version6GenerateAndVerifyInlineSignatureTest();
+ Version6SkeskVersion2SeipdTest();
+ SkeskWithArgon2Test();
+ PkeskTest();
+ }
+ }
+}
\ No newline at end of file
diff --git a/crypto/test/src/openpgp/test/PgpInteroperabilityTestSuite.cs b/crypto/test/src/openpgp/test/PgpInteroperabilityTestSuite.cs
new file mode 100644
index 0000000000..f3791c6558
--- /dev/null
+++ b/crypto/test/src/openpgp/test/PgpInteroperabilityTestSuite.cs
@@ -0,0 +1,818 @@
+using NUnit.Framework;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.Test;
+using System;
+using System.IO;
+using System.Linq;
+using System.Runtime.Serialization.Formatters;
+using System.Text;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
+{
+ [TestFixture]
+ public class PgpInteroperabilityTestSuite
+ : SimpleTest
+ {
+ // v4 EdDSA/ECDH key "Alice" from "OpenPGP Example Keys and Certificates"
+ // https://www.ietf.org/archive/id/draft-bre-openpgp-samples-01.html#name-alices-ed25519-samples
+ private static readonly byte[] alicePubkey = Base64.Decode(
+ "mDMEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U" +
+ "b7O1u120JkFsaWNlIExvdmVsYWNlIDxhbGljZUBvcGVucGdwLmV4YW1wbGU+iJAE" +
+ "ExYIADgCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AWIQTrhbtfozp14V6UTmPy" +
+ "MVUMT0fjjgUCXaWfOgAKCRDyMVUMT0fjjukrAPoDnHBSogOmsHOsd9qGsiZpgRnO" +
+ "dypvbm+QtXZqth9rvwD9HcDC0tC+PHAsO7OTh1S1TC9RiJsvawAfCPaQZoed8gK4" +
+ "OARcRwTpEgorBgEEAZdVAQUBAQdAQv8GIa2rSTzgqbXCpDDYMiKRVitCsy203x3s" +
+ "E9+eviIDAQgHiHgEGBYIACAWIQTrhbtfozp14V6UTmPyMVUMT0fjjgUCXEcE6QIb" +
+ "DAAKCRDyMVUMT0fjjlnQAQDFHUs6TIcxrNTtEZFjUFm1M0PJ1Dng/cDW4xN80fsn" +
+ "0QEA22Kr7VkCjeAEC08VSTeV+QFsmz55/lntWkwYWhmvOgE=");
+
+ private static readonly byte[] aliceSecretkey = Base64.Decode(
+ "lFgEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U" +
+ "b7O1u10AAP9XBeW6lzGOLx7zHH9AsUDUTb2pggYGMzd0P3ulJ2AfvQ4RtCZBbGlj" +
+ "ZSBMb3ZlbGFjZSA8YWxpY2VAb3BlbnBncC5leGFtcGxlPoiQBBMWCAA4AhsDBQsJ" +
+ "CAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE64W7X6M6deFelE5j8jFVDE9H444FAl2l" +
+ "nzoACgkQ8jFVDE9H447pKwD6A5xwUqIDprBzrHfahrImaYEZzncqb25vkLV2arYf" +
+ "a78A/R3AwtLQvjxwLDuzk4dUtUwvUYibL2sAHwj2kGaHnfICnF0EXEcE6RIKKwYB" +
+ "BAGXVQEFAQEHQEL/BiGtq0k84Km1wqQw2DIikVYrQrMttN8d7BPfnr4iAwEIBwAA" +
+ "/3/xFPG6U17rhTuq+07gmEvaFYKfxRB6sgAYiW6TMTpQEK6IeAQYFggAIBYhBOuF" +
+ "u1+jOnXhXpROY/IxVQxPR+OOBQJcRwTpAhsMAAoJEPIxVQxPR+OOWdABAMUdSzpM" +
+ "hzGs1O0RkWNQWbUzQ8nUOeD9wNbjE3zR+yfRAQDbYqvtWQKN4AQLTxVJN5X5AWyb" +
+ "Pnn+We1aTBhaGa86AQ==");
+
+ // v4 RSA-3072 key "Bob" from "OpenPGP Example Keys and Certificates"
+ // https://www.ietf.org/archive/id/draft-bre-openpgp-samples-01.html#name-bobs-rsa-3072-samples
+ private static readonly byte[] bobPubkey = Base64.Decode(
+ "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" +
+ "NEJd3XZRzaXZE2aAMQ==");
+
+ private static readonly byte[] bobSecretkey = Base64.Decode(
+ "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");
+
+ // v4 DSA/ElGamal key "Carol" from "OpenPGP interoperability test suite"
+ // https://tests.sequoia-pgp.org/#Encrypt-Decrypt_roundtrip_with_key__Carol_
+ private static readonly byte[] carolPubkey = Base64.Decode(
+ "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"
+ );
+
+ // v6 Ed25519/X25519 key from RFC 9580
+ // https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-6-certificat
+ // https://www.rfc-editor.org/rfc/rfc9580#name-sample-version-6-secret-key
+ private static readonly byte[] v6Certificate = Base64.Decode(
+ "xioGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laPCsQYf" +
+ "GwoAAABCBYJjh3/jAwsJBwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxy" +
+ "KwwfHifBilZwj2Ul7Ce62azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lw" +
+ "gyU2kCcUmKfvBXbAf6rhRYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaE" +
+ "QsiPlR4zxP/TP7mhfVEe7XWPxtnMUMtf15OyA51YBM4qBmOHf+MZAAAAIIaTJINn" +
+ "+eUBXbki+PSAld2nhJh/LVmFsS+60WyvXkQ1wpsGGBsKAAAALAWCY4d/4wKbDCIh" +
+ "BssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce62azJAAAAAAQBIKbpGG2dWTX8" +
+ "j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDEM0g12vYxoWM8Y81W+bHBw805" +
+ "I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUrk0mXubZvyl4GBg==");
+
+ private static readonly byte[] v6UnlockedSecretKey = Base64.Decode(
+ "xUsGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laMAGXKB" +
+ "exK+cH6NX1hs5hNhIB00TrJmosgv3mg1ditlsLfCsQYfGwoAAABCBYJjh3/jAwsJ" +
+ "BwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6" +
+ "2azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lwgyU2kCcUmKfvBXbAf6rh" +
+ "RYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaEQsiPlR4zxP/TP7mhfVEe" +
+ "7XWPxtnMUMtf15OyA51YBMdLBmOHf+MZAAAAIIaTJINn+eUBXbki+PSAld2nhJh/" +
+ "LVmFsS+60WyvXkQ1AE1gCk95TUR3XFeibg/u/tVY6a//1q0NWC1X+yui3O24wpsG" +
+ "GBsKAAAALAWCY4d/4wKbDCIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6" +
+ "2azJAAAAAAQBIKbpGG2dWTX8j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDE" +
+ "M0g12vYxoWM8Y81W+bHBw805I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUr" +
+ "k0mXubZvyl4GBg==");
+
+ // v5 key "Emma" from "OpenPGP interoperability test suite"
+ // https://tests.sequoia-pgp.org/#Inline_Sign-Verify_roundtrip_with_key__Emma_
+ private static readonly byte[] v5Certificate = Base64.Decode(
+ "mDcFXJH05BYAAAAtCSsGAQQB2kcPAQEHQFhZlVcVVtwf+21xNQPX+ecMJJBL0MPd" +
+ "fj75iux+my8QtBhlbW1hLmdvbGRtYW5AZXhhbXBsZS5uZXSIlgUTFggASCIhBRk0" +
+ "e8mHJGQCX5nfPsLgAA7ZiEiS4fez6kyUAJFZVptUBQJckfTkAhsDBQsJCAcCAyIC" +
+ "AQYVCgkICwIEFgIDAQIeBwIXgAAA9cAA/jiR3yMsZMeEQ40u6uzEoXa6UXeV/S3w" +
+ "wJAXRJy9M8s0AP9vuL/7AyTfFXwwzSjDnYmzS0qAhbLDQ643N+MXGBJ2Bbg8BVyR" +
+ "9OQSAAAAMgorBgEEAZdVAQUBAQdA+nysrzml2UCweAqtpDuncSPlvrcBWKU0yfU0" +
+ "YvYWWAoDAQgHiHoFGBYIACwiIQUZNHvJhyRkAl+Z3z7C4AAO2YhIkuH3s+pMlACR" +
+ "WVabVAUCXJH05AIbDAAAOSQBAP4BOOIR/sGLNMOfeb5fPs/02QMieoiSjIBnijho" +
+ "b2U5AQC+RtOHCHx7TcIYl5/Uyoi+FOvPLcNw4hOv2nwUzSSVAw==");
+
+ private static readonly byte[] v5UnlockedSecretKey = Base64.Decode(
+ "lGEFXJH05BYAAAAtCSsGAQQB2kcPAQEHQFhZlVcVVtwf+21xNQPX+ecMJJBL0MPd" +
+ "fj75iux+my8QAAAAAAAiAQCHZ1SnSUmWqxEsoI6facIVZQu6mph3cBFzzTvcm5lA" +
+ "Ng5ctBhlbW1hLmdvbGRtYW5AZXhhbXBsZS5uZXSIlgUTFggASCIhBRk0e8mHJGQC" +
+ "X5nfPsLgAA7ZiEiS4fez6kyUAJFZVptUBQJckfTkAhsDBQsJCAcCAyICAQYVCgkI" +
+ "CwIEFgIDAQIeBwIXgAAA9cAA/jiR3yMsZMeEQ40u6uzEoXa6UXeV/S3wwJAXRJy9" +
+ "M8s0AP9vuL/7AyTfFXwwzSjDnYmzS0qAhbLDQ643N+MXGBJ2BZxmBVyR9OQSAAAA" +
+ "MgorBgEEAZdVAQUBAQdA+nysrzml2UCweAqtpDuncSPlvrcBWKU0yfU0YvYWWAoD" +
+ "AQgHAAAAAAAiAP9OdAPppjU1WwpqjIItkxr+VPQRT8Zm/Riw7U3F6v3OiBFHiHoF" +
+ "GBYIACwiIQUZNHvJhyRkAl+Z3z7C4AAO2YhIkuH3s+pMlACRWVabVAUCXJH05AIb" +
+ "DAAAOSQBAP4BOOIR/sGLNMOfeb5fPs/02QMieoiSjIBnijhob2U5AQC+RtOHCHx7" +
+ "TcIYl5/Uyoi+FOvPLcNw4hOv2nwUzSSVAw==");
+
+ private static readonly char[] emptyPassphrase = Array.Empty();
+
+ private static PgpSignatureGenerator CreateAndInitPgpSignatureGenerator(PgpSecretKey signingKey, HashAlgorithmTag hashAlgo, char[] passphrase)
+ {
+ PgpSignatureGenerator generator = new PgpSignatureGenerator(signingKey.PublicKey.Algorithm, hashAlgo);
+ PgpPrivateKey privKey = signingKey.ExtractPrivateKey(passphrase);
+ generator.InitSign(PgpSignature.CanonicalTextDocument, privKey, new SecureRandom());
+
+ return generator;
+ }
+
+ private static PgpPublicKeyRingBundle CreateBundle(params PgpPublicKeyRing[] keyrings)
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ foreach (var keyring in keyrings)
+ {
+ keyring.Encode(ms);
+ }
+ return new PgpPublicKeyRingBundle(ms.ToArray());
+ }
+ }
+ private static PgpSecretKeyRingBundle CreateBundle(params PgpSecretKeyRing[] keyrings)
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ foreach (var keyring in keyrings)
+ {
+ keyring.Encode(ms);
+ }
+ return new PgpSecretKeyRingBundle(ms.ToArray());
+ }
+ }
+
+ private void VerifyMultipleInlineSignaturesTest(byte[] message, PgpPublicKeyRingBundle bundle, bool shouldFail = false)
+ {
+ PgpObjectFactory factory = new PgpObjectFactory(message);
+ PgpOnePassSignatureList opss = factory.NextPgpObject() as PgpOnePassSignatureList;
+ for (int i = 0; i < opss.Count; i++)
+ {
+ PgpOnePassSignature ops = opss[i];
+ ops.InitVerify(bundle.GetPublicKey(ops.KeyId));
+ }
+
+ PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+ using (Stream dIn = lit.GetInputStream())
+ {
+
+ byte[] buffer = new byte[30];
+ int bytesRead;
+ while ((bytesRead = dIn.Read(buffer, 0, buffer.Length)) > 0)
+ {
+ for (int i = 0; i < opss.Count; i++)
+ {
+ opss[i].Update(buffer, 0, bytesRead);
+ }
+ }
+ }
+
+ PgpSignatureList sigs = factory.NextPgpObject() as PgpSignatureList;
+ IsEquals(opss.Count, sigs.Count);
+ int sigCount = sigs.Count - 1;
+ for (int i = 0; i <= sigCount; i++)
+ {
+ IsTrue(shouldFail != opss[i].Verify(sigs[sigCount - i]));
+ }
+ }
+
+ [Test]
+ public void MultiplePkeskTest()
+ {
+ // Encrypt-Decrypt roundtrip with multiple keys: the plaintext
+ // "Hello World :)" is encrypted with the X25519 sample key from
+ // Appendix A.3 of RFC 9580 and the 'Alice' ECDH key from
+ // "OpenPGP Example Keys and Certificates"
+ byte[] message = Base64.Decode(
+ "wVQDEsg/HnBvYwgZaeKxsIieN+FvNLxmgMfRKJZGKt8vAa5BYX2k0QAetCMpCQbE" +
+ "mvXtq2XatB3H8NG7zlY2dyYKcHAK0xvgAo8YbinpCZ+xOciOkmDBXgNHZva51fIe" +
+ "thIBB0CRPS2kBUVTTtVLGjBKVCmc+KoPTzUXqVpPJdgiPmNvGTBME7unL3IP2CdO" +
+ "hL+uO3LVBJGfRy3JJDH1SIQhQ7oS47AFIOjpG0R0CBtf8M6dzwDSPwG1BrsfRn86" +
+ "mFm666ZINIHL1IDH1HQVF5OYxcRRVFjTJhms03+nu6N8I6Vy2G5yekVb1Vh2tM39" +
+ "/aGWVXTHJw==");
+
+ PgpSecretKeyRingBundle bundle = CreateBundle(
+ new PgpSecretKeyRing(aliceSecretkey),
+ new PgpSecretKeyRing(v6UnlockedSecretKey));
+
+ byte[] plaintext = Encoding.UTF8.GetBytes("Hello World :)");
+ PgpObjectFactory factory = new PgpObjectFactory(message);
+ PgpEncryptedDataList encDataList = factory.NextPgpObject() as PgpEncryptedDataList;
+ FailIf("invalid PgpEncryptedDataList", encDataList is null);
+
+ IsEquals(2, encDataList.Count);
+
+ // decrypt with RFC 9580 sample X25519 key
+ var encData = encDataList[0] as PgpPublicKeyEncryptedData;
+ FailIf("invalid PgpPublicKeyEncryptedData", encData is null);
+ PgpSecretKey secKey = bundle.GetSecretKey(encData.KeyId);
+ PgpPrivateKey privKey = secKey.ExtractPrivateKey(emptyPassphrase);
+ using (var stream = encData.GetDataStream(privKey))
+ {
+ factory = new PgpObjectFactory(stream);
+ PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+ using (var ms = new MemoryStream())
+ {
+ lit.GetDataStream().CopyTo(ms);
+ var decrypted = ms.ToArray();
+ IsTrue(Arrays.AreEqual(plaintext, decrypted));
+ }
+ }
+
+ // decrypt with 'Alice' ECDH key
+ factory = new PgpObjectFactory(message);
+ encDataList = factory.NextPgpObject() as PgpEncryptedDataList;
+ encData = encDataList[1] as PgpPublicKeyEncryptedData;
+ FailIf("invalid PgpPublicKeyEncryptedData", encData is null);
+ secKey = bundle.GetSecretKey(encData.KeyId);
+ privKey = secKey.ExtractPrivateKey(emptyPassphrase);
+ using (var stream = encData.GetDataStream(privKey))
+ {
+ factory = new PgpObjectFactory(stream);
+ PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+ using (var ms = new MemoryStream())
+ {
+ lit.GetDataStream().CopyTo(ms);
+ var decrypted = ms.ToArray();
+ IsTrue(Arrays.AreEqual(plaintext, decrypted));
+ }
+ }
+
+ }
+
+ [Test]
+ public void MultipleInlineSignatureTest()
+ {
+ // Verify Inline Signature with multiple keys:
+ // v6 key from RFC 9580 and v4 key "Alice" from "OpenPGP Example Keys and Certificates"
+ // https://tests.sequoia-pgp.org/#Inline_Sign_with_minimal_key_from_RFC9760_and_key__Alice___verify_with_key_from_RFC9760
+
+ // inline signed message generated by GopenPGP 3.0.0-alpha
+ byte[] message = Base64.Decode(
+ "xEYGAAobIPdza3bN03j7U7LE/Q/46kHCmsfVx2UmTPsNpUk/V/UWyxhsTwYJppfk" +
+ "1S36bHIrDB8eJ8GKVnCPZSXsJ7rZrMkAxA0DAAoW8jFVDE9H444ByxRiAAAAAABI" +
+ "ZWxsbyBXb3JsZCA6KcJ1BAAWCgAnBQJl4HbKCRDyMVUMT0fjjhYhBOuFu1+jOnXh" +
+ "XpROY/IxVQxPR+OOAACKGAEAsQpg3dNdO4C9eMGn1jvVTjP0r2welMFD68dFU5d8" +
+ "nq8A+gNFdJbX0PP0vNx/kIxpilbdssnF+a04CdVpAkwXmaYPwpgGABsKAAAAKQUC" +
+ "ZeB2yiKhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce62azJAAAAACB3IPdz" +
+ "a3bN03j7U7LE/Q/46kHCmsfVx2UmTPsNpUk/V/UWJsjxFqBQXqDFAaOjiv8oabeX" +
+ "qvELkq1bKLb9fJ+ASfZW9FyI1ORHdCrI5zEnpfrFe4Id+xg9N39MTGq+OoPeDA==");
+
+ PgpPublicKeyRingBundle bundle = CreateBundle(
+ new PgpPublicKeyRing(alicePubkey),
+ new PgpPublicKeyRing(v6Certificate));
+
+ VerifyMultipleInlineSignaturesTest(message, bundle);
+
+ // inline signed message generated by PGPy 0.6.0+dkg-crypto-refresh
+ message = Base64.Decode(
+ "xA0DAAoW8jFVDE9H444AxEYGAAobIFWUOmg2wsfVON4qIM1sWUPd9223ANjaMnHT" +
+ "Mvad9EfVyxhsTwYJppfk1S36bHIrDB8eJ8GKVnCPZSXsJ7rZrMkByxRiAGXgds5I" +
+ "ZWxsbyBXb3JsZCA6KcKYBgAbCgAAACkFgmXgds4iIQbLGGxPBgmml+TVLfpscisM" +
+ "Hx4nwYpWcI9lJewnutmsyQAAAACSWCBVlDpoNsLH1TjeKiDNbFlD3fdttwDY2jJx" +
+ "0zL2nfRH1aouTY4WN/3DFsfP8yFg/BE7Ssaikt7bbXtBSH/AldOtyM1myiFsP+yx" +
+ "8Img2A7eq9+wKTLjhPHl7zSh7y9KEATCdQQAFgoAHQWCZeB2zhYhBOuFu1+jOnXh" +
+ "XpROY/IxVQxPR+OOAAoJEPIxVQxPR+OOgDcBAOz0kSpV4/F9Exxdq6oYlHZdsX5U" +
+ "n9QpjmJVjo7bsMGDAQCd3PA5joXmfoKQhtQT5Qm1dhjfv/c89oPzdjQYmVLnCg==");
+
+ VerifyMultipleInlineSignaturesTest(message, bundle);
+ }
+
+ [Test]
+ public void GenerateAndVerifyMultipleInlineSignatureTest()
+ {
+ // Inline Sign-Verify roundtrip test with multiple keys:
+ // v6 key from RFC 9580 and v4 key "Alice" from "OpenPGP Example Keys and Certificates"
+ byte[] data = Encoding.UTF8.GetBytes("Hello World :)");
+ byte[] message;
+
+ PgpSecretKey[] signingKeys = new PgpSecretKey[] {
+ new PgpSecretKeyRing(v6UnlockedSecretKey).GetSecretKey(),
+ new PgpSecretKeyRing(aliceSecretkey).GetSecretKey()
+ };
+
+ PgpSignatureGenerator[] generators = new PgpSignatureGenerator[] {
+ CreateAndInitPgpSignatureGenerator(signingKeys[0], HashAlgorithmTag.Sha384, emptyPassphrase),
+ CreateAndInitPgpSignatureGenerator(signingKeys[1], HashAlgorithmTag.Sha256, emptyPassphrase)
+ };
+
+ using (MemoryStream ms = new MemoryStream())
+ {
+ using (BcpgOutputStream bcOut = new BcpgOutputStream(ms, newFormatOnly: true))
+ {
+ int sigCount = generators.Length;
+ int count = 1;
+ foreach (PgpSignatureGenerator generator in generators)
+ {
+ generator.GenerateOnePassVersion(count != sigCount).Encode(bcOut);
+ count++;
+ }
+
+ PgpLiteralDataGenerator lGen = new PgpLiteralDataGenerator();
+ DateTime modificationTime = DateTime.UtcNow;
+ using (var lOut = lGen.Open(
+ new UncloseableStream(bcOut),
+ PgpLiteralData.Utf8,
+ "_CONSOLE",
+ data.Length,
+ modificationTime))
+ {
+ lOut.Write(data, 0, data.Length);
+
+ foreach (PgpSignatureGenerator generator in generators)
+ {
+ generator.Update(data);
+ }
+ }
+
+ foreach (PgpSignatureGenerator generator in generators.Reverse())
+ {
+ generator.Generate().Encode(bcOut);
+ }
+ }
+
+ message = ms.ToArray();
+ }
+
+ PgpPublicKeyRingBundle bundle = CreateBundle(
+ new PgpPublicKeyRing(alicePubkey),
+ new PgpPublicKeyRing(v6Certificate));
+
+ VerifyMultipleInlineSignaturesTest(message, bundle);
+
+ //corrupt data;
+ message[95] = 0x50;
+ VerifyMultipleInlineSignaturesTest(message, bundle, shouldFail: true);
+ }
+
+ private void VerifyMultipleDetachedSignaturesTest(byte[] signaturePacket, byte[] data, PgpPublicKeyRingBundle bundle, bool shouldFail = false)
+ {
+ PgpObjectFactory factory = new PgpObjectFactory(signaturePacket);
+ PgpSignatureList sigs = factory.NextPgpObject() as PgpSignatureList;
+
+ IsEquals(sigs.Count, 2);
+ for (int i = 0; i < sigs.Count; i++)
+ {
+ PgpSignature sig = sigs[i];
+ sig.InitVerify(bundle.GetPublicKey(sig.KeyId));
+ sig.Update(data);
+
+ IsTrue(shouldFail != sig.Verify());
+ }
+ }
+
+ [Test]
+ public void MultipleDetachedSignatureTest()
+ {
+ // Verify Detached Signature with multiple keys:
+ // v6 key from RFC 9580 and v4 key "Alice" from "OpenPGP Example Keys and Certificates"
+ // https://tests.sequoia-pgp.org/#Detached_Sign_with_minimal_key_from_RFC9760_and_key__Alice___verify_with_key_from_RFC9760
+
+ byte[] data = Encoding.UTF8.GetBytes("Hello World :)");
+ byte[] corruptedData = Encoding.UTF8.GetBytes("Hello World :(");
+
+ PgpPublicKeyRingBundle bundle = CreateBundle(
+ new PgpPublicKeyRing(alicePubkey),
+ new PgpPublicKeyRing(v6Certificate));
+
+ // Detached Signature generated by GopenPGP 3.0.0-alpha
+ byte[] signaturePacket = Base64.Decode(
+ "wpgGABsKAAAAKQUCZeB2zCKhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6" +
+ "2azJAAAAAEPPIIh5xfXDp5Zmfa7KJ0S+3Z+RBO9j5AC33ZRAwGgWKVuBts2H+I0k" +
+ "GlIQXoyX+2LnurlGQGxZRqwk/z2d4Tk8oAA62CuJ318aZdo8Z4utdmHvsWlluAWl" +
+ "lh0XdZ5l/qBNC8J1BAAWCgAnBQJl4HbMCRDyMVUMT0fjjhYhBOuFu1+jOnXhXpRO" +
+ "Y/IxVQxPR+OOAABPnQEA881lXU6DUMYbXx3rmGa5qSQld9pHxzRYtBT/WCfkzVwA" +
+ "/0/PN5jncrytAiEjb6YwuZuTVjJdTy6xtzuH+XALdREG");
+
+ VerifyMultipleDetachedSignaturesTest(signaturePacket, data, bundle);
+ VerifyMultipleDetachedSignaturesTest(signaturePacket, corruptedData, bundle, shouldFail: true);
+
+ // Detached Signature generated by PGPy 0.6.0+dkg-crypto-refresh
+ signaturePacket = Base64.Decode(
+ "wpgGABsKAAAAKQWCZeB20SIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6" +
+ "2azJAAAAADUkIIqFiPBBvz4Uqsug38k/hVaFdHoHfy82ESRfutwk1ch+TaG8Kk2I" +
+ "7IMcrzKKSp60I7MEGb5CUCzeeM4v883yXlzZhwiBl+enR8kHxcVZzH+z7aS3OptN" +
+ "mrcay8CfwzHJD8J1BAAWCgAdBYJl4HbRFiEE64W7X6M6deFelE5j8jFVDE9H444A" +
+ "CgkQ8jFVDE9H447lbQEAx8hE9sbx1s8kMwuuEUtvoayJyz6R3PyQAIGH72g9XNcA" +
+ "/32a6SYBHAHl8HOrlkZWzUwaIyhOcI5jN6ppiKRZAL8O");
+
+ VerifyMultipleDetachedSignaturesTest(signaturePacket, data, bundle);
+ VerifyMultipleDetachedSignaturesTest(signaturePacket, corruptedData, bundle, shouldFail: true);
+ }
+
+
+ [Test]
+ public void GenerateAndVerifyMultipleDetachedSignatureTest()
+ {
+ // Inline Sign-Verify roundtrip test with multiple keys:
+ // v6 key from RFC 9580 and v4 key "Alice" from "OpenPGP Example Keys and Certificates"
+
+ byte[] data = Encoding.UTF8.GetBytes("Hello World :)");
+ byte[] corruptedData = Encoding.UTF8.GetBytes("Hello World :(");
+ byte[] signaturePacket;
+
+ PgpSecretKey[] signingKeys = new PgpSecretKey[] {
+ new PgpSecretKeyRing(v6UnlockedSecretKey).GetSecretKey(),
+ new PgpSecretKeyRing(aliceSecretkey).GetSecretKey()
+ };
+
+ PgpSignatureGenerator[] generators = new PgpSignatureGenerator[] {
+ CreateAndInitPgpSignatureGenerator(signingKeys[0], HashAlgorithmTag.Sha3_512, emptyPassphrase),
+ CreateAndInitPgpSignatureGenerator(signingKeys[1], HashAlgorithmTag.Sha224, emptyPassphrase)
+ };
+
+ using (MemoryStream ms = new MemoryStream())
+ {
+ using (BcpgOutputStream bcOut = new BcpgOutputStream(ms, newFormatOnly: true))
+ {
+ foreach (PgpSignatureGenerator generator in generators)
+ {
+ generator.Update(data);
+ generator.Generate().Encode(bcOut);
+ }
+ }
+
+ signaturePacket = ms.ToArray();
+ }
+
+ PgpPublicKeyRingBundle bundle = CreateBundle(
+ new PgpPublicKeyRing(alicePubkey),
+ new PgpPublicKeyRing(v6Certificate));
+
+ VerifyMultipleDetachedSignaturesTest(signaturePacket, data, bundle);
+ VerifyMultipleDetachedSignaturesTest(signaturePacket, corruptedData, bundle, shouldFail: true);
+ }
+
+ [Test]
+ public void Version5KeyParsingTest()
+ {
+ string uid = "emma.goldman@example.net";
+ PgpPublicKeyRing pubRing = new PgpPublicKeyRing(v5Certificate);
+ PgpPublicKey[] pubKeys = pubRing.GetPublicKeys().ToArray();
+ IsEquals("wrong number of public keys", pubKeys.Length, 2);
+
+ PgpPublicKey masterKey = pubKeys[0];
+ PgpPublicKey subKey = pubKeys[1];
+
+ IsTrue(masterKey.IsMasterKey);
+ IsTrue(subKey.IsEncryptionKey);
+ IsEquals(masterKey.Algorithm, PublicKeyAlgorithmTag.EdDsa_Legacy);
+ IsEquals(subKey.Algorithm, PublicKeyAlgorithmTag.ECDH);
+
+ IsTrue(masterKey.GetUserIds().Contains(uid));
+ IsTrue(!masterKey.GetUserIds().Contains("emma.g@example.net"));
+
+ IsEquals(masterKey.KeyId, 0x19347BC987246402);
+ IsEquals((ulong)subKey.KeyId, 0xE4557C2B02FFBF4B);
+ IsTrue(AreEqual(masterKey.GetFingerprint(), Hex.Decode("19347BC9872464025F99DF3EC2E0000ED9884892E1F7B3EA4C94009159569B54")));
+ IsTrue(AreEqual(subKey.GetFingerprint(), Hex.Decode("E4557C2B02FFBF4B04F87401EC336AF7133D0F85BE7FD09BAEFD9CAEB8C93965")));
+
+ // verify v5 self sig
+ PgpSignature signature = masterKey.GetSignaturesForId(uid).ToArray()[0];
+ IsEquals(signature.Version, SignaturePacket.Version5);
+ IsEquals(signature.SignatureType, PgpSignature.PositiveCertification);
+ signature.InitVerify(masterKey);
+ IsTrue(signature.VerifyCertification(uid, masterKey));
+
+ // verify subkey binding sig
+ signature = subKey.GetSignatures().ToArray()[0];
+ IsEquals(signature.Version, SignaturePacket.Version5);
+ IsEquals(signature.SignatureType, PgpSignature.SubkeyBinding);
+ signature.InitVerify(masterKey);
+ IsTrue(signature.VerifyCertification(masterKey, subKey));
+ }
+
+ [Test]
+ public void Version5InlineSignatureTest()
+ {
+ // Verify v5 Inline Signature generated by OpenPGP.js 5.5.0
+ // https://tests.sequoia-pgp.org/#Inline_Sign-Verify_roundtrip_with_key__Emma_
+ byte[] message = Base64.Decode(
+ "xA0DAQoWGTR7yYckZAIByxR1AGXgdslIZWxsbyBXb3JsZCA6KcJ3BQEWCgAp" +
+ "BQJl4HbJIiEFGTR7yYckZAJfmd8+wuAADtmISJLh97PqTJQAkVlWm1QAADsI" +
+ "AQD7aH9a0GKcHdFThMsOQ88xAM5PiqPyDV1A/K23rPN28wD/QoPa1yEE3Y2R" +
+ "ZtqtH6jAymdyIwtsa5wLvzUjTmP5OQo=");
+
+ PgpPublicKeyRing pubRing = new PgpPublicKeyRing(v5Certificate);
+ PgpPublicKey signer = pubRing.GetPublicKey();
+
+ PgpObjectFactory factory = new PgpObjectFactory(message);
+
+ PgpOnePassSignatureList opss = factory.NextPgpObject() as PgpOnePassSignatureList;
+ IsEquals(opss.Count, 1);
+ PgpOnePassSignature ops = opss[0];
+ IsEquals(ops.Version, OnePassSignaturePacket.Version3);
+
+ ops.InitVerify(signer);
+ PgpLiteralData literal = factory.NextPgpObject() as PgpLiteralData;
+ using (Stream dIn = literal.GetInputStream())
+ {
+ byte[] buffer = new byte[30];
+ int bytesRead;
+ while ((bytesRead = dIn.Read(buffer, 0, buffer.Length)) > 0)
+ {
+ ops.Update(buffer, 0, bytesRead);
+ }
+ }
+
+ PgpSignatureList sigs = factory.NextPgpObject() as PgpSignatureList;
+ IsEquals(sigs.Count, 1);
+ byte[] metadata = literal.GetMetadata(sigs[0].Version);
+ IsTrue(ops.Verify(sigs[0], metadata));
+ }
+
+ [Test]
+ public void SeipdVersion2WithMultipleMethodsTest()
+ {
+ // Encrypt-Decrypt roundtrip V6 SKESK/PKESK, V2 SEIPD, AES-256 in EAX mode
+ // multiple different passwords and public keys with different algorithms
+ // and S2K schemes
+ byte[] largePlaintext = new byte[50000];
+ Arrays.Fill(largePlaintext, (byte)'A');
+ byte[] enc;
+
+ byte[][] passwords = new byte[][]
+ {
+ Encoding.UTF8.GetBytes("password"),
+ Encoding.UTF8.GetBytes("drowssap"),
+ Encoding.UTF8.GetBytes("P@ssw0rd")
+ };
+
+ var alice = new PgpPublicKeyRing(alicePubkey).GetPublicKeys().First(k => k.IsEncryptionKey);
+ var bob = new PgpPublicKeyRing(bobPubkey).GetPublicKeys().First(k => k.IsEncryptionKey);
+ var v6 = new PgpPublicKeyRing(v6Certificate).GetPublicKeys().First(k => k.IsEncryptionKey);
+ var carol = new PgpPublicKeyRing(carolPubkey).GetPublicKeys().First(k => k.IsEncryptionKey);
+
+ PgpPrivateKey[] privateKeys = new PgpPrivateKey[]
+ {
+ new PgpSecretKeyRing(aliceSecretkey).GetSecretKey(alice.KeyId).ExtractPrivateKey(emptyPassphrase),
+ new PgpSecretKeyRing(bobSecretkey).GetSecretKey(bob.KeyId).ExtractPrivateKey(emptyPassphrase),
+ new PgpSecretKeyRing(v6UnlockedSecretKey).GetSecretKey(v6.KeyId).ExtractPrivateKey(emptyPassphrase)
+ };
+
+ var gen = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Aes256, AeadAlgorithmTag.Eax);
+ gen.AddMethodRaw(passwords[0], S2k.Argon2Parameters.UniversallyRecommendedParameters());
+ gen.AddMethodRaw(passwords[1], S2k.Argon2Parameters.MemoryConstrainedParameters());
+ gen.AddMethodRaw(passwords[2], HashAlgorithmTag.Sha256);
+ gen.AddMethod(alice);
+ gen.AddMethod(bob);
+ gen.AddMethod(v6);
+
+ // Check constraint: An implementation MUST NOT generate ElGamal v6 PKESKs.
+ // https://www.rfc-editor.org/rfc/rfc9580#name-algorithm-specific-fields-fo
+ Assert.Throws(() =>
+ {
+ gen.AddMethod(carol);
+ });
+
+ using (MemoryStream ms = new MemoryStream())
+ {
+ byte[] buffer = new byte[3000];
+ using (Stream cOut = gen.Open(ms, buffer))
+ {
+ using (BcpgOutputStream bcOut = new BcpgOutputStream(cOut, newFormatOnly: true))
+ {
+ PgpLiteralDataGenerator literalDataGen = new PgpLiteralDataGenerator();
+ DateTime modificationTime = DateTime.UtcNow;
+
+ using (Stream lOut = literalDataGen.Open(
+ new UncloseableStream(bcOut),
+ PgpLiteralData.Utf8,
+ PgpLiteralData.Console,
+ largePlaintext.Length,
+ modificationTime))
+ {
+ lOut.Write(largePlaintext, 0, largePlaintext.Length);
+ }
+ }
+ }
+ enc = ms.ToArray();
+ }
+
+ // decrypt
+ for (int i = 0; i < passwords.Length; i++)
+ {
+ PgpObjectFactory factory = new PgpObjectFactory(enc);
+ PgpEncryptedDataList encDataList = factory.NextPgpObject() as PgpEncryptedDataList;
+ PgpPbeEncryptedData encData = encDataList[i] as PgpPbeEncryptedData;
+ using (Stream stream = encData.GetDataStreamRaw(passwords[i]))
+ {
+ factory = new PgpObjectFactory(stream);
+ PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+ using (MemoryStream ms = new MemoryStream())
+ {
+ lit.GetDataStream().CopyTo(ms);
+ byte[] decrypted = ms.ToArray();
+ IsTrue(Arrays.AreEqual(largePlaintext, decrypted));
+ }
+ }
+ }
+
+ for (int i = 0; i < privateKeys.Length; i++)
+ {
+ PgpObjectFactory factory = new PgpObjectFactory(enc);
+ PgpEncryptedDataList encDataList = factory.NextPgpObject() as PgpEncryptedDataList;
+ PgpPublicKeyEncryptedData encData = encDataList[3+i] as PgpPublicKeyEncryptedData;
+
+ using (Stream stream = encData.GetDataStream(privateKeys[i]))
+ {
+ factory = new PgpObjectFactory(stream);
+ PgpLiteralData lit = factory.NextPgpObject() as PgpLiteralData;
+ using (MemoryStream ms = new MemoryStream())
+ {
+ lit.GetDataStream().CopyTo(ms);
+ byte[] decrypted = ms.ToArray();
+ IsTrue(Arrays.AreEqual(largePlaintext, decrypted));
+ }
+ }
+ }
+ }
+
+ public override string Name => "PgpInteroperabilityTestSuite";
+
+ public override void PerformTest()
+ {
+ MultiplePkeskTest();
+ SeipdVersion2WithMultipleMethodsTest();
+
+ MultipleInlineSignatureTest();
+ GenerateAndVerifyMultipleInlineSignatureTest();
+
+ MultipleDetachedSignatureTest();
+ GenerateAndVerifyMultipleDetachedSignatureTest();
+
+ Version5KeyParsingTest();
+ Version5InlineSignatureTest();
+ }
+ }
+}
diff --git a/crypto/test/src/openpgp/test/PgpKeyRingTest.cs b/crypto/test/src/openpgp/test/PgpKeyRingTest.cs
index 821a7f2952..6b2cc3e704 100644
--- a/crypto/test/src/openpgp/test/PgpKeyRingTest.cs
+++ b/crypto/test/src/openpgp/test/PgpKeyRingTest.cs
@@ -2440,7 +2440,7 @@ public void RewrapTest()
// this should succeed
PgpPrivateKey privTmp = pgpKey.ExtractPrivateKey(newPass);
- if (pgpKey.KeyId != oldKeyID || pgpKey.S2kUsage != SecretKeyPacket.UsageChecksum)
+ if (pgpKey.KeyId != oldKeyID || pgpKey.S2kUsage != SecretKeyPacket.UsageSha1)
{
Fail("usage/key ID mismatch");
}