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

Add an ENV Variable for Package Signing Verification on .NET 5+ Linux/MAC #3986

Merged
merged 19 commits into from
Apr 9, 2021
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 39 additions & 2 deletions src/NuGet.Core/NuGet.Packaging/PackageArchiveReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public class PackageArchiveReader : PackageReaderBase
{
private readonly ZipArchive _zipArchive;
private readonly SigningSpecifications _signingSpecifications = SigningSpecifications.V1;
private readonly IEnvironmentVariableReader _environmentVariableReader;

/// <summary>
/// Signature specifications.
Expand All @@ -36,6 +37,27 @@ public class PackageArchiveReader : PackageReaderBase
/// </summary>
protected Stream ZipReadStream { get; set; }

/// <summary>
/// Nupkg package reader
/// </summary>
/// <param name="frameworkProvider">Framework mapping provider for NuGetFramework parsing.</param>
/// <param name="compatibilityProvider">Framework compatibility provider.</param>
private PackageArchiveReader(IFrameworkNameProvider frameworkProvider, IFrameworkCompatibilityProvider compatibilityProvider)
: base(frameworkProvider, compatibilityProvider)
{
_environmentVariableReader = EnvironmentVariableWrapper.Instance;
}

// For testing purposes only
internal PackageArchiveReader(Stream stream, IEnvironmentVariableReader environmentVariableReader)
: this(stream)
{
if (environmentVariableReader != null)
erdembayar marked this conversation as resolved.
Show resolved Hide resolved
{
_environmentVariableReader = environmentVariableReader;
}
}

/// <summary>
/// Nupkg package reader
/// </summary>
Expand Down Expand Up @@ -96,13 +118,13 @@ public PackageArchiveReader(ZipArchive zipArchive)
/// <param name="frameworkProvider">Framework mapping provider for NuGetFramework parsing.</param>
/// <param name="compatibilityProvider">Framework compatibility provider.</param>
public PackageArchiveReader(ZipArchive zipArchive, IFrameworkNameProvider frameworkProvider, IFrameworkCompatibilityProvider compatibilityProvider)
: base(frameworkProvider, compatibilityProvider)
: this(frameworkProvider, compatibilityProvider)
{
_zipArchive = zipArchive ?? throw new ArgumentNullException(nameof(zipArchive));
}

public PackageArchiveReader(string filePath, IFrameworkNameProvider frameworkProvider = null, IFrameworkCompatibilityProvider compatibilityProvider = null)
: base(frameworkProvider ?? DefaultFrameworkNameProvider.Instance, compatibilityProvider ?? DefaultCompatibilityProvider.Instance)
: this(frameworkProvider ?? DefaultFrameworkNameProvider.Instance, compatibilityProvider ?? DefaultCompatibilityProvider.Instance)
{
if (filePath == null)
{
Expand Down Expand Up @@ -454,6 +476,21 @@ public override bool CanVerifySignedPackages(SignedPackageVerifierSettings verif
}
else if (RuntimeEnvironmentHelper.IsLinux || RuntimeEnvironmentHelper.IsMacOSX)
{
// Please note: Linux/MAC case sensitive for env var name.
string signVerifyEnvVariable = _environmentVariableReader.GetEnvironmentVariable("DOTNET_OPT_IN_SECURE_PACKAGE_VERIFICATION");

// Not opt-out option, only opt-in feature.
if (!string.IsNullOrEmpty(signVerifyEnvVariable))
{
if (signVerifyEnvVariable.Equals(bool.TrueString.ToUpperInvariant(), StringComparison.Ordinal))
{
return true;
}

// other values are unsupported
return false;
}

return false;
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ namespace Dotnet.Integration.Test
[Collection("Dotnet Integration Tests")]
public class DotnetRestoreTests
{
private const string OptInPackageVerification = "DOTNET_OPT_IN_SECURE_PACKAGE_VERIFICATION";
private const string OptInPackageVerificationTypo = "DOTNET_OPT_IN_SECURE_PACKAGE_VERIFICATIOn";

private MsbuildIntegrationTestFixture _msbuildFixture;

public DotnetRestoreTests(MsbuildIntegrationTestFixture fixture)
Expand Down Expand Up @@ -265,6 +268,252 @@ await SimpleTestPackageUtility.CreateFolderFeedV3Async(
}
}

[Fact]
public async Task DotnetRestore_WithUnSignedPackageAndSignatureValidationModeAsRequired_OptInEnvVar_True_FailsAsync()
{
using (var pathContext = _msbuildFixture.CreateSimpleTestPathContext())
{
//Arrange
var envVarName = OptInPackageVerification;
var envVarValue = "TRUE";
//Setup packages and feed
var packageX = new SimpleTestPackageContext()
{
Id = "x",
Version = "1.0.0"
};
packageX.Files.Clear();
packageX.AddFile("lib/netcoreapp2.0/x.dll");
packageX.AddFile("ref/netcoreapp2.0/x.dll");
packageX.AddFile("lib/net472/x.dll");
packageX.AddFile("ref/net472/x.dll");

await SimpleTestPackageUtility.CreateFolderFeedV3Async(
pathContext.PackageSource,
PackageSaveMode.Defaultv3,
packageX);

// Set up solution, and project
var solution = new SimpleTestSolutionContext(pathContext.SolutionRoot);

var projectName = "ClassLibrary1";
var workingDirectory = Path.Combine(pathContext.SolutionRoot, projectName);
var projectFile = Path.Combine(workingDirectory, $"{projectName}.csproj");

_msbuildFixture.CreateDotnetNewProject(pathContext.SolutionRoot, projectName, "classlib");

using (FileStream stream = File.Open(projectFile, FileMode.Open, FileAccess.ReadWrite))
{
XDocument xml = XDocument.Load(stream);

var attributes = new Dictionary<string, string>() { { "Version", "1.0.0" } };

ProjectFileUtils.AddItem(
xml,
"PackageReference",
packageX.Id,
string.Empty,
new Dictionary<string, string>(),
attributes);

ProjectFileUtils.WriteXmlToFile(xml, stream);
}

//set nuget.config properties
var doc = new XDocument();
var configuration = new XElement(XName.Get("configuration"));
doc.Add(configuration);

var config = new XElement(XName.Get("config"));
configuration.Add(config);

var signatureValidationMode = new XElement(XName.Get("add"));
signatureValidationMode.Add(new XAttribute(XName.Get("key"), "signatureValidationMode"));
signatureValidationMode.Add(new XAttribute(XName.Get("value"), "require"));
config.Add(signatureValidationMode);

File.WriteAllText(Path.Combine(workingDirectory, "NuGet.Config"), doc.ToString());

// Act
CommandRunnerResult result = _msbuildFixture.RunDotnet(
workingDirectory, "restore",
ignoreExitCode: true,
additionalEnvVars: new Dictionary<string, string>()
{
{ envVarName, envVarValue }
}
);

result.AllOutput.Should().Contain($"error NU3004: Package '{packageX.Id} {packageX.Version}' from source '{pathContext.PackageSource}': signatureValidationMode is set to require, so packages are allowed only if signed by trusted signers; however, this package is unsigned.");
result.Success.Should().BeFalse();
result.ExitCode.Should().Be(1, because: "error text should be displayed as restore failed");
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, technically they're same. I just copied from another already existing integration test.
2nd one actually tells you what went wrong in text.

result.Success.Should().BeFalse();
result.ExitCode.Should().Be(1, because: "error text should be displayed as restore failed");

Copy link
Member

Choose a reason for hiding this comment

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

result.Success.Should().BeFalse(because: "error text should be displayed as restore failed");

The following works too.
Existing code is not always perfect.

}
}

[PlatformFact(Platform.Linux, Platform.Darwin)]
public async Task DotnetRestore_WithUnSignedPackageAndSignatureValidationModeAsRequired_OptInEnvVar_NameCaseSensitive_SucceedAsync()
{
using (var pathContext = _msbuildFixture.CreateSimpleTestPathContext())
{
//Arrange
var envVarName = OptInPackageVerificationTypo;
var envVarValue = "xyz";
//Setup packages and feed
var packageX = new SimpleTestPackageContext()
{
Id = "x",
Version = "1.0.0"
};
packageX.Files.Clear();
packageX.AddFile("lib/netcoreapp2.0/x.dll");
packageX.AddFile("ref/netcoreapp2.0/x.dll");
packageX.AddFile("lib/net472/x.dll");
packageX.AddFile("ref/net472/x.dll");

await SimpleTestPackageUtility.CreateFolderFeedV3Async(
pathContext.PackageSource,
PackageSaveMode.Defaultv3,
packageX);

// Set up solution, and project
var solution = new SimpleTestSolutionContext(pathContext.SolutionRoot);

var projectName = "ClassLibrary1";
var workingDirectory = Path.Combine(pathContext.SolutionRoot, projectName);
var projectFile = Path.Combine(workingDirectory, $"{projectName}.csproj");

_msbuildFixture.CreateDotnetNewProject(pathContext.SolutionRoot, projectName, "classlib");

using (FileStream stream = File.Open(projectFile, FileMode.Open, FileAccess.ReadWrite))
{
XDocument xml = XDocument.Load(stream);

var attributes = new Dictionary<string, string>() { { "Version", "1.0.0" } };

ProjectFileUtils.AddItem(
xml,
"PackageReference",
packageX.Id,
string.Empty,
new Dictionary<string, string>(),
attributes);

ProjectFileUtils.WriteXmlToFile(xml, stream);
}

//set nuget.config properties
var doc = new XDocument();
var configuration = new XElement(XName.Get("configuration"));
doc.Add(configuration);

var config = new XElement(XName.Get("config"));
configuration.Add(config);

var signatureValidationMode = new XElement(XName.Get("add"));
signatureValidationMode.Add(new XAttribute(XName.Get("key"), "signatureValidationMode"));
signatureValidationMode.Add(new XAttribute(XName.Get("value"), "require"));
config.Add(signatureValidationMode);

File.WriteAllText(Path.Combine(workingDirectory, "NuGet.Config"), doc.ToString());

// Act
CommandRunnerResult result = _msbuildFixture.RunDotnet(
workingDirectory, "restore",
ignoreExitCode: true,
additionalEnvVars: new Dictionary<string, string>()
{
{ envVarName, envVarValue }
}
);

result.AllOutput.Should().NotContain($"error NU3004");
result.Success.Should().BeTrue();
result.ExitCode.Should().Be(0);
}
}

[PlatformFact(Platform.Linux, Platform.Darwin)]
public async Task DotnetRestore_WithUnSignedPackageAndSignatureValidationModeAsRequired_ValueCaseSensitive_OptInEnvVar_SucceedAsync()
{
using (var pathContext = _msbuildFixture.CreateSimpleTestPathContext())
{
//Arrange
var envVarName = OptInPackageVerification;
var envVarValue = "true";
//Setup packages and feed
var packageX = new SimpleTestPackageContext()
{
Id = "x",
Version = "1.0.0"
};
packageX.Files.Clear();
packageX.AddFile("lib/netcoreapp2.0/x.dll");
packageX.AddFile("ref/netcoreapp2.0/x.dll");
packageX.AddFile("lib/net472/x.dll");
packageX.AddFile("ref/net472/x.dll");

await SimpleTestPackageUtility.CreateFolderFeedV3Async(
pathContext.PackageSource,
PackageSaveMode.Defaultv3,
packageX);

// Set up solution, and project
var solution = new SimpleTestSolutionContext(pathContext.SolutionRoot);

var projectName = "ClassLibrary1";
var workingDirectory = Path.Combine(pathContext.SolutionRoot, projectName);
var projectFile = Path.Combine(workingDirectory, $"{projectName}.csproj");

_msbuildFixture.CreateDotnetNewProject(pathContext.SolutionRoot, projectName, "classlib");

using (FileStream stream = File.Open(projectFile, FileMode.Open, FileAccess.ReadWrite))
{
XDocument xml = XDocument.Load(stream);

var attributes = new Dictionary<string, string>() { { "Version", "1.0.0" } };

ProjectFileUtils.AddItem(
xml,
"PackageReference",
packageX.Id,
string.Empty,
new Dictionary<string, string>(),
attributes);

ProjectFileUtils.WriteXmlToFile(xml, stream);
}

//set nuget.config properties
var doc = new XDocument();
var configuration = new XElement(XName.Get("configuration"));
doc.Add(configuration);

var config = new XElement(XName.Get("config"));
configuration.Add(config);

var signatureValidationMode = new XElement(XName.Get("add"));
signatureValidationMode.Add(new XAttribute(XName.Get("key"), "signatureValidationMode"));
signatureValidationMode.Add(new XAttribute(XName.Get("value"), "require"));
config.Add(signatureValidationMode);

File.WriteAllText(Path.Combine(workingDirectory, "NuGet.Config"), doc.ToString());

// Act
CommandRunnerResult result = _msbuildFixture.RunDotnet(
workingDirectory, "restore",
ignoreExitCode: true,
additionalEnvVars: new Dictionary<string, string>()
{
{ envVarName, envVarValue }
}
);

result.AllOutput.Should().NotContain($"error NU3004");
result.Success.Should().BeTrue();
result.ExitCode.Should().Be(0);
}
}

[PlatformFact(Platform.Windows)]
public void DotnetRestore_WithAuthorSignedPackageAndSignatureValidationModeAsRequired_Succeeds()
{
Expand Down
Loading