File: Settings\Items\StoreClientCertItem.cs
Web Access
Project: src\src\nuget-client\src\NuGet.Core\NuGet.Configuration\NuGet.Configuration.csproj (NuGet.Configuration)
// 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.Globalization;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Xml.Linq;

namespace NuGet.Configuration
{
    /// <summary>
    ///     A StoreClientCertItem have 4 Attributes:
    ///     - [Required] packageSource
    ///     - [Optional] storeLocation. StoreLocation.CurrentUser by default.
    ///     - [Optional] storeName. StoreName.My by default.
    ///     - [Optional] findBy. X509FindType.FindByThumbprint by default.
    ///     - [Required] findValue
    /// </summary>
    public sealed class StoreClientCertItem : ClientCertItem
    {
        private const X509FindType DefaultFindBy = X509FindType.FindByThumbprint;
        private const StoreLocation DefaultStoreLocation = StoreLocation.CurrentUser;
        private const StoreName DefaultStoreName = StoreName.My;

        public static string GetString(X509FindType type)
        {
            return type.ToString().Replace("FindBy", string.Empty);
        }

        public static string GetString(StoreName storeName)
        {
            return storeName.ToString();
        }

        public static string GetString(StoreLocation storeLocation)
        {
            return storeLocation.ToString();
        }

        public StoreClientCertItem(string packageSource,
                                   string findValue,
                                   StoreLocation? storeLocation = null,
                                   StoreName? storeName = null,
                                   X509FindType? findBy = null)
            : base(packageSource)
        {
            if (!storeLocation.HasValue)
            {
                storeLocation = DefaultStoreLocation;
            }

            if (!storeName.HasValue)
            {
                storeName = DefaultStoreName;
            }

            if (!findBy.HasValue)
            {
                findBy = DefaultFindBy;
            }

            if (string.IsNullOrWhiteSpace(findValue))
            {
                throw new ArgumentException(Resources.Argument_Cannot_Be_Null_Or_Empty, nameof(findValue));
            }

            Update(findValue, storeLocation, storeName, findBy);
        }

        internal StoreClientCertItem(XElement element, SettingsFile origin)
            : base(element, origin)
        {
            var storeLocation = element.Attribute(XName.Get(ConfigurationConstants.StoreLocationAttribute))?.Value;
            if (string.IsNullOrWhiteSpace(storeLocation))
            {
                storeLocation = DefaultStoreLocation.ToString();
            }

            AddAttribute(ConfigurationConstants.StoreLocationAttribute, storeLocation!);

            var storeName = element.Attribute(XName.Get(ConfigurationConstants.StoreNameAttribute))?.Value;
            if (string.IsNullOrWhiteSpace(storeName))
            {
                storeName = DefaultStoreName.ToString();
            }

            AddAttribute(ConfigurationConstants.StoreNameAttribute, storeName!);

            var findBy = element.Attribute(XName.Get(ConfigurationConstants.FindByAttribute))?.Value;
            if (string.IsNullOrWhiteSpace(findBy))
            {
                findBy = GetString(DefaultFindBy);
            }

            AddAttribute(ConfigurationConstants.FindByAttribute, findBy!);

            var findValue = element.Attribute(XName.Get(ConfigurationConstants.FindValueAttribute))?.Value;
            if (string.IsNullOrWhiteSpace(findValue))
            {
                throw new ArgumentException(Resources.Argument_Cannot_Be_Null_Or_Empty);
            }

            AddAttribute(ConfigurationConstants.FindValueAttribute, findValue!);
        }

        public override string ElementName => ConfigurationConstants.StoreCertificate;

        public X509FindType FindType
        {
            get
            {
                if (Enum.TryParse("FindBy" + Attributes[ConfigurationConstants.FindByAttribute], ignoreCase: true, result: out X509FindType result))
                {
                    return result;
                }

                return DefaultFindBy;
            }
        }

        public string FindValue => Attributes[ConfigurationConstants.FindValueAttribute];

        public StoreLocation StoreLocation
        {
            get
            {
                if (Enum.TryParse(Attributes[ConfigurationConstants.StoreLocationAttribute], ignoreCase: true, result: out StoreLocation result))
                {
                    return result;
                }

                return DefaultStoreLocation;
            }
        }

        public StoreName StoreName
        {
            get
            {
                if (Enum.TryParse(Attributes[ConfigurationConstants.StoreNameAttribute], ignoreCase: true, result: out StoreName result))
                {
                    return result;
                }

                return DefaultStoreName;
            }
        }

        protected override IReadOnlyCollection<string> AllowedAttributes { get; }
            = new HashSet<string>(new[]
                {
                    ConfigurationConstants.PackageSourceAttribute,
                    ConfigurationConstants.StoreLocationAttribute,
                    ConfigurationConstants.StoreNameAttribute,
                    ConfigurationConstants.FindByAttribute,
                    ConfigurationConstants.FindValueAttribute
                });

        protected override IReadOnlyDictionary<string, IReadOnlyCollection<string>> AllowedValues { get; } = new Dictionary<string, IReadOnlyCollection<string>>
        {
            {
                ConfigurationConstants.StoreLocationAttribute,
                new HashSet<string>(new[]
                {
                    GetString(StoreLocation.CurrentUser),
                    GetString(StoreLocation.LocalMachine)
                },
                    StringComparer.OrdinalIgnoreCase)
            },
            {
                ConfigurationConstants.StoreNameAttribute,
                new HashSet<string>(new []
                {
                    GetString(StoreName.AddressBook),
                    GetString(StoreName.AuthRoot),
                    GetString(StoreName.CertificateAuthority),
                    GetString(StoreName.Disallowed),
                    GetString(StoreName.My),
                    GetString(StoreName.Root),
                    GetString(StoreName.TrustedPeople),
                    GetString(StoreName.TrustedPublisher)
                },
                    StringComparer.OrdinalIgnoreCase)
            },
            {
                ConfigurationConstants.FindByAttribute,
                new HashSet<string>(new[]
                {
                    GetString(X509FindType.FindByThumbprint),
                    GetString(X509FindType.FindBySubjectName),
                    GetString(X509FindType.FindBySubjectDistinguishedName),
                    GetString(X509FindType.FindByIssuerName),
                    GetString(X509FindType.FindByIssuerDistinguishedName),
                    GetString(X509FindType.FindBySerialNumber),
                    GetString(X509FindType.FindByTimeValid),
                    GetString(X509FindType.FindByTimeNotYetValid),
                    GetString(X509FindType.FindByTimeExpired),
                    GetString(X509FindType.FindByTemplateName),
                    GetString(X509FindType.FindByApplicationPolicy),
                    GetString(X509FindType.FindByCertificatePolicy),
                    GetString(X509FindType.FindByExtension),
                    GetString(X509FindType.FindByKeyUsage),
                    GetString(X509FindType.FindBySubjectKeyIdentifier)
                },
                    StringComparer.OrdinalIgnoreCase)
            }
        };

        protected override IReadOnlyCollection<string> RequiredAttributes
        { get; }
        = new HashSet<string>(new[] { ConfigurationConstants.PackageSourceAttribute, ConfigurationConstants.FindValueAttribute });

        internal override XNode AsXNode()
        {
            if (Node is XElement)
            {
                return Node;
            }

            var element = new XElement(ElementName);

            foreach (KeyValuePair<string, string> attr in Attributes)
            {
                element.SetAttributeValue(attr.Key, attr.Value);
            }

            return element;
        }

        public override SettingBase Clone()
        {
            return new StoreClientCertItem(PackageSource, FindValue, StoreLocation, StoreName, FindType);
        }

        public override IEnumerable<X509Certificate> Search()
        {
            var store = new X509Store(StoreName, StoreLocation);
            try
            {
                store.Open(OpenFlags.ReadOnly);
                X509Certificate2Collection foundCertificates = store.Certificates.Find(FindType, FindValue, false);
                if (foundCertificates.Count == 0)
                {
                    throw new NuGetConfigurationException(string.Format(CultureInfo.CurrentCulture,
                                                                        Resources.Error_StoreCertCertificateNotFound,
                                                                        PackageSource,
                                                                        GetString(StoreLocation),
                                                                        GetString(StoreName),
                                                                        GetString(FindType),
                                                                        FindValue));
                }

                return foundCertificates.OfType<X509Certificate>();
            }
            finally
            {
                store.Close();
            }
        }

        public void Update(string? findValue,
                           StoreLocation? storeLocation = null,
                           StoreName? storeName = null,
                           X509FindType? findBy = null)
        {
            if (storeLocation.HasValue)
            {
                AddOrUpdateAttribute(ConfigurationConstants.StoreLocationAttribute, storeLocation.Value.ToString());
            }

            if (storeName.HasValue)
            {
                AddOrUpdateAttribute(ConfigurationConstants.StoreNameAttribute, storeName.Value.ToString());
            }

            if (findBy.HasValue)
            {
                AddOrUpdateAttribute(ConfigurationConstants.FindByAttribute, GetString(findBy.Value));
            }

            if (!string.IsNullOrWhiteSpace(findValue))
            {
                AddOrUpdateAttribute(ConfigurationConstants.FindValueAttribute, findValue);
            }
        }
    }
}