none
Check private key exportable when the user private key is stored with strong key protection RRS feed

  • Question

  • Hi, I hope to get some help on this topic. We are implementing s/mime certificates to be used in Outlook and on mobile devices. To import the cert on the mobile device the private key must have been stored as exportable during enrollment. Unfortunately many users forget this step and need to re-enroll. I need to run an inventory using a script (powershell distributed via SCCM running under user credentials) that will report users that have installed the certificate incorrectly.

    I am using the System.Security.CryptographyX509Certificate.X509Store 'My', 'CurrentUser' object and retrieve the CspContainerInfo.Exportable property of each installed certificate that has the HasPrivateKey property set.

    This works fine as long as the Certificates are installed without the strong key protection, but as soon as I try to read this CspContainerInfo.Exportable value on such a protected certificate I get the error:

    "exception getting exportable. The requested key container was not found".

    I understand that if I would really want to export the cert it would fail unless I would provide the key protection password, but now I just want to check if it is marked as exportable. If I use the Certmgr.msc GUI and start the Export process steps it also shows me the option "yes, export the private key" without prompting me for the password at that point so there must be a way to get to the same information using powershell and the correct .NET calls.

    Has anyone done this or can help me trying to achieve this?


    Best regards and many thanks in advance, Eric Vegter

    Tuesday, January 20, 2015 11:05 PM

Answers

  • Hello evegter,

    Thanks for your clarification about this issue, according that, I reproduced the issue as you described, it throws the key container is not found. After searching and debugging for this issue, I found below code and it seems to be by designed for such a file:

    public bool Protected {
    
                [System.Security.SecuritySafeCritical]  // auto-generated
    
                get {
    
                    // Assume hardware keys are protected.
    
                    if (this.HardwareDevice == true)
    
                        return true;
    
     
    
                    SafeProvHandle safeProvHandle = SafeProvHandle.InvalidHandle;
    
                    int hr = Utils._OpenCSP(m_parameters, Constants.CRYPT_SILENT, ref safeProvHandle);
    
                    if (hr != Constants.S_OK)
    
                        throw new CryptographicException(Environment.GetResourceString("Cryptography_CSP_NotFound"));
    
                    byte[] isProtected = (byte[]) Utils._GetProviderParameter(safeProvHandle, m_parameters.KeyNumber, Constants.CLR_PROTECTED);
    
                    safeProvHandle.Dispose();
    
                    return (isProtected[0] == 1);
    
                }
    
            }

    Referenced from:

    http://referencesource.microsoft.com/#mscorlib/system/security/cryptography/icspasymmetricalgorithm.cs,2466f94ff7b39bc5

    Since the file is protected with a password, when using it, a password is needed, however, in code, it seems there is not method could accept an argument as the password so the file is not accessible. One workaround is that since the .NET is open-sourced, you could modify it and write yourself logic that could run over the if (hr != Constants.S_OK). However, this might not be recommended, as I think the team has their considerations to design this exception for throwing an exception when accessing a protected file.

    Regards.


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.


    • Edited by Fred BaoModerator Friday, January 23, 2015 9:33 AM
    • Marked as answer by evegter Saturday, January 24, 2015 10:23 AM
    Friday, January 23, 2015 9:32 AM
    Moderator

All replies

  • Hello Vegter,

    >> so there must be a way to get to the same information using powershell and the correct .NET calls.

    I am not experienced with the powershell command, however, as I  made a test according to your description both with the certificates installed with/without the protected password, I could read the exportable property successfully, here is the test code:

    X509Store personalStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
    
                    personalStore.Open(OpenFlags.ReadOnly);
    
                    foreach (var r1 in personalStore.Certificates)
    
                    {
    
                        if (r1.HasPrivateKey)
    
                        {
    
                            var r2 = r1.PrivateKey as RSACryptoServiceProvider;
    
                            if (r2 != null)
    
                            {
    
                                CspKeyContainerInfo info = r2.CspKeyContainerInfo;
    
                                if (info.Exportable)
    
                                {
    
                                    Console.WriteLine("Key + " + r1.GetNameInfo(X509NameType.DnsName, true) + " is exportable.");
    
                                }
    
                            }
    
                            
    
                        }
    
                    }
    

    Please have a try, and if you are working with powershell script in .NET, I suggest that you could post it to power shell development forum as:

    https://social.technet.microsoft.com/Forums/windowsserver/en-US/home?forum=winserverpowershell

    Regards.


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Wednesday, January 21, 2015 5:51 AM
    Moderator
  • Hi Fred, thanks for your feedback.

    I created the following script out of it:

     
    [void][System.Reflection.Assembly]::LoadWithPartialName("System.Security")

    $MyCertStore = New-Object System.Security.Cryptography.X509Certificates.X509Store 'My', 'CurrentUser'
    $MyCertStore.Open('ReadOnly')
    ForEach ($r1 in $MyCertStore.Certificates) {
        if ($r1.HasPrivateKey) {
            $r1.Subject;
            $r2 = $r1.PrivateKey -as [System.Security.Cryptography.RSACryptoServiceProvider];
            if ($r2 -ne $null) {
                [system.Security.Cryptography.CspKeyContainerInfo]$info = $r2.CspKeyContainerInfo;
                $info;
            }
        }
    }

    This code runs fine on certificates that are installed without password protected private key access.

    However the output I get when the cert private key is protected with a password I get:
    MachineKeyStore        : False
    ProviderName           : Microsoft Enhanced Cryptographic Provider v1.0
    ProviderType           : 1
    KeyContainerName       : {0C23C375-7D18-4D85-A70E-8ED0D506B912}
    UniqueKeyContainerName :
    KeyNumber              : Exchange
    Exportable             :
    HardwareDevice         : False
    Removable              : False
    Accessible             : False ---> it cannot access the data due to the password protection?
    Protected              :
    CryptoKeySecurity      :
    RandomlyGenerated      : False

    Any Idea why this happens in my situation? I run the  code on Windows 7 US Enterprise Edition.


    Best regards and many thanks in advance, Eric Vegter

    Wednesday, January 21, 2015 10:37 AM
  • Hello,

    >> I created the following script out of it:

    It seems that you are using a script to process it, I am not experienced with it, is it a power shell script, do you have a try with my pure .NET code to check if this is because the assembly you called cannot allow us to access a protection certificate or caused by the script? If the .NET code could work, then I am wondering this might be caused by the script, and for issues regarding scripts, I suggest that you could post it to the forum I mentioned above.

    Regards.


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.


    Thursday, January 22, 2015 7:11 AM
    Moderator
  • Hello Fred Bao,

    I actually did compile your code using a C# console project and implemented the routine in a try/catch. I added some error messages etc and then ran the EXE from a commandprompt. I get the exact same error as running the powershell code, and I tried running it in a regular and elevated commanpdrompt, as well as with UAC turned of. Still no luck so it does not matter whether I choose the C# code or the powershell version.

    Just to make sure we're talking about exactly the same kind of certificate import: I import a PFX with private key into my current user store and during import I choose all 3 options ('strong private key protection', 'mark as exportable' and 'include all properties') and at the end of the import process I provide a password to protect the private key usage.

    I import another PFX without the strong protection option to be sure I get 2 results.

    The unprotected one reports the privatekey.exportable option without a problem, but the protected one traps and in the exception there is the message "exception getting exportable. The requested key container was not found".

    I really wonder what is happening under the hood when you run the manual export process from the CERTMGR.MSC utility because that GUI does show me the 'yes, export the private key' option without any problem. Could it be that the GUI actually accesses the corresponding SYS file in the personal APPDATA Crypto folder? Just a thought...

    Thanks for your time!


    Best regards and many thanks in advance, Eric Vegter

    Thursday, January 22, 2015 7:52 AM
  • Hello evegter,

    Thanks for your clarification about this issue, according that, I reproduced the issue as you described, it throws the key container is not found. After searching and debugging for this issue, I found below code and it seems to be by designed for such a file:

    public bool Protected {
    
                [System.Security.SecuritySafeCritical]  // auto-generated
    
                get {
    
                    // Assume hardware keys are protected.
    
                    if (this.HardwareDevice == true)
    
                        return true;
    
     
    
                    SafeProvHandle safeProvHandle = SafeProvHandle.InvalidHandle;
    
                    int hr = Utils._OpenCSP(m_parameters, Constants.CRYPT_SILENT, ref safeProvHandle);
    
                    if (hr != Constants.S_OK)
    
                        throw new CryptographicException(Environment.GetResourceString("Cryptography_CSP_NotFound"));
    
                    byte[] isProtected = (byte[]) Utils._GetProviderParameter(safeProvHandle, m_parameters.KeyNumber, Constants.CLR_PROTECTED);
    
                    safeProvHandle.Dispose();
    
                    return (isProtected[0] == 1);
    
                }
    
            }

    Referenced from:

    http://referencesource.microsoft.com/#mscorlib/system/security/cryptography/icspasymmetricalgorithm.cs,2466f94ff7b39bc5

    Since the file is protected with a password, when using it, a password is needed, however, in code, it seems there is not method could accept an argument as the password so the file is not accessible. One workaround is that since the .NET is open-sourced, you could modify it and write yourself logic that could run over the if (hr != Constants.S_OK). However, this might not be recommended, as I think the team has their considerations to design this exception for throwing an exception when accessing a protected file.

    Regards.


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.


    • Edited by Fred BaoModerator Friday, January 23, 2015 9:33 AM
    • Marked as answer by evegter Saturday, January 24, 2015 10:23 AM
    Friday, January 23, 2015 9:32 AM
    Moderator
  • Hi Fred Bao,

    thanks for finding the cause for the issue. Of course it's no really the answer I was hoping to get ;)

    Modifying/testing/deploying parts of the .Net code does stretch a bit further than I feel comfortable with, but I'll definitely take a look at it for educational purposes and see where it takes me.

    Maybe this particular case could be looked at by the MS .Net library developers and enable listing some basic container-properties that do not need access to the private key itself?


    Best regards and many thanks in advance, Eric Vegter

    Saturday, January 24, 2015 10:30 AM