| File: Signing\Verification\AllowListVerificationProvider.cs | Web Access |
| Project: src\nuget-client\src\NuGet.Core\NuGet.Packaging\NuGet.Packaging.csproj (NuGet.Packaging) |
// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using System.Linq; using NuGet.Common; using System.Diagnostics.CodeAnalysis; namespace NuGet.Packaging.Signing { public class AllowListVerificationProvider : ISignatureVerificationProvider { private readonly IReadOnlyCollection<VerificationAllowListEntry>? _allowList; private readonly string _emptyListErrorMessage; private readonly string _noMatchErrorMessage; private readonly bool _requireNonEmptyAllowList; public AllowListVerificationProvider(IReadOnlyCollection<VerificationAllowListEntry>? allowList, bool requireNonEmptyAllowList = false, string emptyListErrorMessage = "", string noMatchErrorMessage = "") { _allowList = allowList; _requireNonEmptyAllowList = requireNonEmptyAllowList; _emptyListErrorMessage = string.IsNullOrEmpty(emptyListErrorMessage) ? Strings.DefaultError_EmptyAllowList : emptyListErrorMessage; _noMatchErrorMessage = string.IsNullOrEmpty(noMatchErrorMessage) ? Strings.DefaultError_NoMatchInAllowList : noMatchErrorMessage; } public Task<PackageVerificationResult> GetTrustResultAsync(ISignedPackageReader package, PrimarySignature signature, SignedPackageVerifierSettings settings, CancellationToken token) { return Task.FromResult(VerifyAllowList(package, signature, settings)); } private PackageVerificationResult VerifyAllowList(ISignedPackageReader package, PrimarySignature signature, SignedPackageVerifierSettings settings) { var treatIssuesAsErrors = !settings.AllowUntrusted; var status = SignatureVerificationStatus.Valid; var issues = new List<SignatureLog>(); if (_allowList == null || _allowList.Count == 0) { if (_requireNonEmptyAllowList) { status = SignatureVerificationStatus.Disallowed; issues.Add(SignatureLog.Error(code: NuGetLogCode.NU3034, message: _emptyListErrorMessage)); } } else if (!IsSignatureAllowed(signature, _allowList)) { if (!settings.AllowUntrusted) { status = SignatureVerificationStatus.Disallowed; } issues.Add(SignatureLog.Issue(fatal: treatIssuesAsErrors, code: NuGetLogCode.NU3034, message: _noMatchErrorMessage)); } return new SignedPackageVerificationResult(status, signature, issues); } private bool IsSignatureAllowed( PrimarySignature signature, IReadOnlyCollection<VerificationAllowListEntry> allowList) { var primarySignatureCertificateFingerprintLookUp = new Dictionary<HashAlgorithmName, string>(); var countersignatureCertificateFingerprintLookUp = new Dictionary<HashAlgorithmName, string>(); var repositoryCountersignature = new Lazy<RepositoryCountersignature?>(() => RepositoryCountersignature.GetRepositoryCountersignature(signature)); foreach (var allowedEntry in allowList) { // Verify the certificate hash allow list objects var certificateHashEntry = allowedEntry as CertificateHashAllowListEntry; if (certificateHashEntry != null) { if (certificateHashEntry.Placement.HasFlag(SignaturePlacement.PrimarySignature)) { // Get information needed for allow list verification var primarySignatureCertificateFingerprint = GetCertificateFingerprint( signature, certificateHashEntry.FingerprintAlgorithm, primarySignatureCertificateFingerprintLookUp); if (IsSignatureTargeted(certificateHashEntry.Target, signature) && StringComparer.OrdinalIgnoreCase.Equals(certificateHashEntry.Fingerprint, primarySignatureCertificateFingerprint)) { if (ShouldVerifyOwners(certificateHashEntry as TrustedSignerAllowListEntry, signature as IRepositorySignature, out var allowedOwners, out var actualOwners)) { if (allowedOwners.Intersect(actualOwners).Any()) { return true; } } else { return true; } } } if (certificateHashEntry.Placement.HasFlag(SignaturePlacement.Countersignature)) { if (repositoryCountersignature.Value != null) { // Get information needed for allow list verification var countersignatureCertificateFingerprint = GetCertificateFingerprint( repositoryCountersignature.Value, certificateHashEntry.FingerprintAlgorithm, countersignatureCertificateFingerprintLookUp); if (IsSignatureTargeted(certificateHashEntry.Target, repositoryCountersignature.Value) && StringComparer.OrdinalIgnoreCase.Equals(certificateHashEntry.Fingerprint, countersignatureCertificateFingerprint)) { if (ShouldVerifyOwners(certificateHashEntry as TrustedSignerAllowListEntry, repositoryCountersignature.Value, out var allowedOwners, out var actualOwners)) { if (allowedOwners.Intersect(actualOwners).Any()) { return true; } } else { return true; } } } } } } return false; } private static bool ShouldVerifyOwners(TrustedSignerAllowListEntry? entry, IRepositorySignature? repoSignature, [NotNullWhen(returnValue: true)] out IReadOnlyList<string>? allowedOwners, [NotNullWhen(returnValue: true)] out IReadOnlyList<string>? actualOwners) { allowedOwners = null; actualOwners = null; if (entry != null && entry.Target.HasFlag(VerificationTarget.Repository) && entry.Owners != null && entry.Owners.Any() && repoSignature != null) { allowedOwners = entry.Owners ?? Enumerable.Empty<string>().ToList(); actualOwners = repoSignature.PackageOwners ?? Enumerable.Empty<string>().ToList(); return true; } return false; } private static bool IsSignatureTargeted(VerificationTarget target, Signature signature) { return (target.HasFlag(VerificationTarget.Author) && signature is AuthorPrimarySignature) || (target.HasFlag(VerificationTarget.Repository) && signature is RepositoryPrimarySignature) || (target.HasFlag(VerificationTarget.Repository) && signature is RepositoryCountersignature); } private static string GetCertificateFingerprint( Signature signature, HashAlgorithmName fingerprintAlgorithm, IDictionary<HashAlgorithmName, string> CertificateFingerprintLookUp) { if (!CertificateFingerprintLookUp.TryGetValue(fingerprintAlgorithm, out var fingerprintString)) { fingerprintString = CertificateUtility.GetHashString(signature.SignerInfo.Certificate!, fingerprintAlgorithm); CertificateFingerprintLookUp[fingerprintAlgorithm] = fingerprintString; } return fingerprintString; } } }