38

I'm trying to construct an X509Certificate2 from a PKCS#12 blob in a byte array and getting a rather puzzling error. This code is running in a desktop application with administrator rights on Windows XP.

The stack trace is as follows, but I got lost trying to troubleshoot because _LoadCertFromBlob is marked [MethodImpl(MethodImplOptions.InternalCall)].

System.Security.Cryptography.CryptographicException: The system cannot find the file specified.
  at System.Security.Cryptography.CryptographicException.ThrowCryptogaphicException(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)
  at System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(Byte[] rawData, String password, X509KeyStorageFlags keyStorageFlags)

EDIT: The blob is a true PKCS#12 generated by BouncyCastle for C# containing a RSA private key and certificate (either self-signed or recently enrolled with a CA) -- what I'm trying to do is convert the private key and certificate from the BouncyCastle library to the System.Security.Cryptography library by exporting from one and importing to the other. This code works on the vast majority of systems it's been tried on; I've just never seen that particular error thrown from that constructor. It may be some sort of environmental weirdness on that one box.

EDIT 2: The error is occurring in a different environment in a different city, and I'm unable to reproduce it locally, so I may end up having to chalk it up to a broken XP installation.

Since you asked, though, here is the fragment in question. The code takes a private key and certificate in BouncyCastle representation, deletes any previous certificates for the same Distinguished Name from the personal key store, and imports the new private key and certificate into the personal key store via an intermediate PKCS#12 blob.

// open the personal keystore
var msMyStore = new X509Store(StoreName.My);
msMyStore.Open(OpenFlags.MaxAllowed);

// remove any certs previously issued for the same DN
var oldCerts =
    msMyStore.Certificates.Cast<X509Certificate2>()
        .Where(c => X509Name
                        .GetInstance(Asn1Object.FromByteArray(c.SubjectName.RawData))
                        .Equivalent(CurrentCertificate.SubjectDN))
        .ToArray();
if (oldCerts.Length > 0) msMyStore.RemoveRange(new X509Certificate2Collection(oldCerts));

// build a PKCS#12 blob from the private key and certificate
var pkcs12store = new Pkcs12StoreBuilder().Build();
pkcs12store.SetKeyEntry(_Pkcs12KeyName,
                        new AsymmetricKeyEntry(KeyPair.Private),
                        new[] {new X509CertificateEntry(CurrentCertificate)});
var pkcs12data = new MemoryStream();
pkcs12store.Save(pkcs12data, _Pkcs12Password.ToCharArray(), Random);

// and import it.  this constructor call blows up
_MyCertificate2 = new X509Certificate2(pkcs12data.ToArray(),
                                       _Pkcs12Password,
                                       X509KeyStorageFlags.Exportable);
msMyStore.Add(_MyCertificate2);
msMyStore.Close();
Jeffrey Hantin
  • 33,679
  • 7
  • 71
  • 92

5 Answers5

45

Do you have PKCS#12 or just PFX-file? In the Microsoft world it is the same, but other think another (see this archived page).

You can try just following

X509Certificate2 cert = X509Certificate2(byte[] rawData, "password");
X509Certificate2 cert2 = X509Certificate2(byte[] rawData, "password",
              X509KeyStorageFlags.MachineKeySet |
              X509KeyStorageFlags.PersistKeySet |
              X509KeyStorageFlags.Exportable);

(X509Certificate2(Byte[])) or

X509Certificate2 cert = X509Certificate2("C:\Path\my.pfx", "password");

(see X509Certificate2(String, String) and Import(String, String, X509KeyStorageFlags) on Microsoft Docs if you need use some flags)

UPDATED: It would be helpful if you insert a code fragment and not only the exception stack trace.

Which X509KeyStorageFlags do you use? You can use Process Monitor to find out which file could not find the X509Certificate2 constructor. It can be for example that there are no default key container for the current user on the Windows XP having the problem. You can create it and retry the import.

Oleg
  • 217,934
  • 30
  • 386
  • 757
  • 4
    Adding X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable to the constructor when loading from byte[] worked for me. – Muxa Mar 18 '12 at 23:20
  • 2
    I encountered this same error on Windows Server 2012, where as the exact same code didn't have issues on Windows 7 or in the Azure cloud. Changing the flag to MachineKeySet fixed the problem. – RMD May 23 '13 at 15:11
  • 2
    Beware the PersistKetSet flag in this example - it leaves key files on disk that do not ever get cleaned up. If you're making this call very frequently then you will end up with a huge clean up task. (Ask me how I know.) If you are using the cert only in memory then just specify MachineKeySet. If you are done with the in-memory cert in code call the Reset method to immediately delete the key file. Best to add once into the store and reload, though. That's what it's for. – James McLachlan Feb 13 '15 at 05:08
  • @JamesMcLachlan: Sorry, but I didn't do anything in the direction since years. The scenario with importing certificate *not once* is very specific. If `PersistKeySet` is not good in your scenario you should just not use it. On the other side if certificate is imported with `PersistKeySet` the key will be placed in the key storage as file (in user profile). `CertGetCertificateContextProperty` with `CERT_KEY_PROV_INFO_PROP_ID` get the information. One can get the file name using `PP_UNIQUE_CONTAINER`. The file is under `CommonApplicationData` (with `Microsoft\Crypto\RSA\MachineKeys` suffix) – Oleg Feb 13 '15 at 09:39
10

I ran into the same issue.

According to this old KB article the problem was that the constructor tries to load the cert into the current user's profile, but the .Net code I was impersonating the user and so it had not loaded the user profile. The constructor requires the loaded user profile to work properly.

From the article:

The X509Certificate2 class constructors attempt to import the certificate into the user profile of the user account that the application runs in. Many times, ASP.NET and COM+ applications impersonate clients. When they do, they do not load the user profiles for the impersonated user for performance reasons. So, they cannot access the "User" certificate store for the impersonated user.

Loading the user profile fixed the error.

Zain Rizvi
  • 21,625
  • 17
  • 82
  • 122
  • The piece of code in question was running inside a desktop application, so I doubt an unloaded user profile was at issue. – Jeffrey Hantin Aug 18 '14 at 00:16
  • @Zain, Thanks for the answer. Do you also know that is this the best way to fix this issue? It makes me consider due to the performance impact. Thx – curiousBoy Apr 18 '16 at 19:41
  • @curiousBoy Like I said in the last line of my answer, make an api call to load the user profile. – Zain Rizvi Apr 18 '16 at 21:10
5

I had this same problem.

  1. Open IIS on the server hosting the site.
  2. Find the application pool for the site.
  3. Click Advanced Settings.
  4. Change "Load User Profile" to true. (may require restart or reboot)

This allows the crypto subsystem to work.

enter image description here

crthompson
  • 14,783
  • 6
  • 51
  • 74
  • 1
    This is the fourth answer that points to problems with IIS app pools. I had this problem in a desktop app. It's entirely possible the user profile was damaged somehow, but it was definitely loaded. – Jeffrey Hantin Mar 06 '17 at 22:38
  • Interesting, its not obvious from your question that its a desktop app. This fix worked for me on my IdentityServer3 MVC app. The project worked fine locally, but as soon as it got up to the server it would crash. This flag was the only fix. Permissions perhaps on the desktop app? – crthompson Mar 07 '17 at 20:56
  • It ran in an administrator-group account on an XP machine, so neither permissions nor UAC should have been at issue. I couldn't reproduce it on any other machine, so my best guess at this point is either a broken profile or a broken OS configuration. – Jeffrey Hantin Mar 07 '17 at 23:22
4

Running into this in an web application on Windows 2012, Setting application pool option Load User Profile to true made it work.

To do this, run inetmgr.exe, go to Advanced Settings for the right application pool, change Load User Profile under Process Model to true.

deerchao
  • 10,012
  • 8
  • 49
  • 59
1

I had exactly the same problem. The same code and data/certs ran fine on Windows 2003 x86 when running under a specific user, but failed under another account (which was also used for running IIS app pools).

Apparently, some other thing exhausted resources on Windows, so that the failing user could not really load the user's profile (his desktop was weird-looking), although there were no related events in Event Viewer.

A reboot solved the problem temporarily. Although this is no permanent solution to the problem, it shows that there's something else (eg, COM+ components, native code services, etc) consuming resources that needs to be investigated. It also shows the instability of Windows platforms...

Ricardo Pardini
  • 908
  • 7
  • 17
  • This particular flavor of instability seems to be related to a combination of heavily interprocess-communication-dependent desktop software and poor robustness in the face of resource exhaustion. I've seen the exact same class of problems with GNOME, and especially Evolution, on Linux in the past. – Jeffrey Hantin Mar 05 '12 at 22:43