Skip to content

Commit

Permalink
Signing: more SDK initialization and enable opt-in on Linux (#4720)
Browse files Browse the repository at this point in the history
  • Loading branch information
dtivel committed Jul 13, 2022
1 parent 2756cfd commit 42fb9be
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using NuGet.Credentials;
using NuGet.LibraryModel;
using NuGet.Packaging;
using NuGet.Packaging.Signing;
using NuGet.Versioning;

namespace Microsoft.Build.NuGetSdkResolver
Expand Down Expand Up @@ -146,6 +147,10 @@ public static SdkResult GetSdkResult(SdkReference sdk, object nuGetVersion, SdkR
{
DefaultCredentialServiceUtility.SetupDefaultCredentialService(logger, nonInteractive: !context.Interactive);

#if !NETFRAMEWORK
X509TrustStore.InitializeForDotNetSdk(logger);
#endif

// Asynchronously run the restore without a commit which find the package on configured feeds, download, and unzip it without generating any other files
// This must be run in its own task because legacy project system evaluates projects on the UI thread which can cause RunWithoutCommit() to deadlock
// https://developercommunity.visualstudio.com/content/problem/311379/solution-load-never-completes-when-project-contain.html
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.IO;
using Microsoft.Extensions.CommandLineUtils;
using NuGet.Common;
using NuGet.Packaging.Signing;

namespace NuGet.CommandLine.XPlat
{
Expand Down Expand Up @@ -101,6 +102,9 @@ public static void Register(CommandLineApplication app, Func<ILogger> getLogger,
PackageId = id.Values[0]
};
var msBuild = new MSBuildAPIUtility(logger);

X509TrustStore.InitializeForDotNetSdk(logger);

var addPackageRefCommandRunner = getCommandRunner();
return addPackageRefCommandRunner.ExecuteCommand(packageRefArgs, msBuild);
});
Expand Down
2 changes: 1 addition & 1 deletion src/NuGet.Core/NuGet.Packaging/PackageArchiveReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ public override bool CanVerifySignedPackages(SignedPackageVerifierSettings verif
// Please note: Linux/MAC case sensitive for env var name.
string signVerifyEnvVariable = _environmentVariableReader.GetEnvironmentVariable("DOTNET_NUGET_SIGNATURE_VERIFICATION");

bool canVerify = RuntimeEnvironmentHelper.IsLinux;
bool canVerify = false;

if (!string.IsNullOrEmpty(signVerifyEnvVariable))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ internal static IX509ChainFactory CreateX509ChainFactoryForDotNetSdk(ILogger log
if (SystemCertificateBundleX509ChainFactory.TryCreate(
out SystemCertificateBundleX509ChainFactory systemBundleFactory))
{
logger.LogVerbose(
logger.LogInformation(
string.Format(
CultureInfo.CurrentCulture,
Strings.ChainBuilding_UsingSystemCertificateBundle,
Expand All @@ -84,7 +84,7 @@ internal static IX509ChainFactory CreateX509ChainFactoryForDotNetSdk(ILogger log
out FallbackCertificateBundleX509ChainFactory fallbackBundleFactory,
fallbackCertificateBundleFile?.FullName))
{
logger.LogVerbose(
logger.LogInformation(
string.Format(
CultureInfo.CurrentCulture,
Strings.ChainBuilding_UsingFallbackCertificateBundle,
Expand All @@ -93,7 +93,7 @@ internal static IX509ChainFactory CreateX509ChainFactoryForDotNetSdk(ILogger log
return fallbackBundleFactory;
}

logger.LogVerbose(Strings.ChainBuilding_UsingNoCertificateBundle);
logger.LogInformation(Strings.ChainBuilding_UsingNoCertificateBundle);

return new NoCertificateBundleX509ChainFactory();
}
Expand All @@ -104,7 +104,7 @@ internal static IX509ChainFactory CreateX509ChainFactoryForDotNetSdk(ILogger log
out FallbackCertificateBundleX509ChainFactory fallbackBundleFactory,
fallbackCertificateBundleFile?.FullName))
{
logger.LogVerbose(
logger.LogInformation(
string.Format(
CultureInfo.CurrentCulture,
Strings.ChainBuilding_UsingFallbackCertificateBundle,
Expand All @@ -113,7 +113,7 @@ internal static IX509ChainFactory CreateX509ChainFactoryForDotNetSdk(ILogger log
return fallbackBundleFactory;
}

logger.LogVerbose(Strings.ChainBuilding_UsingNoCertificateBundle);
logger.LogInformation(Strings.ChainBuilding_UsingNoCertificateBundle);

return new NoCertificateBundleX509ChainFactory();
}
Expand All @@ -125,7 +125,7 @@ internal static IX509ChainFactory CreateX509ChainFactoryForDotNetSdk(ILogger log
// Non-private for testing purposes only
internal static IX509ChainFactory CreateX509ChainFactory(ILogger logger)
{
logger.LogVerbose(Strings.ChainBuilding_UsingDefaultTrustStore);
logger.LogInformation(Strings.ChainBuilding_UsingDefaultTrustStore);

return new DotNetDefaultTrustStoreX509ChainFactory();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using NuGet.Common;
using NuGet.Packaging;
using NuGet.Packaging.Core;
using NuGet.ProjectModel;
Expand Down Expand Up @@ -613,5 +614,75 @@ await SimpleTestPackageUtility.CreateFolderFeedV3Async(
Assert.Contains($"Installed {packageX} {version} from {packageSource2}", result.AllOutput);
Assert.Contains($"NU1100: Unable to resolve '{packageZ} (>= {version})' for 'net5.0'", result.AllOutput);
}

[Fact]
public void AddPkg_WhenSourceIsSignedPackageWithExpiredCertificatesAndWithTimestamps_Success()
{
using (SimpleTestPathContext pathContext = new())
{
var projectName = "project";
var targetFrameworks = "net6.0";
SimpleTestProjectContext projectA = XPlatTestUtils.CreateProject(projectName, pathContext, targetFrameworks);

// This package is important because:
// * it has no package dependencies and thus simplifies the test scenario
// * it is a signed package and thus verifies signed package verification, if enabled
// * the author- and repository-signing certificates have expired
// * the author and repository timestamps may be untrusted on Linux/macOS if a valid certificate bundle isn't found
PackageIdentity package = new("NuGet.Versioning", new NuGetVersion("5.0.0"));
DirectoryInfo packageSourceDirectory = new(Path.Combine(pathContext.WorkingDirectory, "PackageSource"));
var packageFileName = $"{package.Id.ToLowerInvariant()}.{package.Version}.nupkg";

CopyResourceToDirectory(packageFileName, packageSourceDirectory);

string projectDirectory = Path.Combine(pathContext.SolutionRoot, projectName);
string projectFilePath = Path.Combine(projectDirectory, $"{projectName}.csproj");

CommandRunnerResult result = _fixture.RunDotnet(
projectDirectory,
$"add {projectFilePath} package {package.Id} -s {packageSourceDirectory.FullName} -v {package.Version}",
ignoreExitCode: true);

result.Success.Should().BeTrue(because: result.AllOutput);

if (RuntimeEnvironmentHelper.IsWindows)
{
result.AllOutput.Should()
.Contain(
Strings.ChainBuilding_UsingDefaultTrustStore,
because: result.AllOutput);
}
else
{
result.AllOutput.Should()
.ContainAny(
new string[] {
"X.509 certificate chain validation will use the fallback certificate bundle at ",
"X.509 certificate chain validation will use the system certificate bundle at "
},
because: result.AllOutput);
}

LockFileTarget ridlessTarget = projectA.AssetsFile.Targets
.Where(e => string.IsNullOrEmpty(e.RuntimeIdentifier))
.Single();

ridlessTarget.Libraries.Should().Contain(e => e.Type == "package" && e.Name == package.Id);
ridlessTarget.Libraries.Should().Contain(e => e.Version.Equals(package.Version));
}
}

private void CopyResourceToDirectory(string resourceName, DirectoryInfo directory)
{
string fullResourceName = $"Dotnet.Integration.Test.compiler.resources.{resourceName}";
string destinationFilePath = Path.Combine(directory.FullName, resourceName);

directory.Create();

using (Stream stream = GetType().Assembly.GetManifestResourceStream(fullResourceName))
{
stream.CopyToFile(destinationFilePath);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ public X509TrustStoreTests(MsbuildIntegrationTestFixture msbuildFixture, ITestOu
FallbackCertificateBundleX509ChainFactory.SubdirectoryName,
FallbackCertificateBundleX509ChainFactory.FileName));

_logger.LogInformation($"Expected fallback certificate bundle file path: {_fallbackCertificateBundle.FullName}");
_logger.LogInformation($"Fallback certificate bundle file exists: {_fallbackCertificateBundle.Exists}");
_logger.LogVerbose($"Expected fallback certificate bundle file path: {_fallbackCertificateBundle.FullName}");
_logger.LogVerbose($"Fallback certificate bundle file exists: {_fallbackCertificateBundle.Exists}");
}

[PlatformFact(Platform.Windows)]
Expand All @@ -44,8 +44,8 @@ public void CreateX509ChainFactoryForDotNetSdk_OnWindowsAlways_ReturnsInstance()

// 1 message from the API under test and 2 messages from this test class's constructor
Assert.Equal(3, _logger.Messages.Count);
Assert.Equal(1, _logger.VerboseMessages.Count);
Assert.True(_logger.VerboseMessages.TryDequeue(out string actualMessage));
Assert.Equal(1, _logger.InformationMessages.Count);
Assert.True(_logger.InformationMessages.TryPeek(out string actualMessage));
Assert.Equal(Strings.ChainBuilding_UsingDefaultTrustStore, actualMessage);
}

Expand Down Expand Up @@ -75,8 +75,8 @@ public void CreateX509ChainFactoryForDotNetSdk_OnLinuxAlways_ReturnsInstance()

// 1 message from the API under test and 2 messages from this test class's constructor
Assert.Equal(3, _logger.Messages.Count);
Assert.Equal(1, _logger.VerboseMessages.Count);
Assert.True(_logger.VerboseMessages.TryDequeue(out string actualMessage));
Assert.Equal(1, _logger.InformationMessages.Count);
Assert.True(_logger.InformationMessages.TryPeek(out string actualMessage));

string expectedMessage;

Expand Down Expand Up @@ -114,8 +114,8 @@ public void CreateX509ChainFactoryForDotNetSdk_OnMacOsAlways_ReturnsInstance()

// 1 message from the API under test and 2 messages from this test class's constructor
Assert.Equal(3, _logger.Messages.Count);
Assert.Equal(1, _logger.VerboseMessages.Count);
Assert.True(_logger.VerboseMessages.TryDequeue(out string actualMessage));
Assert.Equal(1, _logger.InformationMessages.Count);
Assert.True(_logger.InformationMessages.TryPeek(out string actualMessage));
Assert.Equal(expectedMessage, actualMessage);
}

Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ public void CreateX509ChainFactory_Always_ReturnsInstance()

Assert.IsType<DotNetDefaultTrustStoreX509ChainFactory>(factory);
Assert.Equal(1, _logger.Messages.Count);
Assert.Equal(1, _logger.VerboseMessages.Count);
Assert.True(_logger.VerboseMessages.TryDequeue(out string actualMessage));
Assert.Equal(1, _logger.InformationMessages.Count);
Assert.True(_logger.InformationMessages.TryDequeue(out string actualMessage));
Assert.Equal(Strings.ChainBuilding_UsingDefaultTrustStore, actualMessage);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.IO;
using FluentAssertions;
using Microsoft.Build.Framework;
using Moq;
using NuGet.Packaging;
using NuGet.Test.Utility;
using NuGet.Versioning;
Expand Down Expand Up @@ -77,6 +76,29 @@ public void Resolve_WhenPackageExists_ReturnsSucceededSdkResult()
result.Version.Should().Be(sdkReference.Version);
result.Errors.Should().BeEmpty();
result.Warnings.Should().BeEmpty();

bool wasMessageFound = false;

foreach ((string Message, MessageImportance _) in sdkResolverContext.MockSdkLogger.LoggedMessages)
{
// On Linux and macOS the message will be:
//
// X.509 certificate chain validation will not have any trusted roots.
// Chain building will fail with an untrusted status.
//
// This is because this test is not a .NET SDK test but a unit test.
if (Message.Contains("X.509 certificate chain validation will"))
{
wasMessageFound = true;
break;
}
}

#if NETFRAMEWORK
wasMessageFound.Should().BeFalse();
#else
wasMessageFound.Should().BeTrue();
#endif
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1976,23 +1976,19 @@ private static byte[] CreateNonEmptyZipArchive()
}
#endif

[PlatformFact(Platform.Windows, Platform.Linux)]
public void CanVerifySignedPackages_OnWindowsAndLinux_ReturnsValueBasedOnOperatingSystemAndFramework()
[Fact]
public void CanVerifySignedPackages_Always_ReturnsValueBasedOnOperatingSystemAndFramework()
{
// Arrange
using (var test = TestPackagesCore.GetPackageContentReaderTestPackage())
using (var packageArchiveReader = new PackageArchiveReader(test))
{
bool expectedResult = CanVerifySignedPackages();

// Act
var result = packageArchiveReader.CanVerifySignedPackages(null);
// Assert
#if IS_SIGNING_SUPPORTED
// Verify package signature when signing is supported
Assert.True(result);
#else
// Cannot verify package signature when signing is not supported
Assert.False(result);
#endif
bool actualResult = packageArchiveReader.CanVerifySignedPackages(null);

Assert.Equal(expectedResult, actualResult);
}
}

Expand All @@ -2011,23 +2007,6 @@ public void CanVerifySignedPackages_OnMacOs_ReturnsValueBasedOnOperatingSystemAn
}
}

[Fact]
public void CanVerifySignedPackages_ReturnsValueBasedOnOperatingSystemAndFramework_Fails()
{
// Arrange
using (var test = TestPackagesCore.GetPackageContentReaderTestPackage())
using (var packageArchiveReader = new PackageArchiveReader(test))
{
bool expectedResult = CanVerifySignedPackages();

// Act
bool actualResult = packageArchiveReader.CanVerifySignedPackages(null);

// Assert
Assert.Equal(expectedResult, actualResult);
}
}

[Theory]
[InlineData("TRUE")]
[InlineData("True")]
Expand Down Expand Up @@ -2123,7 +2102,7 @@ public void CanVerifySignedPackages_ReturnsValueBasedOnOperatingSystemAndFramewo

private static bool CanVerifySignedPackages()
{
return (RuntimeEnvironmentHelper.IsWindows || RuntimeEnvironmentHelper.IsLinux) &&
return RuntimeEnvironmentHelper.IsWindows &&
#if IS_SIGNING_SUPPORTED
true;
#else
Expand Down

0 comments on commit 42fb9be

Please sign in to comment.