81
//cert is an EF Entity and 
//    cert.CertificatePKCS12 is a byte[] with the certificate.

var certificate = new X509Certificate(cert.CertificatePKCS12, "SomePassword");

When loading a certificate from our database, on our staging server (Windows 2008 R2/IIS7.5) we get this exception:

System.Security.Cryptography.CryptographicException: An internal error occurred.

   at System.Security.Cryptography.CryptographicException.ThrowCryptographicException(Int32 hr)
   at System.Security.Cryptography.X509Certificates.X509Utils._LoadCertFromBlob(Byte[] rawData, IntPtr password, UInt32 dwFlags, Boolean persistKeySet, SafeCertContextHandle& pCertCtx)
   at System.Security.Cryptography.X509Certificates.X509Certificate.LoadCertificateFromBlob(Byte[] rawData, Object password, X509KeyStorageFlags keyStorageFlags)

NOTE: This issue does not happen locally (Windows 7/Casini).

Any insight is greatly appreciated.

Sam Axe
  • 33,313
  • 9
  • 55
  • 89
lukiffer
  • 11,025
  • 8
  • 46
  • 70
  • Check out http://stackoverflow.com/questions/1345262/an-internal-error-occurred-when-loading-pfx-file-with-x509certificate2 and http://stackoverflow.com/questions/6097380/asp-net-mvc-an-internal-error-occurred-when-loading-certificate-bytes-with-x – alan Apr 02 '12 at 21:43
  • 1
    I suppose that the source of the problem is in the `byte[]` *data* which in `cert.CertificatePKCS12`. Without having the data one can only guess about the reason of the exception "An internal error occurred". So my suggestion is that you create the test certificate which can be used in your environment to reproduce the problem, save it in the file and provide the link and the password (like "SomePassword") for decoding of the certificate. After examining *the data* one will have much more chances to find the reson and to suggest a solution of your problem. – Oleg Apr 04 '12 at 15:25
  • Thanks for the response @Oleg - if the byte array were bad, wouldn't it error both on Win7 and Win2k8? When the byte array is written to a file it imports correctly. – lukiffer Apr 04 '12 at 15:46
  • @lukiffer: I mean not an error, but combination some properties of the certificate, the key and so on. So one have to *analyse* the problem. To be able to reproduce the results or analyse it one need have the PFX file which you use as the array of bytes `cert.CertificatePKCS12`. – Oleg Apr 04 '12 at 16:27
  • @oleg - what properties would cause it to fail on one OS and not another? For obvious security reasons, we cannot release the certificates themselves. – lukiffer Apr 05 '12 at 15:30
  • @lukiffer: To understand what I mean I can compare your question with the following: "My program works on my development computer, but don't work on my server. What line of my code or which API which I use could be the reason?" Like you can write a lot of different programs one can create a lot of different certificates and keys. Without having the code of your program (from my last example) or without having **demo** (not productive) certificate one can only guess so one can't really solve *your* problem. Try to create *demo* certificate which can be used to reproduce the problem. – Oleg Apr 06 '12 at 11:27
  • @lukiffer: Posting the certificate without the private key (like exporting in .CER file) is absolutely safe. So you can do post your productive certificate as CER (not as PFX). The information can reduce a little too common problem which you describe. – Oleg Apr 06 '12 at 11:31

9 Answers9

140

Turns out there's a setting in the IIS Application Pool configuration (Application Pools > Advanced Settings) to load the user profile for the application pool identity user. When set to false, the key containers aren't accessible.

So just set Load User Profile option as True

App Pool-> Advanced Settings Screen

mkb
  • 1,106
  • 1
  • 18
  • 21
lukiffer
  • 11,025
  • 8
  • 46
  • 70
  • Just a note that this is for IIS7; in IIS6, I don't think there is an option to load the user profile. – Randy Levy Jan 24 '13 at 02:34
  • 2
    I'm attempting to do this on Azure and I don't know that I can change the "Load User Profile" option. Any suggestions... I'm still fairly new to the world of certs... – Danimal111 Feb 06 '17 at 20:59
  • 3
    @DanB for Azure add the application setting: WEBSITE_LOAD_USER_PROFILE=1 – emp Jun 25 '20 at 14:31
  • Setting Load User Profile to True only works on user accounts but not for ApplicationPoolIdentity and NetworkService. – Thabiso Mofokeng Jan 22 '21 at 08:47
  • 2
    I don't konw who you are but thanks!!!! it's 3:40 AM here and you can imagine the rest... – Frijey Labs Aug 16 '22 at 07:40
68

More than likely, when you are running from Visual Studio/Cassini, it is accessing your user certificate store, even though you're loading it from bytes. Could you please try this and see if it solves your issue:

var certificate = new X509Certificate(
    cert.CertificatePKCS12, "SomePassword", X509KeyStorageFlags.MachineKeySet);

This will cause IIS (which runs as the ASP.NET user which likely doesn't have access to a user store) to use the Machine store.

This page explains the constructor in more detail, and this page explains the X509KeyStorageFlags enumeration.

Edit: Based on the second link from cyphr, it looks like it might be a good idea (if the previous solution doesn't work), to combine some of the FlagsAttribute enumeration values like so:

var certificate = new X509Certificate(
    cert.CertificatePKCS12, "SomePassword",
    X509KeyStorageFlags.MachineKeySet
    | X509KeyStorageFlags.PersistKeySet
    | X509KeyStorageFlags.Exportable);

Additionally, if you have access, you may want to try changing your Application Pool setting to use LocalService (and then restart the AppPool). This may elevate your permissions to an appropriate level if that is the problem.

Finally, you can use File.WriteAllBytes to write out the CertificatePKCS12 contents to a pfx file and see if you can manually import it using the certificate console under MMC (you can delete after successful import; this is just to test). It could be that your data is getting munged, or the password is incorrect.

Community
  • 1
  • 1
Chris Benard
  • 3,167
  • 2
  • 29
  • 35
  • Thanks for the response, though it's still throwing the same exception after adding just the `X509KeyStorageFlags.MachineKeySet` flag, as well as when adding all three `MachineKeySet`, `PersistKeySet` and `Exportable` flags. – lukiffer Apr 02 '12 at 22:22
  • 1
    Can you change your AppPool's identity to LocalService? – Chris Benard Apr 02 '12 at 22:26
  • Also, use [`File.WriteAllBytes`](http://msdn.microsoft.com/en-us/library/system.io.file.writeallbytes.aspx) to write out the `CertificatePKCS12` contents to a pfx file and see if you can manually import it using the certificate console under MMC (you can delete after successful import; this is just to test). It could be that your data is getting munged, or the password is incorrect. – Chris Benard Apr 02 '12 at 22:29
  • For compliance reasons, our AppPool identity is an Active Directory account with a specific permission set. The application and the AppPool both use this account and the user account has a keystore on all members of the web farm. – lukiffer Apr 02 '12 at 22:30
  • 1
    OK. I updated the answer to include those in case any of those work. I'm trying to earn the +50. :) – Chris Benard Apr 02 '12 at 22:32
  • If none of that works, you may be SOL, since all the people over in [this discussion thread](http://code.google.com/p/apns-sharp/issues/detail?id=47) solved their issue only by elevating to LocalService, NetworkService, or one of the others. :( – Chris Benard Apr 02 '12 at 22:40
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/9618/discussion-between-lukiffer-and-chris-benard) – lukiffer Apr 02 '12 at 22:55
  • 1
    Note that if you plan on calling `SignTool.exe` from your ASP.NET project, you'll still need to enable 'LoadUserProfile' for the `Application Pool`. Otherwise `SignTool.exe` will fail with `An internal error occurred` error... – c00000fd Oct 08 '13 at 06:40
  • Also for .NET Core devs who aren't using Windows - this will creep up as an issue when trying to work cross-platform. OS X seems to handle opening it just fine without those flags for example, even if loading the cert from another location. So you'll want to include these flags to cover the Windows-specific cases too! – pseudoramble Sep 05 '19 at 16:59
34

Use this code:

certificate = new X509Certificate2(System.IO.File.ReadAllBytes(p12File)
                                   , p12FilePassword
                                   , X509KeyStorageFlags.MachineKeySet |
                                     X509KeyStorageFlags.PersistKeySet | 
                                     X509KeyStorageFlags.Exportable);
Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
Francisco
  • 341
  • 3
  • 2
9

I had trouble on Windows 2012 Server R2 where my application could not load certificates for a PFX on disk. Things would work fine running my app as admin, and the exception said Access Denied so it had to be a permissions issue. I tried some of the above advice, but I still had the problem. I found that specifying the following flags as the third parameter of the cert constructor did the trick for me:

 X509KeyStorageFlags.UserKeySet | 
 X509KeyStorageFlags.PersistKeySet | 
 X509KeyStorageFlags.Exportable
Michael Balloni
  • 339
  • 2
  • 4
  • 9
2

To be able really solve your problem and not just guess, what can it be, one need be able to reproduce your problem. If you can't provide test PFX file which have the same problem you have to examine the problem yourself. The first important question is: are the origin of the exception "An internal error occurred" in the private key part of the PKCS12 or in the public part of the certificate itself?

So I would recommend you to try to repeat the same experiment with the same certificate, exported without private key (like .CER file):

var certificate = new X509Certificate(cert.CertificateCER);

or

var certificate = new X509Certificate.CreateFromCertFile("My.cer");

It could help to verify whether the origin of your problem is the private key or some properties of the certificate.

If you will have problem with the CER file you can safe post the link to the file because it have public information only. Alternatively you can at least execute

CertUtil.exe -dump -v "My.cer"

or

CertUtil.exe -dump -v -privatekey -p SomePassword "My.pfx"

(you can use some other options too) and post some parts of the output (for example properties of the private key without the PRIVATEKEYBLOB itself).

Oleg
  • 220,925
  • 34
  • 403
  • 798
2

An alternative to changing the Load User Profile is to make the Application Pool use the Network Service Identity.

See also What exactly happens when I set LoadUserProfile of IIS pool?

Community
  • 1
  • 1
David d C e Freitas
  • 7,481
  • 4
  • 58
  • 67
2

On an application running IIS 10, I managed to fix the access denied error by using LocalSystem identity for the app pool with the following code:

new X509Certificate2(certificateBinaryData, "password"
                               , X509KeyStorageFlags.MachineKeySet |
                                 X509KeyStorageFlags.PersistKeySet);

Enabling Load User Profile didn't work for me and while there where many suggestions to do this, they did not indicate that setting for 'Load User Profile' to True only works on user accounts and not:

  1. ApplicationPoolIdentity
  2. NetworkService
Thabiso Mofokeng
  • 681
  • 9
  • 20
1

You need to import a .cer certificate to your local machine keystore. There's no need to import your .p12 cert - instead use the second certyficate issued to your account by Apple. I think it must be a valid pair of certificates (one in filesystem, second in keystore). You'll have to set all 3 flags in dll of course.

PanRycho
  • 57
  • 2
0

The following code will help you, you can generate algorithm using bouncy castle library:

private static ECDsa GetEllipticCurveAlgorithm(string privateKey)
{
    var keyParams = (ECPrivateKeyParameters)PrivateKeyFactory
        .CreateKey(Convert.FromBase64String(privateKey));

    var normalizedECPoint = keyParams.Parameters.G.Multiply(keyParams.D).Normalize();

    return ECDsa.Create(new ECParameters
    {
        Curve = ECCurve.CreateFromValue(keyParams.PublicKeyParamSet.Id),
        D = keyParams.D.ToByteArrayUnsigned(),
        Q =
    {
        X = normalizedECPoint.XCoord.GetEncoded(),
        Y = normalizedECPoint.YCoord.GetEncoded()
    }
    });
}

and generate the token in the following way:

var signatureAlgorithm = GetEllipticCurveAlgorithm(privateKey);

        ECDsaSecurityKey eCDsaSecurityKey = new ECDsaSecurityKey(signatureAlgorithm)
        {
            KeyId = settings.Apple.KeyId
        };

        var handler = new JwtSecurityTokenHandler();   
        var token = handler.CreateJwtSecurityToken(
            issuer: iss,
            audience: AUD,
            subject: new ClaimsIdentity(new List<Claim> { new Claim("sub", sub) }),
            expires: DateTime.UtcNow.AddMinutes(5), 
            issuedAt: DateTime.UtcNow,
            notBefore: DateTime.UtcNow,
            signingCredentials: new SigningCredentials(eCDsaSecurityKey, SecurityAlgorithms.EcdsaSha256));
Shah Zain
  • 388
  • 4
  • 10