summary refs log tree commit diff
path: root/crypto/src/openpgp
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2015-10-12 15:49:54 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2015-10-12 15:49:54 +0700
commit06ba713c9b19102310675a6c58e07c68d8efb3c7 (patch)
tree2d4e747d988f74abca2a5513713e4ff0e8ed8e69 /crypto/src/openpgp
parentAdd new file entries (diff)
downloadBouncyCastle.NET-ed25519-06ba713c9b19102310675a6c58e07c68d8efb3c7.tar.xz
Port of latest PGP tests and supporting code changes
Diffstat (limited to 'crypto/src/openpgp')
-rw-r--r--crypto/src/openpgp/PgpEncryptedDataGenerator.cs154
-rw-r--r--crypto/src/openpgp/PgpKeyPair.cs2
-rw-r--r--crypto/src/openpgp/PgpPad.cs45
-rw-r--r--crypto/src/openpgp/PgpPrivateKey.cs37
-rw-r--r--crypto/src/openpgp/PgpPublicKey.cs132
-rw-r--r--crypto/src/openpgp/PgpPublicKeyEncryptedData.cs140
-rw-r--r--crypto/src/openpgp/PgpSecretKey.cs235
-rw-r--r--crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs23
-rw-r--r--crypto/src/openpgp/PgpSignatureSubpacketVector.cs12
-rw-r--r--crypto/src/openpgp/PgpUtilities.cs29
-rw-r--r--crypto/src/openpgp/Rfc6637Utilities.cs138
-rw-r--r--crypto/src/openpgp/SXprUtilities.cs102
12 files changed, 863 insertions, 186 deletions
diff --git a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
index f46f99d37..2a2e63961 100644
--- a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
+++ b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
@@ -3,10 +3,13 @@ using System.Collections;
 using System.Diagnostics;
 using System.IO;
 
+using Org.BouncyCastle.Asn1.X9;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.IO;
+using Org.BouncyCastle.Crypto.Generators;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Math.EC;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 
@@ -79,70 +82,133 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             : EncMethod
         {
 			internal PgpPublicKey pubKey;
-            internal BigInteger[] data;
+            internal byte[][] data;
 
-			internal PubMethod(
-                PgpPublicKey pubKey)
+            internal PubMethod(PgpPublicKey pubKey)
             {
                 this.pubKey = pubKey;
             }
 
-			public override void AddSessionInfo(
-                byte[]			si,
+            public override void AddSessionInfo(
+                byte[]			sessionInfo,
 				SecureRandom	random)
             {
-                IBufferedCipher c;
+                byte[] encryptedSessionInfo = EncryptSessionInfo(sessionInfo, random);
+
+                this.data = ProcessSessionInfo(encryptedSessionInfo);
+            }
 
-				switch (pubKey.Algorithm)
+            private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random)
+            {
+                if (pubKey.Algorithm != PublicKeyAlgorithmTag.ECDH)
                 {
-                    case PublicKeyAlgorithmTag.RsaEncrypt:
-                    case PublicKeyAlgorithmTag.RsaGeneral:
-                        c = CipherUtilities.GetCipher("RSA//PKCS1Padding");
-                        break;
-                    case PublicKeyAlgorithmTag.ElGamalEncrypt:
-                    case PublicKeyAlgorithmTag.ElGamalGeneral:
-                        c = CipherUtilities.GetCipher("ElGamal/ECB/PKCS1Padding");
-                        break;
-                    case PublicKeyAlgorithmTag.Dsa:
-                        throw new PgpException("Can't use DSA for encryption.");
-                    case PublicKeyAlgorithmTag.ECDsa:
-                        throw new PgpException("Can't use ECDSA for encryption.");
-                    default:
-                        throw new PgpException("unknown asymmetric algorithm: " + pubKey.Algorithm);
+                    IBufferedCipher c;
+				    switch (pubKey.Algorithm)
+                    {
+                        case PublicKeyAlgorithmTag.RsaEncrypt:
+                        case PublicKeyAlgorithmTag.RsaGeneral:
+                            c = CipherUtilities.GetCipher("RSA//PKCS1Padding");
+                            break;
+                        case PublicKeyAlgorithmTag.ElGamalEncrypt:
+                        case PublicKeyAlgorithmTag.ElGamalGeneral:
+                            c = CipherUtilities.GetCipher("ElGamal/ECB/PKCS1Padding");
+                            break;
+                        case PublicKeyAlgorithmTag.Dsa:
+                            throw new PgpException("Can't use DSA for encryption.");
+                        case PublicKeyAlgorithmTag.ECDsa:
+                            throw new PgpException("Can't use ECDSA for encryption.");
+                        default:
+                            throw new PgpException("unknown asymmetric algorithm: " + pubKey.Algorithm);
+                    }
+
+                    AsymmetricKeyParameter akp = pubKey.GetKey();
+				    c.Init(true, new ParametersWithRandom(akp, random));
+                    return c.DoFinal(sessionInfo);
                 }
 
-				AsymmetricKeyParameter akp = pubKey.GetKey();
+                ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)pubKey.PublicKeyPacket.Key;
+
+                // 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;
+
+                ECPublicKeyParameters pub = (ECPublicKeyParameters)pubKey.GetKey();
+                ECPoint S = pub.Q.Multiply(ephPriv.D).Normalize();
+
+                KeyParameter key = new KeyParameter(Rfc6637Utilities.CreateKey(pubKey.PublicKeyPacket, S));
+
+                IWrapper w = PgpUtilities.CreateWrapper(ecKey.SymmetricKeyAlgorithm);
+                w.Init(true, new ParametersWithRandom(key, random));
+
+                byte[] paddedSessionData = PgpPad.PadSessionData(sessionInfo);
+
+                byte[] C = w.Wrap(paddedSessionData, 0, paddedSessionData.Length);
+                byte[] VB = new MPInteger(new BigInteger(1, ephPub.Q.GetEncoded(false))).GetEncoded();
 
-				c.Init(true, new ParametersWithRandom(akp, random));
+                byte[] rv = new byte[VB.Length + 1 + C.Length];
 
-				byte[] encKey = c.DoFinal(si);
+                Array.Copy(VB, 0, rv, 0, VB.Length);
+                rv[VB.Length] = (byte)C.Length;
+                Array.Copy(C, 0, rv, VB.Length + 1, C.Length);
 
-				switch (pubKey.Algorithm)
+                return rv;
+            }
+
+            private byte[][] ProcessSessionInfo(byte[] encryptedSessionInfo)
+            {
+                byte[][] data;
+
+                switch (pubKey.Algorithm)
                 {
-                    case PublicKeyAlgorithmTag.RsaEncrypt:
-                    case PublicKeyAlgorithmTag.RsaGeneral:
-						data = new BigInteger[]{ new BigInteger(1, encKey) };
-                        break;
-                    case PublicKeyAlgorithmTag.ElGamalEncrypt:
-                    case PublicKeyAlgorithmTag.ElGamalGeneral:
-						int halfLength = encKey.Length / 2;
-						data = new BigInteger[]
-						{
-							new BigInteger(1, encKey, 0, halfLength),
-							new BigInteger(1, encKey, halfLength, halfLength)
-						};
-                        break;
-                    default:
-                        throw new PgpException("unknown asymmetric algorithm: " + encAlgorithm);
+                case PublicKeyAlgorithmTag.RsaEncrypt:
+                case PublicKeyAlgorithmTag.RsaGeneral:
+                    data = new byte[][] { ConvertToEncodedMpi(encryptedSessionInfo) };
+                    break;
+                case PublicKeyAlgorithmTag.ElGamalEncrypt:
+                case PublicKeyAlgorithmTag.ElGamalGeneral:
+                    int halfLength = encryptedSessionInfo.Length / 2;
+                    byte[] b1 = new byte[halfLength];
+                    byte[] b2 = new byte[halfLength];
+
+                    Array.Copy(encryptedSessionInfo, 0, b1, 0, halfLength);
+                    Array.Copy(encryptedSessionInfo, halfLength, b2, 0, halfLength);
+
+                    data = new byte[][] {
+                        ConvertToEncodedMpi(b1),
+                        ConvertToEncodedMpi(b2),
+                    };
+                    break;
+                case PublicKeyAlgorithmTag.ECDH:
+                    data = new byte[][]{ encryptedSessionInfo };
+                    break;
+                default:
+                    throw new PgpException("unknown asymmetric algorithm: " + pubKey.Algorithm);
                 }
+
+                return data;
             }
 
-			public override void Encode(BcpgOutputStream pOut)
+            private byte[] ConvertToEncodedMpi(byte[] encryptedSessionInfo)
+            {
+                try
+                {
+                    return new MPInteger(new BigInteger(1, encryptedSessionInfo)).GetEncoded();
+                }
+                catch (IOException e)
+                {
+                    throw new PgpException("Invalid MPI encoding: " + e.Message, e);
+                }
+            }
+
+            public override void Encode(BcpgOutputStream pOut)
             {
-                PublicKeyEncSessionPacket pk = new PublicKeyEncSessionPacket(
-                    pubKey.KeyId, pubKey.Algorithm, data);
+                PublicKeyEncSessionPacket pk = new PublicKeyEncSessionPacket(pubKey.KeyId, pubKey.Algorithm, data);
 
-				pOut.WritePacket(pk);
+                pOut.WritePacket(pk);
             }
         }
 
diff --git a/crypto/src/openpgp/PgpKeyPair.cs b/crypto/src/openpgp/PgpKeyPair.cs
index 6efb03a42..9cf78fa6f 100644
--- a/crypto/src/openpgp/PgpKeyPair.cs
+++ b/crypto/src/openpgp/PgpKeyPair.cs
@@ -34,7 +34,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             DateTime				time)
         {
             this.pub = new PgpPublicKey(algorithm, pubKey, time);
-			this.priv = new PgpPrivateKey(privKey, pub.KeyId);
+			this.priv = new PgpPrivateKey(pub.KeyId, pub.PublicKeyPacket, privKey);
         }
 
 		/// <summary>Create a key pair from a PgpPrivateKey and a PgpPublicKey.</summary>
diff --git a/crypto/src/openpgp/PgpPad.cs b/crypto/src/openpgp/PgpPad.cs
new file mode 100644
index 000000000..48f7f2f44
--- /dev/null
+++ b/crypto/src/openpgp/PgpPad.cs
@@ -0,0 +1,45 @@
+using System;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+    /// <remarks>Padding functions.</remarks>
+    public sealed class PgpPad
+    {
+        private PgpPad()
+        {
+        }
+
+        public static byte[] PadSessionData(byte[] sessionInfo)
+        {
+            byte[] result = new byte[40];
+
+            Array.Copy(sessionInfo, 0, result, 0, sessionInfo.Length);
+
+            byte padValue = (byte)(result.Length - sessionInfo.Length);
+
+            for (int i = sessionInfo.Length; i != result.Length; i++)
+            {
+                result[i] = padValue;
+            }
+
+            return result;
+        }
+
+        public static byte[] UnpadSessionData(byte[] encoded)
+        {
+            byte padValue = encoded[encoded.Length - 1];
+
+            for (int i = encoded.Length - padValue; i != encoded.Length; i++)
+            {
+                if (encoded[i] != padValue)
+                    throw new PgpException("bad padding found in session data");
+            }
+
+            byte[] taggedKey = new byte[encoded.Length - padValue];
+
+            Array.Copy(encoded, 0, taggedKey, 0, taggedKey.Length);
+
+            return taggedKey;
+        }
+    }
+}
diff --git a/crypto/src/openpgp/PgpPrivateKey.cs b/crypto/src/openpgp/PgpPrivateKey.cs
index 154c87cd7..61487a5b2 100644
--- a/crypto/src/openpgp/PgpPrivateKey.cs
+++ b/crypto/src/openpgp/PgpPrivateKey.cs
@@ -7,33 +7,42 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 	/// <remarks>General class to contain a private key for use with other OpenPGP objects.</remarks>
     public class PgpPrivateKey
     {
-        private readonly long keyId;
+        private readonly long keyID;
+        private readonly PublicKeyPacket publicKeyPacket;
         private readonly AsymmetricKeyParameter privateKey;
 
-		/// <summary>
-		/// Create a PgpPrivateKey from a regular private key and the ID of its
-		/// associated public key.
+        /// <summary>
+		/// Create a PgpPrivateKey from a keyID, the associated public data packet, and a regular private key.
 		/// </summary>
-		/// <param name="privateKey">Private key to use.</param>
-		/// <param name="keyId">ID of the corresponding public key.</param>
-		public PgpPrivateKey(
-            AsymmetricKeyParameter	privateKey,
-            long					keyId)
+		/// <param name="keyID">ID of the corresponding public key.</param>
+        /// <param name="publicKeyPacket">the public key data packet to be associated with this private key.</param>
+        /// <param name="privateKey">the private key data packet to be associated with this private key.</param>
+        public PgpPrivateKey(
+            long                    keyID,
+            PublicKeyPacket         publicKeyPacket,
+            AsymmetricKeyParameter	privateKey)
         {
 			if (!privateKey.IsPrivate)
 				throw new ArgumentException("Expected a private key", "privateKey");
 
-			this.privateKey = privateKey;
-            this.keyId = keyId;
+            this.keyID = keyID;
+            this.publicKeyPacket = publicKeyPacket;
+            this.privateKey = privateKey;
         }
 
-		/// <summary>The keyId associated with the contained private key.</summary>
+        /// <summary>The keyId associated with the contained private key.</summary>
         public long KeyId
         {
-			get { return keyId; }
+			get { return keyID; }
         }
 
-		/// <summary>The contained private key.</summary>
+        /// <summary>The public key packet associated with this private key, if available.</summary>
+        public PublicKeyPacket PublicKeyPacket
+        {
+            get { return publicKeyPacket; }
+        }
+
+        /// <summary>The contained private key.</summary>
         public AsymmetricKeyParameter Key
         {
 			get { return privateKey; }
diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs
index 5bde2c8fe..904e29913 100644
--- a/crypto/src/openpgp/PgpPublicKey.cs
+++ b/crypto/src/openpgp/PgpPublicKey.cs
@@ -2,10 +2,14 @@ using System;
 using System.Collections;
 using System.IO;
 
+using Org.BouncyCastle.Asn1.Sec;
 using Org.BouncyCastle.Asn1.X9;
 using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Generators;
 using Org.BouncyCastle.Crypto.IO;
 using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Math.EC;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Collections;
@@ -15,6 +19,54 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
     /// <remarks>General class to handle a PGP public key object.</remarks>
     public class PgpPublicKey
     {
+        public static byte[] CalculateFingerprint(PublicKeyPacket publicPk)
+        {
+            IBcpgKey key = publicPk.Key;
+            IDigest digest;
+
+            if (publicPk.Version <= 3)
+            {
+                RsaPublicBcpgKey rK = (RsaPublicBcpgKey)key;
+
+                try
+                {
+                    digest = DigestUtilities.GetDigest("MD5");
+                    UpdateDigest(digest, rK.Modulus);
+                    UpdateDigest(digest, rK.PublicExponent);
+                }
+                catch (Exception e)
+                {
+                    throw new PgpException("can't encode key components: " + e.Message, e);
+                }
+            }
+            else
+            {
+                try
+                {
+                    byte[] kBytes = publicPk.GetEncodedContents();
+
+                    digest = DigestUtilities.GetDigest("SHA1");
+
+                    digest.Update(0x99);
+                    digest.Update((byte)(kBytes.Length >> 8));
+                    digest.Update((byte)kBytes.Length);
+                    digest.BlockUpdate(kBytes, 0, kBytes.Length);
+                }
+                catch (Exception e)
+                {
+                    throw new PgpException("can't encode key components: " + e.Message, e);
+                }
+            }
+
+            return DigestUtilities.DoFinal(digest);
+        }
+
+        private static void UpdateDigest(IDigest d, BigInteger b)
+        {
+            byte[] bytes = b.ToByteArrayUnsigned();
+            d.BlockUpdate(bytes, 0, bytes.Length);
+        }
+
         private static readonly int[] MasterKeyCertificationTypes = new int[]
         {
             PgpSignature.PositiveCertification,
@@ -39,51 +91,17 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         {
             IBcpgKey key = publicPk.Key;
 
+            this.fingerprint = CalculateFingerprint(publicPk);
+
             if (publicPk.Version <= 3)
             {
                 RsaPublicBcpgKey rK = (RsaPublicBcpgKey) key;
 
                 this.keyId = rK.Modulus.LongValue;
-
-                try
-                {
-                    IDigest digest = DigestUtilities.GetDigest("MD5");
-
-                    byte[] bytes = rK.Modulus.ToByteArrayUnsigned();
-                    digest.BlockUpdate(bytes, 0, bytes.Length);
-
-                    bytes = rK.PublicExponent.ToByteArrayUnsigned();
-                    digest.BlockUpdate(bytes, 0, bytes.Length);
-
-                    this.fingerprint = DigestUtilities.DoFinal(digest);
-                }
-                //catch (NoSuchAlgorithmException)
-                catch (Exception e)
-                {
-                    throw new IOException("can't find MD5", e);
-                }
-
                 this.keyStrength = rK.Modulus.BitLength;
             }
             else
             {
-                byte[] kBytes = publicPk.GetEncodedContents();
-
-                try
-                {
-                    IDigest digest = DigestUtilities.GetDigest("SHA1");
-
-                    digest.Update(0x99);
-                    digest.Update((byte)(kBytes.Length >> 8));
-                    digest.Update((byte)kBytes.Length);
-                    digest.BlockUpdate(kBytes, 0, kBytes.Length);
-                    this.fingerprint = DigestUtilities.DoFinal(digest);
-                }
-                catch (Exception e)
-                {
-                    throw new IOException("can't find SHA1", e);
-                }
-
                 this.keyId = (long)(((ulong)fingerprint[fingerprint.Length - 8] << 56)
                     | ((ulong)fingerprint[fingerprint.Length - 7] << 48)
                     | ((ulong)fingerprint[fingerprint.Length - 6] << 40)
@@ -107,7 +125,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 }
                 else if (key is ECPublicBcpgKey)
                 {
-                    this.keyStrength = ECNamedCurveTable.GetByOid(((ECPublicBcpgKey)key).CurveOid).Curve.FieldSize;
+                    this.keyStrength = ECKeyPairGenerator.FindECCurveByOid(((ECPublicBcpgKey)key).CurveOid).Curve.FieldSize;
                 }
             }
         }
@@ -146,6 +164,23 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
                 bcpgKey = new DsaPublicBcpgKey(dP.P, dP.Q, dP.G, dK.Y);
             }
+            else if (pubKey is ECPublicKeyParameters)
+            {
+                ECPublicKeyParameters ecK = (ECPublicKeyParameters)pubKey;
+
+                if (algorithm == PublicKeyAlgorithmTag.ECDH)
+                {
+                    bcpgKey = new ECDHPublicBcpgKey(ecK.PublicKeyParamSet, ecK.Q, HashAlgorithmTag.Sha256, SymmetricKeyAlgorithmTag.Aes128);
+                }
+                else if (algorithm == PublicKeyAlgorithmTag.ECDsa)
+                {
+                    bcpgKey = new ECDsaPublicBcpgKey(ecK.PublicKeyParamSet, ecK.Q);
+                }
+                else
+                {
+                    throw new PgpException("unknown EC algorithm");
+                }
+            }
             else if (pubKey is ElGamalPublicKeyParameters)
             {
                 ElGamalPublicKeyParameters eK = (ElGamalPublicKeyParameters) pubKey;
@@ -172,6 +207,11 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
         }
 
+        public PgpPublicKey(PublicKeyPacket publicPk)
+            : this(publicPk, Platform.CreateArrayList(), Platform.CreateArrayList())
+        {
+        }
+
         /// <summary>Constructor for a sub-key.</summary>
         internal PgpPublicKey(
             PublicKeyPacket	publicPk,
@@ -426,14 +466,18 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                     case PublicKeyAlgorithmTag.RsaEncrypt:
                     case PublicKeyAlgorithmTag.RsaGeneral:
                     case PublicKeyAlgorithmTag.RsaSign:
-                        RsaPublicBcpgKey rsaK = (RsaPublicBcpgKey) publicPk.Key;
+                        RsaPublicBcpgKey rsaK = (RsaPublicBcpgKey)publicPk.Key;
                         return new RsaKeyParameters(false, rsaK.Modulus, rsaK.PublicExponent);
                     case PublicKeyAlgorithmTag.Dsa:
-                        DsaPublicBcpgKey dsaK = (DsaPublicBcpgKey) publicPk.Key;
+                        DsaPublicBcpgKey dsaK = (DsaPublicBcpgKey)publicPk.Key;
                         return new DsaPublicKeyParameters(dsaK.Y, new DsaParameters(dsaK.P, dsaK.Q, dsaK.G));
+                    case PublicKeyAlgorithmTag.ECDsa:
+                        return GetECKey("ECDSA");
+                    case PublicKeyAlgorithmTag.ECDH:
+                        return GetECKey("ECDH");
                     case PublicKeyAlgorithmTag.ElGamalEncrypt:
                     case PublicKeyAlgorithmTag.ElGamalGeneral:
-                        ElGamalPublicBcpgKey elK = (ElGamalPublicBcpgKey) publicPk.Key;
+                        ElGamalPublicBcpgKey elK = (ElGamalPublicBcpgKey)publicPk.Key;
                         return new ElGamalPublicKeyParameters(elK.Y, new ElGamalParameters(elK.P, elK.G));
                     default:
                         throw new PgpException("unknown public key algorithm encountered");
@@ -449,6 +493,14 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
         }
 
+        private ECPublicKeyParameters GetECKey(string algorithm)
+        {
+            ECPublicBcpgKey ecK = (ECPublicBcpgKey)publicPk.Key;
+            X9ECParameters x9 = ECKeyPairGenerator.FindECCurveByOid(ecK.CurveOid);
+            ECPoint q = x9.Curve.DecodePoint(BigIntegers.AsUnsignedByteArray(ecK.EncodedPoint));
+            return new ECPublicKeyParameters(algorithm, q, ecK.CurveOid);
+        }
+
         /// <summary>Allows enumeration of any user IDs associated with the key.</summary>
         /// <returns>An <c>IEnumerable</c> of <c>string</c> objects.</returns>
         public IEnumerable GetUserIds()
diff --git a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
index b6504cbcd..c2a351182 100644
--- a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
+++ b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
@@ -1,10 +1,13 @@
 using System;
 using System.IO;
 
+using Org.BouncyCastle.Asn1.X9;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.IO;
+using Org.BouncyCastle.Crypto.Generators;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Math.EC;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities.IO;
 
@@ -77,22 +80,29 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		public SymmetricKeyAlgorithmTag GetSymmetricAlgorithm(
 			PgpPrivateKey privKey)
 		{
-			byte[] plain = fetchSymmetricKeyData(privKey);
+			byte[] sessionData = RecoverSessionData(privKey);
 
-			return (SymmetricKeyAlgorithmTag) plain[0];
+            return (SymmetricKeyAlgorithmTag)sessionData[0];
 		}
 
-		/// <summary>Return the decrypted data stream for the packet.</summary>
+        /// <summary>Return the decrypted data stream for the packet.</summary>
         public Stream GetDataStream(
             PgpPrivateKey privKey)
         {
-			byte[] plain = fetchSymmetricKeyData(privKey);
+			byte[] sessionData = RecoverSessionData(privKey);
 
-			IBufferedCipher c2;
-			string cipherName = PgpUtilities.GetSymmetricCipherName((SymmetricKeyAlgorithmTag) plain[0]);
+            if (!ConfirmCheckSum(sessionData))
+                throw new PgpKeyValidationException("key checksum failed");
+
+            SymmetricKeyAlgorithmTag symmAlg = (SymmetricKeyAlgorithmTag)sessionData[0];
+            if (symmAlg == SymmetricKeyAlgorithmTag.Null)
+                return encData.GetInputStream();
+
+            IBufferedCipher cipher;
+			string cipherName = PgpUtilities.GetSymmetricCipherName(symmAlg);
 			string cName = cipherName;
 
-			try
+            try
             {
                 if (encData is SymmetricEncIntegrityPacket)
                 {
@@ -103,7 +113,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 					cName += "/OpenPGPCFB/NoPadding";
                 }
 
-				c2 = CipherUtilities.GetCipher(cName);
+                cipher = CipherUtilities.GetCipher(cName);
 			}
             catch (PgpException e)
             {
@@ -114,19 +124,16 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 throw new PgpException("exception creating cipher", e);
             }
 
-			if (c2 == null)
-				return encData.GetInputStream();
-
-			try
+            try
             {
 				KeyParameter key = ParameterUtilities.CreateKeyParameter(
-					cipherName, plain, 1, plain.Length - 3);
+					cipherName, sessionData, 1, sessionData.Length - 3);
 
-				byte[] iv = new byte[c2.GetBlockSize()];
+                byte[] iv = new byte[cipher.GetBlockSize()];
 
-				c2.Init(false, new ParametersWithIV(key, iv));
+                cipher.Init(false, new ParametersWithIV(key, iv));
 
-                encStream = BcpgInputStream.Wrap(new CipherStream(encData.GetInputStream(), c2, null));
+                encStream = BcpgInputStream.Wrap(new CipherStream(encData.GetInputStream(), cipher, null));
 
 				if (encData is SymmetricEncIntegrityPacket)
                 {
@@ -178,75 +185,88 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
 		}
 
-		private byte[] fetchSymmetricKeyData(
-			PgpPrivateKey privKey)
+        private byte[] RecoverSessionData(PgpPrivateKey privKey)
 		{
-			IBufferedCipher c1 = GetKeyCipher(keyData.Algorithm);
+            byte[][] secKeyData = keyData.GetEncSessionKey();
+
+            if (keyData.Algorithm == PublicKeyAlgorithmTag.ECDH)
+            {
+                ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)privKey.PublicKeyPacket.Key;
+                X9ECParameters x9Params = ECKeyPairGenerator.FindECCurveByOid(ecKey.CurveOid);
+
+                byte[] enc = secKeyData[0];
+
+                int pLen = ((((enc[0] & 0xff) << 8) + (enc[1] & 0xff)) + 7) / 8;
+                byte[] pEnc = new byte[pLen];
+
+                Array.Copy(enc, 2, pEnc, 0, pLen);
+
+                byte[] keyEnc = new byte[enc[pLen + 2]];
+
+                Array.Copy(enc, 2 + pLen + 1, keyEnc, 0, keyEnc.Length);
+
+                ECPoint publicPoint = x9Params.Curve.DecodePoint(pEnc);
+
+                ECPrivateKeyParameters privKeyParams = (ECPrivateKeyParameters)privKey.Key;
+                ECPoint S = publicPoint.Multiply(privKeyParams.D).Normalize();
+
+                KeyParameter key = new KeyParameter(Rfc6637Utilities.CreateKey(privKey.PublicKeyPacket, S));
+
+                IWrapper w = PgpUtilities.CreateWrapper(ecKey.SymmetricKeyAlgorithm);
+                w.Init(false, key);
 
-			try
+                return PgpPad.UnpadSessionData(w.Unwrap(keyEnc, 0, keyEnc.Length));
+            }
+
+            IBufferedCipher cipher = GetKeyCipher(keyData.Algorithm);
+
+            try
 			{
-				c1.Init(false, privKey.Key);
+                cipher.Init(false, privKey.Key);
 			}
 			catch (InvalidKeyException e)
 			{
 				throw new PgpException("error setting asymmetric cipher", e);
 			}
 
-			BigInteger[] keyD = keyData.GetEncSessionKey();
-
-			if (keyData.Algorithm == PublicKeyAlgorithmTag.RsaEncrypt
+            if (keyData.Algorithm == PublicKeyAlgorithmTag.RsaEncrypt
 				|| keyData.Algorithm == PublicKeyAlgorithmTag.RsaGeneral)
 			{
-				c1.ProcessBytes(keyD[0].ToByteArrayUnsigned());
+                byte[] bi = secKeyData[0];
+
+                cipher.ProcessBytes(bi, 2, bi.Length - 2);
 			}
 			else
 			{
 				ElGamalPrivateKeyParameters k = (ElGamalPrivateKeyParameters)privKey.Key;
 				int size = (k.Parameters.P.BitLength + 7) / 8;
 
-				byte[] bi = keyD[0].ToByteArray();
-
-				int diff = bi.Length - size;
-				if (diff >= 0)
-				{
-					c1.ProcessBytes(bi, diff, size);
-				}
-				else
-				{
-					byte[] zeros = new byte[-diff];
-					c1.ProcessBytes(zeros);
-					c1.ProcessBytes(bi);
-				}
-
-				bi = keyD[1].ToByteArray();
-
-				diff = bi.Length - size;
-				if (diff >= 0)
-				{
-					c1.ProcessBytes(bi, diff, size);
-				}
-				else
-				{
-					byte[] zeros = new byte[-diff];
-					c1.ProcessBytes(zeros);
-					c1.ProcessBytes(bi);
-				}
+                ProcessEncodedMpi(cipher, size, secKeyData[0]);
+                ProcessEncodedMpi(cipher, size, secKeyData[1]);
 			}
 
-			byte[] plain;
-			try
+            try
 			{
-				plain = c1.DoFinal();
+                return cipher.DoFinal();
 			}
 			catch (Exception e)
 			{
 				throw new PgpException("exception decrypting secret key", e);
 			}
-
-			if (!ConfirmCheckSum(plain))
-				throw new PgpKeyValidationException("key checksum failed");
-
-			return plain;
 		}
+
+        private static void ProcessEncodedMpi(IBufferedCipher cipher, int size, byte[] mpiEnc)
+        {
+            if (mpiEnc.Length - 2 > size)  // leading Zero? Shouldn't happen but...
+            {
+                cipher.ProcessBytes(mpiEnc, 3, mpiEnc.Length - 3);
+            }
+            else
+            {
+                byte[] tmp = new byte[size];
+                Array.Copy(mpiEnc, 2, tmp, tmp.Length - (mpiEnc.Length - 2), mpiEnc.Length - 2);
+                cipher.ProcessBytes(tmp, 0, tmp.Length);
+            }
+        }
 	}
 }
diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs
index 872316dd7..980f9222b 100644
--- a/crypto/src/openpgp/PgpSecretKey.cs
+++ b/crypto/src/openpgp/PgpSecretKey.cs
@@ -2,8 +2,11 @@ using System;
 using System.Collections;
 using System.IO;
 
+using Org.BouncyCastle.Asn1.X9;
 using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Generators;
 using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 
@@ -59,6 +62,11 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                     DsaPrivateKeyParameters dsK = (DsaPrivateKeyParameters) privKey.Key;
                     secKey = new DsaSecretBcpgKey(dsK.X);
                     break;
+                case PublicKeyAlgorithmTag.ECDH:
+                case PublicKeyAlgorithmTag.ECDsa:
+                    ECPrivateKeyParameters ecK = (ECPrivateKeyParameters)privKey.Key;
+                    secKey = new ECSecretBcpgKey(ecK.D);
+                    break;
                 case PublicKeyAlgorithmTag.ElGamalEncrypt:
                 case PublicKeyAlgorithmTag.ElGamalGeneral:
                     ElGamalPrivateKeyParameters esK = (ElGamalPrivateKeyParameters) privKey.Key;
@@ -309,24 +317,13 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         private byte[] ExtractKeyData(
             char[] passPhrase)
         {
-            SymmetricKeyAlgorithmTag alg = secret.EncAlgorithm;
+            SymmetricKeyAlgorithmTag encAlgorithm = secret.EncAlgorithm;
             byte[] encData = secret.GetSecretKeyData();
 
-            if (alg == SymmetricKeyAlgorithmTag.Null)
+            if (encAlgorithm == SymmetricKeyAlgorithmTag.Null)
                 // TODO Check checksum here?
                 return encData;
 
-            IBufferedCipher c = null;
-            try
-            {
-                string cName = PgpUtilities.GetSymmetricCipherName(alg);
-                c = CipherUtilities.GetCipher(cName + "/CFB/NoPadding");
-            }
-            catch (Exception e)
-            {
-                throw new PgpException("Exception creating cipher", e);
-            }
-
             // TODO Factor this block out as 'decryptData'
             try
             {
@@ -336,9 +333,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
                 if (secret.PublicKeyPacket.Version >= 4)
                 {
-                    c.Init(false, new ParametersWithIV(key, iv));
-
-                    data = c.DoFinal(encData);
+                    data = RecoverKeyData(encAlgorithm, "/CFB/NoPadding", key, iv, encData, 0, encData.Length);
 
                     bool useSha1 = secret.S2kUsage == SecretKeyPacket.UsageSha1;
                     byte[] check = Checksum(useSha1, data, (useSha1) ? data.Length - 20 : data.Length - 2);
@@ -364,15 +359,14 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
                     for (int i = 0; i != 4; i++)
                     {
-                        c.Init(false, new ParametersWithIV(key, iv));
-
                         int encLen = (((encData[pos] << 8) | (encData[pos + 1] & 0xff)) + 7) / 8;
 
                         data[pos] = encData[pos];
                         data[pos + 1] = encData[pos + 1];
                         pos += 2;
 
-                        c.DoFinal(encData, pos, encLen, data, pos);
+                        byte[] tmp = RecoverKeyData(encAlgorithm, "/CFB/NoPadding", key, iv, encData, pos, encLen);
+                        Array.Copy(tmp, 0, data, pos, encLen);
                         pos += encLen;
 
                         if (i != 3)
@@ -416,6 +410,25 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
         }
 
+        private static byte[] RecoverKeyData(SymmetricKeyAlgorithmTag encAlgorithm, string modeAndPadding,
+            KeyParameter key, byte[] iv, byte[] keyData, int keyOff, int keyLen)
+        {
+            IBufferedCipher c;
+            try
+            {
+                string cName = PgpUtilities.GetSymmetricCipherName(encAlgorithm);
+                c = CipherUtilities.GetCipher(cName + modeAndPadding);
+            }
+            catch (Exception e)
+            {
+                throw new PgpException("Exception creating cipher", e);
+            }
+
+            c.Init(false, new ParametersWithIV(key, iv));
+
+            return c.DoFinal(keyData, keyOff, keyLen);
+        }
+
         /// <summary>Extract a <c>PgpPrivateKey</c> from this secret key's encrypted contents.</summary>
         public PgpPrivateKey ExtractPrivateKey(
             char[] passPhrase)
@@ -453,6 +466,12 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                     DsaParameters dsaParams = new DsaParameters(dsaPub.P, dsaPub.Q, dsaPub.G);
                     privateKey = new DsaPrivateKeyParameters(dsaPriv.X, dsaParams);
                     break;
+                case PublicKeyAlgorithmTag.ECDH:
+                    privateKey = GetECKey("ECDH", bcpgIn);
+                    break;
+                case PublicKeyAlgorithmTag.ECDsa:
+                    privateKey = GetECKey("ECDSA", bcpgIn);
+                    break;
                 case PublicKeyAlgorithmTag.ElGamalEncrypt:
                 case PublicKeyAlgorithmTag.ElGamalGeneral:
                     ElGamalPublicBcpgKey elPub = (ElGamalPublicBcpgKey)pubPk.Key;
@@ -464,7 +483,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                     throw new PgpException("unknown public key algorithm encountered");
                 }
 
-                return new PgpPrivateKey(privateKey, KeyId);
+                return new PgpPrivateKey(KeyId, pubPk, privateKey);
             }
             catch (PgpException e)
             {
@@ -476,6 +495,13 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
         }
 
+        private ECPrivateKeyParameters GetECKey(string algorithm, BcpgInputStream bcpgIn)
+        {
+            ECPublicBcpgKey ecdsaPub = (ECPublicBcpgKey)secret.PublicKeyPacket.Key;
+            ECSecretBcpgKey ecdsaPriv = new ECSecretBcpgKey(bcpgIn);
+            return new ECPrivateKeyParameters(algorithm, ecdsaPriv.X, ecdsaPub.CurveOid);
+        }
+
         private static byte[] Checksum(
             bool	useSha1,
             byte[]	bytes,
@@ -698,5 +724,174 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
             return c.DoFinal(rawKeyData);
         }
+
+        /**
+         * Parse a secret key from one of the GPG S expression keys associating it with the passed in public key.
+         *
+         * @return a secret key object.
+         */
+        public static PgpSecretKey ParseSecretKeyFromSExpr(Stream inputStream, char[] passPhrase, PgpPublicKey pubKey)
+        {
+            SXprUtilities.SkipOpenParenthesis(inputStream);
+
+            string type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+            if (type.Equals("protected-private-key"))
+            {
+                SXprUtilities.SkipOpenParenthesis(inputStream);
+
+                string curveName;
+
+                string keyType = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+                if (keyType.Equals("ecc"))
+                {
+                    SXprUtilities.SkipOpenParenthesis(inputStream);
+
+                    string curveID = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+                    curveName = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+
+                    SXprUtilities.SkipCloseParenthesis(inputStream);
+                }
+                else
+                {
+                    throw new PgpException("no curve details found");
+                }
+
+                byte[] qVal;
+
+                SXprUtilities.SkipOpenParenthesis(inputStream);
+
+                type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+                if (type.Equals("q"))
+                {
+                    qVal = SXprUtilities.ReadBytes(inputStream, inputStream.ReadByte());
+                }
+                else
+                {
+                    throw new PgpException("no q value found");
+                }
+
+                SXprUtilities.SkipCloseParenthesis(inputStream);
+
+                byte[] dValue = GetDValue(inputStream, passPhrase, 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");
+        }
+
+        /**
+        * Parse a secret key from one of the GPG S expression keys.
+        *
+        * @return a secret key object.
+        */
+        public static PgpSecretKey ParseSecretKeyFromSExpr(Stream inputStream, char[] passPhrase)
+        {
+            SXprUtilities.SkipOpenParenthesis(inputStream);
+
+            string type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+            if (type.Equals("protected-private-key"))
+            {
+                SXprUtilities.SkipOpenParenthesis(inputStream);
+
+                string curveName;
+
+                string keyType = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+                if (keyType.Equals("ecc"))
+                {
+                    SXprUtilities.SkipOpenParenthesis(inputStream);
+
+                    string curveID = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+                    curveName = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+
+                    if (curveName.StartsWith("NIST "))
+                    {
+                        curveName = curveName.Substring("NIST ".Length);
+                    }
+
+                    SXprUtilities.SkipCloseParenthesis(inputStream);
+                }
+                else
+                {
+                    throw new PgpException("no curve details found");
+                }
+
+                byte[] qVal;
+
+                SXprUtilities.SkipOpenParenthesis(inputStream);
+
+                type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+                if (type.Equals("q"))
+                {
+                    qVal = SXprUtilities.ReadBytes(inputStream, inputStream.ReadByte());
+                }
+                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)));
+
+                SXprUtilities.SkipCloseParenthesis(inputStream);
+
+                byte[] dValue = GetDValue(inputStream, passPhrase, curveName);
+                // TODO: check SHA-1 hash.
+
+                return new PgpSecretKey(new SecretKeyPacket(pubPacket, SymmetricKeyAlgorithmTag.Null, null, null,
+                    new ECSecretBcpgKey(new BigInteger(1, dValue)).GetEncoded()), new PgpPublicKey(pubPacket));
+            }
+
+            throw new PgpException("unknown key type found");
+        }
+
+        private static byte[] GetDValue(Stream inputStream, char[] passPhrase, string curveName)
+        {
+            string type;
+            SXprUtilities.SkipOpenParenthesis(inputStream);
+
+            string protection;
+            S2k s2k;
+            byte[] iv;
+            byte[] secKeyData;
+
+            type = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+            if (type.Equals("protected"))
+            {
+                protection = SXprUtilities.ReadString(inputStream, inputStream.ReadByte());
+
+                SXprUtilities.SkipOpenParenthesis(inputStream);
+
+                s2k = SXprUtilities.ParseS2k(inputStream);
+
+                iv = SXprUtilities.ReadBytes(inputStream, inputStream.ReadByte());
+
+                SXprUtilities.SkipCloseParenthesis(inputStream);
+
+                secKeyData = SXprUtilities.ReadBytes(inputStream, inputStream.ReadByte());
+            }
+            else
+            {
+                throw new PgpException("protected block not found");
+            }
+
+            // TODO: recognise other algorithms
+            KeyParameter key = PgpUtilities.MakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag.Aes128, s2k, passPhrase);
+
+            byte[] data = RecoverKeyData(SymmetricKeyAlgorithmTag.Aes128, "/CBC/NoPadding", key, iv, secKeyData, 0, secKeyData.Length);
+
+            //
+            // 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());
+        }
     }
 }
diff --git a/crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs b/crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs
index 4adf64012..d2177d09c 100644
--- a/crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs
+++ b/crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs
@@ -25,7 +25,14 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             list.Add(new Exportable(isCritical, isExportable));
         }
 
-		/// <summary>
+        public void SetFeature(
+            bool    isCritical,
+            byte    feature)
+        {
+            list.Add(new Features(isCritical, feature));
+        }
+
+        /// <summary>
 		/// Add a TrustSignature packet to the signature. The values for depth and trust are largely
 		/// installation dependent but there are some guidelines in RFC 4880 - 5.2.3.13.
 		/// </summary>
@@ -117,7 +124,17 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			list.Add(new SignerUserId(isCritical, userId));
         }
 
-		public void SetEmbeddedSignature(
+        public void SetSignerUserId(
+            bool    isCritical,
+            byte[]  rawUserId)
+        {
+            if (rawUserId == null)
+                throw new ArgumentNullException("rawUserId");
+
+            list.Add(new SignerUserId(isCritical, false, rawUserId));
+        }
+
+        public void SetEmbeddedSignature(
 			bool			isCritical,
 			PgpSignature	pgpSignature)
 		{
@@ -136,7 +153,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
 			Array.Copy(sig, sig.Length - data.Length, data, 0, data.Length);
 
-			list.Add(new EmbeddedSignature(isCritical, data));
+			list.Add(new EmbeddedSignature(isCritical, false, data));
 		}
 
 		public void SetPrimaryUserId(
diff --git a/crypto/src/openpgp/PgpSignatureSubpacketVector.cs b/crypto/src/openpgp/PgpSignatureSubpacketVector.cs
index 68fe4b594..156243f4e 100644
--- a/crypto/src/openpgp/PgpSignatureSubpacketVector.cs
+++ b/crypto/src/openpgp/PgpSignatureSubpacketVector.cs
@@ -209,7 +209,17 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			return list;
         }
 
-		[Obsolete("Use 'Count' property instead")]
+        public Features GetFeatures()
+        {
+            SignatureSubpacket p = this.GetSubpacket(SignatureSubpacketTag.Features);
+
+            if (p == null)
+                return null;
+
+            return new Features(p.IsCritical(), p.IsLongLength(), p.GetData());
+        }
+
+        [Obsolete("Use 'Count' property instead")]
 		public int Size
 		{
 			get { return packets.Length; }
diff --git a/crypto/src/openpgp/PgpUtilities.cs b/crypto/src/openpgp/PgpUtilities.cs
index 32e37b819..e4551db07 100644
--- a/crypto/src/openpgp/PgpUtilities.cs
+++ b/crypto/src/openpgp/PgpUtilities.cs
@@ -86,7 +86,13 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 				case PublicKeyAlgorithmTag.Dsa:
 					encAlg = "DSA";
 					break;
-				case PublicKeyAlgorithmTag.ElGamalEncrypt: // in some malformed cases.
+                case PublicKeyAlgorithmTag.ECDH:
+                    encAlg = "ECDH";
+                    break;
+                case PublicKeyAlgorithmTag.ECDsa:
+                    encAlg = "ECDSA";
+                    break;
+                case PublicKeyAlgorithmTag.ElGamalEncrypt: // in some malformed cases.
 				case PublicKeyAlgorithmTag.ElGamalGeneral:
 					encAlg = "ElGamal";
 					break;
@@ -135,7 +141,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
         }
 
-	public static int GetKeySize(SymmetricKeyAlgorithmTag algorithm)
+        public static int GetKeySize(SymmetricKeyAlgorithmTag algorithm)
         {
             int keySize;
             switch (algorithm)
@@ -193,7 +199,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             char[]						passPhrase)
         {
 			int keySize = GetKeySize(algorithm);
-			byte[] pBytes = Strings.ToByteArray(new string(passPhrase));
+			byte[] pBytes = Encoding.UTF8.GetBytes(passPhrase);
 			byte[] keyBytes = new byte[(keySize + 7) / 8];
 
 			int generatedBytes = 0;
@@ -431,5 +437,22 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 				return new ArmoredInputStream(inputStream, hasHeaders);
             }
         }
+
+        internal static IWrapper CreateWrapper(SymmetricKeyAlgorithmTag encAlgorithm)
+        {
+            switch (encAlgorithm)
+            {
+            case SymmetricKeyAlgorithmTag.Aes128:
+            case SymmetricKeyAlgorithmTag.Aes192:
+            case SymmetricKeyAlgorithmTag.Aes256:
+                return WrapperUtilities.GetWrapper("AESWRAP");
+            case SymmetricKeyAlgorithmTag.Camellia128:
+            case SymmetricKeyAlgorithmTag.Camellia192:
+            case SymmetricKeyAlgorithmTag.Camellia256:
+                return WrapperUtilities.GetWrapper("CAMELLIAWRAP");
+            default:
+                throw new PgpException("unknown wrap algorithm: " + encAlgorithm);
+            }
+        }
     }
 }
diff --git a/crypto/src/openpgp/Rfc6637Utilities.cs b/crypto/src/openpgp/Rfc6637Utilities.cs
new file mode 100644
index 000000000..5d992ec51
--- /dev/null
+++ b/crypto/src/openpgp/Rfc6637Utilities.cs
@@ -0,0 +1,138 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Nist;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Math.EC;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+    public sealed class Rfc6637Utilities
+    {
+        private Rfc6637Utilities()
+        {
+        }
+
+        // "Anonymous Sender    ", which is the octet sequence
+        private static readonly byte[] ANONYMOUS_SENDER = Hex.Decode("416E6F6E796D6F75732053656E64657220202020");
+
+        public static string GetAgreementAlgorithm(PublicKeyPacket pubKeyData)
+        {
+            ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)pubKeyData.Key;
+
+            switch (ecKey.HashAlgorithm)
+            {
+            case HashAlgorithmTag.Sha256:
+                return "ECCDHwithSHA256CKDF";
+            case HashAlgorithmTag.Sha384:
+                return "ECCDHwithSHA384CKDF";
+            case HashAlgorithmTag.Sha512:
+                return "ECCDHwithSHA512CKDF";
+            default:
+                throw new ArgumentException("Unknown hash algorithm specified: " + ecKey.HashAlgorithm);
+            }
+        }
+
+        public static DerObjectIdentifier GetKeyEncryptionOID(SymmetricKeyAlgorithmTag algID)
+        {
+            switch (algID)
+            {
+            case SymmetricKeyAlgorithmTag.Aes128:
+                return NistObjectIdentifiers.IdAes128Wrap;
+            case SymmetricKeyAlgorithmTag.Aes192:
+                return NistObjectIdentifiers.IdAes192Wrap;
+            case SymmetricKeyAlgorithmTag.Aes256:
+                return NistObjectIdentifiers.IdAes256Wrap;
+            default:
+                throw new PgpException("unknown symmetric algorithm ID: " + algID);
+            }
+        }
+
+        public static int GetKeyLength(SymmetricKeyAlgorithmTag algID)
+        {
+            switch (algID)
+            {
+            case SymmetricKeyAlgorithmTag.Aes128:
+                return 16;
+            case SymmetricKeyAlgorithmTag.Aes192:
+                return 24;
+            case SymmetricKeyAlgorithmTag.Aes256:
+                return 32;
+            default:
+                throw new PgpException("unknown symmetric algorithm ID: " + 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);
+        }
+
+        // RFC 6637 - Section 8
+        // curve_OID_len = (byte)len(curve_OID);
+        // Param = curve_OID_len || curve_OID || public_key_alg_ID || 03
+        // || 01 || KDF_hash_ID || KEK_alg_ID for AESKeyWrap || "Anonymous
+        // Sender    " || recipient_fingerprint;
+        // Z_len = the key size for the KEK_alg_ID used with AESKeyWrap
+        // Compute Z = KDF( S, Z_len, Param );
+        public static byte[] CreateUserKeyingMaterial(PublicKeyPacket pubKeyData)
+        {
+            MemoryStream pOut = new MemoryStream();
+            ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)pubKeyData.Key;
+            byte[] encOid = ecKey.CurveOid.GetEncoded();
+
+            pOut.Write(encOid, 1, encOid.Length - 1);
+            pOut.WriteByte((byte)pubKeyData.Algorithm);
+            pOut.WriteByte(0x03);
+            pOut.WriteByte(0x01);
+            pOut.WriteByte((byte)ecKey.HashAlgorithm);
+            pOut.WriteByte((byte)ecKey.SymmetricKeyAlgorithm);
+            pOut.Write(ANONYMOUS_SENDER, 0, ANONYMOUS_SENDER.Length);
+
+            byte[] fingerprint = PgpPublicKey.CalculateFingerprint(pubKeyData);
+            pOut.Write(fingerprint, 0, fingerprint.Length);
+
+            return pOut.ToArray();
+        }
+
+        // RFC 6637 - Section 7
+        //   Implements KDF( X, oBits, Param );
+        //   Input: point X = (x,y)
+        //   oBits - the desired size of output
+        //   hBits - the size of output of hash function Hash
+        //   Param - octets representing the parameters
+        //   Assumes that oBits <= hBits
+        //   Convert the point X to the octet string, see section 6:
+        //   ZB' = 04 || x || y
+        //   and extract the x portion from ZB'
+        //         ZB = x;
+        //         MB = Hash ( 00 || 00 || 00 || 01 || ZB || Param );
+        //   return oBits leftmost bits of MB.
+        private static byte[] Kdf(HashAlgorithmTag digestAlg, ECPoint s, int keyLen, byte[] parameters)
+        {
+            byte[] ZB = s.XCoord.GetEncoded();
+
+            string digestName = PgpUtilities.GetDigestName(digestAlg);
+			IDigest digest = DigestUtilities.GetDigest(digestName);
+
+            digest.Update(0x00);
+            digest.Update(0x00);
+            digest.Update(0x00);
+            digest.Update(0x01);
+            digest.BlockUpdate(ZB, 0, ZB.Length);
+            digest.BlockUpdate(parameters, 0, parameters.Length);
+
+            byte[] hash = DigestUtilities.DoFinal(digest);
+
+            return Arrays.CopyOfRange(hash, 0, keyLen);
+        }
+    }
+}
diff --git a/crypto/src/openpgp/SXprUtilities.cs b/crypto/src/openpgp/SXprUtilities.cs
new file mode 100644
index 000000000..68ff373a8
--- /dev/null
+++ b/crypto/src/openpgp/SXprUtilities.cs
@@ -0,0 +1,102 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+    /**
+     * Utility functions for looking a 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
+     * </p>
+     */
+    public sealed class SXprUtilities
+    {
+        private SXprUtilities()
+        {
+        }
+
+        private static int ReadLength(Stream input, int ch)
+        {
+            int len = ch - '0';
+
+            while ((ch = input.ReadByte()) >= 0 && ch != ':')
+            {
+                len = len * 10 + ch - '0';
+            }
+
+            return len;
+        }
+
+        internal static string ReadString(Stream input, int ch)
+        {
+            int len = ReadLength(input, ch);
+
+            char[] chars = new char[len];
+
+            for (int i = 0; i != chars.Length; i++)
+            {
+                chars[i] = (char)input.ReadByte();
+            }
+
+            return new string(chars);
+        }
+
+        internal static byte[] ReadBytes(Stream input, int ch)
+        {
+            int len = ReadLength(input, ch);
+
+            byte[] data = new byte[len];
+
+            Streams.ReadFully(input, data);
+
+            return data;
+        }
+
+        internal static S2k ParseS2k(Stream input)
+        {
+            SkipOpenParenthesis(input);
+
+            string alg = ReadString(input, input.ReadByte());
+            byte[] iv = ReadBytes(input, input.ReadByte());
+            long iterationCount = Int64.Parse(ReadString(input, input.ReadByte()));
+
+            SkipCloseParenthesis(input);
+
+            // we have to return the actual iteration count provided.
+            return new MyS2k(HashAlgorithmTag.Sha1, iv, iterationCount);
+        }
+
+        internal static void SkipOpenParenthesis(Stream input)
+        {
+            int ch = input.ReadByte();
+            if (ch != '(')
+                throw new IOException("unknown character encountered");
+        }
+
+        internal static void SkipCloseParenthesis(Stream input)
+        {
+            int ch = input.ReadByte();
+            if (ch != ')')
+                throw new IOException("unknown character encountered");
+        }
+
+        private class MyS2k : S2k
+        {
+            private readonly long mIterationCount64;
+
+            internal MyS2k(HashAlgorithmTag algorithm, byte[] iv, long iterationCount64)
+                : base(algorithm, iv, (int)iterationCount64)
+            {
+                this.mIterationCount64 = iterationCount64;
+            }
+
+            public override long IterationCount
+            {
+                get { return mIterationCount64; }
+            }
+        }
+    }
+}