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 account’s 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 Account´s 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://msdn2.microsoft.com/en-us/library/aa374341.aspx for detailed information).

Here is the code:

private ProfileManager.PROFILEINFO profile;

 

protected void Application_Start(object sender, EventArgs e)

{

    bool retVal = false;

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

    // TOKEN_IMPERSONATE and TOKEN_DUPLICATE.

    const int SecurityImpersonation = 2;

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

 

    if (IntPtr.Zero == dupeTokenHandle)

    {

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

    }

 

    // Load the profile.

    profile = new ProfileManager.PROFILEINFO();

    profile.dwSize = 32;

    //Domain\User

    profile.lpUserName = @"MyDomain\UserName";

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

 

    if (!retVal)

    {

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

    }

}

 

protected void Application_End(object sender, EventArgs e)

{

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

    CloseHandle(dupeTokenHandle);

}

 

private IntPtr DupeToken(IntPtr token, int Level)

{

    IntPtr dupeTokenHandle = new IntPtr(0);

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

   

    if (false == retVal)

    {

        return IntPtr.Zero;

    }

 

    return dupeTokenHandle;

}

 

internal class ProfileManager

{

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

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

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

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

 

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

    public struct PROFILEINFO

    {

        public int dwSize;

        public int dwFlags;

        public String lpUserName;

        public String lpProfilePath;

        public String lpDefaultPath;

        public String lpServerName;

        public String lpPolicyPath;

        public IntPtr hProfile;

    }

}

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.

Published Thursday, November 01, 2007 5:15 PM by MarianoS
Filed under: ,

Comments

# Certificate Access Error in a IIS hosted WCF service « shockbyte

Pingback from  Certificate Access Error in a IIS hosted WCF service « shockbyte

Leave a Comment

(required) 
(required) 
(optional)
(required)