File: Signing\TrustStore\CertificateBundleX509ChainFactory.cs
Web Access
Project: src\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.

#if NET5_0_OR_GREATER

using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using NuGet.Common;

namespace NuGet.Packaging.Signing
{
    internal abstract class CertificateBundleX509ChainFactory : IX509ChainFactory
    {
        public X509Certificate2Collection Certificates { get; }
        public string? FilePath { get; }

        protected CertificateBundleX509ChainFactory(X509Certificate2Collection certificates, string? filePath = null)
        {
            Certificates = certificates;
            FilePath = filePath;
        }

        public IX509Chain Create()
        {
            X509Chain x509Chain = new();

            x509Chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;

            if (Certificates is not null && Certificates.Count > 0)
            {
                x509Chain.ChainPolicy.CustomTrustStore.AddRange(Certificates);
            }

            return new X509ChainWrapper(x509Chain, GetAdditionalContext);
        }

        protected static bool TryImportFromPemFile(string filePath, out X509Certificate2Collection certificates)
        {
            certificates = new X509Certificate2Collection();

            try
            {
                certificates.ImportFromPemFile(filePath);

                return true;
            }
            catch (Exception ex) when
            (
                ex is CryptographicException ||
                ex is FileNotFoundException ||
                ex is DirectoryNotFoundException
            )
            {
                certificates.Clear();
            }

            return false;
        }

        private ILogMessage? GetAdditionalContext(X509Chain chain)
        {
            if (chain is null)
            {
                throw new ArgumentNullException(nameof(chain));
            }

            ILogMessage? logMessage = null;
            int lastIndex = chain.ChainElements.Count - 1;

            if (lastIndex < 0)
            {
                return logMessage;
            }

            X509ChainElement root = chain.ChainElements[lastIndex];

            // If a certificate chain is untrusted simply the root certificate is untrusted, then create
            // an additional message explaining how one might resolve this lack of trust.
            if (root.ChainElementStatus.Any(status => status.Status.HasFlag(X509ChainStatusFlags.UntrustedRoot)) &&
                !Certificates.Contains(root.Certificate))
            {
                string subject = root.Certificate.Subject;
                string fingerprint = CertificateUtility.GetHashString(root.Certificate, Common.HashAlgorithmName.SHA256);
                string pem = GetPemEncodedCertificate(root.Certificate);
                string message;

                if (string.IsNullOrEmpty(FilePath))
                {
                    message = string.Format(
                        CultureInfo.CurrentCulture,
                        Strings.UntrustedRoot_WithoutCertificateBundle,
                        subject,
                        fingerprint,
                        pem);
                }
                else
                {
                    message = string.Format(
                        CultureInfo.CurrentCulture,
                        Strings.UntrustedRoot_WithCertificateBundle,
                        FilePath,
                        subject,
                        fingerprint,
                        pem);
                }

                logMessage = new LogMessage(LogLevel.Warning, message, NuGetLogCode.NU3042);
            }

            return logMessage;
        }

        private static string GetPemEncodedCertificate(X509Certificate2 certificate)
        {
            ReadOnlyMemory<char> pem = PemEncoding.Write("CERTIFICATE", certificate.RawData);

            return new string(pem.Span);
        }
    }
}

#endif