File: System\Security\Cryptography\X509Certificates\RSACertificateExtensions.cs
Project: ndp\fx\src\Core\System.Core.csproj (System.Core)
// Copyright (c) Microsoft Corporation.  All rights reserved.
 
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
 
namespace System.Security.Cryptography.X509Certificates
{
    /// <summary>
    /// Provides extension methods for retrieving <see cref="RSA" /> implementations for the
    /// public and private keys of a <see cref="X509Certificate2" />.
    /// </summary>
    public static class RSACertificateExtensions
    {
        /// <summary>
        /// Gets the <see cref="RSA" /> public key from the certificate or null if the certificate does not have an RSA public key.
        /// </summary>
        [SecuritySafeCritical]
        public static RSA GetRSAPublicKey(this X509Certificate2 certificate)
        {
            if (certificate == null)
            {
                throw new ArgumentNullException("certificate");
            }
 
            if (!IsRSA(certificate))
            {
                return null;
            }
 
            PublicKey publicKey = certificate.PublicKey;
            AsnEncodedData asn = publicKey.EncodedKeyValue;
            IntPtr structType = new IntPtr(CapiNative.CNG_RSA_PUBLIC_KEY_BLOB);
 
            SafeLocalAllocHandle cngBlobHandle;
            uint cngBlobLength;
            bool result = CapiNative.DecodeObject(structType, asn.RawData, out cngBlobHandle, out cngBlobLength);
 
            if (!result)
            {
                throw new CryptographicException(Marshal.GetLastWin32Error());
            }
 
            byte[] cngBlob = new byte[cngBlobLength];
            using (cngBlobHandle)
            {
                Marshal.Copy(cngBlobHandle.DangerousGetHandle(), cngBlob, 0, cngBlob.Length);
            }
 
            CngKey key = CngKey.Import(cngBlob, CngKeyBlobFormat.GenericPublicBlob);
            return new RSACng(key);
        }
 
        /// <summary>
        /// Gets the <see cref="RSA" /> private key from the certificate or null if the certificate does not have an RSA private key.
        /// </summary>
        [SecuritySafeCritical]
        public static RSA GetRSAPrivateKey(this X509Certificate2 certificate)
        {
            if (certificate == null)
            {
                throw new ArgumentNullException("certificate");
            }
 
            if (!certificate.HasPrivateKey || !IsRSA(certificate))
            {
                return null;
            }
 
            CngKeyHandleOpenOptions openOptions;
 
            using (SafeCertContextHandle certificateContext = X509Native.GetCertificateContext(certificate))
            using (SafeNCryptKeyHandle privateKeyHandle = X509Native.TryAcquireCngPrivateKey(certificateContext, out openOptions))
            {
                if (privateKeyHandle == null)
                {
                    if (LocalAppContextSwitches.DontReliablyClonePrivateKey)
                        return (RSA)certificate.PrivateKey;
 
                    // fall back to CAPI if we cannot acquire the key using CNG.
                    RSACryptoServiceProvider rsaCsp = (RSACryptoServiceProvider)certificate.PrivateKey;
                    CspParameters cspParameters = DSACertificateExtensions.CopyCspParameters(rsaCsp);
                    RSACryptoServiceProvider clone = new RSACryptoServiceProvider(cspParameters);
                    return clone;
                }
 
                CngKey key = CngKey.Open(privateKeyHandle, openOptions);
                return new RSACng(key);
            }
        }
 
        [SecuritySafeCritical]
        public static X509Certificate2 CopyWithPrivateKey(this X509Certificate2 certificate, RSA privateKey)
        {
            if (certificate == null)
                throw new ArgumentNullException(nameof(certificate));
            if (privateKey == null)
                throw new ArgumentNullException(nameof(privateKey));
 
            if (certificate.HasPrivateKey)
                throw new InvalidOperationException(SR.GetString(SR.Cryptography_Cert_AlreadyHasPrivateKey));
 
            using (RSA publicKey = GetRSAPublicKey(certificate))
            {
                if (publicKey == null)
                    throw new ArgumentException(SR.GetString(SR.Cryptography_PrivateKey_WrongAlgorithm));
 
                RSAParameters currentParameters = publicKey.ExportParameters(false);
                RSAParameters newParameters = privateKey.ExportParameters(false);
 
                if (!currentParameters.Modulus.SequenceEqual(newParameters.Modulus) ||
                    !currentParameters.Exponent.SequenceEqual(newParameters.Exponent))
                {
                    throw new ArgumentException(SR.GetString(SR.Cryptography_PrivateKey_DoesNotMatch), nameof(privateKey));
                }
            }
 
            RSACng rsaCng = privateKey as RSACng;
            X509Certificate2 newCert = null;
 
            if (rsaCng != null)
            {
                newCert = CertificateExtensionsCommon.CopyWithPersistedCngKey(certificate, rsaCng.Key);
            }
 
            if (newCert == null)
            {
                RSACryptoServiceProvider rsaCsp = privateKey as RSACryptoServiceProvider;
 
                if (rsaCsp != null)
                {
                    newCert = CertificateExtensionsCommon.CopyWithPersistedCapiKey(certificate, rsaCsp.CspKeyContainerInfo);
                }
            }
 
            if (newCert == null)
            {
                RSAParameters parameters = privateKey.ExportParameters(true);
 
                using (PinAndClear.Track(parameters.D))
                using (PinAndClear.Track(parameters.P))
                using (PinAndClear.Track(parameters.Q))
                using (PinAndClear.Track(parameters.DP))
                using (PinAndClear.Track(parameters.DQ))
                using (PinAndClear.Track(parameters.InverseQ))
                using (rsaCng = new RSACng())
                {
                    rsaCng.ImportParameters(parameters);
 
                    newCert = CertificateExtensionsCommon.CopyWithEphemeralCngKey(certificate, rsaCng.Key);
                }
            }
 
            Debug.Assert(newCert != null);
            Debug.Assert(!ReferenceEquals(certificate, newCert));
            Debug.Assert(!certificate.HasPrivateKey);
            Debug.Assert(newCert.HasPrivateKey);
            return newCert;
        }
 
        private static bool IsRSA(X509Certificate2 certificate)
        {
            uint algorithmId = OidToAlgorithmId(certificate.PublicKey.Oid);
 
            switch (algorithmId)
            {
                case CapiNative.CALG_RSA_SIGN:
                case CapiNative.CALG_RSA_KEYX:
                    return true;
                default:
                    return false;
            }
        }
 
        private static uint OidToAlgorithmId(Oid oid)
        {
            using (SafeLocalAllocHandle oidHandle = X509Utils.StringToAnsiPtr(oid.Value))
            {
                CapiNative.CRYPT_OID_INFO oidInfo = CapiNative.CryptFindOIDInfo(CapiNative.CRYPT_OID_INFO_OID_KEY, oidHandle, 0);
                return oidInfo.Algid;
            }
        }     
    }
}