diff --git a/src/NuGet.Clients/NuGet.CommandLine/Commands/SignCommand.cs b/src/NuGet.Clients/NuGet.CommandLine/Commands/SignCommand.cs
index fa0a6e8c715..3d1766424fe 100644
--- a/src/NuGet.Clients/NuGet.CommandLine/Commands/SignCommand.cs
+++ b/src/NuGet.Clients/NuGet.CommandLine/Commands/SignCommand.cs
@@ -111,7 +111,8 @@ public SignArgs GetSignArgs()
Overwrite = Overwrite,
NonInteractive = NonInteractive,
Timestamper = Timestamper,
- TimestampHashAlgorithm = timestampHashAlgorithm
+ TimestampHashAlgorithm = timestampHashAlgorithm,
+ PasswordProvider = new ConsolePasswordProvider(Console)
};
}
diff --git a/src/NuGet.Clients/NuGet.CommandLine/ConsolePasswordProvider.cs b/src/NuGet.Clients/NuGet.CommandLine/ConsolePasswordProvider.cs
new file mode 100644
index 00000000000..f2a8ed3d4f1
--- /dev/null
+++ b/src/NuGet.Clients/NuGet.CommandLine/ConsolePasswordProvider.cs
@@ -0,0 +1,46 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Globalization;
+using System.Security;
+using System.Threading;
+using System.Threading.Tasks;
+using NuGet.Commands.SignCommand;
+
+namespace NuGet.CommandLine
+{
+ ///
+ /// Allows requesting a user to input their password through Console.
+ ///
+ internal class ConsolePasswordProvider : IPasswordProvider
+ {
+ private IConsole _console;
+
+ public ConsolePasswordProvider(IConsole console)
+ {
+ _console = console ?? throw new ArgumentNullException(nameof(console));
+ }
+
+#if IS_DESKTOP
+ ///
+ /// Requests user to input password and returns it as a SecureString on Console.
+ ///
+ /// Path to the file that needs a password to open.
+ /// Cancellation token.
+ /// SecureString containing the user input password. The SecureString should be disposed after use.
+ public Task GetPassword(string filePath, CancellationToken token)
+ {
+ token.ThrowIfCancellationRequested();
+
+ var password = new SecureString();
+
+ _console.WriteLine(string.Format(CultureInfo.CurrentCulture, NuGetResources.ConsolePasswordProvider_DisplayFile, filePath));
+ _console.Write(NuGetResources.ConsolePasswordProvider_PromptForPassword);
+ _console.ReadSecureString(password);
+
+ return Task.FromResult(password);
+ }
+#endif
+ }
+}
diff --git a/src/NuGet.Clients/NuGet.CommandLine/NuGetResources.Designer.cs b/src/NuGet.Clients/NuGet.CommandLine/NuGetResources.Designer.cs
index 54943d70660..14519275d43 100644
--- a/src/NuGet.Clients/NuGet.CommandLine/NuGetResources.Designer.cs
+++ b/src/NuGet.Clients/NuGet.CommandLine/NuGetResources.Designer.cs
@@ -19,7 +19,7 @@ namespace NuGet.CommandLine {
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class NuGetResources {
@@ -1095,6 +1095,24 @@ public static string ConsoleConfirmMessageAccept_trk {
}
}
+ ///
+ /// Looks up a localized string similar to Please provide password for: {0}.
+ ///
+ public static string ConsolePasswordProvider_DisplayFile {
+ get {
+ return ResourceManager.GetString("ConsolePasswordProvider_DisplayFile", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Password: .
+ ///
+ public static string ConsolePasswordProvider_PromptForPassword {
+ get {
+ return ResourceManager.GetString("ConsolePasswordProvider_PromptForPassword", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to The remote server indicated that the previous request was forbidden. Please provide credentials for: {0}.
///
diff --git a/src/NuGet.Clients/NuGet.CommandLine/NuGetResources.resx b/src/NuGet.Clients/NuGet.CommandLine/NuGetResources.resx
index 4454c4d21f3..09d97004e21 100644
--- a/src/NuGet.Clients/NuGet.CommandLine/NuGetResources.resx
+++ b/src/NuGet.Clients/NuGet.CommandLine/NuGetResources.resx
@@ -6151,4 +6151,11 @@ Oluşturma sırasında NuGet'in paketleri indirmesini önlemek için, Visual Stu
Response file '{0}' cannot be larger than {1}mb
+
+ Please provide password for: {0}
+ 0 - file that requires password
+
+
+ Password:
+
\ No newline at end of file
diff --git a/src/NuGet.Core/NuGet.Commands/SignCommand/CertificateFindOptions.cs b/src/NuGet.Core/NuGet.Commands/SignCommand/CertificateFindOptions.cs
index 8f67b62b37a..8a44b674d43 100644
--- a/src/NuGet.Core/NuGet.Commands/SignCommand/CertificateFindOptions.cs
+++ b/src/NuGet.Core/NuGet.Commands/SignCommand/CertificateFindOptions.cs
@@ -4,6 +4,9 @@
using System;
using System.Security;
using System.Security.Cryptography.X509Certificates;
+using System.Threading;
+using NuGet.Commands.SignCommand;
+using NuGet.Common;
namespace NuGet.Commands
{
@@ -43,5 +46,20 @@ internal class CertificateSourceOptions
///
public string Fingerprint { get; set; }
+ ///
+ /// bool used to indicate if the user can be prompted for password.
+ ///
+ public bool NonInteractive { get; set; }
+
+ ///
+ /// Password provider to get the password from user for opening a pfx file.
+ ///
+ public IPasswordProvider PasswordProvider { get; set; }
+
+ ///
+ /// Cancellation token.
+ ///
+ public CancellationToken Token { get; set; }
+
}
}
diff --git a/src/NuGet.Core/NuGet.Commands/SignCommand/CertificateProvider.cs b/src/NuGet.Core/NuGet.Commands/SignCommand/CertificateProvider.cs
index 86980ef7aee..213792115e2 100644
--- a/src/NuGet.Core/NuGet.Commands/SignCommand/CertificateProvider.cs
+++ b/src/NuGet.Core/NuGet.Commands/SignCommand/CertificateProvider.cs
@@ -3,8 +3,12 @@
using System;
using System.Globalization;
+using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Threading.Tasks;
+using NuGet.Common;
namespace NuGet.Commands
{
@@ -32,7 +36,7 @@ internal static class CertificateProvider
/// CertificateSourceOptions to be used while searching for the certificates.
/// An X509Certificate2Collection object containing matching certificates.
/// If no matching certificates are found then it returns an empty collection.
- public static X509Certificate2Collection GetCertificates(CertificateSourceOptions options)
+ public static async Task GetCertificatesAsync(CertificateSourceOptions options)
{
// check certificate path
var resultCollection = new X509Certificate2Collection();
@@ -40,16 +44,7 @@ public static X509Certificate2Collection GetCertificates(CertificateSourceOption
{
try
{
- X509Certificate2 cert;
-
- if (!string.IsNullOrEmpty(options.CertificatePassword))
- {
- cert = new X509Certificate2(options.CertificatePath, options.CertificatePassword); // use the password if the user provided it.
- }
- else
- {
- cert = new X509Certificate2(options.CertificatePath);
- }
+ var cert = await LoadCertificateFromFileAsync(options);
resultCollection = new X509Certificate2Collection(cert);
}
@@ -82,25 +77,70 @@ public static X509Certificate2Collection GetCertificates(CertificateSourceOption
}
else
{
- var store = new X509Store(options.StoreName, options.StoreLocation);
+ resultCollection = LoadCertificateFromStore(options);
+ }
+
+ return resultCollection;
+ }
- OpenStore(store);
+ private static async Task LoadCertificateFromFileAsync(CertificateSourceOptions options)
+ {
+ X509Certificate2 cert;
- if (!string.IsNullOrEmpty(options.Fingerprint))
+ if (!string.IsNullOrEmpty(options.CertificatePassword))
+ {
+ cert = new X509Certificate2(options.CertificatePath, options.CertificatePassword); // use the password if the user provided it.
+ }
+ else
+ {
+#if IS_DESKTOP
+ try
{
- resultCollection = store.Certificates.Find(X509FindType.FindByThumbprint, options.Fingerprint, validOnly: false);
+ cert = new X509Certificate2(options.CertificatePath);
}
-
- if (!string.IsNullOrEmpty(options.SubjectName))
+ catch (CryptographicException ex)
{
- resultCollection = store.Certificates.Find(X509FindType.FindBySubjectName, options.SubjectName, validOnly: false);
+ // prompt user for password if needed
+ if (ex.HResult == ERROR_INVALID_PASSWORD_HRESULT &&
+ !options.NonInteractive)
+ {
+ using (var password = await options.PasswordProvider.GetPassword(options.CertificatePath, options.Token))
+ {
+ cert = new X509Certificate2(options.CertificatePath, password);
+ }
+ }
+ else
+ {
+ throw ex;
+ }
}
-
-#if IS_DESKTOP
- store.Close();
+#else
+ cert = new X509Certificate2(options.CertificatePath);
#endif
}
+ return cert;
+ }
+
+ private static X509Certificate2Collection LoadCertificateFromStore(CertificateSourceOptions options)
+ {
+ var resultCollection = new X509Certificate2Collection();
+ var store = new X509Store(options.StoreName, options.StoreLocation);
+
+ OpenStore(store);
+
+ if (!string.IsNullOrEmpty(options.Fingerprint))
+ {
+ resultCollection = store.Certificates.Find(X509FindType.FindByThumbprint, options.Fingerprint, validOnly: true);
+ }
+ else if (!string.IsNullOrEmpty(options.SubjectName))
+ {
+ resultCollection = store.Certificates.Find(X509FindType.FindBySubjectName, options.SubjectName, validOnly: true);
+ }
+
+#if IS_DESKTOP
+ store.Close();
+#endif
return resultCollection;
}
diff --git a/src/NuGet.Core/NuGet.Commands/SignCommand/IPasswordProvider.cs b/src/NuGet.Core/NuGet.Commands/SignCommand/IPasswordProvider.cs
new file mode 100644
index 00000000000..fc5b1094d62
--- /dev/null
+++ b/src/NuGet.Core/NuGet.Commands/SignCommand/IPasswordProvider.cs
@@ -0,0 +1,24 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Security;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace NuGet.Commands.SignCommand
+{
+ public interface IPasswordProvider
+ {
+// Currently there is no cross platform interactive scenario
+#if IS_DESKTOP
+ ///
+ /// Requests user to input password and returns it as a SecureString.
+ ///
+ /// Path to the file that needs a password to open.
+ /// Cancellation token.
+ /// SecureString containing the user input password. The SecureString should be disposed after use.
+ Task GetPassword(string filePath, CancellationToken token);
+#endif
+ }
+}
diff --git a/src/NuGet.Core/NuGet.Commands/SignCommand/SignArgs.cs b/src/NuGet.Core/NuGet.Commands/SignCommand/SignArgs.cs
index 33bbe85d95d..0a271b850be 100644
--- a/src/NuGet.Core/NuGet.Commands/SignCommand/SignArgs.cs
+++ b/src/NuGet.Core/NuGet.Commands/SignCommand/SignArgs.cs
@@ -4,8 +4,8 @@
using System;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
+using NuGet.Commands.SignCommand;
using NuGet.Common;
-using NuGet.Packaging.Signing;
namespace NuGet.Commands
{
@@ -94,6 +94,11 @@ public class SignArgs
///
public ILogger Logger { get; set; }
+ ///
+ /// Password provider to get the password from user for opening a pfx file.
+ ///
+ public IPasswordProvider PasswordProvider { get; set; }
+
///
/// Cancellation Token.
///
diff --git a/src/NuGet.Core/NuGet.Commands/SignCommand/SignCommandRunner.cs b/src/NuGet.Core/NuGet.Commands/SignCommand/SignCommandRunner.cs
index c8f5b99929a..b272d6f87a8 100644
--- a/src/NuGet.Core/NuGet.Commands/SignCommand/SignCommandRunner.cs
+++ b/src/NuGet.Core/NuGet.Commands/SignCommand/SignCommandRunner.cs
@@ -29,7 +29,7 @@ public async Task ExecuteCommandAsync(SignArgs signArgs)
var packagesToSign = LocalFolderUtility.ResolvePackageFromPath(signArgs.PackagePath);
LocalFolderUtility.EnsurePackageFileExists(signArgs.PackagePath, packagesToSign);
- var cert = GetCertificate(signArgs);
+ var cert = await GetCertificateAsync(signArgs);
signArgs.Logger.LogInformation(Environment.NewLine);
signArgs.Logger.LogInformation(string.Format(CultureInfo.CurrentCulture,
@@ -174,7 +174,7 @@ private SignPackageRequest GenerateSignPackageRequest(SignArgs signArgs, X509Cer
};
}
- private static X509Certificate2 GetCertificate(SignArgs signArgs)
+ private static async Task GetCertificateAsync(SignArgs signArgs)
{
var certFindOptions = new CertificateSourceOptions()
{
@@ -183,11 +183,14 @@ private static X509Certificate2 GetCertificate(SignArgs signArgs)
Fingerprint = signArgs.CertificateFingerprint,
StoreLocation = signArgs.CertificateStoreLocation,
StoreName = signArgs.CertificateStoreName,
- SubjectName = signArgs.CertificateSubjectName
+ SubjectName = signArgs.CertificateSubjectName,
+ NonInteractive = signArgs.NonInteractive,
+ PasswordProvider = signArgs.PasswordProvider,
+ Token = signArgs.Token
};
// get matching certificates
- var matchingCertCollection = CertificateProvider.GetCertificates(certFindOptions);
+ var matchingCertCollection = await CertificateProvider.GetCertificatesAsync(certFindOptions);
if (matchingCertCollection.Count > 1)
{
diff --git a/test/NuGet.Clients.FuncTests/NuGet.CommandLine.FuncTest/Commands/SignCommandTests.cs b/test/NuGet.Clients.FuncTests/NuGet.CommandLine.FuncTest/Commands/SignCommandTests.cs
index c42a7d22132..affb6d2f52b 100644
--- a/test/NuGet.Clients.FuncTests/NuGet.CommandLine.FuncTest/Commands/SignCommandTests.cs
+++ b/test/NuGet.Clients.FuncTests/NuGet.CommandLine.FuncTest/Commands/SignCommandTests.cs
@@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
+using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using FluentAssertions;
using NuGet.Test.Utility;
@@ -17,6 +18,8 @@ namespace NuGet.CommandLine.FuncTest.Commands
public class SignCommandTests
{
private const string _packageAlreadySignedError = "Error NU5000: The package already contains a signature. Please remove the existing signature before adding a new signature.";
+ private const string _invalidPasswordError = @"Invalid password was provided for the certificate file '{0}'. Please provide a valid password using the '-CertificatePassword' option";
+ private const string _noTimestamperWarningCode = "NU3521";
private SignCommandTestFixture _testFixture;
private TrustedTestCert _trustedTestCert;
@@ -40,11 +43,11 @@ public void SignCommand_SignPackage()
using (var dir = TestDirectory.Create())
using (var zipStream = new SimpleTestPackageContext().CreateAsStream())
{
- var packagePath = Path.Combine(dir, new Guid().ToString());
+ var packagePath = Path.Combine(dir, Guid.NewGuid().ToString());
zipStream.Seek(offset: 0, loc: SeekOrigin.Begin);
- using (Stream fileStream = File.OpenWrite(packagePath))
+ using (var fileStream = File.OpenWrite(packagePath))
{
zipStream.CopyTo(fileStream);
}
@@ -59,7 +62,7 @@ public void SignCommand_SignPackage()
// Assert
result.Success.Should().BeTrue();
- result.AllOutput.Should().Contain("NU3521");
+ result.AllOutput.Should().Contain(_noTimestamperWarningCode);
}
}
@@ -72,11 +75,11 @@ public void SignCommand_SignPackageWithTimestamping()
using (var dir = TestDirectory.Create())
using (var zipStream = new SimpleTestPackageContext().CreateAsStream())
{
- var packagePath = Path.Combine(dir, new Guid().ToString());
+ var packagePath = Path.Combine(dir, Guid.NewGuid().ToString());
zipStream.Seek(offset: 0, loc: SeekOrigin.Begin);
- using (Stream fileStream = File.OpenWrite(packagePath))
+ using (var fileStream = File.OpenWrite(packagePath))
{
zipStream.CopyTo(fileStream);
}
@@ -91,7 +94,7 @@ public void SignCommand_SignPackageWithTimestamping()
// Assert
result.Success.Should().BeTrue();
- result.AllOutput.Should().NotContain("NU3521");
+ result.AllOutput.Should().NotContain(_noTimestamperWarningCode);
}
}
@@ -105,12 +108,13 @@ public void SignCommand_SignPackageWithOutputDirectory()
using (var outputDir = TestDirectory.Create())
using (var zipStream = new SimpleTestPackageContext().CreateAsStream())
{
- var packagePath = Path.Combine(dir, new Guid().ToString());
- var signedPackagePath = Path.Combine(dir, new Guid().ToString());
+ var packageFileName = Guid.NewGuid().ToString();
+ var packagePath = Path.Combine(dir, packageFileName);
+ var signedPackagePath = Path.Combine(outputDir, packageFileName);
zipStream.Seek(offset: 0, loc: SeekOrigin.Begin);
- using (Stream fileStream = File.OpenWrite(packagePath))
+ using (var fileStream = File.OpenWrite(packagePath))
{
zipStream.CopyTo(fileStream);
}
@@ -125,7 +129,7 @@ public void SignCommand_SignPackageWithOutputDirectory()
// Assert
result.Success.Should().BeTrue();
- result.AllOutput.Should().Contain("NU3521");
+ result.AllOutput.Should().Contain(_noTimestamperWarningCode);
File.Exists(signedPackagePath).Should().BeTrue();
}
}
@@ -139,11 +143,11 @@ public void SignCommand_ResignPackageWithoutOverwriteFails()
using (var dir = TestDirectory.Create())
using (var zipStream = new SimpleTestPackageContext().CreateAsStream())
{
- var packagePath = Path.Combine(dir, new Guid().ToString());
+ var packagePath = Path.Combine(dir, Guid.NewGuid().ToString());
zipStream.Seek(offset: 0, loc: SeekOrigin.Begin);
- using (Stream fileStream = File.OpenWrite(packagePath))
+ using (var fileStream = File.OpenWrite(packagePath))
{
zipStream.CopyTo(fileStream);
}
@@ -165,14 +169,14 @@ public void SignCommand_ResignPackageWithoutOverwriteFails()
// Assert
firstResult.Success.Should().BeTrue();
- firstResult.AllOutput.Should().Contain("NU3521");
+ firstResult.AllOutput.Should().Contain(_noTimestamperWarningCode);
secondResult.Success.Should().BeFalse();
secondResult.Errors.Should().Contain(_packageAlreadySignedError);
}
}
[Fact]
- public void SignCommand_ResignPackageWithOverwriteFails()
+ public void SignCommand_ResignPackageWithOverwriteSuccess()
{
// Arrange
var testLogger = new TestLogger();
@@ -180,11 +184,11 @@ public void SignCommand_ResignPackageWithOverwriteFails()
using (var dir = TestDirectory.Create())
using (var zipStream = new SimpleTestPackageContext().CreateAsStream())
{
- var packagePath = Path.Combine(dir, new Guid().ToString());
+ var packagePath = Path.Combine(dir, Guid.NewGuid().ToString());
zipStream.Seek(offset: 0, loc: SeekOrigin.Begin);
- using (Stream fileStream = File.OpenWrite(packagePath))
+ using (var fileStream = File.OpenWrite(packagePath))
{
zipStream.CopyTo(fileStream);
}
@@ -206,9 +210,232 @@ public void SignCommand_ResignPackageWithOverwriteFails()
// Assert
firstResult.Success.Should().BeTrue();
- firstResult.AllOutput.Should().Contain("NU3521");
+ firstResult.AllOutput.Should().Contain(_noTimestamperWarningCode);
secondResult.Success.Should().BeTrue();
- secondResult.AllOutput.Should().Contain("NU3521");
+ secondResult.AllOutput.Should().Contain(_noTimestamperWarningCode);
+ }
+ }
+
+ [Fact]
+ public void SignCommand_SignPackageWithPfxFileSuccess()
+ {
+ // Arrange
+ var testLogger = new TestLogger();
+
+ using (var dir = TestDirectory.Create())
+ using (var zipStream = new SimpleTestPackageContext().CreateAsStream())
+ {
+ var packagePath = Path.Combine(dir, Guid.NewGuid().ToString());
+ var pfxPath = Path.Combine(dir, Guid.NewGuid().ToString());
+
+ var password = Guid.NewGuid().ToString();
+ var pfxBytes = _trustedTestCert.Source.Cert.Export(X509ContentType.Pfx, password);
+
+ using (var fileStream = File.OpenWrite(pfxPath))
+ using (var pfxStream = new MemoryStream(pfxBytes))
+ {
+ pfxStream.CopyTo(fileStream);
+ }
+
+ zipStream.Seek(offset: 0, loc: SeekOrigin.Begin);
+
+ using (var fileStream = File.OpenWrite(packagePath))
+ {
+ zipStream.CopyTo(fileStream);
+ }
+
+ // Act
+ var firstResult = CommandRunner.Run(
+ _nugetExePath,
+ dir,
+ $"sign {packagePath} -CertificatePath {pfxPath} -CertificatePassword {password}",
+ waitForExit: true,
+ timeOutInMilliseconds: 10000);
+
+ // Assert
+ firstResult.Success.Should().BeTrue();
+ firstResult.AllOutput.Should().Contain(_noTimestamperWarningCode);
+ }
+ }
+
+
+ [Fact]
+ public void SignCommand_SignPackageWithPfxFileInteractiveSuccess()
+ {
+ // Arrange
+ var testLogger = new TestLogger();
+
+ using (var dir = TestDirectory.Create())
+ using (var zipStream = new SimpleTestPackageContext().CreateAsStream())
+ {
+ var packagePath = Path.Combine(dir, Guid.NewGuid().ToString());
+ var pfxPath = Path.Combine(dir, Guid.NewGuid().ToString());
+
+ var password = Guid.NewGuid().ToString();
+ var pfxBytes = _trustedTestCert.Source.Cert.Export(X509ContentType.Pfx, password);
+
+ using (var fileStream = File.OpenWrite(pfxPath))
+ using (var pfxStream = new MemoryStream(pfxBytes))
+ {
+ pfxStream.CopyTo(fileStream);
+ }
+
+ zipStream.Seek(offset: 0, loc: SeekOrigin.Begin);
+
+ using (var fileStream = File.OpenWrite(packagePath))
+ {
+ zipStream.CopyTo(fileStream);
+ }
+
+ // Act
+ var firstResult = CommandRunner.Run(
+ _nugetExePath,
+ dir,
+ $"sign {packagePath} -CertificatePath {pfxPath}",
+ waitForExit: true,
+ inputAction: (w) =>
+ {
+ w.WriteLine(password);
+ },
+ timeOutInMilliseconds: 10000);
+
+ // Assert
+ firstResult.Success.Should().BeTrue();
+ firstResult.AllOutput.Should().Contain(_noTimestamperWarningCode);
+ }
+ }
+
+ [Fact]
+ public void SignCommand_SignPackageWithPfxFileInteractiveInvalidPasswordFails()
+ {
+ // Arrange
+ var testLogger = new TestLogger();
+
+ using (var dir = TestDirectory.Create())
+ using (var zipStream = new SimpleTestPackageContext().CreateAsStream())
+ {
+ var packagePath = Path.Combine(dir, Guid.NewGuid().ToString());
+ var pfxPath = Path.Combine(dir, Guid.NewGuid().ToString());
+
+ var password = Guid.NewGuid().ToString();
+ var pfxBytes = _trustedTestCert.Source.Cert.Export(X509ContentType.Pfx, password);
+
+ using (var fileStream = File.OpenWrite(pfxPath))
+ using (var pfxStream = new MemoryStream(pfxBytes))
+ {
+ pfxStream.CopyTo(fileStream);
+ }
+
+ zipStream.Seek(offset: 0, loc: SeekOrigin.Begin);
+
+ using (var fileStream = File.OpenWrite(packagePath))
+ {
+ zipStream.CopyTo(fileStream);
+ }
+
+ // Act
+ var firstResult = CommandRunner.Run(
+ _nugetExePath,
+ dir,
+ $"sign {packagePath} -CertificatePath {pfxPath}",
+ waitForExit: true,
+ inputAction: (w) =>
+ {
+ w.WriteLine(Guid.NewGuid().ToString());
+ },
+ timeOutInMilliseconds: 10000);
+
+ // Assert
+ firstResult.Success.Should().BeFalse();
+ firstResult.AllOutput.Should().Contain(string.Format(_invalidPasswordError, pfxPath));
+ }
+ }
+
+ [Fact]
+ public void SignCommand_SignPackageWithPfxFileWithoutPasswordAndWithNonInteractiveFails()
+ {
+ // Arrange
+ var testLogger = new TestLogger();
+
+ using (var dir = TestDirectory.Create())
+ using (var zipStream = new SimpleTestPackageContext().CreateAsStream())
+ {
+ var packagePath = Path.Combine(dir, Guid.NewGuid().ToString());
+ var pfxPath = Path.Combine(dir, Guid.NewGuid().ToString());
+
+ var password = Guid.NewGuid().ToString();
+ var pfxBytes = _trustedTestCert.Source.Cert.Export(X509ContentType.Pfx, password);
+
+ using (var fileStream = File.OpenWrite(pfxPath))
+ using (var pfxStream = new MemoryStream(pfxBytes))
+ {
+ pfxStream.CopyTo(fileStream);
+ }
+
+ zipStream.Seek(offset: 0, loc: SeekOrigin.Begin);
+
+ using (var fileStream = File.OpenWrite(packagePath))
+ {
+ zipStream.CopyTo(fileStream);
+ }
+
+ // Act
+ var firstResult = CommandRunner.Run(
+ _nugetExePath,
+ dir,
+ $"sign {packagePath} -CertificatePath {pfxPath} -NonInteractive",
+ waitForExit: true,
+ timeOutInMilliseconds: 10000);
+
+ // Assert
+ firstResult.Success.Should().BeFalse();
+ firstResult.AllOutput.Should().Contain(string.Format(_invalidPasswordError, pfxPath));
+ }
+ }
+
+ [Fact]
+ public void SignCommand_SignPackageWithPfxFileWithNonInteractiveAndStdInPasswordFails()
+ {
+ // Arrange
+ var testLogger = new TestLogger();
+
+ using (var dir = TestDirectory.Create())
+ using (var zipStream = new SimpleTestPackageContext().CreateAsStream())
+ {
+ var packagePath = Path.Combine(dir, Guid.NewGuid().ToString());
+ var pfxPath = Path.Combine(dir, Guid.NewGuid().ToString());
+
+ var password = Guid.NewGuid().ToString();
+ var pfxBytes = _trustedTestCert.Source.Cert.Export(X509ContentType.Pfx, password);
+
+ using (var fileStream = File.OpenWrite(pfxPath))
+ using (var pfxStream = new MemoryStream(pfxBytes))
+ {
+ pfxStream.CopyTo(fileStream);
+ }
+
+ zipStream.Seek(offset: 0, loc: SeekOrigin.Begin);
+
+ using (var fileStream = File.OpenWrite(packagePath))
+ {
+ zipStream.CopyTo(fileStream);
+ }
+
+ // Act
+ var firstResult = CommandRunner.Run(
+ _nugetExePath,
+ dir,
+ $"sign {packagePath} -CertificatePath {pfxPath} -NonInteractive",
+ waitForExit: true,
+ inputAction: (w) =>
+ {
+ w.WriteLine(Guid.NewGuid().ToString());
+ },
+ timeOutInMilliseconds: 10000);
+
+ // Assert
+ firstResult.Success.Should().BeFalse();
+ firstResult.AllOutput.Should().Contain(string.Format(_invalidPasswordError, pfxPath));
}
}
}