File: Configuration\CertificateManager.cs
Project: ndp\cdf\src\WCF\Tools\WsatConfig\WsatConfig.csproj (WsatConfig)
//------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------------------------
 
namespace Microsoft.Tools.ServiceModel.WsatConfig
{
    using Microsoft.Win32;
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Runtime.InteropServices;
    using System.Security;
    using System.Security.Permissions;
    using System.Security.Cryptography.X509Certificates;
    using System.Security.Cryptography;
 
    static class CertificateManager
    {
        static string certificateStore = @"Software\Microsoft\SystemCertificates\My";
 
        // Throw if cannot open a valid store handle
        [SecurityCritical]
        internal static SafeCertificateStore GetCertificateStorePointer(string machineName)
        {
            SafeCertificateStore storeHandle;
 
            RegistryExceptionHelper registryExceptionHelper = new RegistryExceptionHelper(machineName, RegistryHive.LocalMachine, certificateStore);
 
            if (Utilities.IsLocalMachineName(machineName))
            {
                SafeRegistryKey hive = new SafeRegistryKey(new IntPtr((int)Microsoft.Win32.RegistryHive.LocalMachine), false);
                SafeRegistryKey regKey = null;
 
                try
                {
                    int ret = SafeNativeMethods.RegOpenKeyEx(
                        hive,
                        certificateStore,
                        0,
                        SafeNativeMethods.KEY_READ,
                        out regKey);
                    if (ret != SafeNativeMethods.ERROR_SUCCESS)
                    {
                        throw registryExceptionHelper.CreateRegistryAccessException(ret);
                    }
 
                    storeHandle = SafeNativeMethods.CertOpenStore_ptr(
                            SafeNativeMethods.CERT_STORE_PROV_REG,
                            0,
                            0,
                            SafeNativeMethods.CERT_STORE_OPEN_EXISTING_FLAG |
                            SafeNativeMethods.CERT_STORE_READONLY_FLAG,
                            regKey);
                    if (storeHandle.IsInvalid)
                    {
                        throw new WsatAdminException(WsatAdminErrorCode.CERT_STORE_ACCESS, SR.GetString(SR.ErrorAccessCertStore, Marshal.GetLastWin32Error()));
                    }
                    return storeHandle;
                }
                finally
                {
                    if (regKey != null)
                    {
                        regKey.Close();
                    }
                    hive.Close();
                }
            }
#if WSAT_UI
            else
            {
                SafeRegistryKey remoteBase = null;
                SafeRegistryKey finalKey = null;
 
                try
                {
                    int ret = SafeNativeMethods.RegConnectRegistry(
                        machineName,
                        new SafeRegistryKey(new IntPtr((int)Microsoft.Win32.RegistryHive.LocalMachine), false),
                        out remoteBase);
                    if (ret != SafeNativeMethods.ERROR_SUCCESS)
                    {
                        throw registryExceptionHelper.CreateRegistryAccessException(ret);
                    }
 
                    ret = SafeNativeMethods.RegOpenKeyEx(
                        remoteBase,
                        certificateStore,
                        0,
                        SafeNativeMethods.KEY_READ,
                        out finalKey);
                    if (ret != SafeNativeMethods.ERROR_SUCCESS)
                    {
                        throw registryExceptionHelper.CreateRegistryAccessException(ret);
                    }
 
                    storeHandle = SafeNativeMethods.CertOpenStore_ptr(
                                                    SafeNativeMethods.CERT_STORE_PROV_REG,
                                                    0, 0,
                                                    SafeNativeMethods.CERT_REGISTRY_STORE_REMOTE_FLAG |
                                                    SafeNativeMethods.CERT_STORE_READONLY_FLAG |
                                                    SafeNativeMethods.CERT_STORE_OPEN_EXISTING_FLAG,
                                                    finalKey);
                    if (storeHandle.IsInvalid)
                    {
                        throw new WsatAdminException(WsatAdminErrorCode.CERT_STORE_ACCESS, SR.GetString(SR.ErrorAccessCertStore, Marshal.GetLastWin32Error()));
                    }
                    return storeHandle;                    
                }
                finally
                {
                    if (remoteBase != null)
                    {
                        remoteBase.Close();
                    }
                    if (finalKey != null)
                    {
                        finalKey.Close();
                    }
                }
            }
#else
            else
            {
                throw new WsatAdminException(WsatAdminErrorCode.CERT_STORE_ACCESS, SR.GetString(SR.ErrorAccessCertStore, 0));
            }
#endif
        }
 
#if WSAT_CMDLINE
        // Input: "Issuer\SubjectName". Issuer and SubjectName can be wildcard character '*'
        // This one is not required to support remote machine operation so we could rely on the NDP certificate API
        [SecurityCritical]
        internal static X509Certificate2 GetCertificateFromIssuerAndSubjectName(string constraint)
        {
            if (string.IsNullOrEmpty(constraint) || constraint.Length < 3)
            {
                return null;
            }
 
            int separatorIndex = constraint.IndexOf('\\');
            if (separatorIndex <= 0)   // issuer can't be empty but can be '*'
            {
                return null;
            }
 
            string issuer, subjectName;
            issuer = constraint.Substring(0, separatorIndex);            
            subjectName = constraint.Substring(separatorIndex + 1, constraint.Length - separatorIndex - 1);
            if (Utilities.SafeCompare(subjectName, "{EMPTY}"))
            {
                subjectName = string.Empty;
            }
 
            X509Store store = null;
            try
            {
                store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
                store.Open(OpenFlags.ReadOnly);
                X509Certificate2Collection certs = store.Certificates;
                if (certs == null || certs.Count == 0)
                {
                    return null;
                }
 
                if (!Utilities.SafeCompare(issuer, "*"))
                {
                    certs = certs.Find(X509FindType.FindByIssuerDistinguishedName, issuer, false);
                }
                if (certs.Count > 0 && !Utilities.SafeCompare(subjectName, "*"))
                {
                    certs = certs.Find(X509FindType.FindBySubjectDistinguishedName, subjectName, false);
                }
 
                if (certs.Count == 1)
                {
                    return certs[0];
                }
            }
            catch (ArgumentException)
            {
                // does nothing
            }
            catch (SecurityException)
            {
                // does nothing
            }
            catch (CryptographicException)
            {
                // does nothing
            }
            finally
            {
                if (store != null)
                {
                    store.Close();
                }
            }
 
            return null;
        }
#endif
 
        [SecurityCritical]
        internal static X509Certificate2 GetCertificateFromThumbprint(string thumbprint, string machineName)
        {
            if (String.IsNullOrEmpty(thumbprint))
            {
                return null;
            }
 
            X509Certificate2 cert = null;
            SafeCertificateStore storeHandle = CertificateManager.GetCertificateStorePointer(machineName);
            SafeCertificateContext prev = new SafeCertificateContext();
            SafeCertificateContext current = new SafeCertificateContext();
 
            bool foundThumbprint = false;
            do
            {
                // the CertFindCertificateInStore function frees the SafeHandleCertificateContext
                // referenced by non-null values of "prev"
#pragma warning suppress 56523
                current = SafeNativeMethods.CertFindCertificateInStore(
                    storeHandle,
                    SafeNativeMethods.X509_ASN_ENCODING,
                    0,
                    SafeNativeMethods.CERT_FIND_ANY,
                    IntPtr.Zero,
                    prev);
 
                prev = current;
                if (!current.IsInvalid)
                {
                    cert = current.GetNewX509Certificate();
                    if (Utilities.SafeCompare(cert.Thumbprint, thumbprint))
                    {
                        foundThumbprint = true;
                    }
                }
            } while (!current.IsInvalid && !foundThumbprint);
 
            storeHandle.Close();
            prev.Close();
 
            if (!current.IsInvalid)
            {
                current.Close();
                return cert;
            }
            else
            {
                return null;
            }
        }
 
#if WSAT_CMDLINE
        internal static X509Certificate2 GetMachineIdentityCertificate()
        {
            X509Store store = null;
            try
            {
                store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
                store.Open(OpenFlags.ReadOnly);
                string hostName = System.Net.Dns.GetHostEntry(string.Empty).HostName;
                X509Certificate2 result = null;
                int count = 0;
 
                // The best way to find the Issued-to-machine certificate is to walk through each one and compare the DnsName                
                foreach (X509Certificate2 cert in store.Certificates)
                {                    
                    if (Utilities.SafeCompare(cert.GetNameInfo(X509NameType.DnsName, false), hostName))
                    {
                        try
                        {
                            WsatConfiguration.ValidateIdentityCertificateThrow(cert, false);
                            if (++count > 1)
                            {
                                break;
                            }
                            result = cert;
                        }
                        catch (WsatAdminException)
                        {
                            // Explicitly ignore
                        }
                    }
                }
 
                // We only use the cert if we found one and only one
                return (count == 1) ? result : null;
            }
            catch (ArgumentException)
            {
                return null;
            }
            catch (SecurityException)
            {
                return null;
            }
            catch (CryptographicException)
            {
                return null;
            }
            finally
            {
                if (store != null)
                {
                    store.Close();
                }
            }
        }
#endif
    }
}