Verifying whether the broken piece is c2WTS or Active Directory
If you have tried my tool to troubleshoot c2WTS with SharePoint, c2WTSTester, verified that the service is running as expected, the account used by SharePoint is valid in c2WTS but you still failed to receive a valid token for some error that does not make much sense, fear no more. It may be that c2WTS is not the problem but rather a victim of some Active Directory misfortune. In the cases I supported where the problem was not c2WTS misconfiguration, the two main reasons were:
1. The configuration for the alternate UPN suffixes were not enabled in the Name Suffix Routing in Active Directory so the main UPN worked while the other failed (for example Skype has merged with Microsoft and a UPN like nouser@microsoft.com worked while nouser1@skype.com did not). To resolve these problems, go here: https://technet.microsoft.com/en-us/library/cc754857.aspx
2. Customer is running c2WTS service under SYSTEM or NETWORK credentials and when c2WTS is called the machine is kicked off from the domain and the connection is only re-established when the machine is rebooted. This happens specially in Virtual Machines or restored machines because the generated password for the machine does not match the password in one or more domain controllers (NETWORK and SYSTEM identify in the network as DOMAIN\MACHINAME$ and passes a previously exchanged random password created when the machine joins). This error is the worst for it may happen randomly (DCs in round-robin and the one with bad password happens to be the one authenticating at the time). This problem can be resolved by rejoining the domain or resetting the machine password: https://support.microsoft.com/kb/325850
Identifying the problem
c2WTS is a wrapper for the Windows API function LsaLogonUser which cannot be called from a process that is not running in full trust (as sandboxed or non-administrative SharePoint pages). .NET offers an interface to this API function via WindowsIdentity constructor which also requires full trust. So, in order to applications running in less privilege to acquire a token based on a UPN, a middleware service running in full trust acquire the token in its behalf. This service as you may have imagined is the Claims to Windows NT Token Service (c2WTS). The full service wrapper will eventually at some point just run this code impersonating the service account (which by default is SYSTEM):
WindowsIdentity s4u = new WindowsIdentity(upn);
WindowsImpersonationContext wic = s4u.Impersonate();
As a wrapper c2WTS includes some overhead that may difficult the identification of the main problem. So, c2WTSTester will identify the common wrapper problems (service configuration and access to the service) but it will return a generic error if the problem is in Active Directory configuration.
I put together a new test application that bypasses c2WTS and emulates the core functionality: the token acquisition based on the provided upn. I am listing the code below and at the end I provide a link to download the application executable and the source code if you are interested in modifications. Besides testing the name resolution, it also helps identify Kerberos delegation problems as it lets you choose a target to be reached by the acquired token. It accepts either a URL (http or https) or a share (starting with \\). If you wish to test a WCF service, modify the source code to add a reference to the service, create a proxy and call a method.
Another common scenario is to run c2WTS as SYSTEM or NETWORK. In this case, please download psexec from SysInternals here: https://technet.microsoft.com/en-us/sysinternals/bb897553.aspx and run this command in a elevated command prompt to open another command prompt as SYSTEM account:
c:\sysinternals\psexec64 \\localhost -s -i cmd.exe OR c:\sysinternals\psexec \\localhost -s -i cmd.exe for 32-bits systems. From this command prompt run the application.
using System;
using System.Text;
using System.Security.Principal;
using System.IO;
using System.Net;
using System.Diagnostics;
// This sample code is provided “AS IS” with no guarantees and its meant for proof-of-concept ONLY.
// You need to accept this license to use it: https://rodneyviana.codeplex.com/license
//The code samples are provided AS IS without warranty of any kind.
// Microsoft disclaims all implied warranties including, without limitation,
// any implied warranties of merchantability or of fitness for a particular purpose.
// The entire risk arising out of the use or performance of the sample scripts and documentation remains with you.
// In no event shall Microsoft, its authors, or anyone else involved in the creation, production,
// or delivery of the scripts be liable for any damages whatsoever (including, without limitation, damages for
// loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising
// out of the use of or inability to use the sample scripts or documentation, even if Microsoft has been advised
// of the possibility of such damages.
namespace S4ULogonViaDotNet
{
class Program
{
static protected ConsoleColor back = Console.BackgroundColor;
static protected ConsoleColor fore = Console.ForegroundColor;
static protected void Bold()
{
Console.BackgroundColor = ConsoleColor.Blue;
Console.ForegroundColor = ConsoleColor.White;
}
static protected void DisplayLink(string Url)
{
Console.ForegroundColor = ConsoleColor.Blue;
Console.BackgroundColor = ConsoleColor.Yellow;
Console.Write(Url);
Normal();
}
static protected void Normal()
{
Console.BackgroundColor = back;
Console.ForegroundColor = fore;
}
static void Main(string[] args)
{
Console.WriteLine("S4ULogonViaDotNet");
Console.WriteLine("=================\n");
Console.WriteLine("Test UPN login using the same Windows API that c2WTS (Claims to ");
Console.WriteLine("Windows NT Token Service) uses. If the test fails here, the problem");
Console.WriteLine("is in your Active Directory configuration or your Kerberos settings.");
Console.WriteLine("");
Console.Write("License: GPLv2 (see full license here: ");
DisplayLink("https://rodneyviana.codeplex.com/license");
Console.WriteLine(")");
Console.WriteLine("");
Bold();
Console.WriteLine("The process is running under credential: {0}\n * Make sure it matches c2WTS service account.", GetPrincipal());
Normal();
Console.WriteLine("");
Console.WriteLine("IMPORTANT: To run this application as SYSTEM account (default for c2WTS): ");
Console.Write(" 1. Download SysInternals psexec (or psexec64) at\n ");
DisplayLink("https://technet.microsoft.com/en-us/sysinternals/bb897553.aspx");
Console.WriteLine("");
Console.Write(" 2. Run from an elevated command prompt:\n ");
Bold();
Console.WriteLine("psexec \\\\localhost -s -i {0}\n", Environment.CommandLine);
Normal();
string upn = null;
string target = null;
if (args.Length == 0)
{
Console.Write("Enter upn (e.g. john@contoso.com): ");
upn = Console.ReadLine();
Console.Write(@"Enter target (e.g. https://server1 or \\server1\share1\file1.txt): ");
target = Console.ReadLine();
}
else
{
upn = args[0];
if(args.Length > 1)
target = args[1];
}
if (String.IsNullOrEmpty(upn) || upn.Contains("?"))
{
Console.WriteLine("\nUsage: S4ULogonViaDotNet [UPN] [file]");
Console.WriteLine("\nExample: S4ULogonViaDotNet john@contoso.com https://server1");
return;
}
try
{
WindowsIdentity s4u = new WindowsIdentity(upn);
WindowsImpersonationContext wic = s4u.Impersonate();
Console.WriteLine("");
Bold();
Console.WriteLine("Identity {0} acquired successfuly", WindowsIdentity.GetCurrent().Name);
Normal();
if (target.StartsWith("\\"))
{
using (StreamReader infile = new StreamReader(target))
{
//
// It will throw exception if it can't read
//
infile.ReadLine();
infile.Close();
}
} else
if (target.ToLower().StartsWith("http"))
{
/*
* If you want to test access to a WCF service instead of a
* regular web page, change the code below to create a
* WCF proxy and call a method to test
*/
WebClient wc = new WebClient();
string reply = wc.DownloadString(target);
}
else
{
if(!String.IsNullOrEmpty(target))
throw new Exception("Warning: Resource not recognized or empty. It has to start with \\\\ for a share or with http for a web resource");
}
if (!String.IsNullOrEmpty(target))
Console.WriteLine("Reading target {0} was succesfull.", target);
}
catch (Exception ex)
{
Console.WriteLine("{0}: {1}\n{2}", ex.GetType().ToString(), ex.Message, ex.StackTrace);
}
Console.Write("Press any key to continue...");
Console.ReadKey();
}
protected static string GetPrincipal()
{
return System.Security.Principal.WindowsIdentity.GetCurrent().Name;
}
}
}
Download here: S4ULogonViaDotNet
Comments
Anonymous
August 06, 2014
Hi, I am running your program under credentials 'NT AUTHORITYSYSTEM' in a separate command window as you suggested. What goes into the 'target' parameter? I tried my SharePoint url (http://servername), but get a 401 Unauthorized.Anonymous
October 08, 2014
Hi Marcel, Sorry for the delayed response. It seems the blog alerts are not working well. Right, the second parameter may be a URL or a computer share that you want to reach when impersonating the user.