From c182bacd246d7ec4159a5e6ee41228c11f4a9a9b Mon Sep 17 00:00:00 2001 From: Andrei Grigorev Date: Wed, 11 Apr 2018 17:08:24 -0700 Subject: [PATCH 1/3] Disabled SSL3 and Enabled TLS1.2 in callback service. (#403) * Disabled SSL3 and Enabled TLS1.2 in callback service. --- src/Validation.Common/Validators/Vcs/VcsCallbackServer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Validation.Common/Validators/Vcs/VcsCallbackServer.cs b/src/Validation.Common/Validators/Vcs/VcsCallbackServer.cs index 499543c2d..3b3f1201a 100644 --- a/src/Validation.Common/Validators/Vcs/VcsCallbackServer.cs +++ b/src/Validation.Common/Validators/Vcs/VcsCallbackServer.cs @@ -74,6 +74,10 @@ public VcsCallbackServerStartup() public void Configuration(IAppBuilder app) { + // Ensure that SSLv3 is disabled and that Tls v1.2 is enabled. + ServicePointManager.SecurityProtocol &= ~SecurityProtocolType.Ssl3; + ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; + if (Services.Logging.ApplicationInsights.Initialized) { app.Use(); From aad31a4385372198370a7a9d810233adda9a2b45 Mon Sep 17 00:00:00 2001 From: Joel Verhagen Date: Tue, 27 Mar 2018 14:14:51 -0700 Subject: [PATCH 2/3] Persist repository signature information in database (#401) Progress on https://github.com/NuGet/NuGetGallery/issues/5754 --- ...et.Services.Validation.Orchestrator.csproj | 2 +- .../Storage/ValidatorStateService.cs | 10 - .../Validation.Common.Job.csproj | 10 +- .../Validation.Common.Job.nuspec | 10 +- .../Job.cs | 4 - .../MinimalSignatureVerificationProvider.cs | 2 +- .../PackageSignatureVerifierFactory.cs | 2 + .../SignaturePartsExtractor.cs | 422 ++++++++++++++---- .../PackageCertificatesValidatorFacts.cs | 6 - .../ValidatorStateServiceFacts.cs | 20 - .../Support/DbSetMockFactory.cs | 1 + ...alidation.PackageSigning.Core.Tests.csproj | 2 +- .../SignaturePartsExtractorFacts.cs | 337 +++++++++++--- .../SignatureValidatorFacts.cs | 4 +- .../SignatureValidatorIntegrationTests.cs | 63 ++- .../Support/TestResources.cs | 16 +- ...Signing.RevalidateCertificate.Tests.csproj | 1 - 17 files changed, 674 insertions(+), 238 deletions(-) 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 f9a9063dc..047d7f454 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.22.0 + 2.23.0 diff --git a/src/Validation.Common.Job/Storage/ValidatorStateService.cs b/src/Validation.Common.Job/Storage/ValidatorStateService.cs index 73e2b2c74..c24a2368d 100644 --- a/src/Validation.Common.Job/Storage/ValidatorStateService.cs +++ b/src/Validation.Common.Job/Storage/ValidatorStateService.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using NuGet.Services.Validation; -using NuGet.Services.Validation.Orchestrator; namespace NuGet.Jobs.Validation.PackageSigning.Storage { @@ -21,20 +20,11 @@ public class ValidatorStateService : IValidatorStateService public ValidatorStateService( IValidationEntitiesContext validationContext, - IValidatorProvider validatorProvider, string validatorName, ILogger logger) { _validationContext = validationContext ?? throw new ArgumentNullException(nameof(validationContext)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - if (validatorProvider == null) - { - throw new ArgumentNullException(nameof(validatorProvider)); - } - if (!validatorProvider.IsValidator(validatorName)) - { - throw new ArgumentException($"\"{validatorName}\" is not a proper validator alias.", nameof(validatorName)); - } _validatorName = validatorName ?? throw new ArgumentNullException(nameof(validatorName)); } diff --git a/src/Validation.Common.Job/Validation.Common.Job.csproj b/src/Validation.Common.Job/Validation.Common.Job.csproj index c4d67bbe7..91a400785 100644 --- a/src/Validation.Common.Job/Validation.Common.Job.csproj +++ b/src/Validation.Common.Job/Validation.Common.Job.csproj @@ -88,19 +88,19 @@ 1.1.2 - 4.7.0-preview1.5029 + 4.7.0-preview4.5067 - 2.22.0 + 2.23.0 - 2.22.0 + 2.23.0 - 2.22.0 + 2.23.0 - 2.22.0 + 2.23.0 4.4.4-dev-26726 diff --git a/src/Validation.Common.Job/Validation.Common.Job.nuspec b/src/Validation.Common.Job/Validation.Common.Job.nuspec index 90740fb95..0adae1f71 100644 --- a/src/Validation.Common.Job/Validation.Common.Job.nuspec +++ b/src/Validation.Common.Job/Validation.Common.Job.nuspec @@ -15,11 +15,11 @@ - - - - - + + + + + diff --git a/src/Validation.PackageSigning.ProcessSignature/Job.cs b/src/Validation.PackageSigning.ProcessSignature/Job.cs index d65fd81ba..fe87a1821 100644 --- a/src/Validation.PackageSigning.ProcessSignature/Job.cs +++ b/src/Validation.PackageSigning.ProcessSignature/Job.cs @@ -78,10 +78,6 @@ protected override void ConfigureAutofacServices(ContainerBuilder containerBuild (pi, ctx) => ValidatorName.PackageSigning) .As(); - containerBuilder - .RegisterType() - .As(); - containerBuilder .RegisterType>() .Keyed>(validateSignatureBindingKey); diff --git a/src/Validation.PackageSigning.ProcessSignature/MinimalSignatureVerificationProvider.cs b/src/Validation.PackageSigning.ProcessSignature/MinimalSignatureVerificationProvider.cs index 077215662..69bb527cd 100644 --- a/src/Validation.PackageSigning.ProcessSignature/MinimalSignatureVerificationProvider.cs +++ b/src/Validation.PackageSigning.ProcessSignature/MinimalSignatureVerificationProvider.cs @@ -21,7 +21,7 @@ public Task GetTrustResultAsync( CancellationToken token) { var result = new SignedPackageVerificationResult( - SignatureVerificationStatus.Trusted, + SignatureVerificationStatus.Valid, signature, Enumerable.Empty()); diff --git a/src/Validation.PackageSigning.ProcessSignature/PackageSignatureVerifierFactory.cs b/src/Validation.PackageSigning.ProcessSignature/PackageSignatureVerifierFactory.cs index 645285145..48fe52726 100644 --- a/src/Validation.PackageSigning.ProcessSignature/PackageSignatureVerifierFactory.cs +++ b/src/Validation.PackageSigning.ProcessSignature/PackageSignatureVerifierFactory.cs @@ -23,6 +23,7 @@ public static IPackageSignatureVerifier CreateMinimal() var settings = new SignedPackageVerifierSettings( allowUnsigned: true, + allowIllegal: false, allowUntrusted: false, // Invalid format of the signature uses this flag to determine success. allowUntrustedSelfIssuedCertificate: true, allowIgnoreTimestamp: true, @@ -48,6 +49,7 @@ public static IPackageSignatureVerifier CreateFull() var settings = new SignedPackageVerifierSettings( allowUnsigned: false, + allowIllegal: false, allowUntrusted: false, allowUntrustedSelfIssuedCertificate: false, allowIgnoreTimestamp: false, diff --git a/src/Validation.PackageSigning.ProcessSignature/SignaturePartsExtractor.cs b/src/Validation.PackageSigning.ProcessSignature/SignaturePartsExtractor.cs index 07a79e438..3eccf8bca 100644 --- a/src/Validation.PackageSigning.ProcessSignature/SignaturePartsExtractor.cs +++ b/src/Validation.PackageSigning.ProcessSignature/SignaturePartsExtractor.cs @@ -30,35 +30,95 @@ public SignaturePartsExtractor( _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - public async Task ExtractAsync(int packageKey, PrimarySignature signature, CancellationToken token) + public async Task ExtractAsync(int packageKey, PrimarySignature primarySignature, CancellationToken cancellationToken) { - // Extract the certificates found in the package signatures. - var extractedCertificates = ExtractCertificates(signature); + using (var context = new Context(packageKey, primarySignature, cancellationToken)) + { + if (primarySignature == null) + { + throw new ArgumentNullException(nameof(primarySignature)); + } - // Prepare signature entities for the database (does not commit). - await SaveSignatureToDatabaseAsync(packageKey, signature, extractedCertificates); + // Extract the certificates found in the package signatures. + ExtractSignaturesAndCertificates(context); - // Save the certificates to blob storage. - await SaveCertificatesToStoreAsync(extractedCertificates, token); + // Prepare signature and certificate entities for the database but don't commit. + await PrepareSignaturesAndCertificatesRecordsAsync(context); - // Commit the database changes. - await _entitiesContext.SaveChangesAsync(); + // Save the certificates to blob storage. + await SaveCertificatesToStoreAsync(context); + + // Commit the database changes. + await _entitiesContext.SaveChangesAsync(); + } } - private ExtractedCertificates ExtractCertificates(PrimarySignature signature) + private static void ExtractSignaturesAndCertificates(Context context) { - if (signature.Timestamps.Count != 1) + if (context.PrimarySignature.Timestamps.Count != 1) { - throw new ArgumentException("There should be exactly one timestamp.", nameof(signature)); + throw new InvalidOperationException("There should be exactly one timestamp on the primary signature."); } - var signatureCertificates = SignatureUtility - .GetPrimarySignatureCertificates(signature); + var primarySignatureCertificates = ExtractPrimarySignatureCertificates(context); + + if (context.PrimarySignature.Type == SignatureType.Author) + { + context.Author = new SignatureAndCertificates(context.PrimarySignature, primarySignatureCertificates); + + var repositoryCountersignature = RepositoryCountersignature.GetRepositoryCountersignature(context.PrimarySignature); + if (repositoryCountersignature != null) + { + if (repositoryCountersignature.Timestamps.Count != 1) + { + throw new InvalidOperationException("There should be exactly one timestamp on the repository countersignature."); + } + + var countersignatureCertificates = ExtractRepositoryCountersignatureCertificates(context, repositoryCountersignature); + context.Repository = new SignatureAndCertificates(repositoryCountersignature, countersignatureCertificates); + } + } + else if (context.PrimarySignature.Type == SignatureType.Repository) + { + context.Repository = new SignatureAndCertificates(context.PrimarySignature, primarySignatureCertificates); + } + else + { + throw new InvalidOperationException("The primary signature must be an author or repository signature."); + } + } + + private static ExtractedCertificates ExtractPrimarySignatureCertificates(Context context) + { + return ExtractCertificates(context, repositoryCountersignature: null); + } + + private static ExtractedCertificates ExtractRepositoryCountersignatureCertificates( + Context context, + RepositoryCountersignature repositoryCountersignature) + { + return ExtractCertificates(context, repositoryCountersignature); + } + + private static ExtractedCertificates ExtractCertificates( + Context context, + RepositoryCountersignature repositoryCountersignature) + { + IX509CertificateChain signatureCertificates; + if (repositoryCountersignature == null) + { + signatureCertificates = SignatureUtility.GetCertificateChain(context.PrimarySignature); + } + else + { + signatureCertificates = SignatureUtility.GetCertificateChain(context.PrimarySignature, repositoryCountersignature); + } + + context.Disposables.Add(signatureCertificates); + if (signatureCertificates == null || !signatureCertificates.Any()) { - throw new ArgumentException( - "The provided signature must have at least one primary signing certificate.", - nameof(signature)); + throw new InvalidOperationException("The provided signature must have at least one signing certificate."); } var hashedSignatureCertificates = signatureCertificates @@ -67,13 +127,21 @@ private ExtractedCertificates ExtractCertificates(PrimarySignature signature) var signatureEndCertificate = hashedSignatureCertificates.First(); var signatureParentCertificates = hashedSignatureCertificates.Skip(1).ToList(); - var timestampCertificates = SignatureUtility - .GetPrimarySignatureTimestampCertificates(signature); + IX509CertificateChain timestampCertificates; + if (repositoryCountersignature == null) + { + timestampCertificates = SignatureUtility.GetTimestampCertificateChain(context.PrimarySignature); + } + else + { + timestampCertificates = SignatureUtility.GetTimestampCertificateChain(context.PrimarySignature, repositoryCountersignature); + } + + context.Disposables.Add(timestampCertificates); + if (timestampCertificates == null || !timestampCertificates.Any()) { - throw new ArgumentException( - "The provided signature must have at least one timestamp certificate.", - nameof(signature)); + throw new InvalidOperationException("The provided signature must have at least one timestamp certificate."); } var hashedTimestampCertificates = timestampCertificates @@ -89,22 +157,51 @@ private ExtractedCertificates ExtractCertificates(PrimarySignature signature) timestampParentCertificates); } - private async Task SaveSignatureToDatabaseAsync(int packageKey, Signature signature, ExtractedCertificates extractedCertificates) + private async Task PrepareSignaturesAndCertificatesRecordsAsync(Context context) { // Initialize the end and parent certificates. - var thumbprintToEndCertificate = await InitializeEndCertificatesAsync( - new[] - { - new CertificateAndUse(extractedCertificates.SignatureEndCertificate, EndCertificateUse.CodeSigning), - new CertificateAndUse(extractedCertificates.TimestampEndCertificate, EndCertificateUse.Timestamping), - }); + var endCertificatesAndUses = new List(); + var parentCertificates = new List(); - var thumbprintToParentCertificate = await InitializeParentCertificatesAsync( - extractedCertificates - .SignatureParentCertificates - .Concat(extractedCertificates.TimestampParentCertificates)); + CollectCertificates(endCertificatesAndUses, parentCertificates, context.Author?.Certificates); + CollectCertificates(endCertificatesAndUses, parentCertificates, context.Repository?.Certificates); + + var thumbprintToEndCertificate = await InitializeEndCertificatesAsync(endCertificatesAndUses); + var thumbprintToParentCertificate = await InitializeParentCertificatesAsync(parentCertificates); // Connect the end and parent certificates. + ConnectCertificates(context.Author?.Certificates, thumbprintToEndCertificate, thumbprintToParentCertificate); + ConnectCertificates(context.Repository?.Certificates, thumbprintToEndCertificate, thumbprintToParentCertificate); + + // Initialize the package signature for the author signature. If the record is already in the database, + // verify that nothing has changed. + await InitializePackageSignatureAndTrustedTimestampAsync( + context.PackageKey, + PackageSignatureType.Author, + context.Author, + thumbprintToEndCertificate, + allowSignatureChanges: false); + + // Initialize the package signature for the repository signature. If the record is already in the database + // and different than the current repository signature, replace the old one with the new one. + await InitializePackageSignatureAndTrustedTimestampAsync( + context.PackageKey, + PackageSignatureType.Repository, + context.Repository, + thumbprintToEndCertificate, + allowSignatureChanges: true); + } + + private void ConnectCertificates( + ExtractedCertificates extractedCertificates, + IReadOnlyDictionary thumbprintToEndCertificate, + IReadOnlyDictionary thumbprintToParentCertificate) + { + if (extractedCertificates == null) + { + return; + } + ConnectCertificates( extractedCertificates.SignatureEndCertificate, extractedCertificates.SignatureParentCertificates, @@ -116,57 +213,69 @@ private async Task SaveSignatureToDatabaseAsync(int packageKey, Signature signat extractedCertificates.TimestampParentCertificates, thumbprintToEndCertificate, thumbprintToParentCertificate); + } + + private async Task InitializePackageSignatureAndTrustedTimestampAsync( + int packageKey, + PackageSignatureType type, + SignatureAndCertificates signatureAndCertificates, + IReadOnlyDictionary thumbprintToEndCertificate, + bool allowSignatureChanges) + { + if (signatureAndCertificates == null) + { + return; + } // Initialize the package signature record. var packageSignature = await InitializePackageSignatureAsync( packageKey, - extractedCertificates.SignatureEndCertificate, - thumbprintToEndCertificate); + type, + signatureAndCertificates.Certificates.SignatureEndCertificate, + thumbprintToEndCertificate, + allowSignatureChanges); // Initialize the trusted timestamp record. InitializeTrustedTimestamp( packageSignature, - signature, - extractedCertificates.TimestampEndCertificate, + signatureAndCertificates.Signature, + signatureAndCertificates.Certificates.TimestampEndCertificate, thumbprintToEndCertificate); } - public async Task InitializePackageSignatureAsync( + private async Task InitializePackageSignatureAsync( int packageKey, + PackageSignatureType type, HashedCertificate signatureEndCertificate, - IReadOnlyDictionary thumbprintToEndCertificate) + IReadOnlyDictionary thumbprintToEndCertificate, + bool replacePackageSignature) { var packageSignatures = await _entitiesContext .PackageSignatures .Include(x => x.TrustedTimestamps) .Include(x => x.EndCertificate) - .Where(x => x.PackageKey == packageKey) + .Where(x => x.PackageKey == packageKey && x.Type == type) .ToListAsync(); if (packageSignatures.Count > 1) { _logger.LogError( - "There are {Count} package signatures for package key {PackageKey}. There should be either zero or one.", + "There are {Count} package signatures for package key {PackageKey} and type {Type}. There should be either zero or one.", packageSignatures.Count, - packageKey); + packageKey, + type); - throw new InvalidOperationException("There should never be more than one package signature per package."); + throw new InvalidOperationException("There should never be more than one package signature per package and signature type."); } PackageSignature packageSignature; if (packageSignatures.Count == 0) { - packageSignature = new PackageSignature - { - CreatedAt = DateTime.UtcNow, - EndCertificate = thumbprintToEndCertificate[signatureEndCertificate.Thumbprint], - PackageKey = packageKey, - Status = PackageSignatureStatus.Unknown, - TrustedTimestamps = new List(), - }; - _entitiesContext.PackageSignatures.Add(packageSignature); - - packageSignature.EndCertificateKey = packageSignature.EndCertificate.Key; + packageSignature = InitializePackageSignature( + packageKey, + type, + signatureEndCertificate, + thumbprintToEndCertificate); } else { @@ -174,21 +283,74 @@ public async Task InitializePackageSignatureAsync( if (packageSignature.EndCertificate.Thumbprint != signatureEndCertificate.Thumbprint) { - _logger.LogError( - "The signature end certificate thumbprint cannot change for package {PackageKey}. The " + - "existing signature end certificate is {ExistingThumbprint}. The new thumprint is " + - "{NewThumbprint}.", - packageKey, - packageSignature.EndCertificate.Thumbprint, - signatureEndCertificate.Thumbprint); - - throw new InvalidOperationException("The thumbprint of the signature end certificate cannot change."); + if (replacePackageSignature) + { + _logger.LogWarning( + "The signature end certificate thumbprint has changed for package {PackageKey} and type " + + "{Type}. The previous signature end certificate is {ExistingThumbprint}. The new thumprint " + + "is {NewThumbprint}. The previous record with key {PackageSignatureKey} will be removed.", + packageKey, + type, + packageSignature.EndCertificate.Thumbprint, + signatureEndCertificate.Thumbprint, + packageSignature.Key); + + // Remove the child trusted timestamps. This should be handled by cascading delete but to be + // explicit and to facilitate unit testing, we explicitly remove them. + foreach (var trustedTimestamp in packageSignature.TrustedTimestamps) + { + _entitiesContext.TrustedTimestamps.Remove(trustedTimestamp); + } + + _entitiesContext.PackageSignatures.Remove(packageSignature); + + packageSignature = InitializePackageSignature( + packageKey, + type, + signatureEndCertificate, + thumbprintToEndCertificate); + } + else + { + _logger.LogError( + "The signature end certificate thumbprint cannot change for package {PackageKey} and type " + + "{Type}. The existing signature end certificate is {ExistingThumbprint}. The new thumprint " + + "is {NewThumbprint}.", + packageKey, + type, + packageSignature.EndCertificate.Thumbprint, + signatureEndCertificate.Thumbprint); + + throw new InvalidOperationException("The thumbprint of the signature end certificate cannot change."); + } } } return packageSignature; } + private PackageSignature InitializePackageSignature( + int packageKey, + PackageSignatureType type, + HashedCertificate signatureEndCertificate, + IReadOnlyDictionary thumbprintToEndCertificate) + { + var packageSignature = new PackageSignature + { + CreatedAt = DateTime.UtcNow, + EndCertificate = thumbprintToEndCertificate[signatureEndCertificate.Thumbprint], + PackageKey = packageKey, + Status = PackageSignatureStatus.Unknown, + Type = type, + TrustedTimestamps = new List(), + }; + + packageSignature.EndCertificateKey = packageSignature.EndCertificate.Key; + _entitiesContext.PackageSignatures.Add(packageSignature); + + return packageSignature; + } + private void InitializeTrustedTimestamp( PackageSignature packageSignature, Signature signature, @@ -198,8 +360,9 @@ private void InitializeTrustedTimestamp( if (packageSignature.TrustedTimestamps.Count > 1) { _logger.LogError( - "There are {Count} trusted timestamps for signature on package {PackageKey}. There should be either zero or one.", + "There are {Count} trusted timestamps for the {SignatureType} signature on package {PackageKey}. There should be either zero or one.", packageSignature.TrustedTimestamps.Count, + signature.Type, packageSignature.PackageKey); throw new InvalidOperationException("There should never be more than one trusted timestamp per package signature."); @@ -230,9 +393,10 @@ private void InitializeTrustedTimestamp( if (trustedTimestamp.EndCertificate.Thumbprint != timestampEndCertificate.Thumbprint) { _logger.LogError( - "The timestamp end certificate thumbprint cannot change for package {PackageKey}. The " + - "existing timestamp end certificate is {ExistingThumbprint}. The new thumprint is " + - "{NewThumbprint}.", + "The timestamp end certificate thumbprint cannot change for the {SignatureType} signature " + + "on package {PackageKey}. The existing timestamp end certificate is {ExistingThumbprint}. " + + "The new thumprint is {NewThumbprint}.", + signature.Type, packageSignature.PackageKey, packageSignature.EndCertificate.Thumbprint, timestampEndCertificate.Thumbprint); @@ -243,8 +407,9 @@ private void InitializeTrustedTimestamp( if (trustedTimestamp.Value != value) { _logger.LogError( - "The trusted timestamp value cannot change for package {PackageKey}. The existing timestamp " + - "value is {ExistingValue}. The new value is {NewValue}.", + "The trusted timestamp value cannot change for the {SignatureType} signature on package " + + "{PackageKey}. The existing timestamp value is {ExistingValue}. The new value is {NewValue}.", + signature.Type, packageSignature.PackageKey, trustedTimestamp.Value, value); @@ -256,7 +421,7 @@ private void InitializeTrustedTimestamp( private void ConnectCertificates( HashedCertificate endCertificate, - IReadOnlyList parentCertificates, + IReadOnlyCollection parentCertificates, IReadOnlyDictionary thumbprintToEndCertificate, IReadOnlyDictionary thumbprintToParentCertificates) { @@ -292,7 +457,7 @@ private void ConnectCertificates( } private async Task> InitializeEndCertificatesAsync( - IEnumerable certificatesAndUses) + IReadOnlyCollection certificatesAndUses) { var thumbprints = certificatesAndUses .Select(x => x.Certificate.Thumbprint) @@ -375,41 +540,126 @@ private async Task> InitializePar return thumbprintToEntity; } - private async Task SaveCertificatesToStoreAsync(ExtractedCertificates extractedCertificates, CancellationToken token) + private async Task SaveCertificatesToStoreAsync(Context context) { - var allCertificates = Enumerable - .Empty() - .Concat(new[] { extractedCertificates.SignatureEndCertificate }) - .Concat(extractedCertificates.SignatureParentCertificates) - .Concat(new[] { extractedCertificates.TimestampEndCertificate }) - .Concat(extractedCertificates.TimestampParentCertificates); + var thumbprintToCertificate = new Dictionary(); + + CollectCertificates(thumbprintToCertificate, context.Author?.Certificates); + CollectCertificates(thumbprintToCertificate, context.Repository?.Certificates); - foreach (var certificate in allCertificates) + foreach (var certificate in thumbprintToCertificate.Values) { - await SaveCertificateToStoreAsync(certificate, token); + if (await _certificateStore.ExistsAsync(certificate.Thumbprint, context.CancellationToken)) + { + continue; + } + + await _certificateStore.SaveAsync(certificate.Certificate, context.CancellationToken); } } - private async Task SaveCertificateToStoreAsync(HashedCertificate certificate, CancellationToken token) + private static void CollectCertificates( + List endCertificatesAndUses, + List parentCertificates, + ExtractedCertificates extractedCertificates) { - if (await _certificateStore.ExistsAsync(certificate.Thumbprint, token)) + if (extractedCertificates == null) { return; } - await _certificateStore.SaveAsync(certificate.Certificate, token); + endCertificatesAndUses.Add(new EndCertificateAndUse(extractedCertificates.SignatureEndCertificate, EndCertificateUse.CodeSigning)); + endCertificatesAndUses.Add(new EndCertificateAndUse(extractedCertificates.TimestampEndCertificate, EndCertificateUse.Timestamping)); + + parentCertificates.AddRange(extractedCertificates.SignatureParentCertificates); + parentCertificates.AddRange(extractedCertificates.TimestampParentCertificates); } - private class CertificateAndUse + private static void CollectCertificates( + Dictionary thumbprintToCertificate, + ExtractedCertificates extractedCertificates) { - public CertificateAndUse(HashedCertificate hashedCertificate, EndCertificateUse endCertificateUse) + if (extractedCertificates == null) { - Certificate = hashedCertificate; + return; + } + + CollectCertificate(thumbprintToCertificate, extractedCertificates.SignatureEndCertificate); + CollectCertificates(thumbprintToCertificate, extractedCertificates.SignatureParentCertificates); + CollectCertificate(thumbprintToCertificate, extractedCertificates.TimestampEndCertificate); + CollectCertificates(thumbprintToCertificate, extractedCertificates.TimestampParentCertificates); + } + + private static void CollectCertificates( + Dictionary thumbprintToCertificate, + IEnumerable hashedCertificates) + { + foreach (var hashedCertificate in hashedCertificates) + { + CollectCertificate(thumbprintToCertificate, hashedCertificate); + } + } + + private static void CollectCertificate( + Dictionary thumbprintToCertificate, + HashedCertificate hashedCertificate) + { + if (!thumbprintToCertificate.ContainsKey(hashedCertificate.Thumbprint)) + { + thumbprintToCertificate.Add(hashedCertificate.Thumbprint, hashedCertificate); + } + } + + private class EndCertificateAndUse + { + public EndCertificateAndUse(HashedCertificate hashedCertificate, EndCertificateUse endCertificateUse) + { + Certificate = hashedCertificate ?? throw new ArgumentNullException(nameof(hashedCertificate)); Use = endCertificateUse; } public HashedCertificate Certificate { get; } public EndCertificateUse Use { get; } } + + private class Context : IDisposable + { + public Context(int packageKey, PrimarySignature primarySignature, CancellationToken cancellationToken) + { + PackageKey = packageKey; + PrimarySignature = primarySignature ?? throw new ArgumentNullException(nameof(primarySignature)); + CancellationToken = cancellationToken; + Disposables = new List(); + } + + public int PackageKey { get; } + public PrimarySignature PrimarySignature { get; } + public CancellationToken CancellationToken { get; } + + public List Disposables { get; } + + public SignatureAndCertificates Author { get; set; } + public SignatureAndCertificates Repository { get; set; } + + public void Dispose() + { + foreach (var disposable in Disposables) + { + disposable?.Dispose(); + } + } + } + + private class SignatureAndCertificates + { + public SignatureAndCertificates(Signature signature, ExtractedCertificates certificates) + { + Signature = signature ?? throw new ArgumentNullException(nameof(signature)); + Certificates = certificates ?? throw new ArgumentNullException(nameof(certificates)); + } + + public Signature Signature { get; } + public ExtractedCertificates Certificates { get; } + } } } diff --git a/tests/NuGet.Services.Validation.Orchestrator.Tests/PackageSigning/ValidateCertificate/PackageCertificatesValidatorFacts.cs b/tests/NuGet.Services.Validation.Orchestrator.Tests/PackageSigning/ValidateCertificate/PackageCertificatesValidatorFacts.cs index 939c580a3..3dc06b8bf 100644 --- a/tests/NuGet.Services.Validation.Orchestrator.Tests/PackageSigning/ValidateCertificate/PackageCertificatesValidatorFacts.cs +++ b/tests/NuGet.Services.Validation.Orchestrator.Tests/PackageSigning/ValidateCertificate/PackageCertificatesValidatorFacts.cs @@ -1173,7 +1173,6 @@ public async Task RevokedSignaturesAreInvalidated() public abstract class FactsBase { protected readonly Mock _validationContext; - protected readonly Mock _validatorProvider; protected readonly Mock _certificateVerifier; protected readonly Mock _telemetryService; protected readonly Mock> _logger; @@ -1183,14 +1182,10 @@ public abstract class FactsBase public FactsBase() { _validationContext = new Mock(); - _validatorProvider = new Mock(); _certificateVerifier = new Mock(); _telemetryService = new Mock(); _logger = new Mock>(); - _validatorProvider.Setup(vp => vp.IsValidator(It.IsAny())).Returns(true); - _validatorProvider.Setup(vp => vp.IsProcessor(It.IsAny())).Returns(true); - _validationRequest = new Mock(); _validationRequest.Setup(x => x.NupkgUrl).Returns(NupkgUrl); _validationRequest.Setup(x => x.PackageId).Returns(PackageId); @@ -1201,7 +1196,6 @@ public FactsBase() var validatorStateServiceLogger = new Mock>(); var validatorStateService = new ValidatorStateService( _validationContext.Object, - _validatorProvider.Object, ValidatorName.PackageCertificate, validatorStateServiceLogger.Object); diff --git a/tests/NuGet.Services.Validation.Orchestrator.Tests/ValidatorStateServiceFacts.cs b/tests/NuGet.Services.Validation.Orchestrator.Tests/ValidatorStateServiceFacts.cs index 241bb39fe..93b6d1a65 100644 --- a/tests/NuGet.Services.Validation.Orchestrator.Tests/ValidatorStateServiceFacts.cs +++ b/tests/NuGet.Services.Validation.Orchestrator.Tests/ValidatorStateServiceFacts.cs @@ -483,24 +483,9 @@ public async Task PersistsStatus() } } - public class TheConstructor : FactsBase - { - [Fact] - public void ThrowsWhenValidatorNameIsNotProperAlias() - { - const string notAValidatorAlias = "pew-pew"; - - _validatorProvider.Setup(vp => vp.IsValidator(notAValidatorAlias)).Returns(false); - - var ex = Assert.Throws(() => CreateValidatorStateService(notAValidatorAlias)); - Assert.Equal("validatorName", ex.ParamName); - } - } - public abstract class FactsBase { protected readonly Mock _validationContext; - protected readonly Mock _validatorProvider; protected readonly Mock> _logger; protected readonly Mock _validationRequest; protected readonly ValidatorStateService _target; @@ -508,12 +493,8 @@ public abstract class FactsBase public FactsBase() { _validationContext = new Mock(); - _validatorProvider = new Mock(); _logger = new Mock>(); - _validatorProvider.Setup(vp => vp.IsValidator(It.IsAny())).Returns(true); - _validatorProvider.Setup(vp => vp.IsProcessor(It.IsAny())).Returns(true); - _validationRequest = new Mock(); _validationRequest.Setup(x => x.NupkgUrl).Returns(NupkgUrl); _validationRequest.Setup(x => x.PackageId).Returns(PackageId); @@ -527,7 +508,6 @@ public FactsBase() protected ValidatorStateService CreateValidatorStateService(string validatorName) => new ValidatorStateService( _validationContext.Object, - _validatorProvider.Object, validatorName, _logger.Object); } diff --git a/tests/Validation.PackageSigning.Core.Tests/Support/DbSetMockFactory.cs b/tests/Validation.PackageSigning.Core.Tests/Support/DbSetMockFactory.cs index 20ba8dfdb..c18aa7569 100644 --- a/tests/Validation.PackageSigning.Core.Tests/Support/DbSetMockFactory.cs +++ b/tests/Validation.PackageSigning.Core.Tests/Support/DbSetMockFactory.cs @@ -20,6 +20,7 @@ public static IDbSet Create(params T[] sourceList) where T : class dbSet.As>().Setup(m => m.ElementType).Returns(() => list.AsQueryable().ElementType); dbSet.As>().Setup(m => m.GetEnumerator()).Returns(() => list.GetEnumerator()); dbSet.Setup(m => m.Add(It.IsAny())).Callback(e => list.Add(e)); + dbSet.Setup(m => m.Remove(It.IsAny())).Callback(e => list.Remove(e)); return dbSet.Object; } diff --git a/tests/Validation.PackageSigning.Core.Tests/Validation.PackageSigning.Core.Tests.csproj b/tests/Validation.PackageSigning.Core.Tests/Validation.PackageSigning.Core.Tests.csproj index c20480845..dd6ae84aa 100644 --- a/tests/Validation.PackageSigning.Core.Tests/Validation.PackageSigning.Core.Tests.csproj +++ b/tests/Validation.PackageSigning.Core.Tests/Validation.PackageSigning.Core.Tests.csproj @@ -73,7 +73,7 @@ 4.4.0 - 4.7.0-preview1.5029 + 4.7.0-preview4.5067 2.3.1 diff --git a/tests/Validation.PackageSigning.ProcessSignature.Tests/SignaturePartsExtractorFacts.cs b/tests/Validation.PackageSigning.ProcessSignature.Tests/SignaturePartsExtractorFacts.cs index 47aae5bf4..0b4077061 100644 --- a/tests/Validation.PackageSigning.ProcessSignature.Tests/SignaturePartsExtractorFacts.cs +++ b/tests/Validation.PackageSigning.ProcessSignature.Tests/SignaturePartsExtractorFacts.cs @@ -30,35 +30,82 @@ public class SignaturePartsExtractorFacts .Parse("2018-01-26T22:09:01.0000000Z") .ToUniversalTime(); + private static readonly DateTime RepoSignedLeaf1TimestampValue = DateTime + .Parse("2018-03-21T17:55:33.0000000Z") + .ToUniversalTime(); + + private static readonly DateTime AuthorAndRepoSignedPrimaryTimestampValue = DateTime + .Parse("2018-03-21T17:55:50.0000000Z") + .ToUniversalTime(); + + private static readonly DateTime AuthorAndRepoSignedCounterTimestampValue = DateTime + .Parse("2018-03-21T17:56:00.0000000Z") + .ToUniversalTime(); + /// /// This contains the SHA-256 thumbprints of the test certificate chain as well as the time stamping /// authoring (TSA) chain. In this case, Symantec's TSA is used. /// private static readonly ExtractedCertificatesThumbprints Leaf1Certificates = new ExtractedCertificatesThumbprints { - SignatureEndCertificate = new SubjectAndThumbprint( + PrimarySignature = new SignatureCertificateThumprints + { + SignatureEndCertificate = new SubjectAndThumbprint( "CN=NUGET_DO_NOT_TRUST.leaf-1.test.test, OU=Test Organizational Unit Name, O=Test Organization Name, L=Redmond, S=WA, C=US", TestResources.Leaf1Thumbprint), - SignatureParentCertificates = new[] - { - new SubjectAndThumbprint( - "CN=NUGET_DO_NOT_TRUST.intermediate.test.test, OU=Test Organizational Unit Name, O=Test Organization Name, L=Redmond, S=WA, C=US", - "7358e4597696b1d02e7aa2b3cf30a7cf154f2c8ff0710fd0dc3ace17e3784054"), - new SubjectAndThumbprint( - "CN=NUGET_DO_NOT_TRUST.root.test.test, OU=Test Organizational Unit Name, O=Test Organization Name, L=Redmond, S=WA, C=US", - TestResources.RootThumbprint), + SignatureParentCertificates = new[] + { + new SubjectAndThumbprint( + "CN=NUGET_DO_NOT_TRUST.intermediate.test.test, OU=Test Organizational Unit Name, O=Test Organization Name, L=Redmond, S=WA, C=US", + "7358e4597696b1d02e7aa2b3cf30a7cf154f2c8ff0710fd0dc3ace17e3784054"), + new SubjectAndThumbprint( + "CN=NUGET_DO_NOT_TRUST.root.test.test, OU=Test Organizational Unit Name, O=Test Organization Name, L=Redmond, S=WA, C=US", + TestResources.RootThumbprint), + }, + TimestampEndCertificate = new SubjectAndThumbprint( + "CN=Symantec SHA256 TimeStamping Signer - G2, OU=Symantec Trust Network, O=Symantec Corporation, C=US", + TestResources.Leaf1TimestampThumbprint), + TimestampParentCertificates = new[] + { + new SubjectAndThumbprint( + "CN=Symantec SHA256 TimeStamping CA, OU=Symantec Trust Network, O=Symantec Corporation, C=US", + "f3516ddcc8afc808788bd8b0e840bda2b5e23c6244252ca3000bb6c87170402a"), + new SubjectAndThumbprint( + "CN=VeriSign Universal Root Certification Authority, OU=\"(c) 2008 VeriSign, Inc. - For authorized use only\", OU=VeriSign Trust Network, O=\"VeriSign, Inc.\", C=US", + "2399561127a57125de8cefea610ddf2fa078b5c8067f4e828290bfb860e84b3c"), + }, }, - TimestampEndCertificate = new SubjectAndThumbprint( - "CN=Symantec SHA256 TimeStamping Signer - G2, OU=Symantec Trust Network, O=Symantec Corporation, C=US", - TestResources.Leaf1TimestampThumbprint), - TimestampParentCertificates = new[] + }; + + private static readonly ExtractedCertificatesThumbprints AuthorAndRepoSignedCertificates = new ExtractedCertificatesThumbprints + { + PrimarySignature = Leaf1Certificates.PrimarySignature, + Countersignature = new SignatureCertificateThumprints { - new SubjectAndThumbprint( - "CN=Symantec SHA256 TimeStamping CA, OU=Symantec Trust Network, O=Symantec Corporation, C=US", - "f3516ddcc8afc808788bd8b0e840bda2b5e23c6244252ca3000bb6c87170402a"), - new SubjectAndThumbprint( - "CN=VeriSign Universal Root Certification Authority, OU=\"(c) 2008 VeriSign, Inc. - For authorized use only\", OU=VeriSign Trust Network, O=\"VeriSign, Inc.\", C=US", - "2399561127a57125de8cefea610ddf2fa078b5c8067f4e828290bfb860e84b3c"), + SignatureEndCertificate = new SubjectAndThumbprint( + "CN=NUGET_DO_NOT_TRUST.leaf-2.test.test, OU=Test Organizational Unit Name, O=Test Organization Name, L=Redmond, S=WA, C=US", + TestResources.Leaf2Thumbprint), + SignatureParentCertificates = new[] + { + new SubjectAndThumbprint( + "CN=NUGET_DO_NOT_TRUST.intermediate.test.test, OU=Test Organizational Unit Name, O=Test Organization Name, L=Redmond, S=WA, C=US", + "7358e4597696b1d02e7aa2b3cf30a7cf154f2c8ff0710fd0dc3ace17e3784054"), + new SubjectAndThumbprint( + "CN=NUGET_DO_NOT_TRUST.root.test.test, OU=Test Organizational Unit Name, O=Test Organization Name, L=Redmond, S=WA, C=US", + TestResources.RootThumbprint), + }, + TimestampEndCertificate = new SubjectAndThumbprint( + "CN=Symantec SHA256 TimeStamping Signer - G2, OU=Symantec Trust Network, O=Symantec Corporation, C=US", + TestResources.Leaf1TimestampThumbprint), + TimestampParentCertificates = new[] + { + new SubjectAndThumbprint( + "CN=Symantec SHA256 TimeStamping CA, OU=Symantec Trust Network, O=Symantec Corporation, C=US", + "f3516ddcc8afc808788bd8b0e840bda2b5e23c6244252ca3000bb6c87170402a"), + new SubjectAndThumbprint( + "CN=VeriSign Universal Root Certification Authority, OU=\"(c) 2008 VeriSign, Inc. - For authorized use only\", OU=VeriSign Trust Network, O=\"VeriSign, Inc.\", C=US", + "2399561127a57125de8cefea610ddf2fa078b5c8067f4e828290bfb860e84b3c"), + }, }, }; @@ -86,7 +133,7 @@ public ExtractAsync() _certificateStore .Setup(x => x.SaveAsync(It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask) - .Callback((cert, _) => _savedCertificates.Add(cert)); + .Callback((cert, _) => _savedCertificates.Add(new X509Certificate2(cert.RawData))); _entitiesContext = new Mock(); _entitiesContext @@ -114,7 +161,7 @@ public ExtractAsync() } [Fact] - public async Task SaveSigningAndTimestampCertificates() + public async Task SaveSigningAndTimestampCertificatesForAuthorPrimarySignature() { // Arrange var signature = await TestResources.LoadPrimarySignatureAsync(TestResources.SignedPackageLeaf1); @@ -123,7 +170,35 @@ public async Task SaveSigningAndTimestampCertificates() await _target.ExtractAsync(_packageKey, signature, _token); // Assert - VerifySavedCertificates(Leaf1Certificates, Leaf1TimestampValue); + VerifyExtractedInformation(Leaf1Certificates, Leaf1TimestampValue, PackageSignatureType.Author); + } + + [Fact] + public async Task SaveSigningAndTimestampCertificatesForRepositoryPrimarySignature() + { + // Arrange + var signature = await TestResources.LoadPrimarySignatureAsync(TestResources.RepoSignedPackageLeaf1); + + // Act + await _target.ExtractAsync(_packageKey, signature, _token); + + // Assert + VerifyExtractedInformation(Leaf1Certificates, RepoSignedLeaf1TimestampValue, PackageSignatureType.Repository); + } + + [Fact] + public async Task SaveSigningAndTimestampCertificatesForAuthorAndReposignedPackage() + { + // Arrange + var signature = await TestResources.LoadPrimarySignatureAsync(TestResources.AuthorAndRepoSignedPackageLeaf1); + + // Act + await _target.ExtractAsync(_packageKey, signature, _token); + + // Assert + VerifyStoredCertificates(AuthorAndRepoSignedCertificates); + VerifyPackageSignatureRecord(AuthorAndRepoSignedCertificates.PrimarySignature, AuthorAndRepoSignedPrimaryTimestampValue, PackageSignatureType.Author); + VerifyPackageSignatureRecord(AuthorAndRepoSignedCertificates.Countersignature, AuthorAndRepoSignedCounterTimestampValue, PackageSignatureType.Repository); } [Fact] @@ -191,7 +266,7 @@ public async Task DoesNotDuplicateWhenDataAlreadyExist() await _target.ExtractAsync(_packageKey, signature, _token); // Assert - VerifySavedCertificates(Leaf1Certificates, Leaf1TimestampValue); + VerifyExtractedInformation(Leaf1Certificates, Leaf1TimestampValue, PackageSignatureType.Author); Assert.Equal(2, _entitiesContext.Object.EndCertificates.Count()); Assert.Equal(4, _entitiesContext.Object.ParentCertificates.Count()); Assert.Equal(4, _entitiesContext.Object.CertificateChainLinks.Count()); @@ -236,6 +311,7 @@ public async Task DoesNotDuplicateWhenSomeDataAlreadyExist() Status = PackageSignatureStatus.Valid, CreatedAt = new DateTime(2017, 1, 1, 8, 30, 0, DateTimeKind.Utc), PackageKey = _packageKey, + Type = PackageSignatureType.Author, TrustedTimestamps = new List(), }; @@ -256,7 +332,7 @@ public async Task DoesNotDuplicateWhenSomeDataAlreadyExist() await _target.ExtractAsync(_packageKey, signature, _token); // Assert - VerifySavedCertificates(Leaf1Certificates, Leaf1TimestampValue); + VerifyExtractedInformation(Leaf1Certificates, Leaf1TimestampValue, PackageSignatureType.Author); Assert.Equal(2, _entitiesContext.Object.EndCertificates.Count()); Assert.Equal(4, _entitiesContext.Object.ParentCertificates.Count()); Assert.Equal(4, _entitiesContext.Object.CertificateChainLinks.Count()); @@ -294,21 +370,25 @@ public async Task RejectsCertificateWithMultipleUses() Assert.Empty(_savedCertificates); } - [Fact] - public async Task RejectsPackageWithMultipleSignatures() + [Theory] + [InlineData(PackageSignatureType.Author, TestResources.SignedPackageLeaf1)] + [InlineData(PackageSignatureType.Repository, TestResources.RepoSignedPackageLeaf1)] + public async Task RejectsPackageWithMultipleSignatures(PackageSignatureType type, string resourceName) { // Arrange - var signature = await TestResources.LoadPrimarySignatureAsync(TestResources.SignedPackageLeaf1); + var signature = await TestResources.LoadPrimarySignatureAsync(resourceName); var existingPackageSignature1 = new PackageSignature { Key = 1, PackageKey = _packageKey, + Type = type, }; var existingPackageSignature2 = new PackageSignature { Key = 2, PackageKey = _packageKey, + Type = type, }; _entitiesContext @@ -318,7 +398,7 @@ public async Task RejectsPackageWithMultipleSignatures() // Act & Assert var ex = await Assert.ThrowsAsync( () => _target.ExtractAsync(_packageKey, signature, _token)); - Assert.Equal("There should never be more than one package signature per package.", ex.Message); + Assert.Equal("There should never be more than one package signature per package and signature type.", ex.Message); _entitiesContext.Verify( x => x.SaveChangesAsync(), Times.Never); @@ -326,7 +406,7 @@ public async Task RejectsPackageWithMultipleSignatures() } [Fact] - public async Task RejectsPackageWithChangedSigningCertificateThumbprint() + public async Task RejectsAuthorSignedPackageWithChangedSigningCertificateThumbprint() { // Arrange var signature = await TestResources.LoadPrimarySignatureAsync(TestResources.SignedPackageLeaf1); @@ -337,7 +417,8 @@ public async Task RejectsPackageWithChangedSigningCertificateThumbprint() EndCertificate = new EndCertificate { Thumbprint = "something else", - } + }, + Type = PackageSignatureType.Author, }; _entitiesContext @@ -354,6 +435,53 @@ public async Task RejectsPackageWithChangedSigningCertificateThumbprint() Assert.Empty(_savedCertificates); } + [Fact] + public async Task AcceptsRepoSignedPackageWithChangedSigningCertificateThumbprint() + { + // Arrange + var signature = await TestResources.LoadPrimarySignatureAsync(TestResources.RepoSignedPackageLeaf1); + var existingTrustedTimestamp = new TrustedTimestamp + { + EndCertificate = new EndCertificate + { + Thumbprint = "something else B", + }, + }; + var existingPackageSignature = new PackageSignature + { + Key = 1, + PackageKey = _packageKey, + EndCertificate = new EndCertificate + { + Thumbprint = "something else A", + }, + Type = PackageSignatureType.Repository, + TrustedTimestamps = new List + { + existingTrustedTimestamp + }, + }; + + _entitiesContext + .Setup(x => x.PackageSignatures) + .Returns(DbSetMockFactory.Create(existingPackageSignature)); + _entitiesContext + .Setup(x => x.TrustedTimestamps) + .Returns(DbSetMockFactory.Create(existingTrustedTimestamp)); + + // Act + await _target.ExtractAsync(_packageKey, signature, _token); + + // Assert + var newPackageSignature = Assert.Single(_entitiesContext.Object.PackageSignatures); + Assert.NotSame(existingPackageSignature, newPackageSignature); + Assert.Equal(TestResources.Leaf1Thumbprint, newPackageSignature.EndCertificate.Thumbprint); + + var newTrustedTimestamp = Assert.Single(_entitiesContext.Object.TrustedTimestamps); + Assert.NotSame(existingTrustedTimestamp, newTrustedTimestamp); + Assert.Equal(TestResources.Leaf1TimestampThumbprint, newTrustedTimestamp.EndCertificate.Thumbprint); + } + [Fact] public async Task RejectsPackageWithMultipleTimestamps() { @@ -371,7 +499,8 @@ public async Task RejectsPackageWithMultipleTimestamps() { new TrustedTimestamp(), new TrustedTimestamp(), - }, + }, + Type = PackageSignatureType.Author, }; _entitiesContext @@ -403,14 +532,15 @@ public async Task RejectsPackageWithChangedTimestampCertificateThumbprint() }, TrustedTimestamps = new[] { - new TrustedTimestamp + new TrustedTimestamp + { + EndCertificate = new EndCertificate { - EndCertificate = new EndCertificate - { - Thumbprint = "something else", - }, + Thumbprint = "something else", }, }, + }, + Type = PackageSignatureType.Author, }; _entitiesContext @@ -451,6 +581,7 @@ public async Task RejectsPackageWithChangedTimestampValue() Value = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc), }, }, + Type = PackageSignatureType.Author, }; _entitiesContext @@ -479,7 +610,7 @@ public async Task IgnoreExtraCertificates() await _target.ExtractAsync(_packageKey, signature, _token); // Assert - VerifySavedCertificates(Leaf1Certificates, Leaf1TimestampValue); + VerifyExtractedInformation(Leaf1Certificates, Leaf1TimestampValue, PackageSignatureType.Author); Assert.Equal( Leaf1Certificates.Certificates.Count + 1, signature.SignedCms.Certificates.Count + signature.Timestamps.Sum(x => x.SignedCms.Certificates.Count)); @@ -528,69 +659,96 @@ private void AssignIds() } } - private void VerifySavedCertificates(ExtractedCertificatesThumbprints expected, DateTime timestampValue) + private void VerifyExtractedInformation( + ExtractedCertificatesThumbprints expected, + DateTime timestampValue, + PackageSignatureType signatureType) { // Assert the certificates saved to the store. - Assert.Equal(expected.Certificates.Count, _savedCertificates.Count); - for (var i = 0; i < _savedCertificates.Count; i++) - { - var subject = _savedCertificates[i].Subject; - var thumbprint = _savedCertificates[i].ComputeSHA256Thumbprint(); - Assert.Equal(expected.Certificates[i].Subject, subject); - Assert.Equal(expected.Certificates[i].Thumbprint, thumbprint); - } + VerifyStoredCertificates(expected); // Assert the certificates saved to the database. - var signatureEndCertificate = Assert.Single(_entitiesContext - .Object - .EndCertificates - .Where(x => x.Thumbprint == expected.SignatureEndCertificate.Thumbprint)); - - Assert.Equal(EndCertificateUse.CodeSigning, signatureEndCertificate.Use); + var trustedTimestamp = VerifyPackageSignatureRecord(expected.PrimarySignature, timestampValue, signatureType); Assert.Equal( expected + .PrimarySignature .SignatureParentCertificates .Select(x => x.Thumbprint) .OrderBy(x => x), - signatureEndCertificate + trustedTimestamp + .PackageSignature + .EndCertificate .CertificateChainLinks .Select(x => x.ParentCertificate.Thumbprint) .OrderBy(x => x)); - var timestampEndCertificate = Assert.Single(_entitiesContext - .Object - .EndCertificates - .Where(x => x.Thumbprint == expected.TimestampEndCertificate.Thumbprint)); - - Assert.Equal(EndCertificateUse.Timestamping, timestampEndCertificate.Use); - Assert.Equal( expected + .PrimarySignature .TimestampParentCertificates .Select(x => x.Thumbprint) .OrderBy(x => x), - timestampEndCertificate + trustedTimestamp + .EndCertificate .CertificateChainLinks .Select(x => x.ParentCertificate.Thumbprint) .OrderBy(x => x)); + } - _entitiesContext.Verify(x => x.SaveChangesAsync(), Times.Once); + private TrustedTimestamp VerifyPackageSignatureRecord( + SignatureCertificateThumprints expected, + DateTime timestampValue, + PackageSignatureType signatureType) + { + var signatureEndCertificate = Assert.Single(_entitiesContext + .Object + .EndCertificates + .Where(x => x.Thumbprint == expected.SignatureEndCertificate.Thumbprint)); + Assert.Equal(EndCertificateUse.CodeSigning, signatureEndCertificate.Use); + + var timestampEndCertificate = Assert.Single(_entitiesContext + .Object + .EndCertificates + .Where(x => x.Thumbprint == expected.TimestampEndCertificate.Thumbprint)); + Assert.Equal(EndCertificateUse.Timestamping, timestampEndCertificate.Use); - var packageSignature = Assert.Single(_entitiesContext.Object.PackageSignatures); + var packageSignature = Assert.Single(_entitiesContext + .Object + .PackageSignatures + .Where(x => x.Type == signatureType)); Assert.Equal(_packageKey, packageSignature.PackageKey); Assert.NotEqual(default(DateTime), packageSignature.CreatedAt); Assert.Equal(expected.SignatureEndCertificate.Thumbprint, packageSignature.EndCertificate.Thumbprint); Assert.Same(signatureEndCertificate, packageSignature.EndCertificate); Assert.NotNull(packageSignature.TrustedTimestamps); - var trustedTimestamp = Assert.Single(_entitiesContext.Object.TrustedTimestamps); + var trustedTimestamp = Assert.Single(_entitiesContext + .Object + .TrustedTimestamps + .Where(x => x.PackageSignature == packageSignature)); Assert.Same(trustedTimestamp, packageSignature.TrustedTimestamps.Single()); Assert.Same(packageSignature, trustedTimestamp.PackageSignature); Assert.Equal(expected.TimestampEndCertificate.Thumbprint, trustedTimestamp.EndCertificate.Thumbprint); Assert.Same(timestampEndCertificate, trustedTimestamp.EndCertificate); Assert.Equal(timestampValue, trustedTimestamp.Value); Assert.Equal(TrustedTimestampStatus.Valid, trustedTimestamp.Status); + + _entitiesContext.Verify(x => x.SaveChangesAsync(), Times.Once); + + return trustedTimestamp; + } + + private void VerifyStoredCertificates(ExtractedCertificatesThumbprints expected) + { + Assert.Equal(expected.Certificates.Count, _savedCertificates.Count); + for (var i = 0; i < _savedCertificates.Count; i++) + { + var subject = _savedCertificates[i].Subject; + var thumbprint = _savedCertificates[i].ComputeSHA256Thumbprint(); + Assert.Equal(expected.Certificates[i].Subject, subject); + Assert.Equal(expected.Certificates[i].Thumbprint, thumbprint); + } } } @@ -631,18 +789,55 @@ private static PrimarySignature AddCertificates(SignedCms destination, SignedCms } private class ExtractedCertificatesThumbprints + { + public SignatureCertificateThumprints PrimarySignature { get; set; } + public SignatureCertificateThumprints Countersignature { get; set; } + + public IReadOnlyList Certificates + { + get + { + var all = Enumerable.Empty(); + + if (PrimarySignature != null) + { + all = all + .Concat(new[] { PrimarySignature.SignatureEndCertificate }) + .Concat(PrimarySignature.SignatureParentCertificates) + .Concat(new[] { PrimarySignature.TimestampEndCertificate }) + .Concat(PrimarySignature.TimestampParentCertificates); + } + + if (Countersignature != null) + { + all = all + .Concat(new[] { Countersignature.SignatureEndCertificate }) + .Concat(Countersignature.SignatureParentCertificates) + .Concat(new[] { Countersignature.TimestampEndCertificate }) + .Concat(Countersignature.TimestampParentCertificates); + } + + var thumbprints = new HashSet(); + var output = new List(); + foreach (var certificate in all) + { + if (thumbprints.Add(certificate.Thumbprint)) + { + output.Add(certificate); + } + } + + return output; + } + } + } + + private class SignatureCertificateThumprints { public IReadOnlyList SignatureParentCertificates { get; set; } public SubjectAndThumbprint SignatureEndCertificate { get; set; } public IReadOnlyList TimestampParentCertificates { get; set; } public SubjectAndThumbprint TimestampEndCertificate { get; set; } - public IReadOnlyList Certificates => Enumerable - .Empty() - .Concat(new[] { SignatureEndCertificate }) - .Concat(SignatureParentCertificates) - .Concat(new[] { TimestampEndCertificate }) - .Concat(TimestampParentCertificates) - .ToList(); } } } diff --git a/tests/Validation.PackageSigning.ProcessSignature.Tests/SignatureValidatorFacts.cs b/tests/Validation.PackageSigning.ProcessSignature.Tests/SignatureValidatorFacts.cs index a36d22c98..8040f19d0 100644 --- a/tests/Validation.PackageSigning.ProcessSignature.Tests/SignatureValidatorFacts.cs +++ b/tests/Validation.PackageSigning.ProcessSignature.Tests/SignatureValidatorFacts.cs @@ -208,7 +208,7 @@ public async Task RejectsPackagesWithMimimalVerificationErrors() results: new[] { new InvalidSignaturePackageVerificationResult( - SignatureVerificationStatus.Invalid, + SignatureVerificationStatus.Illegal, new[] { SignatureLog.Issue( @@ -264,7 +264,7 @@ public async Task RejectsPackagesWithFullVerificationErrors() results: new[] { new InvalidSignaturePackageVerificationResult( - SignatureVerificationStatus.Invalid, + SignatureVerificationStatus.Illegal, new[] { SignatureLog.Issue( diff --git a/tests/Validation.PackageSigning.ProcessSignature.Tests/SignatureValidatorIntegrationTests.cs b/tests/Validation.PackageSigning.ProcessSignature.Tests/SignatureValidatorIntegrationTests.cs index 85898500c..203d6ec6c 100644 --- a/tests/Validation.PackageSigning.ProcessSignature.Tests/SignatureValidatorIntegrationTests.cs +++ b/tests/Validation.PackageSigning.ProcessSignature.Tests/SignatureValidatorIntegrationTests.cs @@ -38,8 +38,10 @@ public class SignatureValidatorIntegrationTests : IDisposable { private readonly CertificateIntegrationTestFixture _fixture; private readonly ITestOutputHelper _output; - private readonly Mock _packageSigningStateService; - private readonly Mock _signaturePartsExtractor; + private readonly PackageSigningStateService _packageSigningStateService; + private readonly Mock _certificateStore; + private readonly Mock _validationEntitiesContext; + private readonly SignaturePartsExtractor _signaturePartsExtractor; private readonly Mock _packageFileService; private readonly Uri _nupkgUri; private readonly Mock> _certificates; @@ -61,11 +63,39 @@ public SignatureValidatorIntegrationTests(CertificateIntegrationTestFixture fixt _fixture = fixture ?? throw new ArgumentNullException(nameof(fixture)); _output = output ?? throw new ArgumentNullException(nameof(output)); - // These dependencies have their own dependencies on the database or blob storage, which don't have good - // integration test infrastructure in the jobs yet. Therefore, we'll mock them for now. - _packageSigningStateService = new Mock(); + _validationEntitiesContext = new Mock(); + _validationEntitiesContext + .Setup(x => x.PackageSigningStates) + .Returns(DbSetMockFactory.Create()); + _validationEntitiesContext + .Setup(x => x.ParentCertificates) + .Returns(DbSetMockFactory.Create()); + _validationEntitiesContext + .Setup(x => x.EndCertificates) + .Returns(DbSetMockFactory.Create()); + _validationEntitiesContext + .Setup(x => x.CertificateChainLinks) + .Returns(DbSetMockFactory.Create()); + _validationEntitiesContext + .Setup(x => x.PackageSignatures) + .Returns(DbSetMockFactory.Create()); + _validationEntitiesContext + .Setup(x => x.TrustedTimestamps) + .Returns(DbSetMockFactory.Create()); - _signaturePartsExtractor = new Mock(); + var loggerFactory = new LoggerFactory(); + loggerFactory.AddXunit(output); + + _packageSigningStateService = new PackageSigningStateService( + _validationEntitiesContext.Object, + loggerFactory.CreateLogger()); + + _certificateStore = new Mock(); + + _signaturePartsExtractor = new SignaturePartsExtractor( + _certificateStore.Object, + _validationEntitiesContext.Object, + loggerFactory.CreateLogger()); _packageFileService = new Mock(); _nupkgUri = new Uri("https://example-storage/TestProcessor/b777135f-1aac-4ec2-a3eb-1f64fe1880d5/nuget.versioning.4.3.0.nupkg"); @@ -98,8 +128,6 @@ public SignatureValidatorIntegrationTests(CertificateIntegrationTestFixture fixt _telemetryClient = new Mock(); _telemetryService = new TelemetryService(_telemetryClient.Object); - var loggerFactory = new LoggerFactory(); - loggerFactory.AddXunit(output); _logger = new RecordingLogger(loggerFactory.CreateLogger()); // Initialize data. @@ -113,10 +141,10 @@ public SignatureValidatorIntegrationTests(CertificateIntegrationTestFixture fixt // Initialize the subject of testing. _target = new SignatureValidator( - _packageSigningStateService.Object, + _packageSigningStateService, _minimalPackageSignatureVerifier, _fullPackageSignatureVerifier, - _signaturePartsExtractor.Object, + _signaturePartsExtractor, _packageFileService.Object, _certificates.Object, _telemetryService, @@ -834,13 +862,14 @@ private void AllowCertificateThumbprint(string thumbprint) private void VerifyPackageSigningStatus(SignatureValidatorResult result, ValidationStatus validationStatus, PackageSigningStatus packageSigningStatus) { Assert.Equal(validationStatus, result.State); - _packageSigningStateService.Verify( - x => x.SetPackageSigningState( - _packageKey, - _message.PackageId, - _message.PackageVersion, - packageSigningStatus), - Times.Once); + var state = _validationEntitiesContext + .Object + .PackageSigningStates + .Where(x => x.PackageKey == _packageKey) + .SingleOrDefault(); + Assert.Equal(state.PackageId, _message.PackageId); + Assert.Equal(state.PackageNormalizedVersion, _message.PackageVersion); + Assert.Equal(state.SigningStatus, packageSigningStatus); } private static void VerifyNU3008(SignatureValidatorResult result) diff --git a/tests/Validation.PackageSigning.ProcessSignature.Tests/Support/TestResources.cs b/tests/Validation.PackageSigning.ProcessSignature.Tests/Support/TestResources.cs index c88ecb350..3cee0307e 100644 --- a/tests/Validation.PackageSigning.ProcessSignature.Tests/Support/TestResources.cs +++ b/tests/Validation.PackageSigning.ProcessSignature.Tests/Support/TestResources.cs @@ -10,13 +10,13 @@ namespace Validation.PackageSigning.ProcessSignature.Tests { public static class TestResources { - private const string ResourceNamespace = "Validation.PackageSigning.ProcessSignature.Tests.TestData"; - public const string SignedPackageLeaf1 = ResourceNamespace + ".TestSigned.leaf-1.1.0.0.nupkg"; - public const string SignedPackageLeaf2 = ResourceNamespace + ".TestSigned.leaf-2.2.0.0.nupkg"; - public const string UnsignedPackage = ResourceNamespace + ".TestUnsigned.1.0.0.nupkg"; - public const string Zip64Package = ResourceNamespace + ".Zip64Package.1.0.0.nupkg"; - public const string RepoSignedPackageLeaf1 = ResourceNamespace + ".TestRepoSigned.leaf-1.1.0.0.nupkg"; - public const string AuthorAndRepoSignedPackageLeaf1 = ResourceNamespace + ".TestAuthorAndRepoSigned.leaf-1.1.0.0.nupkg"; + private const string ResourcePrefix = "Validation.PackageSigning.ProcessSignature.Tests.TestData."; + 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 UnsignedPackage = "TestUnsigned.1.0.0.nupkg"; + public const string Zip64Package = "Zip64Package.1.0.0.nupkg"; + public const string RepoSignedPackageLeaf1 = "TestRepoSigned.leaf-1.1.0.0.nupkg"; + public const string AuthorAndRepoSignedPackageLeaf1 = "TestAuthorAndRepoSigned.leaf-1.1.0.0.nupkg"; /// /// This is the SHA-256 thumbprint of the root CA certificate for the signing certificate of . @@ -45,7 +45,7 @@ public static MemoryStream GetResourceStream(string resourceName) { var resourceStream = typeof(TestResources) .Assembly - .GetManifestResourceStream(resourceName); + .GetManifestResourceStream(ResourcePrefix + resourceName); if (resourceStream == null) { diff --git a/tests/Validation.PackageSigning.RevalidateCertificate.Tests/Validation.PackageSigning.RevalidateCertificate.Tests.csproj b/tests/Validation.PackageSigning.RevalidateCertificate.Tests/Validation.PackageSigning.RevalidateCertificate.Tests.csproj index ecc6d20fb..5e714bfda 100644 --- a/tests/Validation.PackageSigning.RevalidateCertificate.Tests/Validation.PackageSigning.RevalidateCertificate.Tests.csproj +++ b/tests/Validation.PackageSigning.RevalidateCertificate.Tests/Validation.PackageSigning.RevalidateCertificate.Tests.csproj @@ -36,7 +36,6 @@ - From c67227ee7874a83ac56fad91c8d77f3d41ef16e5 Mon Sep 17 00:00:00 2001 From: Joel Verhagen Date: Wed, 11 Apr 2018 15:42:04 -0700 Subject: [PATCH 3/3] Filter repository signatures from Validate Certificate, Revalidate jobs (#402) Address https://github.com/NuGet/Engineering/issues/1198 --- .../PackageCertificatesValidator.cs | 1 + .../CertificateRevalidator.cs | 1 + .../CertificateValidationService.cs | 2 + .../PackageCertificatesValidatorFacts.cs | 118 +++++++++++++++++- .../CertificateRevalidatorFacts.cs | 24 +++- ...alidationMessageHandlerIntegrationTests.cs | 36 +++++- .../CertificateValidationServiceFacts.cs | 108 ++++++++++++++-- 7 files changed, 269 insertions(+), 21 deletions(-) diff --git a/src/NuGet.Services.Validation.Orchestrator/PackageSigning/ValidateCertificate/PackageCertificatesValidator.cs b/src/NuGet.Services.Validation.Orchestrator/PackageSigning/ValidateCertificate/PackageCertificatesValidator.cs index b3b1a5b19..95754ad30 100644 --- a/src/NuGet.Services.Validation.Orchestrator/PackageSigning/ValidateCertificate/PackageCertificatesValidator.cs +++ b/src/NuGet.Services.Validation.Orchestrator/PackageSigning/ValidateCertificate/PackageCertificatesValidator.cs @@ -236,6 +236,7 @@ private Task FindSignatureAsync(IValidationRequest request) .PackageSignatures .Include(s => s.EndCertificate) .Include(s => s.TrustedTimestamps.Select(t => t.EndCertificate)) + .Where(s => s.Type == PackageSignatureType.Author) .SingleAsync(s => s.PackageKey == request.PackageKey); } diff --git a/src/Validation.PackageSigning.RevalidateCertificate/CertificateRevalidator.cs b/src/Validation.PackageSigning.RevalidateCertificate/CertificateRevalidator.cs index 63995077c..537e5c12b 100644 --- a/src/Validation.PackageSigning.RevalidateCertificate/CertificateRevalidator.cs +++ b/src/Validation.PackageSigning.RevalidateCertificate/CertificateRevalidator.cs @@ -73,6 +73,7 @@ private async Task> FindPromotableSignaturesAsync() var potentialSignatures = await _context.PackageSignatures .Where(s => s.Status == PackageSignatureStatus.InGracePeriod) + .Where(s => s.Type == PackageSignatureType.Author) .Include(s => s.EndCertificate) .Include(s => s.TrustedTimestamps.Select(t => t.EndCertificate)) .OrderBy(s => s.CreatedAt) diff --git a/src/Validation.PackageSigning.ValidateCertificate/CertificateValidationService.cs b/src/Validation.PackageSigning.ValidateCertificate/CertificateValidationService.cs index 0c1bb7cf8..b2ae20ff5 100644 --- a/src/Validation.PackageSigning.ValidateCertificate/CertificateValidationService.cs +++ b/src/Validation.PackageSigning.ValidateCertificate/CertificateValidationService.cs @@ -286,11 +286,13 @@ private Task> FindSignaturesAsync(EndCertificate certific { case EndCertificateUse.CodeSigning: packageSignatures = _context.PackageSignatures + .Where(s => s.Type == PackageSignatureType.Author) .Where(s => s.EndCertificate.Thumbprint == certificate.Thumbprint); break; case EndCertificateUse.Timestamping: packageSignatures = _context.PackageSignatures + .Where(s => s.Type == PackageSignatureType.Author) .Where(s => s.TrustedTimestamps.Any(t => t.EndCertificate.Thumbprint == certificate.Thumbprint)); break; diff --git a/tests/NuGet.Services.Validation.Orchestrator.Tests/PackageSigning/ValidateCertificate/PackageCertificatesValidatorFacts.cs b/tests/NuGet.Services.Validation.Orchestrator.Tests/PackageSigning/ValidateCertificate/PackageCertificatesValidatorFacts.cs index 3dc06b8bf..46d93c982 100644 --- a/tests/NuGet.Services.Validation.Orchestrator.Tests/PackageSigning/ValidateCertificate/PackageCertificatesValidatorFacts.cs +++ b/tests/NuGet.Services.Validation.Orchestrator.Tests/PackageSigning/ValidateCertificate/PackageCertificatesValidatorFacts.cs @@ -160,6 +160,7 @@ public async Task ReturnsExpectedStatusForCertificateValidations(ValidationStatu PackageKey = PackageKey, Status = PackageSignatureStatus.Unknown, EndCertificate = certificate, + Type = PackageSignatureType.Author, }; certificate.PackageSignatures = new[] { packageSignature }; @@ -271,6 +272,7 @@ public async Task InvalidSignatureFailsValidation( Status = packageSignatureStatus, PackageSigningState = packageSigningState, EndCertificate = certificate, + Type = PackageSignatureType.Author, }; var timestamp = new TrustedTimestamp @@ -335,6 +337,7 @@ public static IEnumerable ValidSignaturesArePromotedData() EndCertificate = cert1SecondAgo, } }, + Type = PackageSignatureType.Author, }, }; @@ -354,6 +357,7 @@ public static IEnumerable ValidSignaturesArePromotedData() EndCertificate = cert1SecondAgo, } }, + Type = PackageSignatureType.Author, }, }; @@ -373,6 +377,7 @@ public static IEnumerable ValidSignaturesArePromotedData() EndCertificate = cert1YearAgo, } }, + Type = PackageSignatureType.Author, }, }; @@ -396,6 +401,7 @@ public static IEnumerable ValidSignaturesArePromotedData() EndCertificate = cert1YearAgo, } }, + Type = PackageSignatureType.Author, }, }; @@ -415,6 +421,7 @@ public static IEnumerable ValidSignaturesArePromotedData() EndCertificate = cert1SecondAgo, } }, + Type = PackageSignatureType.Author, }, }; } @@ -489,7 +496,8 @@ public async Task ThrowsIfValidSignaturesHasTimestampWithRevokedCertificate() var signature = new PackageSignature { PackageKey = PackageKey, - Status = PackageSignatureStatus.Unknown + Status = PackageSignatureStatus.Unknown, + Type = PackageSignatureType.Author, }; var timestamp = new TrustedTimestamp @@ -630,7 +638,8 @@ public async Task ReturnsSucceededIfAllCertificatesAlreadyValidated() var packageSignature = new PackageSignature { PackageKey = PackageKey, - Status = PackageSignatureStatus.Valid + Status = PackageSignatureStatus.Valid, + Type = PackageSignatureType.Author, }; var timestamp = new TrustedTimestamp @@ -712,7 +721,8 @@ public async Task ReturnsIncompleteIfThereAreCertificatesToValidate() var packageSignature = new PackageSignature { PackageKey = PackageKey, - Status = PackageSignatureStatus.Valid + Status = PackageSignatureStatus.Valid, + Type = PackageSignatureType.Author, }; var timestamp = new TrustedTimestamp @@ -792,7 +802,8 @@ public async Task CertificateRevokedAfterPackageWasSignedDoesntInvalidateSignatu var packageSignature = new PackageSignature { PackageKey = PackageKey, - Status = PackageSignatureStatus.Valid + Status = PackageSignatureStatus.Valid, + Type = PackageSignatureType.Author, }; var timestamp = new TrustedTimestamp @@ -875,7 +886,8 @@ public async Task InvalidCertificatesAlwaysInvalidateSignature() var packageSignature = new PackageSignature { PackageKey = PackageKey, - Status = PackageSignatureStatus.Valid + Status = PackageSignatureStatus.Valid, + Type = PackageSignatureType.Author, }; var timestamp = new TrustedTimestamp @@ -1051,7 +1063,8 @@ public async Task OnRevalidationAllNonRevokedCertificatesAreVerified( var packageSignature = new PackageSignature { PackageKey = PackageKey, - Status = PackageSignatureStatus.Valid + Status = PackageSignatureStatus.Valid, + Type = PackageSignatureType.Author, }; var timestamp = new TrustedTimestamp @@ -1122,6 +1135,7 @@ public async Task RevokedSignaturesAreInvalidated() { PackageKey = PackageKey, Status = PackageSignatureStatus.Valid, + Type = PackageSignatureType.Author, }; var timestamp = new TrustedTimestamp @@ -1167,6 +1181,98 @@ public async Task RevokedSignaturesAreInvalidated() Assert.Equal(PackageSigningStatus.Invalid, packageSigningState.SigningStatus); } + [Theory] + [InlineData(PackageSignatureType.Repository)] + [InlineData((PackageSignatureType)0)] + public async Task NonAuthorSignaturesAreIgnored(PackageSignatureType type) + { + // Arrange + var validatorStatus = new ValidatorStatus + { + ValidationId = ValidationId, + ValidatorName = nameof(PackageCertificatesValidator), + PackageKey = PackageKey, + State = ValidationStatus.NotStarted, + ValidatorIssues = new List(), + }; + + var packageSigningState = new PackageSigningState + { + PackageKey = PackageKey, + PackageId = PackageId, + PackageNormalizedVersion = PackageNormalizedVersion, + SigningStatus = PackageSigningStatus.Valid, + }; + + var authorPackageSignature = new PackageSignature + { + PackageKey = PackageKey, + Status = PackageSignatureStatus.Valid, + Type = PackageSignatureType.Author, + }; + + var repositoryPackageSignature = new PackageSignature + { + PackageKey = PackageKey, + Status = PackageSignatureStatus.Unknown, + Type = type, + }; + + var timestamp = new TrustedTimestamp + { + Value = DateTime.UtcNow.AddDays(-10) + }; + + var signatureCertificate = new EndCertificate + { + Key = 123, + Status = EndCertificateStatus.Good, + StatusUpdateTime = DateTime.UtcNow.AddSeconds(-10), + NextStatusUpdateTime = DateTime.UtcNow.AddDays(1), + LastVerificationTime = DateTime.UtcNow.AddSeconds(-10), + RevocationTime = null, + ValidationFailures = 0, + }; + + var timestampCertificate = new EndCertificate + { + Key = 456, + Status = EndCertificateStatus.Good, + StatusUpdateTime = DateTime.UtcNow.AddSeconds(-10), + NextStatusUpdateTime = DateTime.UtcNow.AddDays(1), + LastVerificationTime = DateTime.UtcNow.AddSeconds(-10), + RevocationTime = null, + ValidationFailures = 0, + }; + + packageSigningState.PackageSignatures = new[] { authorPackageSignature, repositoryPackageSignature }; + authorPackageSignature.PackageSigningState = packageSigningState; + authorPackageSignature.TrustedTimestamps = new[] { timestamp }; + authorPackageSignature.EndCertificate = signatureCertificate; + timestamp.EndCertificate = timestampCertificate; + signatureCertificate.PackageSignatures = new[] { authorPackageSignature }; + timestampCertificate.TrustedTimestamps = new[] { timestamp }; + + _validationContext.Mock( + validatorStatuses: new[] { validatorStatus }, + packageSigningStates: new[] { packageSigningState }, + packageSignatures: new[] { authorPackageSignature, repositoryPackageSignature }, + trustedTimestamps: new[] { timestamp }, + endCertificates: new[] { signatureCertificate, timestampCertificate }); + + // Act & Assert + var actual = await _target.StartAsync(_validationRequest.Object); + + _certificateVerifier.Verify(v => v.EnqueueVerificationAsync(It.IsAny(), It.IsAny()), Times.Never); + _validationContext.Verify(c => c.SaveChangesAsync(), Times.Once); + _telemetryService.Verify( + x => x.TrackDurationToStartPackageCertificatesValidator(It.IsAny()), + Times.Never); + + Assert.Equal(ValidationStatus.Succeeded, actual.Status); + Assert.Equal(ValidationStatus.Succeeded, validatorStatus.State); + } + public static IEnumerable ValidationStatusesThatAreStarted = validationStatusesThatAreStarted.Select(s => new object[] { s }); } diff --git a/tests/Validation.PackageSigning.RevalidateCertificate.Tests/CertificateRevalidatorFacts.cs b/tests/Validation.PackageSigning.RevalidateCertificate.Tests/CertificateRevalidatorFacts.cs index b66d9b5f9..44204c694 100644 --- a/tests/Validation.PackageSigning.RevalidateCertificate.Tests/CertificateRevalidatorFacts.cs +++ b/tests/Validation.PackageSigning.RevalidateCertificate.Tests/CertificateRevalidatorFacts.cs @@ -55,6 +55,26 @@ public async Task DoesNoPromotionsIfNonePromotable() Assert.Equal(PackageSignatureStatus.InGracePeriod, signature2.Status); } + [Theory] + [InlineData(PackageSignatureType.Repository)] + [InlineData((PackageSignatureType)0)] + public async Task DoesNotPromoteNonAuthorSignatures(PackageSignatureType type) + { + // Arrange - make signature nonpromotable due to repository type. + var signature = PromotableSignature; + + signature.Type = type; + + _context.Mock(packageSignatures: new[] { signature }); + + // Act & Assert + await _target.PromoteSignaturesAsync(); + + _context.Verify(c => c.SaveChangesAsync(), Times.Never); + + Assert.Equal(PackageSignatureStatus.InGracePeriod, signature.Status); + } + [Fact] public async Task PromotesSignaturesIfPossible() { @@ -371,7 +391,9 @@ public Base() StatusUpdateTime = DateTime.UtcNow, } } - } + }, + + Type = PackageSignatureType.Author, }; protected EndCertificate StaleCertificate => diff --git a/tests/Validation.PackageSigning.ValidateCertificate.Tests/CertificateValidationMessageHandlerIntegrationTests.cs b/tests/Validation.PackageSigning.ValidateCertificate.Tests/CertificateValidationMessageHandlerIntegrationTests.cs index c68713958..6ccf45889 100644 --- a/tests/Validation.PackageSigning.ValidateCertificate.Tests/CertificateValidationMessageHandlerIntegrationTests.cs +++ b/tests/Validation.PackageSigning.ValidateCertificate.Tests/CertificateValidationMessageHandlerIntegrationTests.cs @@ -72,9 +72,21 @@ public async Task ValidateSigningCertificate( var packageSigningState2 = new PackageSigningState { SigningStatus = PackageSigningStatus.Valid }; var packageSigningState3 = new PackageSigningState { SigningStatus = PackageSigningStatus.Valid }; - var signatureAtIngestion = new PackageSignature { Status = PackageSignatureStatus.Unknown }; - var signatureInGracePeriod = new PackageSignature { Status = PackageSignatureStatus.InGracePeriod }; - var signatureAfterGracePeriod = new PackageSignature { Status = PackageSignatureStatus.Valid }; + var signatureAtIngestion = new PackageSignature + { + Status = PackageSignatureStatus.Unknown, + Type = PackageSignatureType.Author, + }; + var signatureInGracePeriod = new PackageSignature + { + Status = PackageSignatureStatus.InGracePeriod, + Type = PackageSignatureType.Author, + }; + var signatureAfterGracePeriod = new PackageSignature + { + Status = PackageSignatureStatus.Valid, + Type = PackageSignatureType.Author, + }; var trustedTimestamp1 = new TrustedTimestamp { Status = TrustedTimestampStatus.Valid, Value = signatureTime }; var trustedTimestamp2 = new TrustedTimestamp { Status = TrustedTimestampStatus.Valid, Value = signatureTime }; @@ -212,9 +224,21 @@ public async Task ValidateTimestampingCertificate() var packageSigningState = new PackageSigningState { SigningStatus = PackageSigningStatus.Valid }; - var signatureAtIngestion = new PackageSignature { Status = PackageSignatureStatus.Unknown }; - var signatureInGracePeriod = new PackageSignature { Status = PackageSignatureStatus.InGracePeriod }; - var signatureAfterGracePeriod = new PackageSignature { Status = PackageSignatureStatus.Valid }; + var signatureAtIngestion = new PackageSignature + { + Status = PackageSignatureStatus.Unknown, + Type = PackageSignatureType.Author, + }; + var signatureInGracePeriod = new PackageSignature + { + Status = PackageSignatureStatus.InGracePeriod, + Type = PackageSignatureType.Author, + }; + var signatureAfterGracePeriod = new PackageSignature + { + Status = PackageSignatureStatus.Valid, + Type = PackageSignatureType.Author, + }; var endCertificate = new EndCertificate { diff --git a/tests/Validation.PackageSigning.ValidateCertificate.Tests/CertificateValidationServiceFacts.cs b/tests/Validation.PackageSigning.ValidateCertificate.Tests/CertificateValidationServiceFacts.cs index e2bf30372..f96bea25e 100644 --- a/tests/Validation.PackageSigning.ValidateCertificate.Tests/CertificateValidationServiceFacts.cs +++ b/tests/Validation.PackageSigning.ValidateCertificate.Tests/CertificateValidationServiceFacts.cs @@ -100,8 +100,18 @@ public async Task InvalidResultInvalidatesDependentSignatures() statusFlags: X509ChainStatusFlags.ExplicitDistrust); var signingState = new PackageSigningState { SigningStatus = PackageSigningStatus.Valid }; - var signature1 = new PackageSignature { Key = 123, Status = PackageSignatureStatus.Valid }; - var signature2 = new PackageSignature { Key = 456, Status = PackageSignatureStatus.Valid }; + var signature1 = new PackageSignature + { + Key = 123, + Status = PackageSignatureStatus.Valid, + Type = PackageSignatureType.Author, + }; + var signature2 = new PackageSignature + { + Key = 456, + Status = PackageSignatureStatus.Valid, + Type = PackageSignatureType.Author, + }; var timestamp = new TrustedTimestamp { Value = DateTime.UtcNow }; signingState.PackageSignatures = new[] { signature1, signature2 }; @@ -144,6 +154,60 @@ public async Task InvalidResultInvalidatesDependentSignatures() _context.Verify(c => c.SaveChangesAsync(), Times.Once); } + [Theory] + [InlineData(PackageSignatureType.Repository)] + [InlineData((PackageSignatureType)0)] + public async Task RevokedResultDoesNotInvalidateDependentNonAuthorSignaturesSignatures(PackageSignatureType type) + { + // Arrange + var revocationTime = DateTime.UtcNow; + + var verificationResult = new CertificateVerificationResult( + status: EndCertificateStatus.Revoked, + statusFlags: X509ChainStatusFlags.Revoked, + revocationTime: revocationTime); + + var signingState = new PackageSigningState { SigningStatus = PackageSigningStatus.Valid }; + var signature2 = new PackageSignature { Key = 12, Status = PackageSignatureStatus.Valid, Type = type }; + var timestamp2 = new TrustedTimestamp { Value = revocationTime.AddDays(1), Status = TrustedTimestampStatus.Valid }; + + signingState.PackageSignatures = new[] { signature2 }; + signature2.PackageSigningState = signingState; + signature2.EndCertificate = _certificate1; + signature2.TrustedTimestamps = new[] { timestamp2 }; + timestamp2.PackageSignature = signature2; + timestamp2.EndCertificate = _certificate2; + _certificate1.Use = EndCertificateUse.CodeSigning; + _certificate2.Use = EndCertificateUse.Timestamping; + _certificate1.PackageSignatures = new[] { signature2 }; + _certificate2.TrustedTimestamps = new[] { timestamp2 }; + + _context.Mock( + packageSigningStates: new[] { signingState }, + packageSignatures: new[] { signature2 }, + trustedTimestamps: new[] { timestamp2 }, + endCertificates: new[] { _certificate1, _certificate2 }); + + var result = await _target.TrySaveResultAsync(_certificateValidation1, verificationResult); + + Assert.True(result); + + Assert.Equal(EndCertificateStatus.Revoked, _certificateValidation1.Status); + + Assert.Equal(EndCertificateStatus.Revoked, _certificate1.Status); + Assert.Equal(0, _certificate1.ValidationFailures); + Assert.Equal(revocationTime, _certificate1.RevocationTime); + + Assert.Equal(PackageSignatureStatus.Valid, signature2.Status); + + Assert.Equal(PackageSigningStatus.Valid, signingState.SigningStatus); + + _telemetryService.Verify(a => a.TrackUnableToValidateCertificateEvent(It.IsAny()), Times.Never); + _telemetryService.Verify(a => a.TrackPackageSignatureShouldBeInvalidatedEvent(It.IsAny()), Times.Never); + _telemetryService.Verify(a => a.TrackUnableToValidateCertificateEvent(It.IsAny()), Times.Never); + _context.Verify(c => c.SaveChangesAsync(), Times.Once); + } + [Fact] public async Task RevokedResultInvalidatesDependentSignatures() { @@ -158,9 +222,24 @@ public async Task RevokedResultInvalidatesDependentSignatures() revocationTime: revocationTime); var signingState = new PackageSigningState { SigningStatus = PackageSigningStatus.Valid }; - var signature1 = new PackageSignature { Key = 12, Status = PackageSignatureStatus.Valid }; - var signature2 = new PackageSignature { Key = 23, Status = PackageSignatureStatus.Valid }; - var signature3 = new PackageSignature { Key = 34, Status = PackageSignatureStatus.Valid }; + var signature1 = new PackageSignature + { + Key = 12, + Status = PackageSignatureStatus.Valid, + Type = PackageSignatureType.Author, + }; + var signature2 = new PackageSignature + { + Key = 23, + Status = PackageSignatureStatus.Valid, + Type = PackageSignatureType.Author, + }; + var signature3 = new PackageSignature + { + Key = 34, + Status = PackageSignatureStatus.Valid, + Type = PackageSignatureType.Author, + }; var timestamp1 = new TrustedTimestamp { Value = revocationTime.AddDays(-1), Status = TrustedTimestampStatus.Valid }; var timestamp2 = new TrustedTimestamp { Value = revocationTime.AddDays(1), Status = TrustedTimestampStatus.Valid }; var timestamp3 = new TrustedTimestamp { Value = revocationTime.AddDays(-1), Status = TrustedTimestampStatus.Valid }; @@ -223,7 +302,12 @@ public async Task RevokedResultInvalidatesDependentTimestamps() revocationTime: DateTime.UtcNow); var signingState = new PackageSigningState { SigningStatus = PackageSigningStatus.Valid }; - var signature1 = new PackageSignature { Key = 12, Status = PackageSignatureStatus.Valid }; + var signature1 = new PackageSignature + { + Key = 12, + Status = PackageSignatureStatus.Valid, + Type = PackageSignatureType.Author, + }; var timestamp1 = new TrustedTimestamp { Value = DateTime.UtcNow, Status = TrustedTimestampStatus.Valid }; signingState.PackageSignatures = new[] { signature1 }; @@ -260,7 +344,11 @@ public async Task UnknownResultUpdatesCertificateValidation() status: EndCertificateStatus.Unknown, statusFlags: X509ChainStatusFlags.RevocationStatusUnknown); - var signature = new PackageSignature { Status = PackageSignatureStatus.Valid }; + var signature = new PackageSignature + { + Status = PackageSignatureStatus.Valid, + Type = PackageSignatureType.Author, + }; var timestamp = new TrustedTimestamp { Value = DateTime.UtcNow }; signature.EndCertificate = _certificate1; @@ -290,7 +378,11 @@ public async Task UnknownResultAlertsIfReachesMaxFailureThreshold() status: EndCertificateStatus.Unknown, statusFlags: X509ChainStatusFlags.RevocationStatusUnknown); - var signature = new PackageSignature { Status = PackageSignatureStatus.Valid }; + var signature = new PackageSignature + { + Status = PackageSignatureStatus.Valid, + Type = PackageSignatureType.Author, + }; var timestamp = new TrustedTimestamp { Value = DateTime.UtcNow }; signature.EndCertificate = _certificate1;