Certificate Access Error in a IIS hosted WCF Service

The problem appears when a WCF Service hosted in an IIS tries to load a certificate from the Windows Certificates Store with the account of the Application Pool where the service runs, and the account2019s profile is not previously loaded. When a user logs on interactively, the system automatically loads the user's profile. If a service or an application impersonates a user, the system does not load the user's profile. Therefore, the service or application should load the user's profile with LoadUserProfile.

When this happens the operation throws the following exception:

System.Security.Cryptography.CryptographicException: The system cannot find the file specified.

See http://support.microsoft.com/kb/939761 Microsoft Knowledge Base Article for detailed information.

A workaround to this problem is to load the Application Pool Identity AccountB4s profile before the service call is executed. Placing the code in the Application_Start() method on the Global.asax of the IIS host will solve the problem (see http://msdn.microsoft.com/en-us/library/aa374341.aspx for detailed information).

Here is the code:

private ProfileManager.PROFILEINFO profile;

A0

protected void Application_Start(object sender, EventArgs e)

{

A0A0A0 bool retVal = false;

A0A0A0 // Need to duplicate the token. LoadUserProfile needs a token with

A0A0A0 // TOKEN_IMPERSONATE and TOKEN_DUPLICATE.

A0A0A0 const int SecurityImpersonation = 2;

A0A0A0 dupeTokenHandle = DupeToken(WindowsIdentity.GetCurrent().Token, SecurityImpersonation);

A0

A0A0A0 if (IntPtr.Zero == dupeTokenHandle)

A0A0A0 {

A0A0A0A0A0A0A0 throw new Exception("Unable to duplicate token.");

A0A0A0 }

A0

A0A0A0 // Load the profile.

A0A0A0 profile = new ProfileManager.PROFILEINFO();

A0A0A0 profile.dwSize = 32;

A0A0A0 //Domain\User

A0A0A0 profile.lpUserName = @"MyDomain\UserName";

A0A0A0 retVal = ProfileManager.LoadUserProfile(dupeTokenHandle, ref profile);

A0

A0A0A0 if (!retVal)

A0A0A0 {

A0A0A0A0A0A0A0 throw new Exception("Error loading user profile. " + Marshal.GetLastWin32Error());

A0A0A0 }

}

A0

protected void Application_End(object sender, EventArgs e)

{

A0A0A0 ProfileManager.UnloadUserProfile(WindowsIdentity.GetCurrent().Token, profile.hProfile);

A0A0A0 CloseHandle(dupeTokenHandle);

}

A0

private IntPtr DupeToken(IntPtr token, int Level)

{

A0A0A0 IntPtr dupeTokenHandle = new IntPtr(0);

A0A0A0 bool retVal = DuplicateToken(token, Level, ref dupeTokenHandle);

A0A0A0

A0A0A0 if (false == retVal)

A0A0A0 {

A0A0A0A0A0A0A0 return IntPtr.Zero;

A0A0A0 }

A0

A0A0A0 return dupeTokenHandle;

}

A0

internal class ProfileManager

{

A0A0A0 [DllImport("Userenv.dll", SetLastError = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]

A0A0A0 internal static extern bool LoadUserProfile(IntPtr hToken, ref PROFILEINFO lpProfileInfo);

A0A0A0 [DllImport("Userenv.dll", SetLastError = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]

A0A0A0 internal static extern bool UnloadUserProfile(IntPtr hToken, IntPtr hProfile);

A0

A0A0A0 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]

A0A0A0 public struct PROFILEINFO

A0A0A0 {

A0A0A0A0A0A0A0 public int dwSize;

A0A0A0A0A0A0A0 public int dwFlags;

A0A0A0A0A0A0A0 public String lpUserName;

A0A0A0A0A0A0A0 public String lpProfilePath;

A0A0A0A0A0A0A0 public String lpDefaultPath;

A0A0A0A0A0A0A0 public String lpServerName;

A0A0A0A0A0A0A0 public String lpPolicyPath;

A0A0A0A0A0A0A0 public IntPtr hProfile;

A0A0A0 }

}

An alternative workaround consists in creating a Windows Service account that loads at system start-up using the Application Pool Service Identity.

Thanks to JavierA for helping me to find the solution.

No Comments