diff --git a/src/NuGet.Services.Validation.Orchestrator/NuGet.Services.Validation.Orchestrator.csproj b/src/NuGet.Services.Validation.Orchestrator/NuGet.Services.Validation.Orchestrator.csproj index 047d7f454..0e7df2ef7 100644 --- a/src/NuGet.Services.Validation.Orchestrator/NuGet.Services.Validation.Orchestrator.csproj +++ b/src/NuGet.Services.Validation.Orchestrator/NuGet.Services.Validation.Orchestrator.csproj @@ -107,7 +107,7 @@ - 2.23.0 + 2.25.0-master-29664 diff --git a/src/Validation.Common.Job/Validation.Common.Job.csproj b/src/Validation.Common.Job/Validation.Common.Job.csproj index 91a400785..fbbdfad1e 100644 --- a/src/Validation.Common.Job/Validation.Common.Job.csproj +++ b/src/Validation.Common.Job/Validation.Common.Job.csproj @@ -100,10 +100,10 @@ 2.23.0 - 2.23.0 + 2.25.0-master-29664 - 4.4.4-dev-26726 + 4.4.4-dev-29942 2.5.0 diff --git a/src/Validation.Common.Job/Validation.Common.Job.nuspec b/src/Validation.Common.Job/Validation.Common.Job.nuspec index 0adae1f71..a03fae7c3 100644 --- a/src/Validation.Common.Job/Validation.Common.Job.nuspec +++ b/src/Validation.Common.Job/Validation.Common.Job.nuspec @@ -19,8 +19,8 @@ - - + + diff --git a/src/Validation.PackageSigning.ProcessSignature/Job.cs b/src/Validation.PackageSigning.ProcessSignature/Job.cs index fe87a1821..09aa0026f 100644 --- a/src/Validation.PackageSigning.ProcessSignature/Job.cs +++ b/src/Validation.PackageSigning.ProcessSignature/Job.cs @@ -1,13 +1,13 @@ // 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 Autofac; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.WindowsAzure.Storage; +using NuGet.Jobs.Configuration; using NuGet.Jobs.Validation.PackageSigning.Configuration; using NuGet.Jobs.Validation.PackageSigning.Messages; using NuGet.Jobs.Validation.PackageSigning.Storage; @@ -30,7 +30,12 @@ protected override void ConfigureJobServices(IServiceCollection services, IConfi services.AddTransient, SubscriptionProcessor>(); - services.AddTransient, EntityRepository>(); + services.AddScoped(serviceProvider => + new EntitiesContext( + serviceProvider.GetRequiredService>().Value.ConnectionString, + readOnly: false)); + services.Add(ServiceDescriptor.Transient(typeof(IEntityRepository<>), typeof(EntityRepository<>))); + services.AddTransient(); services.AddTransient(); @@ -61,7 +66,7 @@ protected override void ConfigureJobServices(IServiceCollection services, IConfi PackageSignatureVerifierFactory.CreateFull(), p.GetRequiredService(), p.GetRequiredService(), - p.GetRequiredService>(), + p.GetRequiredService(), p.GetRequiredService(), p.GetRequiredService>())); } diff --git a/src/Validation.PackageSigning.ProcessSignature/SignatureValidator.cs b/src/Validation.PackageSigning.ProcessSignature/SignatureValidator.cs index e3b14498c..f92e61c30 100644 --- a/src/Validation.PackageSigning.ProcessSignature/SignatureValidator.cs +++ b/src/Validation.PackageSigning.ProcessSignature/SignatureValidator.cs @@ -19,6 +19,7 @@ using NuGet.Services.Validation; using NuGet.Services.Validation.Issues; using NuGetGallery; +using NuGetGallery.Extensions; namespace NuGet.Jobs.Validation.PackageSigning.ProcessSignature { @@ -32,7 +33,7 @@ public class SignatureValidator : ISignatureValidator private readonly IPackageSignatureVerifier _fullPackageSignatureVerifier; private readonly ISignaturePartsExtractor _signaturePartsExtractor; private readonly IProcessorPackageFileService _packageFileService; - private readonly IEntityRepository _certificates; + private readonly ICorePackageService _corePackageService; private readonly ITelemetryService _telemetryService; private readonly ILogger _logger; @@ -42,7 +43,7 @@ public SignatureValidator( IPackageSignatureVerifier fullPackageSignatureVerifier, ISignaturePartsExtractor signaturePartsExtractor, IProcessorPackageFileService packageFileService, - IEntityRepository certificates, + ICorePackageService corePackageService, ITelemetryService telemetryService, ILogger logger) { @@ -51,7 +52,7 @@ public SignatureValidator( _fullPackageSignatureVerifier = fullPackageSignatureVerifier ?? throw new ArgumentNullException(nameof(fullPackageSignatureVerifier)); _signaturePartsExtractor = signaturePartsExtractor ?? throw new ArgumentNullException(nameof(signaturePartsExtractor)); _packageFileService = packageFileService ?? throw new ArgumentNullException(nameof(packageFileService)); - _certificates = certificates ?? throw new ArgumentNullException(nameof(certificates)); + _corePackageService = corePackageService ?? throw new ArgumentNullException(nameof(corePackageService)); _telemetryService = telemetryService ?? throw new ArgumentNullException(nameof(telemetryService)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -71,20 +72,30 @@ public async Task ValidateAsync( return await RejectAsync(context, ValidationIssue.PackageIsZip64); } - // Validate signed packages and accept unsigned packages. if (await context.PackageReader.IsSignedAsync(cancellationToken)) { return await HandleSignedPackageAsync(context); } - else - { - return await HandleUnsignedPackageAsync(context); - } + + return await HandleUnsignedPackageAsync(context); } } - + private async Task HandleUnsignedPackageAsync(Context context) { + var packageRegistration = _corePackageService.FindPackageRegistrationById(context.Message.PackageId); + + if (packageRegistration.IsSigningRequired()) + { + _logger.LogWarning( + "Package {PackageId} {PackageVersion} for validation {ValidationId} must be signed but is unsigned.", + context.Message.PackageId, + context.Message.PackageVersion, + context.Message.ValidationId); + + return await RejectAsync(context, ValidationIssue.PackageIsNotSigned); + } + _logger.LogInformation( "Package {PackageId} {PackageVersion} is unsigned, no additional validations necessary for {ValidationId}.", context.Message.PackageId, @@ -178,7 +189,7 @@ private async Task PerformInitialValidationsAsync(Cont // We now know we can safely read the signature. context.Signature = await context.PackageReader.GetPrimarySignatureAsync(context.CancellationToken); - + // Only reject counter signatures that have the author commitment type. Repository counter signatures // are removed and replaced if they are invalid and valid ones are left as-is. Counter signatures // without author or repository signature commitment type are not produced by the client but @@ -323,12 +334,12 @@ private async Task PerformFinalValidationAsync(Context .SignerInfo .Certificate .ComputeSHA256Thumbprint(); - var isKnownCertificate = _certificates - .GetAll() - .Any(c => signingFingerprint == c.Thumbprint); - if (!isKnownCertificate) + + var packageRegistration = _corePackageService.FindPackageRegistrationById(context.Message.PackageId); + + if (!packageRegistration.IsAcceptableSigningCertificate(signingFingerprint)) { - _logger.LogInformation( + _logger.LogWarning( "Signed package {PackageId} {PackageVersion} is blocked for validation {ValidationId} since it has an unknown certificate fingerprint: {UnknownFingerprint}", context.Message.PackageId, context.Message.PackageVersion, @@ -357,6 +368,11 @@ private async Task PerformFinalValidationAsync(Context context.Message.ValidationId, signingFingerprint); + await _corePackageService.UpdatePackageSigningCertificateAsync( + context.Message.PackageId, + context.Message.PackageVersion, + signingFingerprint); + return null; } diff --git a/tests/Validation.PackageSigning.ProcessSignature.Tests/SignatureValidatorFacts.cs b/tests/Validation.PackageSigning.ProcessSignature.Tests/SignatureValidatorFacts.cs index 8040f19d0..a98b02a64 100644 --- a/tests/Validation.PackageSigning.ProcessSignature.Tests/SignatureValidatorFacts.cs +++ b/tests/Validation.PackageSigning.ProcessSignature.Tests/SignatureValidatorFacts.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -29,15 +28,15 @@ public class ValidateAsync { private MemoryStream _packageStream; private readonly int _packageKey; - private readonly SignatureValidationMessage _message; + private SignatureValidationMessage _message; private readonly CancellationToken _cancellationToken; private readonly Mock _packageSigningStateService; - private VerifySignaturesResult _mimialVerifyResult; + private VerifySignaturesResult _mimimalVerifyResult; private readonly Mock _mimimalPackageSignatureVerifier; private VerifySignaturesResult _fullVerifyResult; private readonly Mock _fullPackageSignatureVerifier; private readonly Mock _signaturePartsExtractor; - private readonly Mock> _certificates; + private readonly Mock _corePackageService; private readonly ILogger _logger; private readonly Mock _packageFileService; private readonly Uri _nupkgUri; @@ -57,11 +56,11 @@ public ValidateAsync(ITestOutputHelper output) _packageSigningStateService = new Mock(); - _mimialVerifyResult = new VerifySignaturesResult(true); + _mimimalVerifyResult = new VerifySignaturesResult(true); _mimimalPackageSignatureVerifier = new Mock(); _mimimalPackageSignatureVerifier .Setup(x => x.VerifySignaturesAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .ReturnsAsync(() => _mimialVerifyResult); + .ReturnsAsync(() => _mimimalVerifyResult); _fullVerifyResult = new VerifySignaturesResult(true); _fullPackageSignatureVerifier = new Mock(); @@ -70,14 +69,10 @@ public ValidateAsync(ITestOutputHelper output) .ReturnsAsync(() => _fullVerifyResult); _signaturePartsExtractor = new Mock(); - _certificates = new Mock>(); + _corePackageService = new Mock(); var loggerFactory = new LoggerFactory().AddXunit(output); _logger = loggerFactory.CreateLogger(); - _certificates - .Setup(x => x.GetAll()) - .Returns(Enumerable.Empty().AsQueryable()); - _packageFileService = new Mock(); _nupkgUri = new Uri("https://example-storage/TestProcessor/b777135f-1aac-4ec2-a3eb-1f64fe1880d5/nuget.versioning.4.3.0.nupkg"); _packageFileService @@ -92,7 +87,7 @@ public ValidateAsync(ITestOutputHelper output) _fullPackageSignatureVerifier.Object, _signaturePartsExtractor.Object, _packageFileService.Object, - _certificates.Object, + _corePackageService.Object, _telemetryService.Object, _logger); } @@ -136,6 +131,8 @@ private void Validate( x => x.ExtractAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } + + _corePackageService.VerifyAll(); } [Fact] @@ -157,12 +154,45 @@ public async Task RejectsZip64Packages() Assert.Equal(ValidationIssueCode.PackageIsZip64, issue.IssueCode); } + [Fact] + public async Task RejectsUnsignedPackagesWhenSigningIsRequired() + { + // Arrange + _packageStream = TestResources.GetResourceStream(TestResources.UnsignedPackage); + TestUtility.RequireSignedPackage(_corePackageService, TestResources.UnsignedPackageId); + _message = new SignatureValidationMessage( + TestResources.UnsignedPackageId, + TestResources.UnsignedPackageVersion, + new Uri($"https://unit.test/{TestResources.UnsignedPackage.ToLowerInvariant()}"), + Guid.NewGuid()); + + // Act + var result = await _target.ValidateAsync( + _packageKey, + _packageStream, + _message, + _cancellationToken); + + // Assert + Validate(result, ValidationStatus.Failed, PackageSigningStatus.Invalid); + var issue = Assert.Single(result.Issues); + Assert.Equal(ValidationIssueCode.PackageIsNotSigned, issue.IssueCode); + } + [Fact] public async Task AcceptsSignedPackagesWithKnownCertificates() { // Arrange _packageStream = TestResources.GetResourceStream(TestResources.SignedPackageLeaf1); - ConfigureKnownSignedPackage(TestResources.Leaf1Thumbprint); + TestUtility.RequireSignedPackage( + _corePackageService, + TestResources.SignedPackageLeafId, + TestResources.Leaf1Thumbprint); + _message = new SignatureValidationMessage( + TestResources.SignedPackageLeafId, + TestResources.SignedPackageLeaf1Version, + new Uri($"https://unit.test/{TestResources.SignedPackageLeaf1.ToLowerInvariant()}"), + Guid.NewGuid()); // Act var result = await _target.ValidateAsync( @@ -181,7 +211,12 @@ public async Task RejectsSignedPackagesWithFailedMinimalVerifyResult() { // Arrange _packageStream = TestResources.GetResourceStream(TestResources.SignedPackageLeaf1); - _mimialVerifyResult = new VerifySignaturesResult(valid: false); + _mimimalVerifyResult = new VerifySignaturesResult(valid: false); + _message = new SignatureValidationMessage( + TestResources.SignedPackageLeafId, + TestResources.SignedPackageLeaf1Version, + new Uri($"https://unit.test/{TestResources.SignedPackageLeaf1.ToLowerInvariant()}"), + Guid.NewGuid()); // Act var result = await _target.ValidateAsync( @@ -203,7 +238,7 @@ public async Task RejectsPackagesWithMimimalVerificationErrors() { // Arrange _packageStream = TestResources.GetResourceStream(TestResources.SignedPackageLeaf1); - _mimialVerifyResult = new VerifySignaturesResult( + _mimimalVerifyResult = new VerifySignaturesResult( valid: false, results: new[] { @@ -217,6 +252,11 @@ public async Task RejectsPackagesWithMimimalVerificationErrors() message: "The package signature is invalid."), }) }); + _message = new SignatureValidationMessage( + TestResources.SignedPackageLeafId, + TestResources.SignedPackageLeaf1Version, + new Uri($"https://unit.test/{TestResources.SignedPackageLeaf1.ToLowerInvariant()}"), + Guid.NewGuid()); // Act var result = await _target.ValidateAsync( @@ -238,8 +278,16 @@ public async Task RejectsSignedPackagesWithKnownCertificatesButFailedFullVerifyR { // Arrange _packageStream = TestResources.GetResourceStream(TestResources.SignedPackageLeaf1); - ConfigureKnownSignedPackage(TestResources.Leaf1Thumbprint); + TestUtility.RequireSignedPackage( + _corePackageService, + TestResources.SignedPackageLeafId, + TestResources.Leaf1Thumbprint); _fullVerifyResult = new VerifySignaturesResult(valid: false); + _message = new SignatureValidationMessage( + TestResources.SignedPackageLeafId, + TestResources.SignedPackageLeaf1Version, + new Uri($"https://unit.test/{TestResources.SignedPackageLeaf1.ToLowerInvariant()}"), + Guid.NewGuid()); // Act var result = await _target.ValidateAsync( @@ -258,7 +306,10 @@ public async Task RejectsPackagesWithFullVerificationErrors() { // Arrange _packageStream = TestResources.GetResourceStream(TestResources.SignedPackageLeaf1); - ConfigureKnownSignedPackage(TestResources.Leaf1Thumbprint); + TestUtility.RequireSignedPackage( + _corePackageService, + TestResources.SignedPackageLeafId, + TestResources.Leaf1Thumbprint); _fullVerifyResult = new VerifySignaturesResult( valid: false, results: new[] @@ -281,6 +332,11 @@ public async Task RejectsPackagesWithFullVerificationErrors() message: "Some other thing happened."), }) }); + _message = new SignatureValidationMessage( + TestResources.SignedPackageLeafId, + TestResources.SignedPackageLeaf1Version, + new Uri($"https://unit.test/{TestResources.SignedPackageLeaf1.ToLowerInvariant()}"), + Guid.NewGuid()); // Act var result = await _target.ValidateAsync( @@ -305,7 +361,15 @@ public async Task RejectsSignedPackagesWithUnknownCertificates() { // Arrange _packageStream = TestResources.GetResourceStream(TestResources.SignedPackageLeaf1); - ConfigureKnownSignedPackage(TestResources.Leaf2Thumbprint); + TestUtility.RequireSignedPackage( + _corePackageService, + TestResources.SignedPackageLeafId, + TestResources.Leaf2Thumbprint); + _message = new SignatureValidationMessage( + TestResources.SignedPackageLeafId, + TestResources.SignedPackageLeaf1Version, + new Uri($"https://unit.test/{TestResources.SignedPackageLeaf1.ToLowerInvariant()}"), + Guid.NewGuid()); // Act var result = await _target.ValidateAsync( @@ -325,8 +389,9 @@ public async Task DoesNotUploadPackageWhenValidationFailed() { // Arrange _packageStream = TestResources.GetResourceStream(TestResources.AuthorAndRepoSignedPackageLeaf1); + TestUtility.RequireUnsignedPackage(_corePackageService, _message.PackageId); - // Arrange & Act + // Act var result = await _target.ValidateAsync( _packageKey, _packageStream, @@ -341,13 +406,40 @@ public async Task DoesNotUploadPackageWhenValidationFailed() } [Theory] - [InlineData(TestResources.RepoSignedPackageLeaf1, PackageSigningStatus.Unsigned)] - [InlineData(TestResources.AuthorAndRepoSignedPackageLeaf1, PackageSigningStatus.Valid)] - public async Task StripsAndAcceptsPackagesWithRepositorySignatures(string resourceName, PackageSigningStatus signingStatus) + [InlineData( + TestResources.RepoSignedPackageLeaf1, + TestResources.RepoSignedPackageLeafId, + TestResources.RepoSignedPackageLeaf1Version, + PackageSigningStatus.Unsigned, + false)] + [InlineData( + TestResources.AuthorAndRepoSignedPackageLeaf1, + TestResources.AuthorAndRepoSignedPackageLeafId, + TestResources.AuthorAndRepoSignedPackageLeaf1Version, + PackageSigningStatus.Valid, + true)] + public async Task StripsAndAcceptsPackagesWithRepositorySignatures( + string resourceName, + string packageId, + string packageVersion, + PackageSigningStatus signingStatus, + bool allowSignedPackage) { // Arrange _packageStream = TestResources.GetResourceStream(resourceName); - ConfigureKnownSignedPackage(TestResources.Leaf1Thumbprint); + if (allowSignedPackage) + { + TestUtility.RequireSignedPackage(_corePackageService, packageId, TestResources.Leaf1Thumbprint); + } + else + { + TestUtility.RequireUnsignedPackage(_corePackageService, packageId); + } + _message = new SignatureValidationMessage( + packageId, + packageVersion, + new Uri($"https://unit.test/{resourceName.ToLowerInvariant()}"), + Guid.NewGuid()); Stream uploadedStream = null; _packageFileService @@ -355,7 +447,7 @@ public async Task StripsAndAcceptsPackagesWithRepositorySignatures(string resour .Returns(Task.CompletedTask) .Callback((_, __, ___, s) => uploadedStream = s); - // Arrange & Act + // Act var result = await _target.ValidateAsync( _packageKey, _packageStream, @@ -376,12 +468,105 @@ public async Task StripsAndAcceptsPackagesWithRepositorySignatures(string resour } [Fact] - public async Task AcceptsUnsignedPackages() + public async Task StripsAndRejectsPackagesWithRepositorySignatureWhenPackageMustBeAuthorSigned() + { + _packageStream = TestResources.GetResourceStream(TestResources.RepoSignedPackageLeaf1); + TestUtility.RequireSignedPackage(_corePackageService, TestResources.RepoSignedPackageLeafId); + _message = new SignatureValidationMessage( + TestResources.RepoSignedPackageLeafId, + TestResources.RepoSignedPackageLeaf1Version, + new Uri($"https://unit.test/{TestResources.RepoSignedPackageLeaf1.ToLowerInvariant()}"), + Guid.NewGuid()); + + Stream uploadedStream = null; + _packageFileService + .Setup(x => x.SaveAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask) + .Callback((_, __, ___, s) => uploadedStream = s); + + var result = await _target.ValidateAsync( + _packageKey, + _packageStream, + _message, + _cancellationToken); + + Validate(result, ValidationStatus.Failed, PackageSigningStatus.Invalid); + Assert.Equal(1, result.Issues.Count); + var issue = Assert.IsType(result.Issues[0]); + Assert.Equal(ValidationIssueCode.PackageIsNotSigned, issue.IssueCode); + } + + [Fact] + public async Task StripsAndRejectsPackagesWithRepositorySignatureWhenPackageIsAuthorSignedWithUnknownCertificate() + { + _packageStream = TestResources.GetResourceStream(TestResources.AuthorAndRepoSignedPackageLeaf1); + TestUtility.RequireSignedPackage(_corePackageService, TestResources.AuthorAndRepoSignedPackageLeafId, TestResources.Leaf2Thumbprint); + _message = new SignatureValidationMessage( + TestResources.AuthorAndRepoSignedPackageLeafId, + TestResources.AuthorAndRepoSignedPackageLeaf1Version, + new Uri($"https://unit.test/{TestResources.AuthorAndRepoSignedPackageLeaf1.ToLowerInvariant()}"), + Guid.NewGuid()); + + var result = await _target.ValidateAsync( + _packageKey, + _packageStream, + _message, + _cancellationToken); + + Validate(result, ValidationStatus.Failed, PackageSigningStatus.Invalid); + Assert.Equal(1, result.Issues.Count); + var issue = Assert.IsType(result.Issues[0]); + Assert.Equal(ValidationIssueCode.PackageIsSigned, issue.IssueCode); + } + + [Fact] + public async Task WhenPackageSupportsButDoesNotRequireSigning_AcceptsUnsignedPackages() { // Arrange + var user1 = new User() + { + Key = 1 + }; + var user2 = new User() + { + Key = 2 + }; + var packageRegistration = new PackageRegistration() + { + Key = 3, + Id = TestResources.UnsignedPackageId + }; + var certificate = new Certificate() + { + Key = 4, + Thumbprint = TestResources.Leaf1Thumbprint + }; + var userCertificate = new UserCertificate() + { + Key = 5, + CertificateKey = certificate.Key, + Certificate = certificate, + UserKey = user1.Key, + User = user1 + }; + + user1.UserCertificates.Add(userCertificate); + certificate.UserCertificates.Add(userCertificate); + + packageRegistration.Owners.Add(user1); + packageRegistration.Owners.Add(user2); + _packageStream = TestResources.GetResourceStream(TestResources.UnsignedPackage); + _corePackageService + .Setup(x => x.FindPackageRegistrationById(It.Is(id => id == _message.PackageId))) + .Returns(packageRegistration); + _message = new SignatureValidationMessage( + TestResources.UnsignedPackageId, + TestResources.UnsignedPackageVersion, + new Uri($"https://unit.test/{TestResources.UnsignedPackage.ToLowerInvariant()}"), + Guid.NewGuid()); - // Arrange & Act + // Act var result = await _target.ValidateAsync( _packageKey, _packageStream, @@ -393,12 +578,53 @@ public async Task AcceptsUnsignedPackages() Assert.Empty(result.Issues); } - private void ConfigureKnownSignedPackage(string thumbprint) + [Fact] + public async Task WhenPackageSupportsButDoesNotRequireSigning_AcceptsPackagesSignedWithKnownCertificates() { - _certificates - .Setup(x => x.GetAll()) - .Returns(new[] { new Certificate { Thumbprint = thumbprint } }.AsQueryable()); + // Arrange + _packageStream = TestResources.GetResourceStream(TestResources.SignedPackageLeaf1); + TestUtility.RequireSignedPackage(_corePackageService, TestResources.SignedPackageLeafId, TestResources.Leaf1Thumbprint); + _message = new SignatureValidationMessage( + TestResources.SignedPackageLeafId, + TestResources.SignedPackageLeaf1Version, + new Uri($"https://unit.test/{TestResources.SignedPackageLeaf1.ToLowerInvariant()}"), + Guid.NewGuid()); + + // Act + var result = await _target.ValidateAsync( + _packageKey, + _packageStream, + _message, + _cancellationToken); + + // Assert + Validate(result, ValidationStatus.Succeeded, PackageSigningStatus.Valid); + Assert.Empty(result.Issues); + } + + [Fact] + public async Task WhenPackageRequiresUnsignedPackages_AcceptsUnsignedPackages() + { + // Arrange + _packageStream = TestResources.GetResourceStream(TestResources.UnsignedPackage); + TestUtility.RequireUnsignedPackage(_corePackageService, TestResources.UnsignedPackageId); + _message = new SignatureValidationMessage( + TestResources.UnsignedPackageId, + TestResources.UnsignedPackageVersion, + new Uri($"https://unit.test/{TestResources.UnsignedPackage.ToLowerInvariant()}"), + Guid.NewGuid()); + + // Act + var result = await _target.ValidateAsync( + _packageKey, + _packageStream, + _message, + _cancellationToken); + + // Assert + Validate(result, ValidationStatus.Succeeded, PackageSigningStatus.Unsigned); + Assert.Empty(result.Issues); } } } -} +} \ No newline at end of file diff --git a/tests/Validation.PackageSigning.ProcessSignature.Tests/SignatureValidatorIntegrationTests.cs b/tests/Validation.PackageSigning.ProcessSignature.Tests/SignatureValidatorIntegrationTests.cs index 203d6ec6c..5419d0071 100644 --- a/tests/Validation.PackageSigning.ProcessSignature.Tests/SignatureValidatorIntegrationTests.cs +++ b/tests/Validation.PackageSigning.ProcessSignature.Tests/SignatureValidatorIntegrationTests.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; @@ -44,8 +43,7 @@ public class SignatureValidatorIntegrationTests : IDisposable private readonly SignaturePartsExtractor _signaturePartsExtractor; private readonly Mock _packageFileService; private readonly Uri _nupkgUri; - private readonly Mock> _certificates; - private readonly List _trustedThumbprints; + private readonly Mock _corePackageService; private readonly IPackageSignatureVerifier _minimalPackageSignatureVerifier; private readonly IPackageSignatureVerifier _fullPackageSignatureVerifier; private readonly Mock _telemetryClient; @@ -54,7 +52,7 @@ public class SignatureValidatorIntegrationTests : IDisposable private readonly int _packageKey; private Stream _packageStream; private byte[] _savedPackageBytes; - private readonly SignatureValidationMessage _message; + private SignatureValidationMessage _message; private readonly CancellationToken _token; private readonly SignatureValidator _target; @@ -91,7 +89,7 @@ public SignatureValidatorIntegrationTests(CertificateIntegrationTestFixture fixt loggerFactory.CreateLogger()); _certificateStore = new Mock(); - + _signaturePartsExtractor = new SignaturePartsExtractor( _certificateStore.Object, _validationEntitiesContext.Object, @@ -115,11 +113,7 @@ public SignatureValidatorIntegrationTests(CertificateIntegrationTestFixture fixt } }); - _certificates = new Mock>(); - _trustedThumbprints = new List(); - _certificates - .Setup(x => x.GetAll()) - .Returns(() => _trustedThumbprints.Select(x => new Certificate { Thumbprint = x }).AsQueryable()); + _corePackageService = new Mock(); // These dependencies are concrete. _minimalPackageSignatureVerifier = PackageSignatureVerifierFactory.CreateMinimal(); @@ -146,14 +140,14 @@ public SignatureValidatorIntegrationTests(CertificateIntegrationTestFixture fixt _fullPackageSignatureVerifier, _signaturePartsExtractor, _packageFileService.Object, - _certificates.Object, + _corePackageService.Object, _telemetryService, _logger); } public async Task GetSignedPackageStream1Async() { - AllowCertificateThumbprint(await _fixture.GetSigningCertificateThumbprintAsync()); + TestUtility.RequireSignedPackage(_corePackageService, _message.PackageId, await _fixture.GetSigningCertificateThumbprintAsync()); return await _fixture.GetSignedPackageStream1Async(_output); } @@ -179,9 +173,15 @@ public async Task AcceptsValidSignedPackage() public async Task RejectsUntrustedSigningCertificate() { // Arrange - AllowCertificateThumbprint(TestResources.Leaf1Thumbprint); + TestUtility.RequireSignedPackage(_corePackageService, TestResources.SignedPackageLeafId, TestResources.Leaf1Thumbprint); _packageStream = TestResources.GetResourceStream(TestResources.SignedPackageLeaf1); + _message = new SignatureValidationMessage( + TestResources.SignedPackageLeafId, + TestResources.SignedPackageLeaf1Version, + new Uri($"https://unit.test/validation/{TestResources.SignedPackageLeaf1.ToLowerInvariant()}"), + Guid.NewGuid()); + // Act var result = await _target.ValidateAsync( _packageKey, @@ -223,10 +223,18 @@ await _fixture.GetSigningCertificateAsync(), _output); } - AllowCertificateThumbprint(await _fixture.GetSigningCertificateThumbprintAsync()); + TestUtility.RequireSignedPackage(_corePackageService, + TestResources.SignedPackageLeafId, + await _fixture.GetSigningCertificateThumbprintAsync()); _packageStream = new MemoryStream(packageBytes); + _message = new SignatureValidationMessage( + TestResources.SignedPackageLeafId, + TestResources.SignedPackageLeaf1Version, + new Uri($"https://unit.test/validation/{TestResources.SignedPackageLeaf1.ToLowerInvariant()}"), + Guid.NewGuid()); + // Act var result = await _target.ValidateAsync( _packageKey, @@ -274,10 +282,18 @@ await _fixture.GetSigningCertificateAsync(), // https://github.com/NuGet/Home/issues/6508 await Task.Delay(TimeSpan.FromSeconds(1)); - AllowCertificateThumbprint(await _fixture.GetSigningCertificateThumbprintAsync()); + TestUtility.RequireSignedPackage(_corePackageService, + TestResources.SignedPackageLeafId, + await _fixture.GetSigningCertificateThumbprintAsync()); _packageStream = new MemoryStream(packageBytes); + _message = new SignatureValidationMessage( + TestResources.SignedPackageLeafId, + TestResources.SignedPackageLeaf1Version, + new Uri($"https://unit.test/validation/{TestResources.SignedPackageLeaf1.ToLowerInvariant()}"), + Guid.NewGuid()); + SignatureValidatorResult result; using (testServer.RegisterResponders(timestampService, addOcsp: false)) { @@ -329,7 +345,7 @@ await _fixture.GetTimestampServiceUrlAsync(), // https://github.com/NuGet/Home/issues/6508 await Task.Delay(TimeSpan.FromSeconds(1)); - AllowCertificateThumbprint(signingCertificate.ComputeSHA256Thumbprint()); + TestUtility.RequireSignedPackage(_corePackageService, TestResources.SignedPackageLeafId, signingCertificate.ComputeSHA256Thumbprint()); _packageStream = new MemoryStream(packageBytes); @@ -454,12 +470,15 @@ public async Task RejectsMultipleSignatures() { // Arrange SetSignatureContent( + TestResources.SignedPackageLeafId, TestResources.GetResourceStream(TestResources.SignedPackageLeaf1), configuredSignedCms: signedCms => { using (var additionalCertificate = SigningTestUtility.GenerateCertificate(subjectName: null, modifyGenerator: null)) { - AllowCertificateThumbprint(additionalCertificate.ComputeSHA256Thumbprint()); + TestUtility.RequireSignedPackage(_corePackageService, + TestResources.SignedPackageLeafId, + additionalCertificate.ComputeSHA256Thumbprint()); signedCms.ComputeSignature(new CmsSigner(additionalCertificate)); } }); @@ -491,7 +510,7 @@ public async Task RejectsAuthorCounterSignatures() { using (var counterCertificate = SigningTestUtility.GenerateCertificate(subjectName: null, modifyGenerator: null)) { - AllowCertificateThumbprint(counterCertificate.ComputeSHA256Thumbprint()); + TestUtility.RequireSignedPackage(_corePackageService, _message.PackageId, counterCertificate.ComputeSHA256Thumbprint()); var cmsSigner = new CmsSigner(counterCertificate); cmsSigner.SignedAttributes.Add(AttributeUtility.CreateCommitmentTypeIndication(SignatureType.Author)); @@ -557,6 +576,11 @@ await _fixture.GetTimestampServiceUrlAsync(), public async Task StripsRepositorySignatures() { // Arrange + _message = new SignatureValidationMessage( + TestResources.UnsignedPackageId, + TestResources.UnsignedPackageVersion, + new Uri($"https://unit.test/validation/{TestResources.UnsignedPackage.ToLowerInvariant()}"), + Guid.NewGuid()); var packageBytes = await _fixture.GenerateSignedPackageBytesAsync( TestResources.GetResourceStream(TestResources.UnsignedPackage), new RepositorySignPackageRequest( @@ -568,6 +592,7 @@ await _fixture.GetSigningCertificateAsync(), await _fixture.GetTimestampServiceUrlAsync(), _output); var packageStream = new MemoryStream(packageBytes); + TestUtility.RequireUnsignedPackage(_corePackageService, TestResources.UnsignedPackageId); // Act var result = await _target.ValidateAsync( @@ -603,7 +628,7 @@ public async Task RejectsMutuallyExclusiveCounterSignaturesCommitmentTypes(Signa { using (var counterCertificate = SigningTestUtility.GenerateCertificate(subjectName: null, modifyGenerator: null)) { - AllowCertificateThumbprint(counterCertificate.ComputeSHA256Thumbprint()); + TestUtility.RequireSignedPackage(_corePackageService, _message.PackageId, counterCertificate.ComputeSHA256Thumbprint()); var cmsSigner = new CmsSigner(counterCertificate); foreach (var type in counterSignatureTypes) @@ -637,6 +662,11 @@ public async Task RejectsMutuallyExclusiveCounterSignaturesCommitmentTypes(Signa public async Task AllowsNonAuthorAndRepositoryCounterSignatures(string commitmentTypeOidBase64) { // Arrange + _message = new SignatureValidationMessage( + TestResources.SignedPackageLeafId, + TestResources.SignedPackageLeaf1Version, + new Uri($"https://unit.test/validation/{TestResources.SignedPackageLeaf1.ToLowerInvariant()}"), + Guid.NewGuid()); var packageStream = await GetSignedPackageStream1Async(); ModifySignatureContent( packageStream, @@ -644,8 +674,6 @@ public async Task AllowsNonAuthorAndRepositoryCounterSignatures(string commitmen { using (var counterCertificate = SigningTestUtility.GenerateCertificate(subjectName: null, modifyGenerator: null)) { - AllowCertificateThumbprint(counterCertificate.ComputeSHA256Thumbprint()); - var cmsSigner = new CmsSigner(counterCertificate); if (commitmentTypeOidBase64 != null) @@ -660,7 +688,7 @@ public async Task AllowsNonAuthorAndRepositoryCounterSignatures(string commitmen cmsSigner.SignedAttributes.Add(attribute); } - + signedCms.SignerInfos[0].ComputeCounterSignature(cmsSigner); } }); @@ -684,6 +712,7 @@ public async Task RejectsInvalidSignatureContent() { // Arrange SetSignatureContent( + TestResources.SignedPackageLeafId, TestResources.GetResourceStream(TestResources.SignedPackageLeaf1), "!!--:::FOO..."); @@ -708,6 +737,7 @@ public async Task RejectInvalidSignatureContentVersion() { // Arrange SetSignatureContent( + TestResources.SignedPackageLeafId, TestResources.GetResourceStream(TestResources.SignedPackageLeaf1), "Version:2" + Environment.NewLine + Environment.NewLine + "2.16.840.1.101.3.4.2.1-Hash:hash"); @@ -733,6 +763,7 @@ public async Task RejectsNonAuthorSignature() NuGetHashAlgorithmName.SHA256, hashValue: "hash"); SetSignatureContent( + TestResources.SignedPackageLeafId, TestResources.GetResourceStream(TestResources.SignedPackageLeaf1), content.GetBytes()); @@ -798,7 +829,6 @@ private void SetSignatureFileContent(Stream packageStream, byte[] fileContent) } } - private void ModifySignatureContent(Stream packageStream, Action configuredSignedCms = null) { SignedCms signedCms; @@ -822,7 +852,11 @@ private void ModifySignatureContent(Stream packageStream, Action conf SetSignatureFileContent(packageStream, signedCms.Encode()); } - private void SetSignatureContent(Stream packageStream, byte[] signatureContent = null, Action configuredSignedCms = null) + private void SetSignatureContent( + string packageId, + Stream packageStream, + byte[] signatureContent = null, + Action configuredSignedCms = null) { if (signatureContent == null) { @@ -834,7 +868,7 @@ private void SetSignatureContent(Stream packageStream, byte[] signatureContent = using (var certificate = SigningTestUtility.GenerateCertificate(subjectName: null, modifyGenerator: null)) { - AllowCertificateThumbprint(certificate.ComputeSHA256Thumbprint()); + TestUtility.RequireSignedPackage(_corePackageService, packageId, certificate.ComputeSHA256Thumbprint()); var contentInfo = new ContentInfo(signatureContent); var signedCms = new SignedCms(contentInfo); @@ -849,15 +883,12 @@ private void SetSignatureContent(Stream packageStream, byte[] signatureContent = } } - private void SetSignatureContent(Stream packageStream, string signatureContent) + private void SetSignatureContent(string packageId, Stream packageStream, string signatureContent) { - SetSignatureContent(packageStream, signatureContent: Encoding.UTF8.GetBytes(signatureContent)); + SetSignatureContent(packageId, packageStream, signatureContent: Encoding.UTF8.GetBytes(signatureContent)); } - private void AllowCertificateThumbprint(string thumbprint) - { - _trustedThumbprints.Add(thumbprint); - } + private void VerifyPackageSigningStatus(SignatureValidatorResult result, ValidationStatus validationStatus, PackageSigningStatus packageSigningStatus) { @@ -901,4 +932,4 @@ public Stream GetSource() } } } -} +} \ No newline at end of file diff --git a/tests/Validation.PackageSigning.ProcessSignature.Tests/Support/TestResources.cs b/tests/Validation.PackageSigning.ProcessSignature.Tests/Support/TestResources.cs index 3cee0307e..46f1f6cd8 100644 --- a/tests/Validation.PackageSigning.ProcessSignature.Tests/Support/TestResources.cs +++ b/tests/Validation.PackageSigning.ProcessSignature.Tests/Support/TestResources.cs @@ -11,12 +11,20 @@ namespace Validation.PackageSigning.ProcessSignature.Tests public static class TestResources { private const string ResourcePrefix = "Validation.PackageSigning.ProcessSignature.Tests.TestData."; + public const string SignedPackageLeafId = "TestSigned.leaf"; + public const string SignedPackageLeaf1Version = "1.1.0"; public const string SignedPackageLeaf1 = "TestSigned.leaf-1.1.0.0.nupkg"; public const string SignedPackageLeaf2 = "TestSigned.leaf-2.2.0.0.nupkg"; + public const string UnsignedPackageId = "TestUnsigned"; + public const string UnsignedPackageVersion = "1.0.0"; public const string UnsignedPackage = "TestUnsigned.1.0.0.nupkg"; public const string Zip64Package = "Zip64Package.1.0.0.nupkg"; + public const string RepoSignedPackageLeafId = "TestRepoSigned.leaf"; public const string RepoSignedPackageLeaf1 = "TestRepoSigned.leaf-1.1.0.0.nupkg"; + public const string RepoSignedPackageLeaf1Version = "1.1.0.0"; + public const string AuthorAndRepoSignedPackageLeafId = "TestAuthorAndRepoSigned.leaf"; public const string AuthorAndRepoSignedPackageLeaf1 = "TestAuthorAndRepoSigned.leaf-1.1.0.0.nupkg"; + public const string AuthorAndRepoSignedPackageLeaf1Version = "1.1.0.0"; /// /// This is the SHA-256 thumbprint of the root CA certificate for the signing certificate of . diff --git a/tests/Validation.PackageSigning.ProcessSignature.Tests/Support/TestUtility.cs b/tests/Validation.PackageSigning.ProcessSignature.Tests/Support/TestUtility.cs new file mode 100644 index 000000000..df0a25a40 --- /dev/null +++ b/tests/Validation.PackageSigning.ProcessSignature.Tests/Support/TestUtility.cs @@ -0,0 +1,67 @@ +// 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 Moq; +using NuGetGallery; + +namespace Validation.PackageSigning.ProcessSignature.Tests +{ + internal static class TestUtility + { + internal static void RequireUnsignedPackage(Mock corePackageService, string packageId) + { + var packageRegistration = new PackageRegistration() + { + Key = 1, + Id = packageId + }; + var user = new User() + { + Key = 2 + }; + + packageRegistration.Owners.Add(user); + + corePackageService + .Setup(x => x.FindPackageRegistrationById(It.Is(id => id == packageId))) + .Returns(packageRegistration); + } + + internal static void RequireSignedPackage( + Mock corePackageService, + string packageId, + string thumbprint = null) + { + var packageRegistration = new PackageRegistration() + { + Key = 1, + Id = packageId + }; + var user = new User() + { + Key = 2 + }; + var certificate = new Certificate() + { + Key = 3, + Thumbprint = thumbprint ?? Guid.NewGuid().ToString() + }; + + user.UserCertificates.Add(new UserCertificate() + { + Key = 4, + CertificateKey = certificate.Key, + Certificate = certificate, + UserKey = user.Key, + User = user + }); + + packageRegistration.Owners.Add(user); + + corePackageService + .Setup(x => x.FindPackageRegistrationById(It.Is(id => id == packageId))) + .Returns(packageRegistration); + } + } +} \ No newline at end of file diff --git a/tests/Validation.PackageSigning.ProcessSignature.Tests/Validation.PackageSigning.ProcessSignature.Tests.csproj b/tests/Validation.PackageSigning.ProcessSignature.Tests/Validation.PackageSigning.ProcessSignature.Tests.csproj index b2021513c..a157a4f50 100644 --- a/tests/Validation.PackageSigning.ProcessSignature.Tests/Validation.PackageSigning.ProcessSignature.Tests.csproj +++ b/tests/Validation.PackageSigning.ProcessSignature.Tests/Validation.PackageSigning.ProcessSignature.Tests.csproj @@ -47,6 +47,7 @@ +