Parcourir la source

* Allow a private key to be read from a file which is already open, but which allows read access. This allows multiple connections to use the same private key file concurrently.
* Added tests for PrivateKeyFile, and fix all failing tests.
* Add private keys also as embedded resources to test project for .NET 3.5.

Gert Driesen il y a 11 ans
Parent
commit
9f97b90c41

+ 24 - 2
Renci.SshClient/Renci.SshNet.Tests.NET35/Renci.SshNet.Tests.NET35.csproj

@@ -726,11 +726,33 @@
       <SubType>Designer</SubType>
     </EmbeddedResource>
   </ItemGroup>
-  <ItemGroup />
+  <ItemGroup>
+    <EmbeddedResource Include="..\Renci.SshNet.Tests\Data\Key.RSA.Encrypted.Aes.128.CBC.12345.txt">
+      <Link>Data\Key.RSA.Encrypted.Aes.128.CBC.12345.txt</Link>
+    </EmbeddedResource>
+    <EmbeddedResource Include="..\Renci.SshNet.Tests\Data\Key.RSA.Encrypted.Aes.192.CBC.12345.txt">
+      <Link>Data\Key.RSA.Encrypted.Aes.192.CBC.12345.txt</Link>
+    </EmbeddedResource>
+    <EmbeddedResource Include="..\Renci.SshNet.Tests\Data\Key.RSA.Encrypted.Aes.256.CBC.12345.txt">
+      <Link>Data\Key.RSA.Encrypted.Aes.256.CBC.12345.txt</Link>
+    </EmbeddedResource>
+    <EmbeddedResource Include="..\Renci.SshNet.Tests\Data\Key.RSA.Encrypted.Des.CBC.12345.txt">
+      <Link>Data\Key.RSA.Encrypted.Des.CBC.12345.txt</Link>
+    </EmbeddedResource>
+    <EmbeddedResource Include="..\Renci.SshNet.Tests\Data\Key.RSA.Encrypted.Des.Ede3.CBC.12345.txt">
+      <Link>Data\Key.RSA.Encrypted.Des.Ede3.CBC.12345.txt</Link>
+    </EmbeddedResource>
+    <EmbeddedResource Include="..\Renci.SshNet.Tests\Data\Key.RSA.Encrypted.Des.Ede3.CFB.1234567890.txt">
+      <Link>Data\Key.RSA.Encrypted.Des.Ede3.CFB.1234567890.txt</Link>
+    </EmbeddedResource>
+    <EmbeddedResource Include="..\Renci.SshNet.Tests\Data\Key.RSA.txt">
+      <Link>Data\Key.RSA.txt</Link>
+    </EmbeddedResource>
+  </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ProjectExtensions>
     <VisualStudio>
-      <UserProperties ProjectLinkReference="c45379b9-17b1-4e89-bc2e-6d41726413e8" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
+      <UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="c45379b9-17b1-4e89-bc2e-6d41726413e8" />
     </VisualStudio>
   </ProjectExtensions>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

+ 294 - 43
Renci.SshClient/Renci.SshNet.Tests/Classes/PrivateKeyFileTest.cs

@@ -1,9 +1,8 @@
 using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Renci.SshNet.Common;
 using Renci.SshNet.Tests.Common;
 using System;
 using System.IO;
-using System.Reflection;
-using System.Text;
 
 namespace Renci.SshNet.Tests.Classes
 {
@@ -13,20 +12,127 @@ namespace Renci.SshNet.Tests.Classes
     [TestClass]
     public class PrivateKeyFileTest : TestBase
     {
+        private string _temporaryFile;
+
+        [TestInitialize]
+        public void SetUp()
+        {
+            _temporaryFile = GetTempFileName();
+        }
+
+        [TestCleanup]
+        public void TearDown()
+        {
+            if (_temporaryFile != null)
+                File.Delete(_temporaryFile);
+        }
+
+        /// <summary>
+        /// A test for <see cref="PrivateKeyFile(string)"/> ctor.
+        ///</summary>
+        [WorkItem(703), TestMethod]
+        public void ConstructorWithFileNameShouldThrowArgumentNullExceptionWhenFileNameIsEmpty()
+        {
+            var fileName = string.Empty;
+            try
+            {
+                new PrivateKeyFile(fileName);
+                Assert.Fail();
+            }
+            catch (ArgumentNullException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("fileName", ex.ParamName);
+            }
+        }
+
+        /// <summary>
+        /// A test for <see cref="PrivateKeyFile(string)"/> ctor.
+        ///</summary>
+        [WorkItem(703), TestMethod]
+        public void ConstructorWithFileNameShouldThrowArgumentNullExceptionWhenFileNameIsNull()
+        {
+            var fileName = string.Empty;
+            try
+            {
+                new PrivateKeyFile(fileName);
+                Assert.Fail();
+            }
+            catch (ArgumentNullException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("fileName", ex.ParamName);
+            }
+        }
+
+        /// <summary>
+        /// A test for <see cref="PrivateKeyFile(string, string)"/> ctor.
+        ///</summary>
+        [WorkItem(703), TestMethod]
+        public void ConstructorWithFileNameAndPassphraseShouldThrowArgumentNullExceptionWhenFileNameIsEmpty()
+        {
+            var fileName = string.Empty;
+            try
+            {
+                new PrivateKeyFile(fileName, "12345");
+                Assert.Fail();
+            }
+            catch (ArgumentNullException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("fileName", ex.ParamName);
+            }
+        }
+
+        /// <summary>
+        /// A test for <see cref="PrivateKeyFile(string, string)"/> ctor.
+        ///</summary>
         [WorkItem(703), TestMethod]
-        [ExpectedException(typeof(ArgumentNullException))]
-        public void Test_PrivateKeyFile_EmptyFileName()
+        public void ConstructorWithFileNameAndPassphraseShouldThrowArgumentNullExceptionWhenFileNameIsNull()
         {
-            string fileName = string.Empty;
-            var keyFile = new PrivateKeyFile(fileName);
+            var fileName = string.Empty;
+            try
+            {
+                new PrivateKeyFile(fileName, "12345");
+                Assert.Fail();
+            }
+            catch (ArgumentNullException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("fileName", ex.ParamName);
+            }
         }
 
         [WorkItem(703), TestMethod]
-        [ExpectedException(typeof(ArgumentNullException))]
-        public void Test_PrivateKeyFile_StreamIsNull()
+        public void ConstructorWithPrivateKeyShouldThrowArgumentNullExceptionWhenPrivateKeyIsNull()
         {
-            Stream stream = null;
-            var keyFile = new PrivateKeyFile(stream);
+            Stream privateKey = null;
+            try
+            {
+                new PrivateKeyFile(privateKey);
+                Assert.Fail();
+            }
+            catch (ArgumentNullException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("privateKey", ex.ParamName);
+            }
+        }
+
+        [WorkItem(703), TestMethod]
+        public void ConstructorWithPrivateKeyAndPassphraseShouldThrowArgumentNullExceptionWhenPrivateKeyIsNull()
+        {
+            Stream privateKey = null;
+            try
+            {
+                new PrivateKeyFile(privateKey, "12345");
+                Assert.Fail();
+            }
+            catch (ArgumentNullException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("privateKey", ex.ParamName);
+            }
         }
 
         [TestMethod]
@@ -34,7 +140,10 @@ namespace Renci.SshNet.Tests.Classes
         [TestCategory("PrivateKey")]
         public void Test_PrivateKey_RSA()
         {
-            new PrivateKeyFile(this.GetData("Key.RSA.txt"));
+            using (var stream = this.GetData("Key.RSA.txt"))
+            {
+                new PrivateKeyFile(stream);
+            }
         }
 
         [TestMethod]
@@ -42,7 +151,10 @@ namespace Renci.SshNet.Tests.Classes
         [TestCategory("PrivateKey")]
         public void Test_PrivateKey_RSA_DES_CBC()
         {
-            new PrivateKeyFile(this.GetData("Key.RSA.Encrypted.Des.CBC.12345.txt"), "12345");
+            using (var stream = this.GetData("Key.RSA.Encrypted.Des.CBC.12345.txt"))
+            {
+                new PrivateKeyFile(stream, "12345");
+            }
         }
 
         [TestMethod]
@@ -50,7 +162,10 @@ namespace Renci.SshNet.Tests.Classes
         [TestCategory("PrivateKey")]
         public void Test_PrivateKey_RSA_DES_EDE3_CBC()
         {
-            new PrivateKeyFile(this.GetData("Key.RSA.Encrypted.Des.Ede3.CBC.12345.txt"), "12345");
+            using (var stream = this.GetData("Key.RSA.Encrypted.Des.Ede3.CBC.12345.txt"))
+            {
+                new PrivateKeyFile(stream, "12345");
+            }
         }
 
         [TestMethod]
@@ -58,7 +173,10 @@ namespace Renci.SshNet.Tests.Classes
         [TestCategory("PrivateKey")]
         public void Test_PrivateKey_RSA_AES_128_CBC()
         {
-            new PrivateKeyFile(this.GetData("Key.RSA.Encrypted.Aes.128.CBC.12345.txt"), "12345");
+            using (var stream = this.GetData("Key.RSA.Encrypted.Aes.128.CBC.12345.txt"))
+            {
+                new PrivateKeyFile(stream, "12345");
+            }
         }
 
         [TestMethod]
@@ -66,7 +184,10 @@ namespace Renci.SshNet.Tests.Classes
         [TestCategory("PrivateKey")]
         public void Test_PrivateKey_RSA_AES_192_CBC()
         {
-            new PrivateKeyFile(this.GetData("Key.RSA.Encrypted.Aes.192.CBC.12345.txt"), "12345");
+            using (var stream = this.GetData("Key.RSA.Encrypted.Aes.192.CBC.12345.txt"))
+            {
+                new PrivateKeyFile(stream, "12345");
+            }
         }
 
         [TestMethod]
@@ -74,7 +195,10 @@ namespace Renci.SshNet.Tests.Classes
         [TestCategory("PrivateKey")]
         public void Test_PrivateKey_RSA_AES_256_CBC()
         {
-            new PrivateKeyFile(this.GetData("Key.RSA.Encrypted.Aes.256.CBC.12345.txt"), "12345");
+            using (var stream = this.GetData("Key.RSA.Encrypted.Aes.256.CBC.12345.txt"))
+            {
+                new PrivateKeyFile(stream, "12345");
+            }
         }
 
         [TestMethod]
@@ -82,7 +206,10 @@ namespace Renci.SshNet.Tests.Classes
         [TestCategory("PrivateKey")]
         public void Test_PrivateKey_RSA_DES_EDE3_CFB()
         {
-            new PrivateKeyFile(this.GetData("Key.RSA.Encrypted.Des.Ede3.CFB.1234567890.txt"), "1234567890");
+            using (var stream = this.GetData("Key.RSA.Encrypted.Des.Ede3.CFB.1234567890.txt"))
+            {
+                new PrivateKeyFile(stream, "1234567890");
+            }
         }
 
         /// <summary>
@@ -91,56 +218,180 @@ namespace Renci.SshNet.Tests.Classes
         [TestMethod()]
         public void DisposeTest()
         {
-            Stream privateKey = null; // TODO: Initialize to an appropriate value
-            PrivateKeyFile target = new PrivateKeyFile(privateKey); // TODO: Initialize to an appropriate value
-            target.Dispose();
-            Assert.Inconclusive("A method that does not return a value cannot be verified.");
+            using (var privateKeyStream = GetData("Key.RSA.txt"))
+            {
+                var target = new PrivateKeyFile(privateKeyStream);
+                target.Dispose();
+            }
         }
 
         /// <summary>
-        ///A test for PrivateKeyFile Constructor
+        /// A test for <see cref="PrivateKeyFile(Stream, string)"/> ctor.
         ///</summary>
         [TestMethod()]
-        public void PrivateKeyFileConstructorTest()
+        public void ConstructorWithStreamAndPassphrase()
         {
-            Stream privateKey = null; // TODO: Initialize to an appropriate value
-            string passPhrase = string.Empty; // TODO: Initialize to an appropriate value
-            PrivateKeyFile target = new PrivateKeyFile(privateKey, passPhrase);
-            Assert.Inconclusive("TODO: Implement code to verify target");
+            using (var stream = GetData("Key.RSA.Encrypted.Aes.128.CBC.12345.txt"))
+            {
+                var privateKeyFile = new PrivateKeyFile(stream, "12345");
+                Assert.IsNotNull(privateKeyFile.HostKey);
+            }
         }
 
         /// <summary>
-        ///A test for PrivateKeyFile Constructor
+        /// A test for <see cref="PrivateKeyFile(string, string)"/> ctor.
         ///</summary>
         [TestMethod()]
-        public void PrivateKeyFileConstructorTest1()
+        public void ConstructorWithFileNameAndPassphrase()
         {
-            string fileName = string.Empty; // TODO: Initialize to an appropriate value
-            string passPhrase = string.Empty; // TODO: Initialize to an appropriate value
-            PrivateKeyFile target = new PrivateKeyFile(fileName, passPhrase);
-            Assert.Inconclusive("TODO: Implement code to verify target");
+            using (var stream = GetData("Key.RSA.Encrypted.Aes.128.CBC.12345.txt"))
+            {
+                SaveStreamToFile(stream, _temporaryFile);
+            }
+
+            using (var fs = File.Open(_temporaryFile, FileMode.Open, FileAccess.Read, FileShare.Read))
+            {
+                var privateKeyFile = new PrivateKeyFile(_temporaryFile, "12345");
+                Assert.IsNotNull(privateKeyFile.HostKey);
+
+                fs.Close();
+            }
         }
 
         /// <summary>
-        ///A test for PrivateKeyFile Constructor
+        /// A test for <see cref="PrivateKeyFile(string, string)"/> ctor.
         ///</summary>
         [TestMethod()]
-        public void PrivateKeyFileConstructorTest2()
+        public void ConstructorWithFileNameAndPassphraseShouldThrowSshPassPhraseNullOrEmptyExceptionWhenPrivateKeyIsEncryptedAndPassphraseIsEmpty()
         {
-            string fileName = string.Empty; // TODO: Initialize to an appropriate value
-            PrivateKeyFile target = new PrivateKeyFile(fileName);
-            Assert.Inconclusive("TODO: Implement code to verify target");
+            var passphrase = string.Empty;
+
+            using (var stream = GetData("Key.RSA.Encrypted.Aes.128.CBC.12345.txt"))
+            {
+                SaveStreamToFile(stream, _temporaryFile);
+            }
+
+            try
+            {
+                new PrivateKeyFile(_temporaryFile, passphrase);
+                Assert.Fail();
+            }
+            catch (SshPassPhraseNullOrEmptyException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("Private key is encrypted but passphrase is empty.", ex.Message);
+            }
         }
 
         /// <summary>
-        ///A test for PrivateKeyFile Constructor
+        /// A test for <see cref="PrivateKeyFile(string, string)"/> ctor.
         ///</summary>
         [TestMethod()]
-        public void PrivateKeyFileConstructorTest3()
+        public void ConstructorWithFileNameAndPassphraseShouldThrowSshPassPhraseNullOrEmptyExceptionWhenPrivateKeyIsEncryptedAndPassphraseIsNull()
+        {
+            string passphrase = null;
+
+            using (var stream = GetData("Key.RSA.Encrypted.Aes.128.CBC.12345.txt"))
+            {
+                SaveStreamToFile(stream, _temporaryFile);
+            }
+
+            try
+            {
+                new PrivateKeyFile(_temporaryFile, passphrase);
+                Assert.Fail();
+            }
+            catch (SshPassPhraseNullOrEmptyException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("Private key is encrypted but passphrase is empty.", ex.Message);
+            }
+        }
+
+        /// <summary>
+        /// A test for <see cref="PrivateKeyFile(string)"/> ctor.
+        ///</summary>
+        [TestMethod()]
+        public void ConstructorWithFileName()
+        {
+            using (var stream = GetData("Key.RSA.Encrypted.Aes.128.CBC.12345.txt"))
+            {
+                SaveStreamToFile(stream, _temporaryFile);
+            }
+
+            var privateKeyFile = new PrivateKeyFile(_temporaryFile, "12345");
+            Assert.IsNotNull(privateKeyFile.HostKey);
+        }
+
+        /// <summary>
+        /// A test for <see cref="PrivateKeyFile(Stream)"/> ctor.
+        ///</summary>
+        [TestMethod()]
+        public void ConstructorWithStream()
+        {
+            using (var stream = GetData("Key.RSA.txt"))
+            {
+                var privateKeyFile = new PrivateKeyFile(stream);
+                Assert.IsNotNull(privateKeyFile.HostKey);
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("PrivateKey")]
+        public void ConstructorWithFileNameShouldBeAbleToReadFileThatIsSharedForReadAccess()
+        {
+            using (var stream = GetData("Key.RSA.txt"))
+            {
+                SaveStreamToFile(stream, _temporaryFile);
+            }
+
+            using (var fs = File.Open(_temporaryFile, FileMode.Open, FileAccess.Read, FileShare.Read))
+            {
+                var privateKeyFile = new PrivateKeyFile(_temporaryFile);
+                Assert.IsNotNull(privateKeyFile.HostKey);
+
+                fs.Close();
+            }
+        }
+
+        [TestMethod]
+        [TestCategory("PrivateKey")]
+        public void ConstructorWithFileNameAndPassPhraseShouldBeAbleToReadFileThatIsSharedForReadAccess()
+        {
+            using (var stream = GetData("Key.RSA.Encrypted.Aes.128.CBC.12345.txt"))
+            {
+                SaveStreamToFile(stream, _temporaryFile);
+            }
+
+            using (var fs = File.Open(_temporaryFile, FileMode.Open, FileAccess.Read, FileShare.Read))
+            {
+                var privateKeyFile = new PrivateKeyFile(_temporaryFile, "12345");
+                Assert.IsNotNull(privateKeyFile.HostKey);
+
+                fs.Close();
+            }
+        }
+
+        private void SaveStreamToFile(Stream stream, string fileName)
+        {
+            var buffer = new byte[4000];
+
+            using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
+            {
+                var bytesRead = stream.Read(buffer, 0, buffer.Length);
+                while (bytesRead > 0)
+                {
+                    fs.Write(buffer, 0, bytesRead);
+                    bytesRead = stream.Read(buffer, 0, buffer.Length);
+                }
+            }
+        }
+
+        private string GetTempFileName()
         {
-            Stream privateKey = null; // TODO: Initialize to an appropriate value
-            PrivateKeyFile target = new PrivateKeyFile(privateKey);
-            Assert.Inconclusive("TODO: Implement code to verify target");
+            var tempFile = Path.GetTempFileName();
+            File.Delete(tempFile);
+            return tempFile;
         }
     }
 }

+ 2 - 8
Renci.SshClient/Renci.SshNet/PrivateKeyFile.cs

@@ -54,14 +54,8 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentNullException"><paramref name="fileName"/> is null or empty.</exception>
         /// <remarks>This method calls <see cref="System.IO.File.Open(string, System.IO.FileMode)"/> internally, this method does not catch exceptions from <see cref="System.IO.File.Open(string, System.IO.FileMode)"/>.</remarks>
         public PrivateKeyFile(string fileName)
+            : this(fileName, null)
         {
-            if (string.IsNullOrEmpty(fileName))
-                throw new ArgumentNullException("fileName");
-
-            using (var keyFile = File.Open(fileName, FileMode.Open, FileAccess.Read))
-            {
-                this.Open(keyFile, null);
-            }
         }
 
         /// <summary>
@@ -76,7 +70,7 @@ namespace Renci.SshNet
             if (string.IsNullOrEmpty(fileName))
                 throw new ArgumentNullException("fileName");
 
-            using (var keyFile = File.Open(fileName, FileMode.Open, FileAccess.Read))
+            using (var keyFile = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
             {
                 this.Open(keyFile, passPhrase);
             }