Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for NuGet.exe sign command with fake signing #1755

Merged
merged 15 commits into from
Oct 20, 2017
8 changes: 4 additions & 4 deletions src/NuGet.Clients/NuGet.CommandLine/Commands/Command.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public void Execute()
}
else
{
if (String.IsNullOrEmpty(ConfigFile))
if (string.IsNullOrEmpty(ConfigFile))
{
Settings = Configuration.Settings.LoadDefaultSettings(
CurrentDirectory,
Expand Down Expand Up @@ -216,13 +216,13 @@ public virtual CommandAttribute GetCommandAttribute()
}

// Use the command name minus the suffix if present and default description
string name = GetType().Name;
int idx = name.LastIndexOf(CommandSuffix, StringComparison.OrdinalIgnoreCase);
var name = GetType().Name;
var idx = name.LastIndexOf(CommandSuffix, StringComparison.OrdinalIgnoreCase);
if (idx >= 0)
{
name = name.Substring(0, idx);
}
if (!String.IsNullOrEmpty(name))
if (!string.IsNullOrEmpty(name))
{
return new CommandAttribute(name, LocalizedResourceManager.GetString("DefaultCommandDescription"));
}
Expand Down
218 changes: 218 additions & 0 deletions src/NuGet.Clients/NuGet.CommandLine/Commands/SignCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
// 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.IO;
using System.Linq;
using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using NuGet.Commands;
using NuGet.Packaging.Signing;
using NuGet.Shared;

namespace NuGet.CommandLine
{
[Command(typeof(NuGetCommand), "sign", "SignCommandDescription",
MinArgs = 1,
MaxArgs = 1,
UsageSummaryResourceName = "SignCommandUsageSummary",
UsageExampleResourceName = "SignCommandUsageExamples",
UsageDescriptionResourceName = "SignCommandUsageDescription")]
public class SignCommand : Command
{
// Default constructor used only for testing, since the Command Default Constructor is protected
public SignCommand() : base()
{
}

// List of possible values - https://github.com/Microsoft/referencesource/blob/master/mscorlib/system/security/cryptography/HashAlgorithmName.cs
private static string[] _acceptedHashAlgorithms = { "SHA256", "SHA384", "SHA512" };

[Option(typeof(NuGetCommand), "SignCommandOutputDirectoryDescription")]
public string OutputDirectory { get; set; }

[Option(typeof(NuGetCommand), "SignCommandCertificatePathDescription")]
public string CertificatePath { get; set; }

[Option(typeof(NuGetCommand), "SignCommandCertificateStoreNameDescription")]
public string CertificateStoreName { get; set; }

[Option(typeof(NuGetCommand), "SignCommandCertificateStoreLocationDescription")]
public string CertificateStoreLocation { get; set; }

[Option(typeof(NuGetCommand), "SignCommandCertificateSubjectNameDescription")]
public string CertificateSubjectName { get; set; }

[Option(typeof(NuGetCommand), "SignCommandCertificateFingerprintDescription")]
public string CertificateFingerprint { get; set; }

[Option(typeof(NuGetCommand), "SignCommandCertificatePasswordDescription")]
public string CertificatePassword { get; set; }

[Option(typeof(NuGetCommand), "SignCommandCryptographicServiceProviderDescription")]
public string CryptographicServiceProvider { get; set; }

[Option(typeof(NuGetCommand), "SignCommandKeyContainerDescription")]
public string KeyContainer { get; set; }

[Option(typeof(NuGetCommand), "SignCommandHashAlgorithmDescription")]
public string HashAlgorithm { get; set; }

[Option(typeof(NuGetCommand), "SignCommandTimestamperDescription")]
public string Timestamper { get; set; }

[Option(typeof(NuGetCommand), "SignCommandTimestampHashAlgorithmDescription")]
public string TimestampHashAlgorithm { get; set; }

[Option(typeof(NuGetCommand), "SignCommandOverwriteDescription")]
public bool Overwrite { get; set; }

public override Task ExecuteCommandAsync()
{
var signArgs = GetSignArgs();
var signCommandRunner = new SignCommandRunner();
var result = signCommandRunner.ExecuteCommand(signArgs);

return Task.FromResult(result);
}

public SignArgs GetSignArgs()
{
ValidatePackagePath();
ValidateTimestamper();
ValidateCertificateInputs();
ValidateOutputDirectory();

var storeLocation = ValidateAndParseStoreLocation();
var storeName = ValidateAndParseStoreName();
var hashAlgorithm = ValidateAndParseHashAlgorithm(HashAlgorithm, nameof(HashAlgorithm));
var timestampHashAlgorithm = ValidateAndParseHashAlgorithm(TimestampHashAlgorithm, nameof(TimestampHashAlgorithm));

return new SignArgs()
{
PackagePath = Arguments[0],
OutputDirectory = OutputDirectory,
CertificatePath = CertificatePath,
CertificateStoreName = storeName,
CertificateStoreLocation = storeLocation,
CertificateSubjectName = CertificateSubjectName,
CertificateFingerprint = CertificateFingerprint,
CertificatePassword = CertificatePassword,
CryptographicServiceProvider = CryptographicServiceProvider,
KeyContainer = KeyContainer,
HashingAlgorithm = hashAlgorithm,
Logger = Console,
Overwrite = Overwrite,
NonInteractive = NonInteractive,
Timestamper = Timestamper,
TimestampHashAlgorithm = timestampHashAlgorithm
};
}

private HashAlgorithmName ValidateAndParseHashAlgorithm(string value, string name)
{
var hashAlgorithm = HashAlgorithmName.SHA256;

if (!string.IsNullOrEmpty(value))
{
if (!_acceptedHashAlgorithms.Contains(value, StringComparer.InvariantCultureIgnoreCase) ||
!Enum.TryParse(value, ignoreCase: true, result: out hashAlgorithm))
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
NuGetCommand.SignCommandInvalidArgumentException,
name));
}
}

return hashAlgorithm;
}

private StoreName ValidateAndParseStoreName()
{
var storeName = StoreName.My;

if (!string.IsNullOrEmpty(CertificateStoreName) &&
!Enum.TryParse(CertificateStoreName, ignoreCase: true, result: out storeName))
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
NuGetCommand.SignCommandInvalidArgumentException,
nameof(CertificateStoreName)));
}

return storeName;
}

private StoreLocation ValidateAndParseStoreLocation()
{
var storeLocation = StoreLocation.CurrentUser;

if (!string.IsNullOrEmpty(CertificateStoreLocation) &&
!Enum.TryParse(CertificateStoreLocation, ignoreCase: true, result: out storeLocation))
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
NuGetCommand.SignCommandInvalidArgumentException,
nameof(CertificateStoreLocation)));
}

return storeLocation;
}

private void ValidatePackagePath()
{
// Assert mandatory argument
if (Arguments.Count < 1 ||
string.IsNullOrEmpty(Arguments[0]))
{
throw new ArgumentException(NuGetCommand.SignCommandNoPackageException);
}
}

private void ValidateTimestamper()
{
if (string.IsNullOrEmpty(Timestamper))
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
NuGetCommand.SignCommandNoArgumentException,
nameof(Timestamper)));
}
}

private void ValidateOutputDirectory()
{
if (!string.IsNullOrEmpty(OutputDirectory) &&
!Directory.Exists(OutputDirectory))
{
Directory.CreateDirectory(OutputDirectory);
}
}

private void ValidateCertificateInputs()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it an error to specify one but not both of CryptographicServiceProvider and KeyContainer?

{
if (string.IsNullOrEmpty(CertificatePath) &&
string.IsNullOrEmpty(CertificateFingerprint) &&
string.IsNullOrEmpty(CertificateSubjectName))
{
// THrow if user gave no certificate input
throw new ArgumentException(NuGetCommand.SignCommandNoCertificateException);
}
else if (!string.IsNullOrEmpty(CertificatePath) &&
((!string.IsNullOrEmpty(CertificateFingerprint) ||
!string.IsNullOrEmpty(CertificateSubjectName)) ||
!string.IsNullOrEmpty(CertificateStoreLocation) ||
!string.IsNullOrEmpty(CertificateStoreName)))
{
// Thow if the user provided a path and any one of the other options
throw new ArgumentException(NuGetCommand.SignCommandMultipleCertificateException);
}
else if (!string.IsNullOrEmpty(CertificateFingerprint) && !string.IsNullOrEmpty(CertificateSubjectName))
{
// Thow if the user provided a fingerprint and a subject
throw new ArgumentException(NuGetCommand.SignCommandMultipleCertificateException);
}
}
}
}
16 changes: 8 additions & 8 deletions src/NuGet.Clients/NuGet.CommandLine/NuGet.CommandLine.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -88,22 +88,22 @@
<MakeDir Directories="$(ArtifactsDirectory)$(VsixOutputDirName)" />
<Exec Command="$(IlmergeCommand)" ContinueOnError="false" />
</Target>

<PropertyGroup>
<NuspecFile>NuGet.CommandLine.nuspec</NuspecFile>
<NuspecProperties>version=$(Version);configuration=$(Configuration)</NuspecProperties>
<NuspecBasePath>$(ArtifactsDirectory)$(VsixOutputDirName)</NuspecBasePath>
</PropertyGroup>

<Import Project="$(BuildCommonDirectory)common.targets" />

<Target Name="GetSigningInputs" Returns="@(DllsToSign)">
<ItemGroup>
<DllsToSign Include="$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.exe">
<StrongName>MsSharedLib72</StrongName>
<Authenticode>Microsoft</Authenticode>
</DllsToSign>
</ItemGroup>
<ItemGroup>
<DllsToSign Include="$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.exe">
<StrongName>MsSharedLib72</StrongName>
<Authenticode>Microsoft</Authenticode>
</DllsToSign>
</ItemGroup>
</Target>

<Target Name="GetSymbolsToIndex" Returns="@(SymbolsToIndex)">
Expand Down
Loading