From b5f50578a41d9197230d44e000d441e653ee56bc Mon Sep 17 00:00:00 2001
From: Filip Navara <navara@emclient.com>
Date: Fri, 4 Dec 2020 11:21:22 +0100
Subject: [PATCH 1/8] Initial implementation of OpenPGP EdDSA and usage of the
 Curve25519 in ECDH.

---
 crypto/src/asn1/gnu/GNUObjectIdentifiers.cs   |  6 +--
 crypto/src/asn1/misc/MiscObjectIdentifiers.cs |  3 ++
 crypto/src/bcpg/PublicKeyPacket.cs            |  1 +
 crypto/src/bcpg/SignaturePacket.cs            |  1 +
 crypto/src/crypto/ec/CustomNamedCurves.cs     |  5 +-
 .../src/crypto/signers/EdDsa255519Signer.cs   | 47 +++++++++++++++++++
 .../src/openpgp/PgpEncryptedDataGenerator.cs  | 46 +++++++++++++-----
 crypto/src/openpgp/PgpPublicKey.cs            | 13 ++++-
 .../src/openpgp/PgpPublicKeyEncryptedData.cs  | 25 ++++++++--
 crypto/src/openpgp/PgpSecretKey.cs            | 10 +++-
 crypto/src/openpgp/PgpUtilities.cs            |  5 +-
 crypto/src/openpgp/Rfc6637Utilities.cs        | 18 +++++--
 crypto/src/security/SignerUtilities.cs        |  7 +++
 13 files changed, 156 insertions(+), 31 deletions(-)
 create mode 100644 crypto/src/crypto/signers/EdDsa255519Signer.cs

diff --git a/crypto/src/asn1/gnu/GNUObjectIdentifiers.cs b/crypto/src/asn1/gnu/GNUObjectIdentifiers.cs
index b322ef233b..6688ac1786 100644
--- a/crypto/src/asn1/gnu/GNUObjectIdentifiers.cs
+++ b/crypto/src/asn1/gnu/GNUObjectIdentifiers.cs
@@ -28,9 +28,9 @@ public abstract class GnuObjectIdentifiers
 		public static readonly DerObjectIdentifier Crc					= new DerObjectIdentifier("1.3.6.1.4.1.11591.14"); // CRC algorithms
 		public static readonly DerObjectIdentifier Crc32				= new DerObjectIdentifier("1.3.6.1.4.1.11591.14.1"); // CRC 32
 
-        /** 1.3.6.1.4.1.11591.15 - ellipticCurve */
-        public static readonly DerObjectIdentifier EllipticCurve = new DerObjectIdentifier("1.3.6.1.4.1.11591.15");
+		/** 1.3.6.1.4.1.11591.15 - ellipticCurve */
+		public static readonly DerObjectIdentifier EllipticCurve = new DerObjectIdentifier("1.3.6.1.4.1.11591.15");
 
-        public static readonly DerObjectIdentifier Ed25519   = EllipticCurve.Branch("1");
+		public static readonly DerObjectIdentifier Ed25519   = EllipticCurve.Branch("1");
 	}
 }
diff --git a/crypto/src/asn1/misc/MiscObjectIdentifiers.cs b/crypto/src/asn1/misc/MiscObjectIdentifiers.cs
index 1f101882f3..0ca5f289b6 100644
--- a/crypto/src/asn1/misc/MiscObjectIdentifiers.cs
+++ b/crypto/src/asn1/misc/MiscObjectIdentifiers.cs
@@ -74,6 +74,9 @@ public abstract class MiscObjectIdentifiers
         public static readonly DerObjectIdentifier cryptlib_algorithm_blowfish_CFB = cryptlib_algorithm.Branch("1.3");
         public static readonly DerObjectIdentifier cryptlib_algorithm_blowfish_OFB = cryptlib_algorithm.Branch("1.4");
 
+        // OpenPGP (draft-ietf-openpgp-rfc4880bis-10)
+        public static readonly DerObjectIdentifier Curve25519 = cryptlib_algorithm.Branch("5.1");
+
         //
         // Blake2b
         //
diff --git a/crypto/src/bcpg/PublicKeyPacket.cs b/crypto/src/bcpg/PublicKeyPacket.cs
index bbed941dc7..f3b07ecdd0 100644
--- a/crypto/src/bcpg/PublicKeyPacket.cs
+++ b/crypto/src/bcpg/PublicKeyPacket.cs
@@ -48,6 +48,7 @@ internal PublicKeyPacket(
                     key = new ECDHPublicBcpgKey(bcpgIn);
                     break;
                 case PublicKeyAlgorithmTag.ECDsa:
+                case PublicKeyAlgorithmTag.EdDsa:
                     key = new ECDsaPublicBcpgKey(bcpgIn);
                     break;
                 default:
diff --git a/crypto/src/bcpg/SignaturePacket.cs b/crypto/src/bcpg/SignaturePacket.cs
index 70138d584f..fb236dc533 100644
--- a/crypto/src/bcpg/SignaturePacket.cs
+++ b/crypto/src/bcpg/SignaturePacket.cs
@@ -147,6 +147,7 @@ internal SignaturePacket(
 					signature = new MPInteger[]{ p, g, y };
                     break;
                 case PublicKeyAlgorithmTag.ECDsa:
+                case PublicKeyAlgorithmTag.EdDsa:
                     MPInteger ecR = new MPInteger(bcpgIn);
                     MPInteger ecS = new MPInteger(bcpgIn);
                     signature = new MPInteger[]{ ecR, ecS };
diff --git a/crypto/src/crypto/ec/CustomNamedCurves.cs b/crypto/src/crypto/ec/CustomNamedCurves.cs
index 0eee66a338..53d0cf979e 100644
--- a/crypto/src/crypto/ec/CustomNamedCurves.cs
+++ b/crypto/src/crypto/ec/CustomNamedCurves.cs
@@ -3,6 +3,8 @@
 
 using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Asn1.GM;
+using Org.BouncyCastle.Asn1.Gnu;
+using Org.BouncyCastle.Asn1.Misc;
 using Org.BouncyCastle.Asn1.Sec;
 using Org.BouncyCastle.Asn1.X9;
 using Org.BouncyCastle.Math;
@@ -789,7 +791,8 @@ private static void DefineCurveAlias(string name, DerObjectIdentifier oid)
 
         static CustomNamedCurves()
         {
-            DefineCurve("curve25519", Curve25519Holder.Instance);
+            DefineCurveWithOid("ed25519", GnuObjectIdentifiers.Ed25519, Curve25519Holder.Instance);
+            DefineCurveWithOid("curve25519", MiscObjectIdentifiers.Curve25519, Curve25519Holder.Instance);
 
             //DefineCurveWithOid("secp112r1", SecObjectIdentifiers.SecP112r1, SecP112R1Holder.Instance);
             //DefineCurveWithOid("secp112r2", SecObjectIdentifiers.SecP112r2, SecP112R2Holder.Instance);
diff --git a/crypto/src/crypto/signers/EdDsa255519Signer.cs b/crypto/src/crypto/signers/EdDsa255519Signer.cs
new file mode 100644
index 0000000000..345f5ddb23
--- /dev/null
+++ b/crypto/src/crypto/signers/EdDsa255519Signer.cs
@@ -0,0 +1,47 @@
+using System;
+
+using Org.BouncyCastle.Math;
+
+namespace Org.BouncyCastle.Crypto.Signers
+{
+	public class EdDsa22519Signer : IDsa
+	{
+		private Ed25519Signer signer;
+
+		public virtual string AlgorithmName => "EDDSA";
+
+		public EdDsa22519Signer()
+		{
+			signer = new Ed25519Signer();
+		}
+
+		public virtual void Init(bool forSigning, ICipherParameters parameters)
+		{
+			signer.Init(forSigning, parameters);
+		}
+
+		public virtual BigInteger[] GenerateSignature(byte[] message)
+		{
+			signer.BlockUpdate(message, 0, message.Length);
+			byte[] sigBytes = signer.GenerateSignature();
+			byte[] rBytes = new byte[32];
+			Array.Copy(sigBytes, rBytes, 32);
+			byte[] sBytes = new byte[sigBytes.Length - 32];
+			Array.Copy(sigBytes, 32, sBytes, 0, sigBytes.Length - 32);
+			BigInteger r = new BigInteger(1, rBytes);
+			BigInteger s = new BigInteger(1, sBytes);
+			return new BigInteger[2] { r, s };
+		}
+
+		public virtual bool VerifySignature(byte[] message, BigInteger r, BigInteger s)
+		{
+			signer.BlockUpdate(message, 0, message.Length);
+			byte[] rBytes = r.ToByteArrayUnsigned();
+			byte[] sBytes = s.ToByteArrayUnsigned();
+			byte[] sigBytes = new byte[rBytes.Length + sBytes.Length];
+			Array.Copy(rBytes, sigBytes, rBytes.Length);
+			Array.Copy(sBytes, 0, sigBytes, rBytes.Length, sBytes.Length);
+			return signer.VerifySignature(sigBytes);
+		}
+	}
+}
diff --git a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
index 336baf00d7..81847cabd3 100644
--- a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
+++ b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
@@ -3,6 +3,7 @@
 using System.Diagnostics;
 using System.IO;
 
+using Org.BouncyCastle.Asn1.Misc;
 using Org.BouncyCastle.Asn1.X9;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.IO;
@@ -10,6 +11,7 @@
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Math.EC;
+using Org.BouncyCastle.Math.EC.Rfc7748;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 
@@ -105,7 +107,7 @@ private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random)
                 if (pubKey.Algorithm != PublicKeyAlgorithmTag.ECDH)
                 {
                     IBufferedCipher c;
-				    switch (pubKey.Algorithm)
+                    switch (pubKey.Algorithm)
                     {
                         case PublicKeyAlgorithmTag.RsaEncrypt:
                         case PublicKeyAlgorithmTag.RsaGeneral:
@@ -119,29 +121,49 @@ private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random)
                             throw new PgpException("Can't use DSA for encryption.");
                         case PublicKeyAlgorithmTag.ECDsa:
                             throw new PgpException("Can't use ECDSA for encryption.");
+                        case PublicKeyAlgorithmTag.EdDsa:
+                            throw new PgpException("Can't use EdDSA for encryption.");
                         default:
                             throw new PgpException("unknown asymmetric algorithm: " + pubKey.Algorithm);
                     }
 
                     AsymmetricKeyParameter akp = pubKey.GetKey();
-				    c.Init(true, new ParametersWithRandom(akp, random));
+                    c.Init(true, new ParametersWithRandom(akp, random));
                     return c.DoFinal(sessionInfo);
                 }
 
                 ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)pubKey.PublicKeyPacket.Key;
+                KeyParameter key;
+                byte[] encodedPublicKey;
 
-                // Generate the ephemeral key pair
-                IAsymmetricCipherKeyPairGenerator gen = GeneratorUtilities.GetKeyPairGenerator("ECDH");
-                gen.Init(new ECKeyGenerationParameters(ecKey.CurveOid, random));
+                if (ecKey.CurveOid.Id.Equals(MiscObjectIdentifiers.Curve25519.Id))
+                {
+                    byte[] privateKey = new byte[X25519.PointSize];
+                    X25519.GeneratePrivateKey(random, privateKey);
+                    byte[] sharedKey = new byte[32];
+                    X25519.CalculateAgreement(privateKey, 0, BigIntegers.AsUnsignedByteArray(ecKey.EncodedPoint), 1, sharedKey, 0);
+                    byte[] publicKey = new byte[X25519.PointSize + 1];
+                    publicKey[0] = 0x40; // compressed point
+                    X25519.GeneratePublicKey(privateKey, 0, publicKey, 1);
+                    encodedPublicKey = publicKey;
+                    key = new KeyParameter(Rfc6637Utilities.CreateKey(pubKey.PublicKeyPacket, sharedKey));
+                }
+                else
+                {
+                    // Generate the ephemeral key pair
+                    IAsymmetricCipherKeyPairGenerator gen = GeneratorUtilities.GetKeyPairGenerator("ECDH");
+                    gen.Init(new ECKeyGenerationParameters(ecKey.CurveOid, random));
 
-                AsymmetricCipherKeyPair ephKp = gen.GenerateKeyPair();
-                ECPrivateKeyParameters ephPriv = (ECPrivateKeyParameters)ephKp.Private;
-                ECPublicKeyParameters ephPub = (ECPublicKeyParameters)ephKp.Public;
+                    AsymmetricCipherKeyPair ephKp = gen.GenerateKeyPair();
+                    ECPrivateKeyParameters ephPriv = (ECPrivateKeyParameters)ephKp.Private;
+                    ECPublicKeyParameters ephPub = (ECPublicKeyParameters)ephKp.Public;
 
-                ECPublicKeyParameters pub = (ECPublicKeyParameters)pubKey.GetKey();
-                ECPoint S = pub.Q.Multiply(ephPriv.D).Normalize();
+                    ECPublicKeyParameters pub = (ECPublicKeyParameters)pubKey.GetKey();
+                    ECPoint S = pub.Q.Multiply(ephPriv.D).Normalize();
 
-                KeyParameter key = new KeyParameter(Rfc6637Utilities.CreateKey(pubKey.PublicKeyPacket, S));
+                    key = new KeyParameter(Rfc6637Utilities.CreateKey(pubKey.PublicKeyPacket, S));
+                    encodedPublicKey = ephPub.Q.GetEncoded(false);
+                }
 
                 IWrapper w = PgpUtilities.CreateWrapper(ecKey.SymmetricKeyAlgorithm);
                 w.Init(true, new ParametersWithRandom(key, random));
@@ -149,7 +171,7 @@ private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random)
                 byte[] paddedSessionData = PgpPad.PadSessionData(sessionInfo, sessionKeyObfuscation);
 
                 byte[] C = w.Wrap(paddedSessionData, 0, paddedSessionData.Length);
-                byte[] VB = new MPInteger(new BigInteger(1, ephPub.Q.GetEncoded(false))).GetEncoded();
+                byte[] VB = new MPInteger(new BigInteger(1, encodedPublicKey)).GetEncoded();
 
                 byte[] rv = new byte[VB.Length + 1 + C.Length];
 
diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs
index 92422c4132..b66ca844f2 100644
--- a/crypto/src/openpgp/PgpPublicKey.cs
+++ b/crypto/src/openpgp/PgpPublicKey.cs
@@ -1,7 +1,7 @@
 using System;
 using System.Collections;
 using System.IO;
-
+using Org.BouncyCastle.Asn1.Misc;
 using Org.BouncyCastle.Asn1.Sec;
 using Org.BouncyCastle.Asn1.X9;
 using Org.BouncyCastle.Crypto;
@@ -177,6 +177,10 @@ public PgpPublicKey(
                 {
                     bcpgKey = new ECDsaPublicBcpgKey(ecK.PublicKeyParamSet, ecK.Q);
                 }
+                else if (algorithm == PublicKeyAlgorithmTag.EdDsa)
+                {
+                    bcpgKey = new ECDsaPublicBcpgKey(ecK.PublicKeyParamSet, ecK.Q);
+                }
                 else
                 {
                     throw new PgpException("unknown EC algorithm");
@@ -495,7 +499,12 @@ public AsymmetricKeyParameter GetKey()
                     case PublicKeyAlgorithmTag.ECDsa:
                         return GetECKey("ECDSA");
                     case PublicKeyAlgorithmTag.ECDH:
-                        return GetECKey("ECDH");
+                        if (((ECPublicBcpgKey)publicPk.Key).CurveOid.Id.Equals(MiscObjectIdentifiers.Curve25519.Id))
+                            return new X25519PublicKeyParameters(((ECPublicBcpgKey)publicPk.Key).EncodedPoint.ToByteArrayUnsigned(), 0);
+                        else
+                            return GetECKey("ECDH");
+                    case PublicKeyAlgorithmTag.EdDsa:
+                        return new Ed25519PublicKeyParameters(((ECPublicBcpgKey)publicPk.Key).EncodedPoint.ToByteArrayUnsigned(), 1);
                     case PublicKeyAlgorithmTag.ElGamalEncrypt:
                     case PublicKeyAlgorithmTag.ElGamalGeneral:
                         ElGamalPublicBcpgKey elK = (ElGamalPublicBcpgKey)publicPk.Key;
diff --git a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
index 04fe3ad37c..be05f36fe4 100644
--- a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
+++ b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
@@ -8,8 +8,11 @@
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Math.EC;
+using Org.BouncyCastle.Math.EC.Custom.Djb;
+using Org.BouncyCastle.Math.EC.Rfc7748;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities.IO;
+using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
@@ -210,12 +213,24 @@ private byte[] RecoverSessionData(PgpPrivateKey privKey)
                 byte[] keyEnc = new byte[keyLen];
                 Array.Copy(enc, 2 + pLen + 1, keyEnc, 0, keyEnc.Length);
 
-                ECPoint publicPoint = x9Params.Curve.DecodePoint(pEnc);
+                KeyParameter key;
 
-                ECPrivateKeyParameters privKeyParams = (ECPrivateKeyParameters)privKey.Key;
-                ECPoint S = publicPoint.Multiply(privKeyParams.D).Normalize();
-
-                KeyParameter key = new KeyParameter(Rfc6637Utilities.CreateKey(privKey.PublicKeyPacket, S));
+                if (privKey.Key is X25519PrivateKeyParameters x25519privKeyParams)
+                {
+                    byte[] sharedKey = new byte[32];
+                    byte[] reversedPrivateKey = new byte[32];
+                    x25519privKeyParams.Encode(reversedPrivateKey, 0);
+                    Array.Reverse((Array)reversedPrivateKey);
+                    X25519.ScalarMult(reversedPrivateKey, 0, pEnc, 1, sharedKey, 0);
+                    key = new KeyParameter(Rfc6637Utilities.CreateKey(privKey.PublicKeyPacket, sharedKey));
+                }
+                else
+                {
+                    ECPoint publicPoint = x9Params.Curve.DecodePoint(pEnc);
+                    ECPrivateKeyParameters privKeyParams = (ECPrivateKeyParameters)privKey.Key;
+                    ECPoint S = publicPoint.Multiply(privKeyParams.D).Normalize();
+                    key = new KeyParameter(Rfc6637Utilities.CreateKey(privKey.PublicKeyPacket, S));
+                }
 
                 IWrapper w = PgpUtilities.CreateWrapper(ecKey.SymmetricKeyAlgorithm);
                 w.Init(false, key);
diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs
index a3ffd4a4ad..7271fa2ef4 100644
--- a/crypto/src/openpgp/PgpSecretKey.cs
+++ b/crypto/src/openpgp/PgpSecretKey.cs
@@ -1,7 +1,7 @@
 using System;
 using System.Collections;
 using System.IO;
-
+using Org.BouncyCastle.Asn1.Misc;
 using Org.BouncyCastle.Asn1.X9;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Generators;
@@ -674,11 +674,17 @@ internal PgpPrivateKey DoExtractPrivateKey(byte[] rawPassPhrase, bool clearPassP
                     privateKey = new DsaPrivateKeyParameters(dsaPriv.X, dsaParams);
                     break;
                 case PublicKeyAlgorithmTag.ECDH:
-                    privateKey = GetECKey("ECDH", bcpgIn);
+                    if (((ECPublicBcpgKey)secret.PublicKeyPacket.Key).CurveOid.Id.Equals(MiscObjectIdentifiers.Curve25519.Id))
+                        privateKey = new X25519PrivateKeyParameters(new ECSecretBcpgKey(bcpgIn).X.ToByteArrayUnsigned(), 0);
+                    else
+                        privateKey = GetECKey("ECDH", bcpgIn);
                     break;
                 case PublicKeyAlgorithmTag.ECDsa:
                     privateKey = GetECKey("ECDSA", bcpgIn);
                     break;
+                case PublicKeyAlgorithmTag.EdDsa:
+                    privateKey = new Ed25519PrivateKeyParameters(new ECSecretBcpgKey(bcpgIn).X.ToByteArrayUnsigned(), 0);
+                    break;
                 case PublicKeyAlgorithmTag.ElGamalEncrypt:
                 case PublicKeyAlgorithmTag.ElGamalGeneral:
                     ElGamalPublicBcpgKey elPub = (ElGamalPublicBcpgKey)pubPk.Key;
diff --git a/crypto/src/openpgp/PgpUtilities.cs b/crypto/src/openpgp/PgpUtilities.cs
index fd5ab62328..cc6b211d5d 100644
--- a/crypto/src/openpgp/PgpUtilities.cs
+++ b/crypto/src/openpgp/PgpUtilities.cs
@@ -95,7 +95,10 @@ public static string GetSignatureName(
                 case PublicKeyAlgorithmTag.ECDsa:
                     encAlg = "ECDSA";
                     break;
-                case PublicKeyAlgorithmTag.ElGamalEncrypt: // in some malformed cases.
+				case PublicKeyAlgorithmTag.EdDsa:
+					encAlg = "EdDSA";
+					break;
+				case PublicKeyAlgorithmTag.ElGamalEncrypt: // in some malformed cases.
 				case PublicKeyAlgorithmTag.ElGamalGeneral:
 					encAlg = "ElGamal";
 					break;
diff --git a/crypto/src/openpgp/Rfc6637Utilities.cs b/crypto/src/openpgp/Rfc6637Utilities.cs
index 5d992ec515..97a2baaf14 100644
--- a/crypto/src/openpgp/Rfc6637Utilities.cs
+++ b/crypto/src/openpgp/Rfc6637Utilities.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.IO;
 
 using Org.BouncyCastle.Asn1;
@@ -70,12 +70,17 @@ public static int GetKeyLength(SymmetricKeyAlgorithmTag algID)
         public static byte[] CreateKey(PublicKeyPacket pubKeyData, ECPoint s)
         {
             byte[] userKeyingMaterial = CreateUserKeyingMaterial(pubKeyData);
-
             ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)pubKeyData.Key;
-
             return Kdf(ecKey.HashAlgorithm, s, GetKeyLength(ecKey.SymmetricKeyAlgorithm), userKeyingMaterial);
         }
 
+        public static byte[] CreateKey(PublicKeyPacket pubKeyData, byte[] ZB)
+        {
+            byte[] userKeyingMaterial = CreateUserKeyingMaterial(pubKeyData);
+            ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)pubKeyData.Key;
+            return Kdf(ecKey.HashAlgorithm, ZB, GetKeyLength(ecKey.SymmetricKeyAlgorithm), userKeyingMaterial);
+        }
+
         // RFC 6637 - Section 8
         // curve_OID_len = (byte)len(curve_OID);
         // Param = curve_OID_len || curve_OID || public_key_alg_ID || 03
@@ -118,10 +123,13 @@ public static byte[] CreateUserKeyingMaterial(PublicKeyPacket pubKeyData)
         //   return oBits leftmost bits of MB.
         private static byte[] Kdf(HashAlgorithmTag digestAlg, ECPoint s, int keyLen, byte[] parameters)
         {
-            byte[] ZB = s.XCoord.GetEncoded();
+            return Kdf(digestAlg, s.XCoord.GetEncoded(), keyLen, parameters);
+        }
 
+        private static byte[] Kdf(HashAlgorithmTag digestAlg, byte[] ZB, int keyLen, byte[] parameters)
+        {
             string digestName = PgpUtilities.GetDigestName(digestAlg);
-			IDigest digest = DigestUtilities.GetDigest(digestName);
+            IDigest digest = DigestUtilities.GetDigest(digestName);
 
             digest.Update(0x00);
             digest.Update(0x00);
diff --git a/crypto/src/security/SignerUtilities.cs b/crypto/src/security/SignerUtilities.cs
index 8a289897e9..1f263d17fa 100644
--- a/crypto/src/security/SignerUtilities.cs
+++ b/crypto/src/security/SignerUtilities.cs
@@ -610,6 +610,13 @@ public static ISigner GetSigner(
                 return new DsaDigestSigner(new ECDsaSigner(), digest);
             }
 
+            if (Platform.EndsWith(mechanism, "WITHEDDSA"))
+            {
+                string digestName = mechanism.Substring(0, mechanism.LastIndexOf("WITH"));
+                IDigest digest = DigestUtilities.GetDigest(digestName);
+                return new DsaDigestSigner(new EdDsa22519Signer(), digest);
+            }
+
             if (Platform.EndsWith(mechanism, "withCVC-ECDSA")
                 || Platform.EndsWith(mechanism, "withPLAIN-ECDSA"))
             {

From a52b34b83ea6fcc4c3931ba1ca0c3080afae6069 Mon Sep 17 00:00:00 2001
From: Filip Navara <navara@emclient.com>
Date: Fri, 11 Dec 2020 17:31:32 +0100
Subject: [PATCH 2/8] Add unit test for EdDSA, update S Expression parser to
 support more of the syntax used by GnuPG

---
 crypto/src/openpgp/PgpPublicKey.cs           |  10 +
 crypto/src/openpgp/PgpSecretKey.cs           | 145 ++++++++----
 crypto/src/openpgp/SXprUtilities.cs          | 212 ++++++++++++++---
 crypto/test/src/openpgp/test/PgpEdDsaTest.cs | 232 +++++++++++++++++++
 4 files changed, 522 insertions(+), 77 deletions(-)
 create mode 100644 crypto/test/src/openpgp/test/PgpEdDsaTest.cs

diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs
index b66ca844f2..0a5137d3ad 100644
--- a/crypto/src/openpgp/PgpPublicKey.cs
+++ b/crypto/src/openpgp/PgpPublicKey.cs
@@ -1,6 +1,7 @@
 using System;
 using System.Collections;
 using System.IO;
+using Org.BouncyCastle.Asn1.Gnu;
 using Org.BouncyCastle.Asn1.Misc;
 using Org.BouncyCastle.Asn1.Sec;
 using Org.BouncyCastle.Asn1.X9;
@@ -10,6 +11,7 @@
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Math.EC;
+using Org.BouncyCastle.Math.EC.Rfc8032;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Collections;
@@ -186,6 +188,14 @@ public PgpPublicKey(
                     throw new PgpException("unknown EC algorithm");
                 }
             }
+            else if (pubKey is Ed25519PublicKeyParameters)
+            {
+                Ed25519PublicKeyParameters ecK = (Ed25519PublicKeyParameters)pubKey;
+                byte[] encodedPoint = new byte[Ed25519.PublicKeySize + 1];
+                encodedPoint[0] = 0x40;
+                ecK.Encode(encodedPoint, 1);
+                bcpgKey = new ECDsaPublicBcpgKey(GnuObjectIdentifiers.Ed25519, new BigInteger(1, encodedPoint));
+            }
             else if (pubKey is ElGamalPublicKeyParameters)
             {
                 ElGamalPublicKeyParameters eK = (ElGamalPublicKeyParameters) pubKey;
diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs
index 7271fa2ef4..60774aa051 100644
--- a/crypto/src/openpgp/PgpSecretKey.cs
+++ b/crypto/src/openpgp/PgpSecretKey.cs
@@ -1,9 +1,12 @@
 using System;
 using System.Collections;
+using System.Diagnostics;
 using System.IO;
+using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Asn1.Misc;
 using Org.BouncyCastle.Asn1.X9;
 using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.EC;
 using Org.BouncyCastle.Crypto.Generators;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
@@ -57,6 +60,10 @@ internal PgpSecretKey(
                     ECPrivateKeyParameters ecK = (ECPrivateKeyParameters)privKey.Key;
                     secKey = new ECSecretBcpgKey(ecK.D);
                     break;
+                case PublicKeyAlgorithmTag.EdDsa:
+                    Ed25519PrivateKeyParameters edK = (Ed25519PrivateKeyParameters)privKey.Key;
+                    secKey = new ECSecretBcpgKey(new BigInteger(1, edK.GetEncoded()));
+                    break;
                 case PublicKeyAlgorithmTag.ElGamalEncrypt:
                 case PublicKeyAlgorithmTag.ElGamalGeneral:
                     ElGamalPrivateKeyParameters esK = (ElGamalPrivateKeyParameters) privKey.Key;
@@ -1111,24 +1118,26 @@ public static PgpSecretKey ParseSecretKeyFromSExprRaw(Stream inputStream, byte[]
 
         internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase, PgpPublicKey pubKey)
         {
-            SXprUtilities.SkipOpenParenthesis(inputStream);
+            SXprUtilities reader = new SXprUtilities(inputStream);
+
+            reader.SkipOpenParenthesis();
 
-            string type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+            string type = reader.ReadString();
             if (type.Equals("protected-private-key"))
             {
-                SXprUtilities.SkipOpenParenthesis(inputStream);
+                reader.SkipOpenParenthesis();
 
                 string curveName;
 
-                string keyType = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+                string keyType = reader.ReadString();
                 if (keyType.Equals("ecc"))
                 {
-                    SXprUtilities.SkipOpenParenthesis(inputStream);
+                    reader.SkipOpenParenthesis();
 
-                    string curveID = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
-                    curveName = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+                    string curveID = reader.ReadString();
+                    curveName = reader.ReadString();
 
-                    SXprUtilities.SkipCloseParenthesis(inputStream);
+                    reader.SkipCloseParenthesis();
                 }
                 else
                 {
@@ -1137,21 +1146,21 @@ internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[
 
                 byte[] qVal;
 
-                SXprUtilities.SkipOpenParenthesis(inputStream);
+                reader.SkipOpenParenthesis();
 
-                type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+                type = reader.ReadString();
                 if (type.Equals("q"))
                 {
-                    qVal = SXprUtilities.ReadBytes(inputStream, inputStream.ReadByte());
+                    qVal = reader.ReadBytes();
                 }
                 else
                 {
                     throw new PgpException("no q value found");
                 }
 
-                SXprUtilities.SkipCloseParenthesis(inputStream);
+                reader.SkipCloseParenthesis();
 
-                byte[] dValue = GetDValue(inputStream, rawPassPhrase, clearPassPhrase, curveName);
+                byte[] dValue = GetDValue(reader, rawPassPhrase, clearPassPhrase, curveName);
                 // TODO: check SHA-1 hash.
 
                 return new PgpSecretKey(new SecretKeyPacket(pubKey.PublicKeyPacket, SymmetricKeyAlgorithmTag.Null, null, null,
@@ -1170,7 +1179,7 @@ internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[
         /// </remarks>
         public static PgpSecretKey ParseSecretKeyFromSExpr(Stream inputStream, char[] passPhrase)
         {
-            return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, false), true);
+            return DoParseSecretKeyFromSExpr(new SXprUtilities(inputStream), PgpUtilities.EncodePassPhrase(passPhrase, false), true);
         }
 
         /// <summary>
@@ -1181,7 +1190,7 @@ public static PgpSecretKey ParseSecretKeyFromSExpr(Stream inputStream, char[] pa
         /// </remarks>
         public static PgpSecretKey ParseSecretKeyFromSExprUtf8(Stream inputStream, char[] passPhrase)
         {
-            return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, true), true);
+            return DoParseSecretKeyFromSExpr(new SXprUtilities(inputStream), PgpUtilities.EncodePassPhrase(passPhrase, true), true);
         }
 
         /// <summary>
@@ -1192,37 +1201,48 @@ public static PgpSecretKey ParseSecretKeyFromSExprUtf8(Stream inputStream, char[
         /// </remarks>
         public static PgpSecretKey ParseSecretKeyFromSExprRaw(Stream inputStream, byte[] rawPassPhrase)
         {
-            return DoParseSecretKeyFromSExpr(inputStream, rawPassPhrase, false);
+            return DoParseSecretKeyFromSExpr(new SXprUtilities(inputStream), rawPassPhrase, false);
         }
 
         /// <summary>
         /// Parse a secret key from one of the GPG S expression keys.
         /// </summary>
-        internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase)
+        internal static PgpSecretKey DoParseSecretKeyFromSExpr(SXprUtilities reader, byte[] rawPassPhrase, bool clearPassPhrase)
         {
-            SXprUtilities.SkipOpenParenthesis(inputStream);
+            reader.SkipOpenParenthesis();
 
-            string type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+            string type = reader.ReadString();
             if (type.Equals("protected-private-key"))
             {
-                SXprUtilities.SkipOpenParenthesis(inputStream);
+                reader.SkipOpenParenthesis();
 
                 string curveName;
+                DerObjectIdentifier curveOid;
 
-                string keyType = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+                string keyType = reader.ReadString();
                 if (keyType.Equals("ecc"))
                 {
-                    SXprUtilities.SkipOpenParenthesis(inputStream);
+                    reader.SkipOpenParenthesis();
 
-                    string curveID = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
-                    curveName = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+                    string curveID = reader.ReadString();
+                    curveName = reader.ReadString();
 
                     if (Platform.StartsWith(curveName, "NIST "))
                     {
                         curveName = curveName.Substring("NIST ".Length);
                     }
 
-                    SXprUtilities.SkipCloseParenthesis(inputStream);
+                    curveOid = ECNamedCurveTable.GetOid(curveName);
+                    if (curveOid == null)
+                    {
+                        curveOid = CustomNamedCurves.GetOid(curveName);
+                    }
+                    if (curveOid == null)
+                    {
+                        throw new PgpException("unknown curve");
+                    }
+
+                    reader.SkipCloseParenthesis();
                 }
                 else
                 {
@@ -1230,25 +1250,35 @@ internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[
                 }
 
                 byte[] qVal;
+                string flags = null;
 
-                SXprUtilities.SkipOpenParenthesis(inputStream);
+                reader.SkipOpenParenthesis();
 
-                type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+                type = reader.ReadString();
+                if (type == "flags")
+                {
+                    // Skip over flags
+                    flags = reader.ReadString();
+                    reader.SkipCloseParenthesis();
+                    reader.SkipOpenParenthesis();
+                    type = reader.ReadString();
+                }
                 if (type.Equals("q"))
                 {
-                    qVal = SXprUtilities.ReadBytes(inputStream, inputStream.ReadByte());
+                    qVal = reader.ReadBytes();
                 }
                 else
                 {
                     throw new PgpException("no q value found");
                 }
 
-                PublicKeyPacket pubPacket = new PublicKeyPacket(PublicKeyAlgorithmTag.ECDsa, DateTime.UtcNow,
-                    new ECDsaPublicBcpgKey(ECNamedCurveTable.GetOid(curveName), new BigInteger(1, qVal)));
+                PublicKeyPacket pubPacket = new PublicKeyPacket(
+                    flags == "eddsa" ? PublicKeyAlgorithmTag.EdDsa : PublicKeyAlgorithmTag.ECDsa, DateTime.UtcNow,
+                    new ECDsaPublicBcpgKey(curveOid, new BigInteger(1, qVal)));
 
-                SXprUtilities.SkipCloseParenthesis(inputStream);
+                reader.SkipCloseParenthesis();
 
-                byte[] dValue = GetDValue(inputStream, rawPassPhrase, clearPassPhrase, curveName);
+                byte[] dValue = GetDValue(reader, rawPassPhrase, clearPassPhrase, curveName);
                 // TODO: check SHA-1 hash.
 
                 return new PgpSecretKey(new SecretKeyPacket(pubPacket, SymmetricKeyAlgorithmTag.Null, null, null,
@@ -1258,51 +1288,68 @@ internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[
             throw new PgpException("unknown key type found");
         }
 
-        private static byte[] GetDValue(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase, string curveName)
+        private static byte[] GetDValue(SXprUtilities reader, byte[] rawPassPhrase, bool clearPassPhrase, string curveName)
         {
             string type;
-            SXprUtilities.SkipOpenParenthesis(inputStream);
+            reader.SkipOpenParenthesis();
 
             string protection;
             S2k s2k;
             byte[] iv;
             byte[] secKeyData;
 
-            type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+            type = reader.ReadString();
             if (type.Equals("protected"))
             {
-                protection = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+                protection = reader.ReadString();
 
-                SXprUtilities.SkipOpenParenthesis(inputStream);
+                reader.SkipOpenParenthesis();
 
-                s2k = SXprUtilities.ParseS2k(inputStream);
+                s2k = reader.ParseS2k();
 
-                iv = SXprUtilities.ReadBytes(inputStream, inputStream.ReadByte());
+                iv = reader.ReadBytes();
 
-                SXprUtilities.SkipCloseParenthesis(inputStream);
+                reader.SkipCloseParenthesis();
 
-                secKeyData = SXprUtilities.ReadBytes(inputStream, inputStream.ReadByte());
+                secKeyData = reader.ReadBytes();
             }
             else
             {
                 throw new PgpException("protected block not found");
             }
 
-            // TODO: recognise other algorithms
-            KeyParameter key = PgpUtilities.DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag.Aes128, s2k, rawPassPhrase, clearPassPhrase);
+            // Valid values of protection: openpgp-s2k3-sha1-aes-cbc, openpgp-s2k3-ocb-aes, openpgp-native
+            byte[] data;
+            KeyParameter key;
+
+            switch (protection)
+            {
+                case "openpgp-s2k3-sha1-aes-cbc":
+                    key = PgpUtilities.DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag.Aes128, s2k, rawPassPhrase, clearPassPhrase);
+                    data = RecoverKeyData(SymmetricKeyAlgorithmTag.Aes128, "/CBC/NoPadding", key, iv, secKeyData, 0, secKeyData.Length);
+                    break;
+
+                case "openpgp-s2k3-ocb-aes":
+                    key = PgpUtilities.DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag.Aes128, s2k, rawPassPhrase, clearPassPhrase);
+                    data = RecoverKeyData(SymmetricKeyAlgorithmTag.Aes128, "/OCB/NoPadding", key, iv, secKeyData, 0, secKeyData.Length);
+                    break;
 
-            byte[] data = RecoverKeyData(SymmetricKeyAlgorithmTag.Aes128, "/CBC/NoPadding", key, iv, secKeyData, 0, secKeyData.Length);
+                case "openpgp-native":
+                default:
+                    throw new PgpException(protection + " key format is not supported yet");
+            }
 
             //
             // parse the secret key S-expr
             //
             Stream keyIn = new MemoryStream(data, false);
 
-            SXprUtilities.SkipOpenParenthesis(keyIn);
-            SXprUtilities.SkipOpenParenthesis(keyIn);
-            SXprUtilities.SkipOpenParenthesis(keyIn);
-            String name = SXprUtilities.ReadString(keyIn, keyIn.ReadByte());
-            return SXprUtilities.ReadBytes(keyIn, keyIn.ReadByte());
+            reader = new SXprUtilities(keyIn);
+            reader.SkipOpenParenthesis();
+            reader.SkipOpenParenthesis();
+            reader.SkipOpenParenthesis();
+            String name = reader.ReadString();
+            return reader.ReadBytes();
         }
     }
 }
diff --git a/crypto/src/openpgp/SXprUtilities.cs b/crypto/src/openpgp/SXprUtilities.cs
index 68ff373a83..7cd4862065 100644
--- a/crypto/src/openpgp/SXprUtilities.cs
+++ b/crypto/src/openpgp/SXprUtilities.cs
@@ -1,6 +1,7 @@
 using System;
 using System.IO;
-
+using System.Text;
+using Org.BouncyCastle.Utilities.Encoders;
 using Org.BouncyCastle.Utilities.IO;
 
 namespace Org.BouncyCastle.Bcpg.OpenPgp
@@ -10,75 +11,230 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
      * <p>
      * Format documented here:
      * http://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=agent/keyformat.txt;h=42c4b1f06faf1bbe71ffadc2fee0fad6bec91a97;hb=refs/heads/master
+     * http://people.csail.mit.edu/rivest/Sexp.txt
      * </p>
      */
-    public sealed class SXprUtilities
+    class SXprUtilities
     {
-        private SXprUtilities()
+        Stream stream;
+        int peekedByte;
+
+        internal SXprUtilities(Stream stream)
+        {
+            this.stream = stream;
+            this.peekedByte = -1;
+        }
+
+        private int ReadByte()
+        {
+            if (this.peekedByte > 0)
+            {
+                int pb = this.peekedByte;
+                this.peekedByte = 0;
+                return pb;
+            }
+            return stream.ReadByte();
+        }
+
+        private void UnreadByte(int pb)
         {
+            this.peekedByte = pb;
         }
 
-        private static int ReadLength(Stream input, int ch)
+        private int ReadLength()
         {
-            int len = ch - '0';
+            int ch;
+            int len = 0;
 
-            while ((ch = input.ReadByte()) >= 0 && ch != ':')
+            while ((ch = ReadByte()) >= 0 && ch >= '0' && ch <= '9')
             {
-                len = len * 10 + ch - '0';
+                len = len * 10 + (ch - '0');
             }
+            UnreadByte(ch);
 
             return len;
         }
 
-        internal static string ReadString(Stream input, int ch)
+        public string ReadString()
         {
-            int len = ReadLength(input, ch);
+            SkipWhitespace();
+            
+            int ch = ReadByte();
+            if (ch >= '0' && ch <= '9')
+            {
+                UnreadByte(ch);
+
+                int len = ReadLength();
+                ch = ReadByte();
+                if (ch == ':')
+                {
+                    char[] chars = new char[len];
 
-            char[] chars = new char[len];
+                    for (int i = 0; i != chars.Length; i++)
+                    {
+                        chars[i] = (char)ReadByte();
+                    }
 
-            for (int i = 0; i != chars.Length; i++)
+                    return new string(chars);
+                }
+                else if (ch == '"')
+                {
+                    return ReadQuotedString(len);
+                }
+                throw new IOException("unsupported encoding");
+            }
+            else if (ch == '"')
+            {
+                return ReadQuotedString(0);
+            }
+            else if (ch == '{' || ch == '|' || ch == '#')
             {
-                chars[i] = (char)input.ReadByte();
+                // TODO: Unsupported encoding
+                throw new IOException("unsupported encoding");
             }
+            else
+            {
+                StringBuilder sb = new StringBuilder();
+                while (IsTokenChar(ch))
+                {
+                    sb.Append((char)ch);
+                    ch = (char)ReadByte();
+                }
+                UnreadByte(ch);
+                return sb.ToString();
+            }
+        }
 
-            return new string(chars);
+        private string ReadQuotedString(int length)
+        {
+            StringBuilder sb = new StringBuilder(length);
+            int ch;
+            bool skipNewLine = false;
+            do
+            {
+                ch = ReadByte();
+                if ((ch == '\n' || ch == '\r') && skipNewLine)
+                {
+                    skipNewLine = false;
+                }
+                else if (ch == '\\')
+                {
+                    ch = (char)ReadByte();
+                    switch (ch)
+                    {
+                        case 'b': sb.Append('\b'); break;
+                        case 't': sb.Append('\t'); break;
+                        case 'v': sb.Append('\v'); break;
+                        case 'n': sb.Append('\n'); break;
+                        case 'r': sb.Append('\r'); break;
+                        case 'f': sb.Append('\f'); break;
+                        case '"': sb.Append('"'); break;
+                        case '\'': sb.Append('\''); break;
+                        case '\r':
+                        case '\n':
+                            skipNewLine = true;
+                            break;
+                        default:
+                            // TODO: Octal value, hexadecimal value
+                            throw new IOException("unsupported encoding");
+                    }
+                }
+                else if (ch != '"' && ch >= 0)
+                {
+                    skipNewLine = false;
+                    sb.Append((char)ch);
+                }
+            }
+            while (ch != '"' && ch > 0);
+            return sb.ToString();
         }
 
-        internal static byte[] ReadBytes(Stream input, int ch)
+        private static bool IsTokenChar(int ch)
         {
-            int len = ReadLength(input, ch);
+            return
+                (ch >= 'a' && ch <= 'z') ||
+                (ch >= 'A' && ch <= 'Z') ||
+                (ch >= '0' && ch <= '9') ||
+                ch == '-' || ch == '.' ||
+                ch == '/' || ch == '_' ||
+                ch == ':' || ch == '*' ||
+                ch == '+' || ch == '=';
+        }
+
+        public byte[] ReadBytes()
+        {
+            SkipWhitespace();
+
+            int ch = ReadByte();
+            if (ch >= '0' && ch <= '9')
+            {
+                UnreadByte(ch);
+
+                int len = ReadLength();
+
+                if (ReadByte() != ':')
+                    throw new IOException("unsupported encoding");
 
-            byte[] data = new byte[len];
+                byte[] data = new byte[len];
 
-            Streams.ReadFully(input, data);
+                Streams.ReadFully(stream, data);
 
-            return data;
+                return data;
+            }
+            else if (ch == '#')
+            {
+                StringBuilder sb = new StringBuilder();
+                do
+                {
+                    ch = ReadByte();
+                    if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'))
+                        sb.Append((char)ch);
+                }
+                while (ch != '#' && ch >= 0);
+                return Hex.Decode(sb.ToString());
+            }
+
+            throw new IOException("unsupported encoding");
         }
 
-        internal static S2k ParseS2k(Stream input)
+        public S2k ParseS2k()
         {
-            SkipOpenParenthesis(input);
+            SkipOpenParenthesis();
 
-            string alg = ReadString(input, input.ReadByte());
-            byte[] iv = ReadBytes(input, input.ReadByte());
-            long iterationCount = Int64.Parse(ReadString(input, input.ReadByte()));
+            string alg = ReadString();
+            byte[] iv = ReadBytes();
+            long iterationCount = Int64.Parse(ReadString());
 
-            SkipCloseParenthesis(input);
+            SkipCloseParenthesis();
 
             // we have to return the actual iteration count provided.
             return new MyS2k(HashAlgorithmTag.Sha1, iv, iterationCount);
         }
 
-        internal static void SkipOpenParenthesis(Stream input)
+        private void SkipWhitespace()
         {
-            int ch = input.ReadByte();
+            int ch = ReadByte();
+            while (ch == ' ' || ch == '\r' || ch == '\n')
+            {
+                ch = ReadByte();
+            }
+            UnreadByte(ch);
+        }
+
+        public void SkipOpenParenthesis()
+        {
+            SkipWhitespace();
+
+            int ch = ReadByte();
             if (ch != '(')
                 throw new IOException("unknown character encountered");
         }
 
-        internal static void SkipCloseParenthesis(Stream input)
+        public void SkipCloseParenthesis()
         {
-            int ch = input.ReadByte();
+            SkipWhitespace();
+
+            int ch = ReadByte();
             if (ch != ')')
                 throw new IOException("unknown character encountered");
         }
diff --git a/crypto/test/src/openpgp/test/PgpEdDsaTest.cs b/crypto/test/src/openpgp/test/PgpEdDsaTest.cs
new file mode 100644
index 0000000000..a670b14902
--- /dev/null
+++ b/crypto/test/src/openpgp/test/PgpEdDsaTest.cs
@@ -0,0 +1,232 @@
+using System;
+using System.Collections;
+using System.IO;
+using System.Text;
+
+using NUnit.Framework;
+using Org.BouncyCastle.Asn1.Gnu;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.Test;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
+{
+    [TestFixture]
+    public class PgpEdDsaTest
+        : SimpleTest
+    {
+        private static readonly byte[] testPubKey =
+            Base64.Decode(
+                "mDMEX9NCKBYJKwYBBAHaRw8BAQdASPhAQySGGPMjoquv5i1IwLRSDJ2QtmLLvER2" +
+                "Cm8UZyW0HkVkRFNBIDx0ZXN0LmVkZHNhQGV4YW1wbGUuY29tPoiQBBMWCAA4FiEE" +
+                "sh83FOYApIfZuLp0emj2ffveCqEFAl/TQigCGwMFCwkIBwIGFQoJCAsCBBYCAwEC" +
+                "HgECF4AACgkQemj2ffveCqF6XQEA2S08fb0Z6LCd9P+eajPNDm1Wrf/y/7nkNwhb" +
+                "DvwiU5kBAM16UvHrzX6CvQFvc7aKvPH+4wrvRewvAGK16a4fBHEE");
+
+        private static readonly byte[] testPrivKey =
+            Base64.Decode(
+                "lIYEX9NCKBYJKwYBBAHaRw8BAQdASPhAQySGGPMjoquv5i1IwLRSDJ2QtmLLvER2" +
+                "Cm8UZyX+BwMC7ubvoFJTTXfOpQ3tDoys52w6tb01rHHtjKVWjXMjiyN8tXHBDC9N" +
+                "UcMYViTDegBXOEgw4TIKn9mkkTDvP3xVFeMH2XBPzu9e9m8GlBODILQeRWREU0Eg" +
+                "PHRlc3QuZWRkc2FAZXhhbXBsZS5jb20+iJAEExYIADgWIQSyHzcU5gCkh9m4unR6" +
+                "aPZ9+94KoQUCX9NCKAIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRB6aPZ9" +
+                "+94KoXpdAQDZLTx9vRnosJ30/55qM80ObVat//L/ueQ3CFsO/CJTmQEAzXpS8evN" +
+                "foK9AW9ztoq88f7jCu9F7C8AYrXprh8EcQQ=");
+
+        private static readonly char[] testPasswd = "test".ToCharArray();
+
+        private static readonly byte[] sExprKey =
+            Base64.Decode(
+                "KHByb3RlY3RlZC1wcml2YXRlLWtleSAoZWNjIChjdXJ2ZSBFZDI1NTE5KShmbGFn" +
+                "cyBlZGRzYSkocQogICM0MDQ4Rjg0MDQzMjQ4NjE4RjMyM0EyQUJBRkU2MkQ0OEMw" +
+                "QjQ1MjBDOUQ5MEI2NjJDQkJDNDQ3NjBBNkYxNDY3MjUjKQogKHByb3RlY3RlZCBv" +
+                "cGVucGdwLXMyazMtb2NiLWFlcyAoKHNoYTEgI0IwRkY2MDAzRUE4RkQ4QkIjCiAg" +
+                "Ijc2OTUzNjAiKSM5NDZEREU3QTUxMzAyRUEyRDc3NDNEOTQjKSM4NDBFMTIyRTdB" +
+                "RDI0RkY1MkE5RUY3QUFDQjgxRUE2CiAyMTkyQjZCMjlCOUI4N0QwNTZBOUE4MTEz" +
+                "QjIzNjlEREM4QUVGMTJDNjRBN0QwOTEwM0Q1MTU1Nzc0Q0Q5RkQ4NzczQTEzCiBD" +
+                "NTgwQ0Y4RkY5OEZERTU3RDVGIykocHJvdGVjdGVkLWF0ICIyMDIwMTIxMVQwOTU2" +
+                "MDEiKSkp");
+
+        private static readonly byte[] referencePubKey =
+            Base64.Decode(
+                "mDMEU/NfCxYJKwYBBAHaRw8BAQdAPwmJlL3ZFu1AUxl5NOSofIBzOhKA1i+AEJku" +
+                "Q+47JAY=");
+
+        private static readonly string referenceMessage = "OpenPGP";
+
+        private static readonly byte[] referenceSignature =
+            Base64.Decode(
+                "iF4EABYIAAYFAlX5X5UACgkQjP3hIZeWWpr2IgEAVvkMypjiECY3vZg/2xbBMd/S" +
+                "ftgr9N3lYG4NdWrtM2YBANCcT6EVJ/A44PV/IgHYLy6iyQMyZfps60iehUuuYbQE");
+
+        private void ReferenceTest()
+        {
+            PgpPublicKeyRing pubKeyRing = new PgpPublicKeyRing(referencePubKey);
+            PgpPublicKey publicKey = pubKeyRing.GetPublicKey();
+
+            PgpObjectFactory pgpFact = new PgpObjectFactory(referenceSignature);
+            PgpSignatureList signatureList = (PgpSignatureList)pgpFact.NextPgpObject();
+            PgpSignature signature = signatureList.Get(0);
+            signature.InitVerify(publicKey);
+            signature.Update(Encoding.ASCII.GetBytes(referenceMessage));
+            if (!signature.Verify())
+            {
+                Fail("signature failed to verify!");
+            }
+        }
+
+        private void GenerateAndSign()
+        {
+            SecureRandom random = SecureRandom.GetInstance("SHA1PRNG");
+
+            IAsymmetricCipherKeyPairGenerator keyGen = GeneratorUtilities.GetKeyPairGenerator("Ed25519");
+            keyGen.Init(new ECKeyGenerationParameters(GnuObjectIdentifiers.Ed25519, random));
+
+            AsymmetricCipherKeyPair kpSign = keyGen.GenerateKeyPair();
+
+            PgpKeyPair eddsaKeyPair = new PgpKeyPair(PublicKeyAlgorithmTag.EdDsa, kpSign, DateTime.UtcNow);
+
+            byte[] msg = Encoding.ASCII.GetBytes("hello world!");
+
+            //
+            // try a signature
+            //
+            PgpSignatureGenerator signGen = new PgpSignatureGenerator(PublicKeyAlgorithmTag.EdDsa, HashAlgorithmTag.Sha256);
+            signGen.InitSign(PgpSignature.BinaryDocument, eddsaKeyPair.PrivateKey);
+
+            signGen.Update(msg);
+
+            PgpSignature sig = signGen.Generate();
+
+            sig.InitVerify(eddsaKeyPair.PublicKey);
+            sig.Update(msg);
+
+            if (!sig.Verify())
+            {
+                Fail("signature failed to verify!");
+            }
+
+            //
+            // generate a key ring
+            //
+            char[] passPhrase = "test".ToCharArray();
+            PgpKeyRingGenerator keyRingGen = new PgpKeyRingGenerator(PgpSignature.PositiveCertification, eddsaKeyPair,
+                "test@bouncycastle.org", SymmetricKeyAlgorithmTag.Aes256, passPhrase, true, null, null, random);
+
+            PgpPublicKeyRing pubRing = keyRingGen.GeneratePublicKeyRing();
+            PgpSecretKeyRing secRing = keyRingGen.GenerateSecretKeyRing();
+
+            PgpPublicKeyRing pubRingEnc = new PgpPublicKeyRing(pubRing.GetEncoded());
+            if (!Arrays.AreEqual(pubRing.GetEncoded(), pubRingEnc.GetEncoded()))
+            {
+                Fail("public key ring encoding failed");
+            }
+
+            PgpSecretKeyRing secRingEnc = new PgpSecretKeyRing(secRing.GetEncoded());
+            if (!Arrays.AreEqual(secRing.GetEncoded(), secRingEnc.GetEncoded()))
+            {
+                Fail("secret key ring encoding failed");
+            }
+
+
+            //
+            // try a signature using encoded key
+            //
+            signGen = new PgpSignatureGenerator(PublicKeyAlgorithmTag.EdDsa, HashAlgorithmTag.Sha256);
+            signGen.InitSign(PgpSignature.BinaryDocument, secRing.GetSecretKey().ExtractPrivateKey(passPhrase));
+            signGen.Update(msg);
+
+            sig = signGen.Generate();
+            sig.InitVerify(secRing.GetSecretKey().PublicKey);
+            sig.Update(msg);
+
+            if (!sig.Verify())
+            {
+                Fail("re-encoded signature failed to verify!");
+            }
+        }
+
+        public override void PerformTest()
+        {
+            ReferenceTest();
+
+            //
+            // Read the public key
+            //
+            PgpPublicKeyRing pubKeyRing = new PgpPublicKeyRing(testPubKey);
+            foreach (PgpSignature certification in pubKeyRing.GetPublicKey().GetSignatures())
+            {
+                certification.InitVerify(pubKeyRing.GetPublicKey());
+
+                if (!certification.VerifyCertification((string)First(pubKeyRing.GetPublicKey().GetUserIds()), pubKeyRing.GetPublicKey()))
+                {
+                    Fail("self certification does not verify");
+                }
+            }
+
+            /*if (pubKeyRing.GetPublicKey().BitStrength != 256)
+            {
+                Fail("incorrect bit strength returned");
+            }*/
+
+            //
+            // Read the private key
+            //
+            PgpSecretKeyRing secretKeyRing = new PgpSecretKeyRing(testPrivKey);
+
+            PgpPrivateKey privKey = secretKeyRing.GetSecretKey().ExtractPrivateKey(testPasswd);
+
+            GenerateAndSign();
+
+            //
+            // sExpr
+            //
+            // TODO: Fails the OCB MAC check when decoding key but works otherwise
+            /*byte[] msg = Encoding.ASCII.GetBytes("hello world!");
+
+            PgpSecretKey key = PgpSecretKey.ParseSecretKeyFromSExpr(new MemoryStream(sExprKey, false), "test".ToCharArray());
+
+            PgpSignatureGenerator signGen = new PgpSignatureGenerator(PublicKeyAlgorithmTag.EdDsa, HashAlgorithmTag.Sha256);
+            signGen.InitSign(PgpSignature.BinaryDocument, key.ExtractPrivateKey(null));
+            signGen.Update(msg);
+
+            PgpSignature sig = signGen.Generate();
+            sig.InitVerify(key.PublicKey);
+            sig.Update(msg);
+
+            if (!sig.Verify())
+            {
+                Fail("signature failed to verify!");
+            }*/
+        }
+
+        private static object First(IEnumerable e)
+        {
+            IEnumerator n = e.GetEnumerator();
+            Assert.IsTrue(n.MoveNext());
+            return n.Current;
+        }
+
+        public override string Name
+        {
+            get { return "PgpEdDsaTest"; }
+        }
+
+        public static void Main(
+            string[] args)
+        {
+            RunTest(new PgpECDsaTest());
+        }
+
+        [Test]
+        public void TestFunction()
+        {
+            string resultText = Perform().ToString();
+
+            Assert.AreEqual(Name + ": Okay", resultText);
+        }
+    }
+}

From 9f98e0cd6f41169f0cb8e012a7916f2bfebcb6c8 Mon Sep 17 00:00:00 2001
From: Filip Navara <navara@emclient.com>
Date: Sun, 13 Dec 2020 11:07:59 +0100
Subject: [PATCH 3/8] Rename SXprUtilities to SXprReader Add SXprWriter to help
 writing canonical S expression Fix decoding AES-OCB encrypted S expression
 keys

---
 crypto/src/openpgp/PgpSecretKey.cs            | 166 ++++++++++--------
 .../{SXprUtilities.cs => SXprReader.cs}       |   8 +-
 crypto/src/openpgp/SXprWriter.cs              |  50 ++++++
 crypto/test/src/openpgp/test/PgpEdDsaTest.cs  |   5 +-
 4 files changed, 151 insertions(+), 78 deletions(-)
 rename crypto/src/openpgp/{SXprUtilities.cs => SXprReader.cs} (97%)
 create mode 100644 crypto/src/openpgp/SXprWriter.cs

diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs
index 60774aa051..4c44fbdf57 100644
--- a/crypto/src/openpgp/PgpSecretKey.cs
+++ b/crypto/src/openpgp/PgpSecretKey.cs
@@ -2,6 +2,7 @@
 using System.Collections;
 using System.Diagnostics;
 using System.IO;
+using crypto.openpgp;
 using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Asn1.Misc;
 using Org.BouncyCastle.Asn1.X9;
@@ -1116,60 +1117,6 @@ public static PgpSecretKey ParseSecretKeyFromSExprRaw(Stream inputStream, byte[]
             return DoParseSecretKeyFromSExpr(inputStream, rawPassPhrase, false, pubKey);
         }
 
-        internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase, PgpPublicKey pubKey)
-        {
-            SXprUtilities reader = new SXprUtilities(inputStream);
-
-            reader.SkipOpenParenthesis();
-
-            string type = reader.ReadString();
-            if (type.Equals("protected-private-key"))
-            {
-                reader.SkipOpenParenthesis();
-
-                string curveName;
-
-                string keyType = reader.ReadString();
-                if (keyType.Equals("ecc"))
-                {
-                    reader.SkipOpenParenthesis();
-
-                    string curveID = reader.ReadString();
-                    curveName = reader.ReadString();
-
-                    reader.SkipCloseParenthesis();
-                }
-                else
-                {
-                    throw new PgpException("no curve details found");
-                }
-
-                byte[] qVal;
-
-                reader.SkipOpenParenthesis();
-
-                type = reader.ReadString();
-                if (type.Equals("q"))
-                {
-                    qVal = reader.ReadBytes();
-                }
-                else
-                {
-                    throw new PgpException("no q value found");
-                }
-
-                reader.SkipCloseParenthesis();
-
-                byte[] dValue = GetDValue(reader, rawPassPhrase, clearPassPhrase, curveName);
-                // TODO: check SHA-1 hash.
-
-                return new PgpSecretKey(new SecretKeyPacket(pubKey.PublicKeyPacket, SymmetricKeyAlgorithmTag.Null, null, null,
-                    new ECSecretBcpgKey(new BigInteger(1, dValue)).GetEncoded()), pubKey);
-            }
-
-            throw new PgpException("unknown key type found");
-        }
-
         /// <summary>
         /// Parse a secret key from one of the GPG S expression keys.
         /// </summary>
@@ -1179,7 +1126,7 @@ internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[
         /// </remarks>
         public static PgpSecretKey ParseSecretKeyFromSExpr(Stream inputStream, char[] passPhrase)
         {
-            return DoParseSecretKeyFromSExpr(new SXprUtilities(inputStream), PgpUtilities.EncodePassPhrase(passPhrase, false), true);
+            return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, false), true, null);
         }
 
         /// <summary>
@@ -1190,7 +1137,7 @@ public static PgpSecretKey ParseSecretKeyFromSExpr(Stream inputStream, char[] pa
         /// </remarks>
         public static PgpSecretKey ParseSecretKeyFromSExprUtf8(Stream inputStream, char[] passPhrase)
         {
-            return DoParseSecretKeyFromSExpr(new SXprUtilities(inputStream), PgpUtilities.EncodePassPhrase(passPhrase, true), true);
+            return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, true), true, null);
         }
 
         /// <summary>
@@ -1201,14 +1148,16 @@ public static PgpSecretKey ParseSecretKeyFromSExprUtf8(Stream inputStream, char[
         /// </remarks>
         public static PgpSecretKey ParseSecretKeyFromSExprRaw(Stream inputStream, byte[] rawPassPhrase)
         {
-            return DoParseSecretKeyFromSExpr(new SXprUtilities(inputStream), rawPassPhrase, false);
+            return DoParseSecretKeyFromSExpr(inputStream, rawPassPhrase, false, null);
         }
 
         /// <summary>
         /// Parse a secret key from one of the GPG S expression keys.
         /// </summary>
-        internal static PgpSecretKey DoParseSecretKeyFromSExpr(SXprUtilities reader, byte[] rawPassPhrase, bool clearPassPhrase)
+        internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase, PgpPublicKey pubKey)
         {
+            SXprReader reader = new SXprReader(inputStream);
+
             reader.SkipOpenParenthesis();
 
             string type = reader.ReadString();
@@ -1272,28 +1221,87 @@ internal static PgpSecretKey DoParseSecretKeyFromSExpr(SXprUtilities reader, byt
                     throw new PgpException("no q value found");
                 }
 
-                PublicKeyPacket pubPacket = new PublicKeyPacket(
-                    flags == "eddsa" ? PublicKeyAlgorithmTag.EdDsa : PublicKeyAlgorithmTag.ECDsa, DateTime.UtcNow,
-                    new ECDsaPublicBcpgKey(curveOid, new BigInteger(1, qVal)));
+                if (pubKey == null)
+                {
+                    PublicKeyPacket pubPacket = new PublicKeyPacket(
+                        flags == "eddsa" ? PublicKeyAlgorithmTag.EdDsa : PublicKeyAlgorithmTag.ECDsa, DateTime.UtcNow,
+                        new ECDsaPublicBcpgKey(curveOid, new BigInteger(1, qVal)));
+                    pubKey = new PgpPublicKey(pubPacket);
+                }
 
                 reader.SkipCloseParenthesis();
 
-                byte[] dValue = GetDValue(reader, rawPassPhrase, clearPassPhrase, curveName);
-                // TODO: check SHA-1 hash.
+                byte[] dValue = GetDValue(reader, pubKey.PublicKeyPacket, rawPassPhrase, clearPassPhrase, curveName);
 
-                return new PgpSecretKey(new SecretKeyPacket(pubPacket, SymmetricKeyAlgorithmTag.Null, null, null,
-                    new ECSecretBcpgKey(new BigInteger(1, dValue)).GetEncoded()), new PgpPublicKey(pubPacket));
+                return new PgpSecretKey(new SecretKeyPacket(pubKey.PublicKeyPacket, SymmetricKeyAlgorithmTag.Null, null, null,
+                    new ECSecretBcpgKey(new BigInteger(1, dValue)).GetEncoded()), pubKey);
             }
 
             throw new PgpException("unknown key type found");
         }
 
-        private static byte[] GetDValue(SXprUtilities reader, byte[] rawPassPhrase, bool clearPassPhrase, string curveName)
+        private static void WriteSExprPublicKey(SXprWriter writer, PublicKeyPacket pubPacket, string curveName, string protectedAt)
+        {
+            writer.StartList();
+            switch (pubPacket.Algorithm)
+            {
+                case PublicKeyAlgorithmTag.ECDsa:
+                case PublicKeyAlgorithmTag.EdDsa:
+                    writer.WriteString("ecc");
+                    writer.StartList();
+                    writer.WriteString("curve");
+                    writer.WriteString(curveName);
+                    writer.EndList();
+                    if (pubPacket.Algorithm == PublicKeyAlgorithmTag.EdDsa)
+                    {
+                        writer.StartList();
+                        writer.WriteString("flags");
+                        writer.WriteString("eddsa");
+                        writer.EndList();
+                    }
+                    writer.StartList();
+                    writer.WriteString("q");
+                    writer.WriteBytes(((ECDsaPublicBcpgKey)pubPacket.Key).EncodedPoint.ToByteArrayUnsigned());
+                    writer.EndList();
+                    break;
+
+                case PublicKeyAlgorithmTag.RsaEncrypt:
+                case PublicKeyAlgorithmTag.RsaSign:
+                case PublicKeyAlgorithmTag.RsaGeneral:
+                    RsaPublicBcpgKey rsaK = (RsaPublicBcpgKey)pubPacket.Key;
+                    writer.WriteString("rsa");
+                    writer.StartList();
+                    writer.WriteString("n");
+                    writer.WriteBytes(rsaK.Modulus.ToByteArrayUnsigned());
+                    writer.EndList();
+                    writer.StartList();
+                    writer.WriteString("e");
+                    writer.WriteBytes(rsaK.PublicExponent.ToByteArrayUnsigned());
+                    writer.EndList();
+                    break;
+
+                // TODO: DSA, etc.
+                default:
+                    throw new PgpException("unsupported algorithm in S expression");
+            }
+
+            if (protectedAt != null)
+            {
+                writer.StartList();
+                writer.WriteString("protected-at");
+                writer.WriteString(protectedAt);
+                writer.EndList();
+            }
+            writer.EndList();
+        }
+
+        private static byte[] GetDValue(SXprReader reader, PublicKeyPacket publicKey, byte[] rawPassPhrase, bool clearPassPhrase, string curveName)
         {
             string type;
             reader.SkipOpenParenthesis();
 
             string protection;
+            string protectedAt = null;
             S2k s2k;
             byte[] iv;
             byte[] secKeyData;
@@ -1312,26 +1320,42 @@ private static byte[] GetDValue(SXprUtilities reader, byte[] rawPassPhrase, bool
                 reader.SkipCloseParenthesis();
 
                 secKeyData = reader.ReadBytes();
+
+                reader.SkipCloseParenthesis();
+
+                reader.SkipOpenParenthesis();
+
+                if (reader.ReadString().Equals("protected-at"))
+                {
+                    protectedAt = reader.ReadString();
+                }
             }
             else
             {
                 throw new PgpException("protected block not found");
             }
 
-            // Valid values of protection: openpgp-s2k3-sha1-aes-cbc, openpgp-s2k3-ocb-aes, openpgp-native
             byte[] data;
             KeyParameter key;
 
             switch (protection)
             {
+                case "openpgp-s2k3-sha1-aes256-cbc":
                 case "openpgp-s2k3-sha1-aes-cbc":
-                    key = PgpUtilities.DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag.Aes128, s2k, rawPassPhrase, clearPassPhrase);
-                    data = RecoverKeyData(SymmetricKeyAlgorithmTag.Aes128, "/CBC/NoPadding", key, iv, secKeyData, 0, secKeyData.Length);
+                    SymmetricKeyAlgorithmTag symmAlg = 
+                        protection.Equals("openpgp-s2k3-sha1-aes256-cbc") ? SymmetricKeyAlgorithmTag.Aes256 : SymmetricKeyAlgorithmTag.Aes128;
+                    key = PgpUtilities.DoMakeKeyFromPassPhrase(symmAlg, s2k, rawPassPhrase, clearPassPhrase);
+                    data = RecoverKeyData(symmAlg, "/CBC/NoPadding", key, iv, secKeyData, 0, secKeyData.Length);
+                    // TODO: check SHA-1 hash.
                     break;
 
                 case "openpgp-s2k3-ocb-aes":
+                    MemoryStream aad = new MemoryStream();
+                    WriteSExprPublicKey(new SXprWriter(aad), publicKey, curveName, protectedAt);
                     key = PgpUtilities.DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag.Aes128, s2k, rawPassPhrase, clearPassPhrase);
-                    data = RecoverKeyData(SymmetricKeyAlgorithmTag.Aes128, "/OCB/NoPadding", key, iv, secKeyData, 0, secKeyData.Length);
+                    IBufferedCipher c = CipherUtilities.GetCipher("AES/OCB");
+                    c.Init(false, new AeadParameters(key, 128, iv, aad.ToArray()));
+                    data = c.DoFinal(secKeyData, 0, secKeyData.Length);
                     break;
 
                 case "openpgp-native":
@@ -1344,7 +1368,7 @@ private static byte[] GetDValue(SXprUtilities reader, byte[] rawPassPhrase, bool
             //
             Stream keyIn = new MemoryStream(data, false);
 
-            reader = new SXprUtilities(keyIn);
+            reader = new SXprReader(keyIn);
             reader.SkipOpenParenthesis();
             reader.SkipOpenParenthesis();
             reader.SkipOpenParenthesis();
diff --git a/crypto/src/openpgp/SXprUtilities.cs b/crypto/src/openpgp/SXprReader.cs
similarity index 97%
rename from crypto/src/openpgp/SXprUtilities.cs
rename to crypto/src/openpgp/SXprReader.cs
index 7cd4862065..7e6d15b341 100644
--- a/crypto/src/openpgp/SXprUtilities.cs
+++ b/crypto/src/openpgp/SXprReader.cs
@@ -7,19 +7,19 @@
 namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
     /**
-     * Utility functions for looking a S-expression keys. This class will move when it finds a better home!
+     * Reader for S-expression keys. This class will move when it finds a better home!
      * <p>
      * Format documented here:
      * http://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=agent/keyformat.txt;h=42c4b1f06faf1bbe71ffadc2fee0fad6bec91a97;hb=refs/heads/master
      * http://people.csail.mit.edu/rivest/Sexp.txt
      * </p>
      */
-    class SXprUtilities
+    class SXprReader
     {
         Stream stream;
         int peekedByte;
 
-        internal SXprUtilities(Stream stream)
+        public SXprReader(Stream stream)
         {
             this.stream = stream;
             this.peekedByte = -1;
@@ -211,7 +211,7 @@ public S2k ParseS2k()
             return new MyS2k(HashAlgorithmTag.Sha1, iv, iterationCount);
         }
 
-        private void SkipWhitespace()
+        public void SkipWhitespace()
         {
             int ch = ReadByte();
             while (ch == ' ' || ch == '\r' || ch == '\n')
diff --git a/crypto/src/openpgp/SXprWriter.cs b/crypto/src/openpgp/SXprWriter.cs
new file mode 100644
index 0000000000..15ee461644
--- /dev/null
+++ b/crypto/src/openpgp/SXprWriter.cs
@@ -0,0 +1,50 @@
+using System;
+using System.IO;
+using System.Text;
+
+namespace crypto.openpgp
+{
+    /**
+     * Writer for S-expression keys
+     * <p>
+     * Format documented here:
+     * http://people.csail.mit.edu/rivest/Sexp.txt
+     * 
+     * Only canonical S expression format is used.
+     * </p>
+     */
+    class SXprWriter
+    {
+        Stream output;
+
+        public SXprWriter(Stream output)
+        {
+            this.output = output;
+        }
+
+        public void StartList()
+        {
+            output.WriteByte((byte)'(');
+        }
+
+        public void EndList()
+        {
+            output.WriteByte((byte)')');
+        }
+
+        public void WriteString(string s)
+        {
+            byte[] stringBytes = Encoding.UTF8.GetBytes(s);
+            byte[] lengthBytes = Encoding.UTF8.GetBytes(stringBytes.Length + ":");
+            output.Write(lengthBytes, 0, lengthBytes.Length);
+            output.Write(stringBytes, 0, stringBytes.Length);
+        }
+
+        public void WriteBytes(byte[] b)
+        {
+            byte[] lengthBytes = Encoding.UTF8.GetBytes(b.Length + ":");
+            output.Write(lengthBytes, 0, lengthBytes.Length);
+            output.Write(b, 0, b.Length);
+        }
+    }
+}
diff --git a/crypto/test/src/openpgp/test/PgpEdDsaTest.cs b/crypto/test/src/openpgp/test/PgpEdDsaTest.cs
index a670b14902..3a9d6c46b2 100644
--- a/crypto/test/src/openpgp/test/PgpEdDsaTest.cs
+++ b/crypto/test/src/openpgp/test/PgpEdDsaTest.cs
@@ -184,8 +184,7 @@ public override void PerformTest()
             //
             // sExpr
             //
-            // TODO: Fails the OCB MAC check when decoding key but works otherwise
-            /*byte[] msg = Encoding.ASCII.GetBytes("hello world!");
+            byte[] msg = Encoding.ASCII.GetBytes("hello world!");
 
             PgpSecretKey key = PgpSecretKey.ParseSecretKeyFromSExpr(new MemoryStream(sExprKey, false), "test".ToCharArray());
 
@@ -200,7 +199,7 @@ public override void PerformTest()
             if (!sig.Verify())
             {
                 Fail("signature failed to verify!");
-            }*/
+            }
         }
 
         private static object First(IEnumerable e)

From 5fba3f4a6ee79f2e7333b59c84973f6e79a4fed2 Mon Sep 17 00:00:00 2001
From: Filip Navara <navara@emclient.com>
Date: Sun, 13 Dec 2020 13:03:09 +0100
Subject: [PATCH 4/8] Fix endianness issue and key generation in ECDH

---
 crypto/src/bcpg/ECDHPublicBCPGKey.cs          |  16 ++
 .../src/openpgp/PgpEncryptedDataGenerator.cs  |  10 +-
 crypto/src/openpgp/PgpPublicKey.cs            |  20 ++-
 .../src/openpgp/PgpPublicKeyEncryptedData.cs  |   9 +-
 crypto/src/openpgp/PgpSecretKey.cs            |  24 ++-
 crypto/test/src/openpgp/test/PgpECDHTest.cs   | 151 +++++++++++++++++-
 6 files changed, 212 insertions(+), 18 deletions(-)

diff --git a/crypto/src/bcpg/ECDHPublicBCPGKey.cs b/crypto/src/bcpg/ECDHPublicBCPGKey.cs
index dc225e31e1..5c6785d261 100644
--- a/crypto/src/bcpg/ECDHPublicBCPGKey.cs
+++ b/crypto/src/bcpg/ECDHPublicBCPGKey.cs
@@ -1,6 +1,7 @@
 using System;
 
 using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Math.EC;
 
 namespace Org.BouncyCastle.Bcpg
@@ -48,6 +49,21 @@ public ECDHPublicBcpgKey(
             VerifySymmetricKeyAlgorithm();
         }
 
+        public ECDHPublicBcpgKey(
+            DerObjectIdentifier oid,
+            BigInteger encodedPoint,
+            HashAlgorithmTag hashAlgorithm,
+            SymmetricKeyAlgorithmTag symmetricKeyAlgorithm)
+            : base(oid, encodedPoint)
+        {
+            reserved = 1;
+            hashFunctionId = hashAlgorithm;
+            symAlgorithmId = symmetricKeyAlgorithm;
+
+            VerifyHashAlgorithm();
+            VerifySymmetricKeyAlgorithm();
+        }
+
         public virtual byte Reserved
         {
             get { return reserved; }
diff --git a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
index 81847cabd3..bd8f14d524 100644
--- a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
+++ b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
@@ -104,6 +104,8 @@ public override void AddSessionInfo(
 
             private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random)
             {
+                AsymmetricKeyParameter akp = pubKey.GetKey();
+                
                 if (pubKey.Algorithm != PublicKeyAlgorithmTag.ECDH)
                 {
                     IBufferedCipher c;
@@ -127,7 +129,6 @@ private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random)
                             throw new PgpException("unknown asymmetric algorithm: " + pubKey.Algorithm);
                     }
 
-                    AsymmetricKeyParameter akp = pubKey.GetKey();
                     c.Init(true, new ParametersWithRandom(akp, random));
                     return c.DoFinal(sessionInfo);
                 }
@@ -136,12 +137,13 @@ private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random)
                 KeyParameter key;
                 byte[] encodedPublicKey;
 
-                if (ecKey.CurveOid.Id.Equals(MiscObjectIdentifiers.Curve25519.Id))
+                if (akp is X25519PublicKeyParameters)
                 {
+                    X25519PublicKeyParameters pub = (X25519PublicKeyParameters)akp;
                     byte[] privateKey = new byte[X25519.PointSize];
                     X25519.GeneratePrivateKey(random, privateKey);
                     byte[] sharedKey = new byte[32];
-                    X25519.CalculateAgreement(privateKey, 0, BigIntegers.AsUnsignedByteArray(ecKey.EncodedPoint), 1, sharedKey, 0);
+                    X25519.CalculateAgreement(privateKey, 0, pub.GetEncoded(), 0, sharedKey, 0);
                     byte[] publicKey = new byte[X25519.PointSize + 1];
                     publicKey[0] = 0x40; // compressed point
                     X25519.GeneratePublicKey(privateKey, 0, publicKey, 1);
@@ -158,7 +160,7 @@ private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random)
                     ECPrivateKeyParameters ephPriv = (ECPrivateKeyParameters)ephKp.Private;
                     ECPublicKeyParameters ephPub = (ECPublicKeyParameters)ephKp.Public;
 
-                    ECPublicKeyParameters pub = (ECPublicKeyParameters)pubKey.GetKey();
+                    ECPublicKeyParameters pub = (ECPublicKeyParameters)akp;
                     ECPoint S = pub.Q.Multiply(ephPriv.D).Normalize();
 
                     key = new KeyParameter(Rfc6637Utilities.CreateKey(pubKey.PublicKeyPacket, S));
diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs
index 0a5137d3ad..f13a392b5d 100644
--- a/crypto/src/openpgp/PgpPublicKey.cs
+++ b/crypto/src/openpgp/PgpPublicKey.cs
@@ -11,6 +11,7 @@
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Math.EC;
+using Org.BouncyCastle.Math.EC.Rfc7748;
 using Org.BouncyCastle.Math.EC.Rfc8032;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
@@ -196,6 +197,19 @@ public PgpPublicKey(
                 ecK.Encode(encodedPoint, 1);
                 bcpgKey = new ECDsaPublicBcpgKey(GnuObjectIdentifiers.Ed25519, new BigInteger(1, encodedPoint));
             }
+            else if (pubKey is X25519PublicKeyParameters)
+            {
+                X25519PublicKeyParameters ecK = (X25519PublicKeyParameters)pubKey;
+                byte[] encodedPoint = new byte[X25519.PointSize + 1];
+                encodedPoint[0] = 0x40;
+                ecK.Encode(encodedPoint, 1);
+                Array.Reverse(encodedPoint, 1, X25519.PointSize);
+                bcpgKey = new ECDHPublicBcpgKey(
+                    MiscObjectIdentifiers.Curve25519,
+                    new BigInteger(1, encodedPoint),
+                    HashAlgorithmTag.Sha256,
+                    SymmetricKeyAlgorithmTag.Aes128);
+            }
             else if (pubKey is ElGamalPublicKeyParameters)
             {
                 ElGamalPublicKeyParameters eK = (ElGamalPublicKeyParameters) pubKey;
@@ -510,7 +524,11 @@ public AsymmetricKeyParameter GetKey()
                         return GetECKey("ECDSA");
                     case PublicKeyAlgorithmTag.ECDH:
                         if (((ECPublicBcpgKey)publicPk.Key).CurveOid.Id.Equals(MiscObjectIdentifiers.Curve25519.Id))
-                            return new X25519PublicKeyParameters(((ECPublicBcpgKey)publicPk.Key).EncodedPoint.ToByteArrayUnsigned(), 0);
+                        {
+                            byte[] encodedPoint = ((ECPublicBcpgKey)publicPk.Key).EncodedPoint.ToByteArrayUnsigned();
+                            Array.Reverse(encodedPoint, 1, X25519.PointSize);
+                            return new X25519PublicKeyParameters(encodedPoint, 1);
+                        }
                         else
                             return GetECKey("ECDH");
                     case PublicKeyAlgorithmTag.EdDsa:
diff --git a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
index be05f36fe4..0c47af3c3e 100644
--- a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
+++ b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
@@ -217,11 +217,10 @@ private byte[] RecoverSessionData(PgpPrivateKey privKey)
 
                 if (privKey.Key is X25519PrivateKeyParameters x25519privKeyParams)
                 {
-                    byte[] sharedKey = new byte[32];
-                    byte[] reversedPrivateKey = new byte[32];
-                    x25519privKeyParams.Encode(reversedPrivateKey, 0);
-                    Array.Reverse((Array)reversedPrivateKey);
-                    X25519.ScalarMult(reversedPrivateKey, 0, pEnc, 1, sharedKey, 0);
+                    byte[] sharedKey = new byte[X25519.PointSize];
+                    byte[] privateKey = new byte[X25519.PointSize];
+                    x25519privKeyParams.Encode(privateKey, 0);
+                    X25519.CalculateAgreement(privateKey, 0, pEnc, 1, sharedKey, 0);
                     key = new KeyParameter(Rfc6637Utilities.CreateKey(privKey.PublicKeyPacket, sharedKey));
                 }
                 else
diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs
index 4c44fbdf57..1eefc2f1d3 100644
--- a/crypto/src/openpgp/PgpSecretKey.cs
+++ b/crypto/src/openpgp/PgpSecretKey.cs
@@ -57,9 +57,23 @@ internal PgpSecretKey(
                     secKey = new DsaSecretBcpgKey(dsK.X);
                     break;
                 case PublicKeyAlgorithmTag.ECDH:
+                    if (privKey.Key is X25519PrivateKeyParameters)
+                    {
+                        X25519PrivateKeyParameters x25519K = (X25519PrivateKeyParameters)privKey.Key;
+                        byte[] secretKey = new byte[X25519PrivateKeyParameters.KeySize];
+                        x25519K.Encode(secretKey, 0);
+                        Array.Reverse(secretKey);
+                        secKey = new ECSecretBcpgKey(new BigInteger(1, secretKey));
+                    }
+                    else
+                    {
+                        ECPrivateKeyParameters ecK = (ECPrivateKeyParameters)privKey.Key;
+                        secKey = new ECSecretBcpgKey(ecK.D);
+                    }
+                    break;
                 case PublicKeyAlgorithmTag.ECDsa:
-                    ECPrivateKeyParameters ecK = (ECPrivateKeyParameters)privKey.Key;
-                    secKey = new ECSecretBcpgKey(ecK.D);
+                    ECPrivateKeyParameters ecdsaK = (ECPrivateKeyParameters)privKey.Key;
+                    secKey = new ECSecretBcpgKey(ecdsaK.D);
                     break;
                 case PublicKeyAlgorithmTag.EdDsa:
                     Ed25519PrivateKeyParameters edK = (Ed25519PrivateKeyParameters)privKey.Key;
@@ -683,7 +697,11 @@ internal PgpPrivateKey DoExtractPrivateKey(byte[] rawPassPhrase, bool clearPassP
                     break;
                 case PublicKeyAlgorithmTag.ECDH:
                     if (((ECPublicBcpgKey)secret.PublicKeyPacket.Key).CurveOid.Id.Equals(MiscObjectIdentifiers.Curve25519.Id))
-                        privateKey = new X25519PrivateKeyParameters(new ECSecretBcpgKey(bcpgIn).X.ToByteArrayUnsigned(), 0);
+                    {
+                        var x = new ECSecretBcpgKey(bcpgIn).X.ToByteArrayUnsigned();
+                        Array.Reverse(x);
+                        privateKey = new X25519PrivateKeyParameters(x, 0);
+                    }
                     else
                         privateKey = GetECKey("ECDH", bcpgIn);
                     break;
diff --git a/crypto/test/src/openpgp/test/PgpECDHTest.cs b/crypto/test/src/openpgp/test/PgpECDHTest.cs
index 94ab2a755e..4f7c372401 100644
--- a/crypto/test/src/openpgp/test/PgpECDHTest.cs
+++ b/crypto/test/src/openpgp/test/PgpECDHTest.cs
@@ -4,13 +4,16 @@
 using System.Text;
 
 using NUnit.Framework;
-
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Gnu;
+using Org.BouncyCastle.Asn1.Misc;
 using Org.BouncyCastle.Asn1.Sec;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.IO;
 using Org.BouncyCastle.Utilities.Test;
 
 namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
@@ -51,6 +54,40 @@ public class PgpECDHTest
                 "6HiuFH7VKWcxPUBjXwf5+Z3uOKEp28tBgNyDrdbr1BbqlgYzIKq/pe9zUbUXfitn" +
                 "vFc6HcGhvmRQreQ+Yw1x3x0HJeoPwg==");
 
+        private static readonly byte[] testX25519PubKey =
+            Base64.Decode(
+                "mDMEX9XwXhYJKwYBBAHaRw8BAQdAR5ZghmMHL8wldNlOkmbaiAOdyF5V5bgZdKq7" +
+                "L+yb4A20HEVDREggPHRlc3QuZWNkaEBleGFtcGxlLmNvbT6IkAQTFggAOBYhBGoy" +
+                "UrxNv7c3S2JjGzewWiN8tfzXBQJf1fBeAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4B" +
+                "AheAAAoJEDewWiN8tfzX0ZMA/AhEvrIgu+29eMQeuHOwX1ZY/UssU5TdVROQzGTL" +
+                "n5cgAP9hIKtt/mZ112HiAHDuWk2JskdtsuopnrEccz4PSEkSDLg4BF/V8F4SCisG" +
+                "AQQBl1UBBQEBB0DLPhNt/6GHDbb7vZW/iMsbXTZpgJNQiT6QA/4EzgYQLwMBCAeI" +
+                "eAQYFggAIBYhBGoyUrxNv7c3S2JjGzewWiN8tfzXBQJf1fBeAhsMAAoJEDewWiN8" +
+                "tfzXU34BAKJJLDee+qJCmUI20sMy/YoKfWmMnH2RBBHmLV8FAJ7vAP0e2wGixEfs" +
+                "oPqe8fHmvjQGxSByOyQGn7yD+oq9nVzTAA==");
+
+        private static readonly byte[] testX25519PrivKey =
+            Base64.Decode(
+                "lIYEX9XwXhYJKwYBBAHaRw8BAQdAR5ZghmMHL8wldNlOkmbaiAOdyF5V5bgZdKq7" +
+                "L+yb4A3+BwMCMscozrXr93fOFmtxu/BJjEJrwRl20Jrv9lryfM+SF4UHgVMmJUpJ" +
+                "1RuTbSnM2KaqHwOgmdrvf2FJnpg1vMafBk1CmopqkRzzrbJ6xQhiPrQcRUNESCA8" +
+                "dGVzdC5lY2RoQGV4YW1wbGUuY29tPoiQBBMWCAA4FiEEajJSvE2/tzdLYmMbN7Ba" +
+                "I3y1/NcFAl/V8F4CGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQN7BaI3y1" +
+                "/NfRkwD8CES+siC77b14xB64c7BfVlj9SyxTlN1VE5DMZMuflyAA/2Egq23+ZnXX" +
+                "YeIAcO5aTYmyR22y6imesRxzPg9ISRIMnIsEX9XwXhIKKwYBBAGXVQEFAQEHQMs+" +
+                "E23/oYcNtvu9lb+IyxtdNmmAk1CJPpAD/gTOBhAvAwEIB/4HAwJ7ShSBrUuUAM5r" +
+                "G4I/gJKo+eBmbNC4NM81eALAF1vcovZPsGsiZ8IgXT64XiC1bpeAoINn6vM4vVbi" +
+                "LqNKqu6ll3ZgQ4po6vCW9GkhuEMmiHgEGBYIACAWIQRqMlK8Tb+3N0tiYxs3sFoj" +
+                "fLX81wUCX9XwXgIbDAAKCRA3sFojfLX811N+AQCiSSw3nvqiQplCNtLDMv2KCn1p" +
+                "jJx9kQQR5i1fBQCe7wD9HtsBosRH7KD6nvHx5r40BsUgcjskBp+8g/qKvZ1c0wA=");
+
+        private static readonly byte[] testX25519Message =
+            Base64.Decode(
+                "hF4DbDc2fNL0VcUSAQdAqdV0v1D4X9cuGrT7+oQBpMFnw1wdfAcxH9xdO00s2HUw" +
+                "qB+XkIRETH7yesynLOKajmYftMWZRyTnW2tJUc1w5NFPjPxcbvd2bYmqkY57uAFg" +
+                "0kcBKhFklH2LRbBNThtQr3jn2YEFbNnhiGfOpoHfCn0oFh5RbXDwm+P3Q3tksvpZ" +
+                "wEGe2VkxLLe7BWnv/sRINQ2YpuaYshe8hw==");
+
         private void Generate()
         {
             SecureRandom random = SecureRandom.GetInstance("SHA1PRNG");
@@ -105,6 +142,71 @@ private void Generate()
             PgpPrivateKey pgpPrivKey = secRing.GetSecretKey().ExtractPrivateKey(passPhrase);
         }
 
+        private void Generate25519()
+        {
+            SecureRandom random = SecureRandom.GetInstance("SHA1PRNG");
+
+            //
+            // Generate a master key
+            //
+            IAsymmetricCipherKeyPairGenerator keyGen = GeneratorUtilities.GetKeyPairGenerator("Ed25519");
+            keyGen.Init(new ECKeyGenerationParameters(GnuObjectIdentifiers.Ed25519, random));
+
+            AsymmetricCipherKeyPair kpSign = keyGen.GenerateKeyPair();
+
+            PgpKeyPair ecdsaKeyPair = new PgpKeyPair(PublicKeyAlgorithmTag.EdDsa, kpSign, DateTime.UtcNow);
+
+            //
+            // Generate an encryption key
+            //
+            keyGen = GeneratorUtilities.GetKeyPairGenerator("X25519");
+            keyGen.Init(new ECKeyGenerationParameters(MiscObjectIdentifiers.Curve25519, random));
+
+            AsymmetricCipherKeyPair kpEnc = keyGen.GenerateKeyPair();
+
+            PgpKeyPair ecdhKeyPair = new PgpKeyPair(PublicKeyAlgorithmTag.ECDH, kpEnc, DateTime.UtcNow);
+
+            //
+            // Generate a key ring
+            //
+            char[] passPhrase = "test".ToCharArray();
+            PgpKeyRingGenerator keyRingGen = new PgpKeyRingGenerator(PgpSignature.PositiveCertification, ecdsaKeyPair,
+                "test@bouncycastle.org", SymmetricKeyAlgorithmTag.Aes256, passPhrase, true, null, null, random);
+            keyRingGen.AddSubKey(ecdhKeyPair);
+
+            PgpPublicKeyRing pubRing = keyRingGen.GeneratePublicKeyRing();
+
+            // TODO: add check of KdfParameters
+            DoBasicKeyRingCheck(pubRing);
+
+            PgpSecretKeyRing secRing = keyRingGen.GenerateSecretKeyRing();
+
+            PgpPublicKeyRing pubRingEnc = new PgpPublicKeyRing(pubRing.GetEncoded());
+            if (!Arrays.AreEqual(pubRing.GetEncoded(), pubRingEnc.GetEncoded()))
+            {
+                Fail("public key ring encoding failed");
+            }
+
+            PgpSecretKeyRing secRingEnc = new PgpSecretKeyRing(secRing.GetEncoded());
+            if (!Arrays.AreEqual(secRing.GetEncoded(), secRingEnc.GetEncoded()))
+            {
+                Fail("secret key ring encoding failed");
+            }
+
+            // Extract back the ECDH key and verify the encoded values to ensure correct endianness
+            PgpSecretKey pgpSecretKey = secRing.GetSecretKey(ecdhKeyPair.KeyId);
+            PgpPrivateKey pgpPrivKey = pgpSecretKey.ExtractPrivateKey(passPhrase);
+
+            if (!Arrays.AreEqual(((X25519PrivateKeyParameters)kpEnc.Private).GetEncoded(), ((X25519PrivateKeyParameters)pgpPrivKey.Key).GetEncoded()))
+            {
+                Fail("private key round trip failed");
+            }
+            if (!Arrays.AreEqual(((X25519PublicKeyParameters)kpEnc.Public).GetEncoded(), ((X25519PublicKeyParameters)pgpSecretKey.PublicKey.GetKey()).GetEncoded()))
+            {
+                Fail("private key round trip failed");
+            }
+        }
+
         private void TestDecrypt(PgpSecretKeyRing secretKeyRing)
         {
             PgpObjectFactory pgpF = new PgpObjectFactory(testMessage);
@@ -134,14 +236,14 @@ private void TestDecrypt(PgpSecretKeyRing secretKeyRing)
     //        }
         }
 
-        private void EncryptDecryptTest()
+        private void EncryptDecryptTest(string algorithm, DerObjectIdentifier curve)
         {
             SecureRandom random = SecureRandom.GetInstance("SHA1PRNG");
 
             byte[] text = Encoding.ASCII.GetBytes("hello world!");
 
-            IAsymmetricCipherKeyPairGenerator keyGen = GeneratorUtilities.GetKeyPairGenerator("ECDH");
-            keyGen.Init(new ECKeyGenerationParameters(SecObjectIdentifiers.SecP256r1, random));
+            IAsymmetricCipherKeyPairGenerator keyGen = GeneratorUtilities.GetKeyPairGenerator(algorithm);
+            keyGen.Init(new ECKeyGenerationParameters(curve, random));
 
             AsymmetricCipherKeyPair kpEnc = keyGen.GenerateKeyPair();
 
@@ -197,6 +299,39 @@ private void EncryptDecryptTest()
             }
         }
 
+        private void GnuPGCrossCheck()
+        {
+            PgpSecretKeyRing secretKeyRing = new PgpSecretKeyRing(testX25519PrivKey);
+
+            PgpObjectFactory pgpF = new PgpObjectFactory(testX25519Message);
+
+            PgpEncryptedDataList encList = (PgpEncryptedDataList)pgpF.NextPgpObject();
+
+            PgpPublicKeyEncryptedData encP = (PgpPublicKeyEncryptedData)encList[0];
+
+            PgpSecretKey secretKey = secretKeyRing.GetSecretKey(0x6c37367cd2f455c5);
+
+            PgpPrivateKey pgpPrivKey = secretKey.ExtractPrivateKey("test".ToCharArray());
+
+            Stream clear = encP.GetDataStream(pgpPrivKey);
+
+            pgpF = new PgpObjectFactory(clear);
+
+            PgpCompressedData c1 = (PgpCompressedData)pgpF.NextPgpObject();
+
+            pgpF = new PgpObjectFactory(c1.GetDataStream());
+
+            PgpLiteralData ld = (PgpLiteralData)pgpF.NextPgpObject();
+
+            Stream inLd = ld.GetDataStream();
+            byte[] bytes = Streams.ReadAll(inLd);
+
+            if (!Arrays.AreEqual(bytes, Encoding.ASCII.GetBytes("hello world!")))
+            {
+                Fail("wrong plain text in decrypted packet");
+            }
+        }
+
         public override void PerformTest()
         {
             //
@@ -213,9 +348,15 @@ public override void PerformTest()
 
             TestDecrypt(secretKeyRing);
 
-            EncryptDecryptTest();
+            EncryptDecryptTest("ECDH", SecObjectIdentifiers.SecP256r1);
+
+            EncryptDecryptTest("X25519", MiscObjectIdentifiers.Curve25519);
+
+            GnuPGCrossCheck();
 
             Generate();
+
+            Generate25519();
         }
 
         private void DoBasicKeyRingCheck(PgpPublicKeyRing pubKeyRing)

From b4aed35b783590dbb3f372d4f87288b1f9b5d903 Mon Sep 17 00:00:00 2001
From: Filip Navara <navara@emclient.com>
Date: Sun, 13 Dec 2020 13:41:22 +0100
Subject: [PATCH 5/8] Add roundtrip test for X25519 with GnuPG keys

---
 crypto/src/openpgp/PgpPublicKey.cs          |  2 -
 crypto/test/src/openpgp/test/PgpECDHTest.cs | 71 +++++++++++++++++++++
 2 files changed, 71 insertions(+), 2 deletions(-)

diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs
index f13a392b5d..462f17e037 100644
--- a/crypto/src/openpgp/PgpPublicKey.cs
+++ b/crypto/src/openpgp/PgpPublicKey.cs
@@ -203,7 +203,6 @@ public PgpPublicKey(
                 byte[] encodedPoint = new byte[X25519.PointSize + 1];
                 encodedPoint[0] = 0x40;
                 ecK.Encode(encodedPoint, 1);
-                Array.Reverse(encodedPoint, 1, X25519.PointSize);
                 bcpgKey = new ECDHPublicBcpgKey(
                     MiscObjectIdentifiers.Curve25519,
                     new BigInteger(1, encodedPoint),
@@ -526,7 +525,6 @@ public AsymmetricKeyParameter GetKey()
                         if (((ECPublicBcpgKey)publicPk.Key).CurveOid.Id.Equals(MiscObjectIdentifiers.Curve25519.Id))
                         {
                             byte[] encodedPoint = ((ECPublicBcpgKey)publicPk.Key).EncodedPoint.ToByteArrayUnsigned();
-                            Array.Reverse(encodedPoint, 1, X25519.PointSize);
                             return new X25519PublicKeyParameters(encodedPoint, 1);
                         }
                         else
diff --git a/crypto/test/src/openpgp/test/PgpECDHTest.cs b/crypto/test/src/openpgp/test/PgpECDHTest.cs
index 4f7c372401..9c307cb353 100644
--- a/crypto/test/src/openpgp/test/PgpECDHTest.cs
+++ b/crypto/test/src/openpgp/test/PgpECDHTest.cs
@@ -299,6 +299,75 @@ private void EncryptDecryptTest(string algorithm, DerObjectIdentifier curve)
             }
         }
 
+
+        private void EncryptDecryptX25519KeysTest()
+        {
+            SecureRandom random = SecureRandom.GetInstance("SHA1PRNG");
+
+            /*IAsymmetricCipherKeyPairGenerator keyGen = GeneratorUtilities.GetKeyPairGenerator(algorithm);
+            keyGen.Init(new ECKeyGenerationParameters(curve, random));
+
+            AsymmetricCipherKeyPair kpEnc = keyGen.GenerateKeyPair();
+
+            PgpKeyPair ecdhKeyPair = new PgpKeyPair(PublicKeyAlgorithmTag.ECDH, kpEnc, DateTime.UtcNow);*/
+            PgpPublicKeyRing publicKeyRing = new PgpPublicKeyRing(testX25519PubKey);
+
+            PgpSecretKeyRing secretKeyRing = new PgpSecretKeyRing(testX25519PrivKey);
+
+            PgpSecretKey secretKey = secretKeyRing.GetSecretKey(0x6c37367cd2f455c5);
+
+            byte[] text = Encoding.ASCII.GetBytes("hello world!");
+
+            PgpLiteralDataGenerator lData = new PgpLiteralDataGenerator();
+            MemoryStream ldOut = new MemoryStream();
+            Stream pOut = lData.Open(ldOut, PgpLiteralDataGenerator.Utf8, PgpLiteralData.Console, text.Length, DateTime.UtcNow);
+
+            pOut.Write(text, 0, text.Length);
+
+            pOut.Close();
+
+            byte[] data = ldOut.ToArray();
+
+            MemoryStream cbOut = new MemoryStream();
+
+            PgpEncryptedDataGenerator cPk = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Cast5, random);
+            cPk.AddMethod(publicKeyRing.GetPublicKey(0x6c37367cd2f455c5));
+
+            Stream cOut = cPk.Open(new UncloseableStream(cbOut), data.Length);
+
+            cOut.Write(data, 0, data.Length);
+
+            cOut.Close();
+
+            PgpObjectFactory pgpF = new PgpObjectFactory(cbOut.ToArray());
+
+            PgpEncryptedDataList encList = (PgpEncryptedDataList)pgpF.NextPgpObject();
+
+            PgpPublicKeyEncryptedData encP = (PgpPublicKeyEncryptedData)encList[0];
+
+            Stream clear = encP.GetDataStream(secretKey.ExtractPrivateKey("test".ToCharArray()));
+
+            pgpF = new PgpObjectFactory(clear);
+
+            PgpLiteralData ld = (PgpLiteralData)pgpF.NextPgpObject();
+
+            clear = ld.GetInputStream();
+            MemoryStream bOut = new MemoryStream();
+
+            int ch;
+            while ((ch = clear.ReadByte()) >= 0)
+            {
+                bOut.WriteByte((byte)ch);
+            }
+
+            byte[] output = bOut.ToArray();
+
+            if (!AreEqual(output, text))
+            {
+                Fail("wrong plain text in Generated packet");
+            }
+        }
+
         private void GnuPGCrossCheck()
         {
             PgpSecretKeyRing secretKeyRing = new PgpSecretKeyRing(testX25519PrivKey);
@@ -357,6 +426,8 @@ public override void PerformTest()
             Generate();
 
             Generate25519();
+
+            EncryptDecryptX25519KeysTest();
         }
 
         private void DoBasicKeyRingCheck(PgpPublicKeyRing pubKeyRing)

From 31c46556f41aa10ac93ab122c591a048dae150c5 Mon Sep 17 00:00:00 2001
From: Filip Navara <navara@emclient.com>
Date: Wed, 23 Jun 2021 14:48:30 +0200
Subject: [PATCH 6/8] Fix namespace

---
 crypto/src/openpgp/PgpSecretKey.cs | 1 -
 crypto/src/openpgp/SXprWriter.cs   | 2 +-
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs
index 1eefc2f1d3..134fdf87dc 100644
--- a/crypto/src/openpgp/PgpSecretKey.cs
+++ b/crypto/src/openpgp/PgpSecretKey.cs
@@ -2,7 +2,6 @@
 using System.Collections;
 using System.Diagnostics;
 using System.IO;
-using crypto.openpgp;
 using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Asn1.Misc;
 using Org.BouncyCastle.Asn1.X9;
diff --git a/crypto/src/openpgp/SXprWriter.cs b/crypto/src/openpgp/SXprWriter.cs
index 15ee461644..640c4b6d6e 100644
--- a/crypto/src/openpgp/SXprWriter.cs
+++ b/crypto/src/openpgp/SXprWriter.cs
@@ -2,7 +2,7 @@
 using System.IO;
 using System.Text;
 
-namespace crypto.openpgp
+namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
     /**
      * Writer for S-expression keys

From be112f4faccc06d58a850387998e0b08aab0fb28 Mon Sep 17 00:00:00 2001
From: Filip Navara <navara@emclient.com>
Date: Wed, 23 Jun 2021 15:00:11 +0200
Subject: [PATCH 7/8] Fix build

---
 crypto/BouncyCastle.Android.csproj            |  4 +-
 crypto/BouncyCastle.csproj                    |  4 +-
 crypto/BouncyCastle.iOS.csproj                |  4 +-
 crypto/crypto.csproj                          | 12 ++++-
 .../src/crypto/signers/EdDsa255519Signer.cs   | 47 -------------------
 5 files changed, 20 insertions(+), 51 deletions(-)
 delete mode 100644 crypto/src/crypto/signers/EdDsa255519Signer.cs

diff --git a/crypto/BouncyCastle.Android.csproj b/crypto/BouncyCastle.Android.csproj
index 592dcc977a..b9d30e2d89 100644
--- a/crypto/BouncyCastle.Android.csproj
+++ b/crypto/BouncyCastle.Android.csproj
@@ -1057,6 +1057,7 @@
     <Compile Include="src\crypto\signers\Ed25519Signer.cs" />
     <Compile Include="src\crypto\signers\Ed448phSigner.cs" />
     <Compile Include="src\crypto\signers\Ed448Signer.cs" />
+    <Compile Include="src\crypto\signers\EdDsa25519Signer.cs" />
     <Compile Include="src\crypto\signers\GOST3410DigestSigner.cs" />
     <Compile Include="src\crypto\signers\GOST3410Signer.cs" />
     <Compile Include="src\crypto\signers\GenericSigner.cs" />
@@ -1486,7 +1487,8 @@
     <Compile Include="src\openpgp\PgpUtilities.cs" />
     <Compile Include="src\openpgp\PgpV3SignatureGenerator.cs" />
     <Compile Include="src\openpgp\Rfc6637Utilities.cs" />
-    <Compile Include="src\openpgp\SXprUtilities.cs" />
+    <Compile Include="src\openpgp\SXprReader.cs" />
+    <Compile Include="src\openpgp\SXprWriter.cs" />
     <Compile Include="src\openpgp\WrappedGeneratorStream.cs" />
     <Compile Include="src\openssl\EncryptionException.cs" />
     <Compile Include="src\openssl\IPasswordFinder.cs" />
diff --git a/crypto/BouncyCastle.csproj b/crypto/BouncyCastle.csproj
index 26db97e1b9..709f383567 100644
--- a/crypto/BouncyCastle.csproj
+++ b/crypto/BouncyCastle.csproj
@@ -1051,6 +1051,7 @@
     <Compile Include="src\crypto\signers\Ed25519Signer.cs" />
     <Compile Include="src\crypto\signers\Ed448phSigner.cs" />
     <Compile Include="src\crypto\signers\Ed448Signer.cs" />
+    <Compile Include="src\crypto\signers\EdDsa25519Signer.cs" />
     <Compile Include="src\crypto\signers\GOST3410DigestSigner.cs" />
     <Compile Include="src\crypto\signers\GOST3410Signer.cs" />
     <Compile Include="src\crypto\signers\GenericSigner.cs" />
@@ -1480,7 +1481,8 @@
     <Compile Include="src\openpgp\PgpUtilities.cs" />
     <Compile Include="src\openpgp\PgpV3SignatureGenerator.cs" />
     <Compile Include="src\openpgp\Rfc6637Utilities.cs" />
-    <Compile Include="src\openpgp\SXprUtilities.cs" />
+    <Compile Include="src\openpgp\SXprReader.cs" />
+    <Compile Include="src\openpgp\SXprWriter.cs" />
     <Compile Include="src\openpgp\WrappedGeneratorStream.cs" />
     <Compile Include="src\openssl\EncryptionException.cs" />
     <Compile Include="src\openssl\IPasswordFinder.cs" />
diff --git a/crypto/BouncyCastle.iOS.csproj b/crypto/BouncyCastle.iOS.csproj
index 17fe3dda53..d6b2fc7461 100644
--- a/crypto/BouncyCastle.iOS.csproj
+++ b/crypto/BouncyCastle.iOS.csproj
@@ -1052,6 +1052,7 @@
     <Compile Include="src\crypto\signers\Ed25519Signer.cs" />
     <Compile Include="src\crypto\signers\Ed448phSigner.cs" />
     <Compile Include="src\crypto\signers\Ed448Signer.cs" />
+    <Compile Include="src\crypto\signers\EdDsa25519Signer.cs" />
     <Compile Include="src\crypto\signers\GOST3410DigestSigner.cs" />
     <Compile Include="src\crypto\signers\GOST3410Signer.cs" />
     <Compile Include="src\crypto\signers\GenericSigner.cs" />
@@ -1481,7 +1482,8 @@
     <Compile Include="src\openpgp\PgpUtilities.cs" />
     <Compile Include="src\openpgp\PgpV3SignatureGenerator.cs" />
     <Compile Include="src\openpgp\Rfc6637Utilities.cs" />
-    <Compile Include="src\openpgp\SXprUtilities.cs" />
+    <Compile Include="src\openpgp\SXprReader.cs" />
+    <Compile Include="src\openpgp\SXprWriter.cs" />
     <Compile Include="src\openpgp\WrappedGeneratorStream.cs" />
     <Compile Include="src\openssl\EncryptionException.cs" />
     <Compile Include="src\openssl\IPasswordFinder.cs" />
diff --git a/crypto/crypto.csproj b/crypto/crypto.csproj
index 1ba78a83c4..95e0a3b3c3 100644
--- a/crypto/crypto.csproj
+++ b/crypto/crypto.csproj
@@ -5143,6 +5143,11 @@
                     SubType = "Code"
                     BuildAction = "Compile"
                 />
+                <File
+                    RelPath = "src\crypto\signers\EdDsa25519Signer.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
                 <File
                     RelPath = "src\crypto\signers\GenericSigner.cs"
                     SubType = "Code"
@@ -7289,7 +7294,12 @@
                     BuildAction = "Compile"
                 />
                 <File
-                    RelPath = "src\openpgp\SXprUtilities.cs"
+                    RelPath = "src\openpgp\SXprReader.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\openpgp\SXprWriter.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
                 />
diff --git a/crypto/src/crypto/signers/EdDsa255519Signer.cs b/crypto/src/crypto/signers/EdDsa255519Signer.cs
deleted file mode 100644
index 345f5ddb23..0000000000
--- a/crypto/src/crypto/signers/EdDsa255519Signer.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using System;
-
-using Org.BouncyCastle.Math;
-
-namespace Org.BouncyCastle.Crypto.Signers
-{
-	public class EdDsa22519Signer : IDsa
-	{
-		private Ed25519Signer signer;
-
-		public virtual string AlgorithmName => "EDDSA";
-
-		public EdDsa22519Signer()
-		{
-			signer = new Ed25519Signer();
-		}
-
-		public virtual void Init(bool forSigning, ICipherParameters parameters)
-		{
-			signer.Init(forSigning, parameters);
-		}
-
-		public virtual BigInteger[] GenerateSignature(byte[] message)
-		{
-			signer.BlockUpdate(message, 0, message.Length);
-			byte[] sigBytes = signer.GenerateSignature();
-			byte[] rBytes = new byte[32];
-			Array.Copy(sigBytes, rBytes, 32);
-			byte[] sBytes = new byte[sigBytes.Length - 32];
-			Array.Copy(sigBytes, 32, sBytes, 0, sigBytes.Length - 32);
-			BigInteger r = new BigInteger(1, rBytes);
-			BigInteger s = new BigInteger(1, sBytes);
-			return new BigInteger[2] { r, s };
-		}
-
-		public virtual bool VerifySignature(byte[] message, BigInteger r, BigInteger s)
-		{
-			signer.BlockUpdate(message, 0, message.Length);
-			byte[] rBytes = r.ToByteArrayUnsigned();
-			byte[] sBytes = s.ToByteArrayUnsigned();
-			byte[] sigBytes = new byte[rBytes.Length + sBytes.Length];
-			Array.Copy(rBytes, sigBytes, rBytes.Length);
-			Array.Copy(sBytes, 0, sigBytes, rBytes.Length, sBytes.Length);
-			return signer.VerifySignature(sigBytes);
-		}
-	}
-}

From 8cbf92dd15ea27d8f6c4c89518543d2f08a1e6d9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Tom=C3=A1=C5=A1ek?= <tomasek@emclient.com>
Date: Wed, 14 Jul 2021 13:24:33 +0200
Subject: [PATCH 8/8] Add missing file Fixed signature bytes array length for
 EdDSA22519

---
 crypto/src/crypto/signers/EdDsa25519Signer.cs | 47 +++++++++++++++++++
 1 file changed, 47 insertions(+)
 create mode 100644 crypto/src/crypto/signers/EdDsa25519Signer.cs

diff --git a/crypto/src/crypto/signers/EdDsa25519Signer.cs b/crypto/src/crypto/signers/EdDsa25519Signer.cs
new file mode 100644
index 0000000000..8a8eb0c9fb
--- /dev/null
+++ b/crypto/src/crypto/signers/EdDsa25519Signer.cs
@@ -0,0 +1,47 @@
+using System;
+
+using Org.BouncyCastle.Math;
+
+namespace Org.BouncyCastle.Crypto.Signers
+{
+	public class EdDsa22519Signer : IDsa
+	{
+		private Ed25519Signer signer;
+
+		public virtual string AlgorithmName => "EDDSA";
+
+		public EdDsa22519Signer()
+		{
+			signer = new Ed25519Signer();
+		}
+
+		public virtual void Init(bool forSigning, ICipherParameters parameters)
+		{
+			signer.Init(forSigning, parameters);
+		}
+
+		public virtual BigInteger[] GenerateSignature(byte[] message)
+		{
+			signer.BlockUpdate(message, 0, message.Length);
+			byte[] sigBytes = signer.GenerateSignature();
+			byte[] rBytes = new byte[32];
+			Array.Copy(sigBytes, rBytes, 32);
+			byte[] sBytes = new byte[sigBytes.Length - 32];
+			Array.Copy(sigBytes, 32, sBytes, 0, sigBytes.Length - 32);
+			BigInteger r = new BigInteger(1, rBytes);
+			BigInteger s = new BigInteger(1, sBytes);
+			return new BigInteger[2] { r, s };
+		}
+
+		public virtual bool VerifySignature(byte[] message, BigInteger r, BigInteger s)
+		{
+			signer.BlockUpdate(message, 0, message.Length);
+			byte[] rBytes = r.ToByteArrayUnsigned();
+			byte[] sBytes = s.ToByteArrayUnsigned();
+			byte[] sigBytes = new byte[64];
+			Array.Copy(rBytes, 0, sigBytes, 32 - rBytes.Length, rBytes.Length);
+			Array.Copy(sBytes, 0, sigBytes, 64 - sBytes.Length, sBytes.Length);
+			return signer.VerifySignature(sigBytes);
+		}
+	}
+}