summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crypto/crypto.csproj5
-rw-r--r--crypto/src/openpgp/PgpSecretKey.cs78
-rw-r--r--crypto/src/openpgp/PgpSecretKeyRing.cs377
-rw-r--r--crypto/src/util/Arrays.cs13
-rw-r--r--crypto/test/src/openpgp/test/PGPNoPrivateKeyTest.cs169
5 files changed, 436 insertions, 206 deletions
diff --git a/crypto/crypto.csproj b/crypto/crypto.csproj
index 1a2f8c132..8fae3a786 100644
--- a/crypto/crypto.csproj
+++ b/crypto/crypto.csproj
@@ -10577,6 +10577,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "test\src\openpgp\test\PGPNoPrivateKeyTest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "test\src\openpgp\test\PGPPacketTest.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs
index 94c892a87..872316dd7 100644
--- a/crypto/src/openpgp/PgpSecretKey.cs
+++ b/crypto/src/openpgp/PgpSecretKey.cs
@@ -5,6 +5,7 @@ using System.IO;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
@@ -75,28 +76,36 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 pOut.WriteObject(secKey);
 
                 byte[] keyData = bOut.ToArray();
-                byte[] checksumBytes = Checksum(useSha1, keyData, keyData.Length);
+                byte[] checksumData = Checksum(useSha1, keyData, keyData.Length);
 
-                pOut.Write(checksumBytes);
-
-                byte[] bOutData = bOut.ToArray();
+                keyData = Arrays.Concatenate(keyData, checksumData);
 
                 if (encAlgorithm == SymmetricKeyAlgorithmTag.Null)
                 {
                     if (isMasterKey)
                     {
-                        this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, null, null, bOutData);
+                        this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, null, null, keyData);
                     }
                     else
                     {
-                        this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, null, null, bOutData);
+                        this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, null, null, keyData);
                     }
                 }
                 else
                 {
                     S2k s2k;
                     byte[] iv;
-                    byte[] encData = EncryptKeyData(bOutData, encAlgorithm, passPhrase, rand, out s2k, out iv);
+
+                    byte[] encData;
+                    if (pub.Version >= 4)
+                    {
+                        encData = EncryptKeyData(keyData, encAlgorithm, passPhrase, rand, out s2k, out iv);
+                    }
+                    else
+                    {
+                        // TODO v3 RSA key encryption
+                        throw Platform.CreateNotImplementedException("v3 RSA");
+                    }
 
                     int s2kUsage = useSha1
                         ?	SecretKeyPacket.UsageSha1
@@ -145,11 +154,11 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             PgpSignatureSubpacketVector	hashedPackets,
             PgpSignatureSubpacketVector	unhashedPackets,
             SecureRandom				rand)
-            : this(keyPair.PrivateKey, certifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets), encAlgorithm, passPhrase, useSha1, rand, true)
+            : this(keyPair.PrivateKey, CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets), encAlgorithm, passPhrase, useSha1, rand, true)
         {
         }
 
-        private static PgpPublicKey certifiedPublicKey(
+        private static PgpPublicKey CertifiedPublicKey(
             int							certificationLevel,
             PgpKeyPair					keyPair,
             string						id,
@@ -254,6 +263,17 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             get { return pub.IsMasterKey; }
         }
 
+        /// <summary>Detect if the Secret Key's Private Key is empty or not</summary>
+        public bool IsPrivateKeyEmpty
+        {
+            get
+            {
+                byte[] secKeyData = secret.GetSecretKeyData();
+
+                return secKeyData == null || secKeyData.Length < 1;
+            }
+        }
+
         /// <summary>The algorithm the key is encrypted with.</summary>
         public SymmetricKeyAlgorithmTag KeyEncryptionAlgorithm
         {
@@ -293,9 +313,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             byte[] encData = secret.GetSecretKeyData();
 
             if (alg == SymmetricKeyAlgorithmTag.Null)
+                // TODO Check checksum here?
                 return encData;
 
-            byte[] data;
             IBufferedCipher c = null;
             try
             {
@@ -307,13 +327,14 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 throw new PgpException("Exception creating cipher", e);
             }
 
-            // TODO Factor this block out as 'encryptData'
+            // TODO Factor this block out as 'decryptData'
             try
             {
                 KeyParameter key = PgpUtilities.MakeKeyFromPassPhrase(secret.EncAlgorithm, secret.S2k, passPhrase);
                 byte[] iv = secret.GetIV();
+                byte[] data;
 
-                if (secret.PublicKeyPacket.Version == 4)
+                if (secret.PublicKeyPacket.Version >= 4)
                 {
                     c.Init(false, new ParametersWithIV(key, iv));
 
@@ -334,6 +355,8 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 {
                     data = new byte[encData.Length];
 
+                    iv = Arrays.Clone(iv);
+
                     //
                     // read in the four numbers
                     //
@@ -359,11 +382,15 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                     }
 
                     //
-                    // verify Checksum
+                    // verify and copy checksum
                     //
+
+                    data[pos] = encData[pos];
+                    data[pos + 1] = encData[pos + 1];
+
                     int cs = ((encData[pos] << 8) & 0xff00) | (encData[pos + 1] & 0xff);
                     int calcCs = 0;
-                    for (int j=0; j < data.Length-2; j++)
+                    for (int j = 0; j < pos; j++)
                     {
                         calcCs += data[j] & 0xff;
                     }
@@ -393,8 +420,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         public PgpPrivateKey ExtractPrivateKey(
             char[] passPhrase)
         {
-            byte[] secKeyData = secret.GetSecretKeyData();
-            if (secKeyData == null || secKeyData.Length < 1)
+            if (IsPrivateKeyEmpty)
                 return null;
 
             PublicKeyPacket pubPk = secret.PublicKeyPacket;
@@ -559,11 +585,15 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             SymmetricKeyAlgorithmTag	newEncAlgorithm,
             SecureRandom				rand)
         {
+            if (key.IsPrivateKeyEmpty)
+                throw new PgpException("no private key in this SecretKey - public key present only.");
+
             byte[]	rawKeyData = key.ExtractKeyData(oldPassPhrase);
             int		s2kUsage = key.secret.S2kUsage;
             byte[]	iv = null;
             S2k		s2k = null;
             byte[]	keyData;
+            PublicKeyPacket pubKeyPacket = key.secret.PublicKeyPacket;
 
             if (newEncAlgorithm == SymmetricKeyAlgorithmTag.Null)
             {
@@ -588,7 +618,15 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             {
                 try
                 {
-                    keyData = EncryptKeyData(rawKeyData, newEncAlgorithm, newPassPhrase, rand, out s2k, out iv);
+                    if (pubKeyPacket.Version >= 4)
+                    {
+                        keyData = EncryptKeyData(rawKeyData, newEncAlgorithm, newPassPhrase, rand, out s2k, out iv);
+                    }
+                    else
+                    {
+                        // TODO v3 RSA key encryption
+                        throw Platform.CreateNotImplementedException("v3 RSA");
+                    }
                 }
                 catch (PgpException e)
                 {
@@ -603,13 +641,11 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             SecretKeyPacket secret;
             if (key.secret is SecretSubkeyPacket)
             {
-                secret = new SecretSubkeyPacket(key.secret.PublicKeyPacket,
-                    newEncAlgorithm, s2kUsage, s2k, iv, keyData);
+                secret = new SecretSubkeyPacket(pubKeyPacket, newEncAlgorithm, s2kUsage, s2k, iv, keyData);
             }
             else
             {
-                secret = new SecretKeyPacket(key.secret.PublicKeyPacket,
-                    newEncAlgorithm, s2kUsage, s2k, iv, keyData);
+                secret = new SecretKeyPacket(pubKeyPacket, newEncAlgorithm, s2kUsage, s2k, iv, keyData);
             }
 
             return new PgpSecretKey(secret, key.pub);
diff --git a/crypto/src/openpgp/PgpSecretKeyRing.cs b/crypto/src/openpgp/PgpSecretKeyRing.cs
index 3e646eaa1..70cd7217c 100644
--- a/crypto/src/openpgp/PgpSecretKeyRing.cs
+++ b/crypto/src/openpgp/PgpSecretKeyRing.cs
@@ -8,57 +8,57 @@ using Org.BouncyCastle.Utilities.Collections;
 
 namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
-	/// <remarks>
-	/// Class to hold a single master secret key and its subkeys.
-	/// <p>
-	/// Often PGP keyring files consist of multiple master keys, if you are trying to process
-	/// or construct one of these you should use the <c>PgpSecretKeyRingBundle</c> class.
-	/// </p>
-	/// </remarks>
-	public class PgpSecretKeyRing
-		: PgpKeyRing
+    /// <remarks>
+    /// Class to hold a single master secret key and its subkeys.
+    /// <p>
+    /// Often PGP keyring files consist of multiple master keys, if you are trying to process
+    /// or construct one of these you should use the <c>PgpSecretKeyRingBundle</c> class.
+    /// </p>
+    /// </remarks>
+    public class PgpSecretKeyRing
+        : PgpKeyRing
     {
         private readonly IList keys;
-		private readonly IList extraPubKeys;
-
-		internal PgpSecretKeyRing(
-			IList keys)
-			: this(keys, Platform.CreateArrayList())
-		{
-		}
-
-		private PgpSecretKeyRing(
-			IList	keys,
-			IList	extraPubKeys)
-		{
-			this.keys = keys;
-			this.extraPubKeys = extraPubKeys;
-		}
-
-		public PgpSecretKeyRing(
+        private readonly IList extraPubKeys;
+
+        internal PgpSecretKeyRing(
+            IList keys)
+            : this(keys, Platform.CreateArrayList())
+        {
+        }
+
+        private PgpSecretKeyRing(
+            IList	keys,
+            IList	extraPubKeys)
+        {
+            this.keys = keys;
+            this.extraPubKeys = extraPubKeys;
+        }
+
+        public PgpSecretKeyRing(
             byte[] encoding)
             : this(new MemoryStream(encoding))
         {
         }
 
-		public PgpSecretKeyRing(
+        public PgpSecretKeyRing(
             Stream inputStream)
         {
-			this.keys = Platform.CreateArrayList();
+            this.keys = Platform.CreateArrayList();
             this.extraPubKeys = Platform.CreateArrayList();
 
-			BcpgInputStream bcpgInput = BcpgInputStream.Wrap(inputStream);
+            BcpgInputStream bcpgInput = BcpgInputStream.Wrap(inputStream);
 
-			PacketTag initialTag = bcpgInput.NextPacketTag();
-			if (initialTag != PacketTag.SecretKey && initialTag != PacketTag.SecretSubkey)
+            PacketTag initialTag = bcpgInput.NextPacketTag();
+            if (initialTag != PacketTag.SecretKey && initialTag != PacketTag.SecretSubkey)
             {
                 throw new IOException("secret key ring doesn't start with secret key tag: "
-					+ "tag 0x" + ((int)initialTag).ToString("X"));
+                    + "tag 0x" + ((int)initialTag).ToString("X"));
             }
 
-			SecretKeyPacket secret = (SecretKeyPacket) bcpgInput.ReadPacket();
+            SecretKeyPacket secret = (SecretKeyPacket) bcpgInput.ReadPacket();
 
-			//
+            //
             // ignore GPG comment packets if found.
             //
             while (bcpgInput.NextPacketTag() == PacketTag.Experimental2)
@@ -66,65 +66,65 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 bcpgInput.ReadPacket();
             }
 
-			TrustPacket trust = ReadOptionalTrustPacket(bcpgInput);
+            TrustPacket trust = ReadOptionalTrustPacket(bcpgInput);
 
-			// revocation and direct signatures
-			IList keySigs = ReadSignaturesAndTrust(bcpgInput);
+            // revocation and direct signatures
+            IList keySigs = ReadSignaturesAndTrust(bcpgInput);
 
-			IList ids, idTrusts, idSigs;
-			ReadUserIDs(bcpgInput, out ids, out idTrusts, out idSigs);
+            IList ids, idTrusts, idSigs;
+            ReadUserIDs(bcpgInput, out ids, out idTrusts, out idSigs);
 
-			keys.Add(new PgpSecretKey(secret, new PgpPublicKey(secret.PublicKeyPacket, trust, keySigs, ids, idTrusts, idSigs)));
+            keys.Add(new PgpSecretKey(secret, new PgpPublicKey(secret.PublicKeyPacket, trust, keySigs, ids, idTrusts, idSigs)));
 
 
-			// Read subkeys
-			while (bcpgInput.NextPacketTag() == PacketTag.SecretSubkey
-				|| bcpgInput.NextPacketTag() == PacketTag.PublicSubkey)
+            // Read subkeys
+            while (bcpgInput.NextPacketTag() == PacketTag.SecretSubkey
+                || bcpgInput.NextPacketTag() == PacketTag.PublicSubkey)
             {
-				if (bcpgInput.NextPacketTag() == PacketTag.SecretSubkey)
-				{
-					SecretSubkeyPacket sub = (SecretSubkeyPacket) bcpgInput.ReadPacket();
-
-					//
-					// ignore GPG comment packets if found.
-					//
-					while (bcpgInput.NextPacketTag() == PacketTag.Experimental2)
-					{
-						bcpgInput.ReadPacket();
-					}
-
-					TrustPacket subTrust = ReadOptionalTrustPacket(bcpgInput);
-					IList sigList = ReadSignaturesAndTrust(bcpgInput);
-
-					keys.Add(new PgpSecretKey(sub, new PgpPublicKey(sub.PublicKeyPacket, subTrust, sigList)));
-				}
-				else
-				{
-					PublicSubkeyPacket sub = (PublicSubkeyPacket) bcpgInput.ReadPacket();
-
-					TrustPacket subTrust = ReadOptionalTrustPacket(bcpgInput);
-					IList sigList = ReadSignaturesAndTrust(bcpgInput);
-
-					extraPubKeys.Add(new PgpPublicKey(sub, subTrust, sigList));
-				}
+                if (bcpgInput.NextPacketTag() == PacketTag.SecretSubkey)
+                {
+                    SecretSubkeyPacket sub = (SecretSubkeyPacket) bcpgInput.ReadPacket();
+
+                    //
+                    // ignore GPG comment packets if found.
+                    //
+                    while (bcpgInput.NextPacketTag() == PacketTag.Experimental2)
+                    {
+                        bcpgInput.ReadPacket();
+                    }
+
+                    TrustPacket subTrust = ReadOptionalTrustPacket(bcpgInput);
+                    IList sigList = ReadSignaturesAndTrust(bcpgInput);
+
+                    keys.Add(new PgpSecretKey(sub, new PgpPublicKey(sub.PublicKeyPacket, subTrust, sigList)));
+                }
+                else
+                {
+                    PublicSubkeyPacket sub = (PublicSubkeyPacket) bcpgInput.ReadPacket();
+
+                    TrustPacket subTrust = ReadOptionalTrustPacket(bcpgInput);
+                    IList sigList = ReadSignaturesAndTrust(bcpgInput);
+
+                    extraPubKeys.Add(new PgpPublicKey(sub, subTrust, sigList));
+                }
             }
         }
 
-		/// <summary>Return the public key for the master key.</summary>
+        /// <summary>Return the public key for the master key.</summary>
         public PgpPublicKey GetPublicKey()
         {
             return ((PgpSecretKey) keys[0]).PublicKey;
         }
 
-		/// <summary>Return the master private key.</summary>
+        /// <summary>Return the master private key.</summary>
         public PgpSecretKey GetSecretKey()
         {
             return (PgpSecretKey) keys[0];
         }
 
-		/// <summary>Allows enumeration of the secret keys.</summary>
-		/// <returns>An <c>IEnumerable</c> of <c>PgpSecretKey</c> objects.</returns>
-		public IEnumerable GetSecretKeys()
+        /// <summary>Allows enumeration of the secret keys.</summary>
+        /// <returns>An <c>IEnumerable</c> of <c>PgpSecretKey</c> objects.</returns>
+        public IEnumerable GetSecretKeys()
         {
             return new EnumerableProxy(keys);
         }
@@ -132,29 +132,29 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         public PgpSecretKey GetSecretKey(
             long keyId)
         {
-			foreach (PgpSecretKey k in keys)
-			{
-				if (keyId == k.KeyId)
-				{
-					return k;
-				}
-			}
-
-			return null;
+            foreach (PgpSecretKey k in keys)
+            {
+                if (keyId == k.KeyId)
+                {
+                    return k;
+                }
+            }
+
+            return null;
+        }
+
+        /// <summary>
+        /// Return an iterator of the public keys in the secret key ring that
+        /// have no matching private key. At the moment only personal certificate data
+        /// appears in this fashion.
+        /// </summary>
+        /// <returns>An <c>IEnumerable</c> of unattached, or extra, public keys.</returns>
+        public IEnumerable GetExtraPublicKeys()
+        {
+            return new EnumerableProxy(extraPubKeys);
         }
 
-		/// <summary>
-		/// Return an iterator of the public keys in the secret key ring that
-		/// have no matching private key. At the moment only personal certificate data
-		/// appears in this fashion.
-		/// </summary>
-		/// <returns>An <c>IEnumerable</c> of unattached, or extra, public keys.</returns>
-		public IEnumerable GetExtraPublicKeys()
-		{
-			return new EnumerableProxy(extraPubKeys);
-		}
-
-		public byte[] GetEncoded()
+        public byte[] GetEncoded()
         {
             MemoryStream bOut = new MemoryStream();
 
@@ -166,117 +166,124 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         public void Encode(
             Stream outStr)
         {
-			if (outStr == null)
-				throw new ArgumentNullException("outStr");
-
-			foreach (PgpSecretKey key in keys)
-			{
-				key.Encode(outStr);
-			}
-			foreach (PgpPublicKey extraPubKey in extraPubKeys)
-			{
-				extraPubKey.Encode(outStr);
-			}
+            if (outStr == null)
+                throw new ArgumentNullException("outStr");
+
+            foreach (PgpSecretKey key in keys)
+            {
+                key.Encode(outStr);
+            }
+            foreach (PgpPublicKey extraPubKey in extraPubKeys)
+            {
+                extraPubKey.Encode(outStr);
+            }
         }
 
-		/// <summary>
-		/// Replace the public key set on the secret ring with the corresponding key off the public ring.
-		/// </summary>
-		/// <param name="secretRing">Secret ring to be changed.</param>
-		/// <param name="publicRing">Public ring containing the new public key set.</param>
-		public static PgpSecretKeyRing ReplacePublicKeys(
-			PgpSecretKeyRing	secretRing,
-			PgpPublicKeyRing	publicRing)
-		{
+        /// <summary>
+        /// Replace the public key set on the secret ring with the corresponding key off the public ring.
+        /// </summary>
+        /// <param name="secretRing">Secret ring to be changed.</param>
+        /// <param name="publicRing">Public ring containing the new public key set.</param>
+        public static PgpSecretKeyRing ReplacePublicKeys(
+            PgpSecretKeyRing	secretRing,
+            PgpPublicKeyRing	publicRing)
+        {
             IList newList = Platform.CreateArrayList(secretRing.keys.Count);
 
-			foreach (PgpSecretKey sk in secretRing.keys)
-			{
-				PgpPublicKey pk = publicRing.GetPublicKey(sk.KeyId);
-
-				newList.Add(PgpSecretKey.ReplacePublicKey(sk, pk));
-			}
-
-			return new PgpSecretKeyRing(newList);
-		}
-
-		/// <summary>
-		/// Return a copy of the passed in secret key ring, with the master key and sub keys encrypted
-		/// using a new password and the passed in algorithm.
-		/// </summary>
-		/// <param name="ring">The <c>PgpSecretKeyRing</c> to be copied.</param>
-		/// <param name="oldPassPhrase">The current password for key.</param>
-		/// <param name="newPassPhrase">The new password for the key.</param>
-		/// <param name="newEncAlgorithm">The algorithm to be used for the encryption.</param>
-		/// <param name="rand">Source of randomness.</param>
-		public static PgpSecretKeyRing CopyWithNewPassword(
-			PgpSecretKeyRing			ring,
-			char[]						oldPassPhrase,
-			char[]						newPassPhrase,
-			SymmetricKeyAlgorithmTag	newEncAlgorithm,
-			SecureRandom				rand)
-		{
+            foreach (PgpSecretKey sk in secretRing.keys)
+            {
+                PgpPublicKey pk = publicRing.GetPublicKey(sk.KeyId);
+
+                newList.Add(PgpSecretKey.ReplacePublicKey(sk, pk));
+            }
+
+            return new PgpSecretKeyRing(newList);
+        }
+
+        /// <summary>
+        /// Return a copy of the passed in secret key ring, with the master key and sub keys encrypted
+        /// using a new password and the passed in algorithm.
+        /// </summary>
+        /// <param name="ring">The <c>PgpSecretKeyRing</c> to be copied.</param>
+        /// <param name="oldPassPhrase">The current password for key.</param>
+        /// <param name="newPassPhrase">The new password for the key.</param>
+        /// <param name="newEncAlgorithm">The algorithm to be used for the encryption.</param>
+        /// <param name="rand">Source of randomness.</param>
+        public static PgpSecretKeyRing CopyWithNewPassword(
+            PgpSecretKeyRing			ring,
+            char[]						oldPassPhrase,
+            char[]						newPassPhrase,
+            SymmetricKeyAlgorithmTag	newEncAlgorithm,
+            SecureRandom				rand)
+        {
             IList newKeys = Platform.CreateArrayList(ring.keys.Count);
-			foreach (PgpSecretKey secretKey in ring.GetSecretKeys())
-			{
-				newKeys.Add(PgpSecretKey.CopyWithNewPassword(secretKey, oldPassPhrase, newPassPhrase, newEncAlgorithm, rand));
-			}
-
-			return new PgpSecretKeyRing(newKeys, ring.extraPubKeys);
-		}
-
-		/// <summary>
-		/// Returns a new key ring with the secret key passed in either added or
-		/// replacing an existing one with the same key ID.
-		/// </summary>
-		/// <param name="secRing">The secret key ring to be modified.</param>
-		/// <param name="secKey">The secret key to be inserted.</param>
-		/// <returns>A new <c>PgpSecretKeyRing</c></returns>
-		public static PgpSecretKeyRing InsertSecretKey(
+            foreach (PgpSecretKey secretKey in ring.GetSecretKeys())
+            {
+                if (secretKey.IsPrivateKeyEmpty)
+                {
+                    newKeys.Add(secretKey);
+                }
+                else
+                {
+                    newKeys.Add(PgpSecretKey.CopyWithNewPassword(secretKey, oldPassPhrase, newPassPhrase, newEncAlgorithm, rand));
+                }
+            }
+
+            return new PgpSecretKeyRing(newKeys, ring.extraPubKeys);
+        }
+
+        /// <summary>
+        /// Returns a new key ring with the secret key passed in either added or
+        /// replacing an existing one with the same key ID.
+        /// </summary>
+        /// <param name="secRing">The secret key ring to be modified.</param>
+        /// <param name="secKey">The secret key to be inserted.</param>
+        /// <returns>A new <c>PgpSecretKeyRing</c></returns>
+        public static PgpSecretKeyRing InsertSecretKey(
             PgpSecretKeyRing  secRing,
             PgpSecretKey      secKey)
         {
             IList keys = Platform.CreateArrayList(secRing.keys);
             bool found = false;
-			bool masterFound = false;
+            bool masterFound = false;
 
-			for (int i = 0; i != keys.Count; i++)
+            for (int i = 0; i != keys.Count; i++)
             {
                 PgpSecretKey key = (PgpSecretKey) keys[i];
 
-				if (key.KeyId == secKey.KeyId)
+                if (key.KeyId == secKey.KeyId)
                 {
                     found = true;
                     keys[i] = secKey;
                 }
-				if (key.IsMasterKey)
-				{
-					masterFound = true;
-				}
-			}
+                if (key.IsMasterKey)
+                {
+                    masterFound = true;
+                }
+            }
 
             if (!found)
             {
-				if (secKey.IsMasterKey)
-				{
-					if (masterFound)
-						throw new ArgumentException("cannot add a master key to a ring that already has one");
-
-					keys.Insert(0, secKey);
-				}
-				else
-				{
-					keys.Add(secKey);
-				}
+                if (secKey.IsMasterKey)
+                {
+                    if (masterFound)
+                        throw new ArgumentException("cannot add a master key to a ring that already has one");
+
+                    keys.Insert(0, secKey);
+                }
+                else
+                {
+                    keys.Add(secKey);
+                }
             }
 
-			return new PgpSecretKeyRing(keys, secRing.extraPubKeys);
-		}
+            return new PgpSecretKeyRing(keys, secRing.extraPubKeys);
+        }
 
-		/// <summary>Returns a new key ring with the secret key passed in removed from the key ring.</summary>
-		/// <param name="secRing">The secret key ring to be modified.</param>
-		/// <param name="secKey">The secret key to be removed.</param>
-		/// <returns>A new <c>PgpSecretKeyRing</c>, or null if secKey is not found.</returns>
+        /// <summary>Returns a new key ring with the secret key passed in removed from the key ring.</summary>
+        /// <param name="secRing">The secret key ring to be modified.</param>
+        /// <param name="secKey">The secret key to be removed.</param>
+        /// <returns>A new <c>PgpSecretKeyRing</c>, or null if secKey is not found.</returns>
         public static PgpSecretKeyRing RemoveSecretKey(
             PgpSecretKeyRing  secRing,
             PgpSecretKey      secKey)
@@ -284,18 +291,18 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             IList keys = Platform.CreateArrayList(secRing.keys);
             bool found = false;
 
-			for (int i = 0; i < keys.Count; i++)
+            for (int i = 0; i < keys.Count; i++)
             {
                 PgpSecretKey key = (PgpSecretKey)keys[i];
 
-				if (key.KeyId == secKey.KeyId)
+                if (key.KeyId == secKey.KeyId)
                 {
                     found = true;
                     keys.RemoveAt(i);
                 }
             }
 
-			return found ? new PgpSecretKeyRing(keys, secRing.extraPubKeys) : null;
+            return found ? new PgpSecretKeyRing(keys, secRing.extraPubKeys) : null;
         }
     }
 }
diff --git a/crypto/src/util/Arrays.cs b/crypto/src/util/Arrays.cs
index 3632587de..cc2025ef3 100644
--- a/crypto/src/util/Arrays.cs
+++ b/crypto/src/util/Arrays.cs
@@ -329,5 +329,18 @@ namespace Org.BouncyCastle.Utilities
             Array.Copy(data, off, result, 0, len);
             return result;
         }
+
+        public static byte[] Concatenate(byte[] a, byte[] b)
+        {
+            if (a == null)
+                return Clone(b);
+            if (b == null)
+                return Clone(a);
+
+            byte[] rv = new byte[a.Length + b.Length];
+            Array.Copy(a, 0, rv, 0, a.Length);
+            Array.Copy(b, 0, rv, a.Length, b.Length);
+            return rv;
+        }
     }
 }
diff --git a/crypto/test/src/openpgp/test/PGPNoPrivateKeyTest.cs b/crypto/test/src/openpgp/test/PGPNoPrivateKeyTest.cs
new file mode 100644
index 000000000..edb96b149
--- /dev/null
+++ b/crypto/test/src/openpgp/test/PGPNoPrivateKeyTest.cs
@@ -0,0 +1,169 @@
+using System;
+using System.IO;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.Test;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
+{
+    [TestFixture]
+    public class PGPNoPrivateKeyTest
+        : SimpleTest
+    {
+        private static string pgpOldPass = "test";
+        private static string pgpNewPass = "newtest";
+
+        private static readonly byte[] pgpPrivateEmpty = Base64.Decode(
+              "lQCVBFGSNGwBBACwABZRIEW/4vDQajcO0FW39yNDcsHBDwPkGT95D7jiVTTRoSs6"
+            + "ACWRAAwGlz4V62U0+nEgasxpifHnu6jati5zxwS16qNvBcxcqZrdZWdvolzCWWsr"
+            + "pFd0juhwesrvvUb5dN/xCJKyLPkp6A+uwv35/cxVSOHFvbW7nnronwinYQARAQAB"
+            + "/gJlAkdOVQG0HlRlc3QgVGVzdGVyc29uIDx0ZXN0QHRlc3QubmV0Poi4BBMBAgAi"
+            + "BQJRkjRsAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRDSr6Hh9tuk5NfI"
+            + "A/4iMPF9k2/7KanWksNrBqhKemsyI7hLTxAwv+AA9B0rOO2QoJYe9OjuKn199fNO"
+            + "JPsAgwy7okvDe3QAUz3WA9GlghM5STYvonFJtl7o4kyjcZ4HO2ZI5Bdc5O9i63QA"
+            + "rNv40qVp++A3Mf+13z7cftKufj0vOfw6YeayLVXcV4h95J0B/gRRkjSNAQQA2l3d"
+            + "ZnFFYXYDoNHz1cOX4787CbKdBIfiALFfdbyQ6TzYkCTJTnVCZlQs2aeyrcdTSZUx"
+            + "N4y9bih4nfJ8uRKyQvLm6O0u6bG16kUDDnnwlsGn3uvTXfUwnSPq8pFY2acde6ZG"
+            + "N25vezNK1R6C7kU3+puNHqBIRANfHTsBElaD2V0AEQEAAf4CAwIUI0+QlwBVFdNa"
+            + "S/ppOwSht7Gr19AK4SHe92VWDKnCBPN2W3vhM4NcZSQCV2oiEMI0akLZ26jqCiRl"
+            + "AvTjLSVDho1rUWbaSxFfKlDQNbxCJKlMQeVfbsWXJMeDkn1AhPru3PBLl6Y1jocd"
+            + "vIVM7aQugNQlwEuFWgtZeODxcgBfX2lQeEMIv0AtWTAMt6MVT8AgnFqiqC4+14t0"
+            + "j2CHP2hqCDr5zw9gerAYQ0F03OS34vDm4Y5DmQFjyB05QO2cIN4DZ9gJg8NAQT+P"
+            + "+bwWR3/i9pTq3InNkoi2uT41OnHsYWgKoEQn62BDxjbvO359crUiq9VvS52v2UXh"
+            + "b6Z+fF3PoXXsobS1QQwTPXAeA/mlAflTp+HrkckatY7DgWbON1SSn4Z1XcWPKBSY"
+            + "epS5+90Tj3byZvN7Laj61ZlXVBvU3x7z6MaBZDf4479fklcUnJ13v+P6uGnTI4YE"
+            + "Q5pPjHn1dDqD2Nl8ZW9ufK9pPYkBPQQYAQIACQUCUZI0jQIbAgCoCRDSr6Hh9tuk"
+            + "5J0gBBkBAgAGBQJRkjSNAAoJEPIU7wJ5Ws2K0F0D/jHx4jrZq7SCv69/4hictjgz"
+            + "nNNFSOm20/brHXMBdp6p9mBqt28WU8fgRkxS0mz+1i7VNTv6ZwUXawfTyOVCPR5B"
+            + "QEC+FA+LvdX0UcJBJpa9tT4koz1JBxmppxxLYdS2A5sslPD5If8QHUaOMEX9O1I+"
+            + "So3rEh3+DuhQj88FUuG8uJAD/3Xtpf/5nEpghLOZdQ/7QkLCoRZk7fwjChQNFSJU"
+            + "5xiZbZ/GsSvU1IqAP/NZBmBO0qDm5m7ahXy71O1bMFtaiUaw2Mb7dwqqDvppbjIB"
+            + "OHdIhSnAorRLcnjm8z51QVMzHmgvKt5/e1q1fzsVzza6DWtYr2X/1VsuouSC1uz1"
+            + "nPdgnQH+BFGSNJ4BBAC3KliQlchs0rctsXbhA/GEfiO0s9tAgVsfJL1PWUkC+26M"
+            + "yBbqkVg5RV+J6dyTSeT6cDI8PMu8XFPO6H2WWdovfs7X9K1lxfnNWxQB2L6t2xre"
+            + "XyFqvTsYEFuGvYmbNyUYvA+daHD0xqX8UrC0J6TYg5ie5I685X8gFKVEtGYG/wAR"
+            + "AQAB/gIDAuMt34hcdJPX03uBj9LtjcnrMNLyF7PVJv4wBXEt7T9Kp8cYZ80Sxpd2"
+            + "11LHzjgiPg1kkkImJ9Ie1qbPZjc9tyiGf47m0TIORnKtwNb2YN+sKLpqZ+ienfTs"
+            + "vc0uyuVGW+8PCt409M9R++0q66sxvb3oKBp2zsr3BbGaISs4OVxY2L8uU3t5j9pi"
+            + "qKdV2XTiV9OZJ+2f1au1tMwhNPzjVJ4GH53TxewSkshRJTZtw2ouUJkdA/bizfNO"
+            + "9XYYvV8sW1/ASe1dnOs+ANDGzumzSA00dWPSveURroG+ZtVXVgkakJJtDwdAYutP"
+            + "kSm28cnsl1OmrBKPonB5N3uDjTlq56vji1d2F5ugAXTTD5PptiML1wEB/TqsRJRX"
+            + "uY7DLy+8iukOVOyoVw63UMX27YUz61JJZYcB7U28gNeRyBsnTEbjmvteoFsYnaGg"
+            + "Owgc+1Zx4rQdZEqxZRmfwmiUgHGyI9OpvoVaTIuDIqDd2ZRWiJ8EGAECAAkFAlGS"
+            + "NJ4CGwwACgkQ0q+h4fbbpOScsgQAmMymSfAmltnHQzKr5k2GvlAqIzl9MqKVm9wA"
+            + "0Cx3grwzPaiqmfspPIueQ8Phexiy6dwfPrwNoKnJOEjM6/sOcWEmLiIoYi+/oQjU"
+            + "12zwogOfzT/1hPpG5zs+GBGX4sorCK663PuovwCEoNrWm+7nItfTwdnFavNuj7s4"
+            + "+b3JLdM=");
+
+        private static readonly byte[] pgpPrivateFull = Base64.Decode(
+              "lQH+BFGSNGwBBACwABZRIEW/4vDQajcO0FW39yNDcsHBDwPkGT95D7jiVTTRoSs6"
+            + "ACWRAAwGlz4V62U0+nEgasxpifHnu6jati5zxwS16qNvBcxcqZrdZWdvolzCWWsr"
+            + "pFd0juhwesrvvUb5dN/xCJKyLPkp6A+uwv35/cxVSOHFvbW7nnronwinYQARAQAB"
+            + "/gIDAuqTuDp/Chfq0TKnSxmm2ZpDuiHD+NFVnCyNuJpvCQk0PnVwmGMH4xvsAZB2"
+            + "TOrfh2XHf/n9J4vjxB6p6Zs1kGBgg9hcHoWf+oEf1Tz/PE/c1tUXG2Hz9wlAgstU"
+            + "my2NpDTYUjQs45p+LaM+WFtLNXzBeqELKlMevs8Xb7n+VHwiTuM3KfXETLCoLz0Q"
+            + "3GmmpOuNnvXBdza7RsDwke0r66HzwX4Le8cMH9Pe7kSMakx9S1UR/uIsxsZYZOKb"
+            + "BieGEumxiAnew0Ri5/8wTd5yYC7BWbYvBUgdMQ1gzkzmJcVky8NVfoZKQ0GkdvMo"
+            + "fMThIVXN1U6+aqzAuUMFCPYQ7fEpfoNLhCnzQPv3RE7Wo2vFMjWBod2J4MSLhBuq"
+            + "Ut+FYLqYqU21Qe4PEyPmGnkVu7Wd8FGjBF+IKZg+ycPi++h/twloD/h7LEaq907C"
+            + "4R3rdOzjZnefDfxVWjLLhqKSSuXxtjSSKwMNdbjYVVJ/tB5UZXN0IFRlc3RlcnNv"
+            + "biA8dGVzdEB0ZXN0Lm5ldD6IuAQTAQIAIgUCUZI0bAIbAwYLCQgHAwIGFQgCCQoL"
+            + "BBYCAwECHgECF4AACgkQ0q+h4fbbpOTXyAP+IjDxfZNv+ymp1pLDawaoSnprMiO4"
+            + "S08QML/gAPQdKzjtkKCWHvTo7ip9ffXzTiT7AIMMu6JLw3t0AFM91gPRpYITOUk2"
+            + "L6JxSbZe6OJMo3GeBztmSOQXXOTvYut0AKzb+NKlafvgNzH/td8+3H7Srn49Lzn8"
+            + "OmHmsi1V3FeIfeSdAf4EUZI0jQEEANpd3WZxRWF2A6DR89XDl+O/OwmynQSH4gCx"
+            + "X3W8kOk82JAkyU51QmZULNmnsq3HU0mVMTeMvW4oeJ3yfLkSskLy5ujtLumxtepF"
+            + "Aw558JbBp97r0131MJ0j6vKRWNmnHXumRjdub3szStUegu5FN/qbjR6gSEQDXx07"
+            + "ARJWg9ldABEBAAH+AgMCFCNPkJcAVRXTWkv6aTsEobexq9fQCuEh3vdlVgypwgTz"
+            + "dlt74TODXGUkAldqIhDCNGpC2duo6gokZQL04y0lQ4aNa1Fm2ksRXypQ0DW8QiSp"
+            + "TEHlX27FlyTHg5J9QIT67tzwS5emNY6HHbyFTO2kLoDUJcBLhVoLWXjg8XIAX19p"
+            + "UHhDCL9ALVkwDLejFU/AIJxaoqguPteLdI9ghz9oagg6+c8PYHqwGENBdNzkt+Lw"
+            + "5uGOQ5kBY8gdOUDtnCDeA2fYCYPDQEE/j/m8Fkd/4vaU6tyJzZKItrk+NTpx7GFo"
+            + "CqBEJ+tgQ8Y27zt+fXK1IqvVb0udr9lF4W+mfnxdz6F17KG0tUEMEz1wHgP5pQH5"
+            + "U6fh65HJGrWOw4FmzjdUkp+GdV3FjygUmHqUufvdE4928mbzey2o+tWZV1Qb1N8e"
+            + "8+jGgWQ3+OO/X5JXFJydd7/j+rhp0yOGBEOaT4x59XQ6g9jZfGVvbnyvaT2JAT0E"
+            + "GAECAAkFAlGSNI0CGwIAqAkQ0q+h4fbbpOSdIAQZAQIABgUCUZI0jQAKCRDyFO8C"
+            + "eVrNitBdA/4x8eI62au0gr+vf+IYnLY4M5zTRUjpttP26x1zAXaeqfZgardvFlPH"
+            + "4EZMUtJs/tYu1TU7+mcFF2sH08jlQj0eQUBAvhQPi73V9FHCQSaWvbU+JKM9SQcZ"
+            + "qaccS2HUtgObLJTw+SH/EB1GjjBF/TtSPkqN6xId/g7oUI/PBVLhvLiQA/917aX/"
+            + "+ZxKYISzmXUP+0JCwqEWZO38IwoUDRUiVOcYmW2fxrEr1NSKgD/zWQZgTtKg5uZu"
+            + "2oV8u9TtWzBbWolGsNjG+3cKqg76aW4yATh3SIUpwKK0S3J45vM+dUFTMx5oLyre"
+            + "f3tatX87Fc82ug1rWK9l/9VbLqLkgtbs9Zz3YJ0B/gRRkjSeAQQAtypYkJXIbNK3"
+            + "LbF24QPxhH4jtLPbQIFbHyS9T1lJAvtujMgW6pFYOUVfienck0nk+nAyPDzLvFxT"
+            + "zuh9llnaL37O1/StZcX5zVsUAdi+rdsa3l8har07GBBbhr2JmzclGLwPnWhw9Mal"
+            + "/FKwtCek2IOYnuSOvOV/IBSlRLRmBv8AEQEAAf4CAwLjLd+IXHST19N7gY/S7Y3J"
+            + "6zDS8hez1Sb+MAVxLe0/SqfHGGfNEsaXdtdSx844Ij4NZJJCJifSHtamz2Y3Pbco"
+            + "hn+O5tEyDkZyrcDW9mDfrCi6amfonp307L3NLsrlRlvvDwreNPTPUfvtKuurMb29"
+            + "6Cgads7K9wWxmiErODlcWNi/LlN7eY/aYqinVdl04lfTmSftn9WrtbTMITT841Se"
+            + "Bh+d08XsEpLIUSU2bcNqLlCZHQP24s3zTvV2GL1fLFtfwEntXZzrPgDQxs7ps0gN"
+            + "NHVj0r3lEa6BvmbVV1YJGpCSbQ8HQGLrT5EptvHJ7JdTpqwSj6JweTd7g405auer"
+            + "44tXdheboAF00w+T6bYjC9cBAf06rESUV7mOwy8vvIrpDlTsqFcOt1DF9u2FM+tS"
+            + "SWWHAe1NvIDXkcgbJ0xG45r7XqBbGJ2hoDsIHPtWceK0HWRKsWUZn8JolIBxsiPT"
+            + "qb6FWkyLgyKg3dmUVoifBBgBAgAJBQJRkjSeAhsMAAoJENKvoeH226TknLIEAJjM"
+            + "pknwJpbZx0Myq+ZNhr5QKiM5fTKilZvcANAsd4K8Mz2oqpn7KTyLnkPD4XsYsunc"
+            + "Hz68DaCpyThIzOv7DnFhJi4iKGIvv6EI1Nds8KIDn80/9YT6Ruc7PhgRl+LKKwiu"
+            + "utz7qL8AhKDa1pvu5yLX08HZxWrzbo+7OPm9yS3T");
+
+        public override void PerformTest()
+        {
+            PgpSecretKeyRing pgpSecRing = new PgpSecretKeyRing(pgpPrivateFull);
+            PgpSecretKey pgpSecKey = pgpSecRing.GetSecretKey();
+            bool isFullEmpty = pgpSecKey.IsPrivateKeyEmpty;
+
+            pgpSecRing = new PgpSecretKeyRing(pgpPrivateEmpty);
+            pgpSecKey = pgpSecRing.GetSecretKey();
+            bool isEmptyEmpty = pgpSecKey.IsPrivateKeyEmpty;
+
+            //
+            // Check isPrivateKeyEmpty() is public
+            //
+
+            if (isFullEmpty || !isEmptyEmpty)
+            {
+                Fail("Empty private keys not detected correctly.");
+            }
+
+            //
+            // Check copyWithNewPassword doesn't throw an exception for secret
+            // keys without private keys (PGPException: unknown S2K type: 101).
+            //
+
+            SecureRandom rand = new SecureRandom();
+
+            try
+            {
+                PgpSecretKey pgpChangedKey = PgpSecretKey.CopyWithNewPassword(pgpSecKey,
+                    pgpOldPass.ToCharArray(), pgpNewPass.ToCharArray(), pgpSecKey.KeyEncryptionAlgorithm, rand);
+            }
+            catch (PgpException e)
+            {
+                if (!e.Message.Equals("no private key in this SecretKey - public key present only."))
+                {
+                    Fail("wrong exception.");
+                }
+            }
+        }
+
+        public override string Name
+        {
+            get { return "PGPNoPrivateKeyTest"; }
+        }
+
+        public static void Main(
+            string[] args)
+        {
+            RunTest(new PGPNoPrivateKeyTest());
+        }
+
+        [Test]
+        public void TestFunction()
+        {
+            string resultText = Perform().ToString();
+
+            Assert.AreEqual(Name + ": Okay", resultText);
+        }
+    }
+}