You are not logged in.
Pages: 1
I'm experimenting with single-sign on using www-auth and ServerSSPIAuth, which involves challenge-response on an http connection.
I've hacked Request() to pass the ConnectionID so I can track the connection (that parts works fine)
However, I'm getting random logon failed in ServerSSPIAuth when the server runs, but if I have a breakpoint in the IDE before the call to ServerSSPIAuth, which effectively imposes a delay, then the authentication always succeeds AFAICT.
AcceptSecurityContext and the rest of SSPI are supposed to be thread-safe, according to MS docs.
The issue can also happen when there is a single client attempting to connect (so it's not a simultaneous authentication issue)
Any ideas?
Offline
Asked on stack overflow as well
http://stackoverflow.com/questions/1372 … gon-denied
Offline
It is difficult to find what is wrong without the code to be looked at.
I suspect this is not a problem of threads, since authentication at mORMot level (in mORmot.pas unit) is reported to work in multi-thread context.
On which code did you base your implementation?
Online
I've added that to the SynopseWebServer demo in DWS
uses dwsUtils;
type
TNTLMAuth = class
SecContext : TSecContext;
User : RawUTF8;
end;
var
vPendingAuth : TFastCompareStringList;
vPendingAuthCS : TFixedCriticalSection;
// and in Process method
vPendingAuthCS.Enter;
try
p:=vPendingAuth.IndexOf(IntToHex(connectionId, 16));
if p>=0 then begin
auth:=TNTLMAuth(vPendingAuth.Objects[p]);
end else begin
auth:=TNTLMAuth.Create;
InvalidateSecContext(auth.SecContext);
vPendingAuth.AddObject(IntToHex(connectionId, 16), auth);
end;
finally
vPendingAuthCS.Leave;
end;
if auth.User='' then begin
auth64:=request.Header('Authorization');
if auth64='' then begin
OutCustomHeader:='WWW-Authenticate: NTLM'#13#10;
OutContentType:=HTML_CONTENT_TYPE;
OutContent:='<HTML><BODY>401 Unauthorized.</BODY></HTML>';
Result:=401;
Exit;
end else if ServerSSPIAuth(auth.SecContext, RawDecode64(RawByteString(StrAfterChar(auth64, ' '))), OutContent) then begin
OutCustomHeader:='WWW-Authenticate: NTLM '+BinToBase64(OutContent)+#13#10;
Result:=401;
Exit;
end;
ServerSSPIAuthUser(auth.SecContext, auth.User);
end;
OutContent:=auth.User;
To the Process method I just added a connectionId parameter, which takes the Req^.ConnectionId for the http.sys server, and a Int64(Socket) for the thread/socket-based one.
RawDecode64 is just an alternate base64 decoder I tried, but results are the same as with the Synopse one.
Offline
Is NTLM required after WWW-Authenticate: ?
Yes, see http://www.innovation.ch/personal/ronald/ntlm.html
For Kerberos, the sequence is the same with "Negotiate" in place of "NTLM". That's more secure but requires setting up an SPN, which is overkill when doing the auth inside a LAN or over a VPN.
Offline
No luck with StackOverflow, either...
Did you check http://synopse.info/forum/viewtopic.php?pid=5809#p5809 ?
Online
Did you check http://synopse.info/forum/viewtopic.php?pid=5809#p5809 ?
Unless I'm mistaken this is an URI-based scheme? ie. the user info needs to be entered or saved on the client-side in some fashion.
WWW-Authenticate allow that to be resolved by the browser from OS user session info, besides single-sign-on convenience, it can also be made a bit more secure in a LAN since OS logons can be more richly administered, can be tied to biometric systems, etc. all things which would be problematic (or time consuming) to reproduce in a web browser.
Besides, it also means there is no need to have user administration in the web app.
Offline
Below is a test app based on the web server, to compile it you should need to expand the Process method to pass the connectionId.
other units are from the DWS svn
program WWWAuth;
{$APPTYPE CONSOLE}
uses
SysUtils,
SynCommons,
SynCrtSock,
dwsUtils,
dwsWebEnvironment,
dwsSynopseWebEnv,
SynSSPIAuth;
type
TNTLMAuth = class
SecContext : TSecContext;
User : RawUTF8;
end;
TTestServer = class
protected
FServer : THttpApiServer;
public
constructor Create(const basePath : TFileName);
destructor Destroy; override;
function Process(const InURL, InMethod, InHeaders, InContent, InContentType: RawByteString;
out OutContent, OutContentType, OutCustomHeader: RawByteString;
connectionId : Int64) : cardinal;
end;
var
vPendingAuth : TFastCompareStringList;
vPendingAuthCS : TFixedCriticalSection;
{ TTestServer }
constructor TTestServer.Create(const basePath : TFileName);
begin
FServer:=THttpApiServer.Create(false);
FServer.AddUrl('', '888', false,'+');
// FServer.RegisterCompress(CompressDeflate); // our server will deflate html :)
FServer.OnRequest:=Process;
end;
destructor TTestServer.Destroy;
begin
FServer.Free;
inherited;
end;
{$WARN SYMBOL_PLATFORM OFF}
function TTestServer.Process(
const InURL, InMethod, InHeaders, InContent, InContentType: RawByteString;
out OutContent, OutContentType, OutCustomHeader: RawByteString;
connectionId : Int64): cardinal;
var
p : Integer;
request : TSynopseWebRequest;
auth : TNTLMAuth;
auth64 : String;
begin
request:=TSynopseWebRequest.Create;
try
request.InURL:=InURL;
request.InMethod:=InMethod;
request.InHeaders:=InHeaders;
request.InContent:=InContent;
request.InContentType:=InContentType;
vPendingAuthCS.Enter;
try
p:=vPendingAuth.IndexOf(IntToHex(connectionId, 16));
if p>=0 then begin
auth:=TNTLMAuth(vPendingAuth.Objects[p]);
end else begin
auth:=TNTLMAuth.Create;
InvalidateSecContext(auth.SecContext);
vPendingAuth.AddObject(IntToHex(connectionId, 16), auth);
end;
finally
vPendingAuthCS.Leave;
end;
if auth.User='' then begin
auth64:=request.Header('Authorization');
if auth64='' then begin
OutCustomHeader:='WWW-Authenticate: NTLM'#13#10;
OutContentType:=HTML_CONTENT_TYPE;
OutContent:='<HTML><BODY>401 Unauthorized.</BODY></HTML>';
Result:=401;
Exit;
end else if ServerSSPIAuth(auth.SecContext, Base64Decode(RawByteString(StrAfterChar(auth64, ' '))), OutContent) then begin
OutCustomHeader:='WWW-Authenticate: NTLM '+BinToBase64(OutContent)+#13#10;
OutContentType:=HTML_CONTENT_TYPE;
OutContent:='<HTML><BODY>401 Unauthorized.</BODY></HTML>';
Result:=401;
Exit;
end;
ServerSSPIAuthUser(auth.SecContext, auth.User);
end;
OutContent:='<html><body>Logged on as '+auth.User+'</body></html>';
OutContentType:=HTML_CONTENT_TYPE;
Result:=200;
finally
request.Free;
end;
end;
begin
vPendingAuth:=TFastCompareStringList.Create;
vPendingAuthCS:=TFixedCriticalSection.Create;
with TTestServer.Create('') do try
write('Server is now running on http://localhost:888/'#13#10#13#10+
'Press [Enter] to quit');
readln;
finally
Free;
end;
end.
Offline
In the code above, you you are using NTLM security package for authentication, and Negotiate is used in module SysSSPIAuth.pas (const SecPkgName = 'Negotiate').
Try to fix the code:
if auth64='' then begin
OutCustomHeader:='WWW-Authenticate: Negotiate'#13#10;
...
end else if ServerSSPIAuth(auth.SecContext, ...) then begin
OutCustomHeader:='WWW-Authenticate: Negotiate '+BinToBase64(OutContent)+#13#10;
...
end;
Offline
In the code above, you you are using NTLM security package for authentication, and Negotiate is used in module SysSSPIAuth.pas (const SecPkgName = 'Negotiate').
You can use Negotiate as well (for Kerberos), but that requires setting up SPN & other site prefixes, which complicates testing the code.
SSPI supports both, I've tried boths, and have the same issue of connection randomly failing in the same conditions, ie. they (AFAICT) never fail for local connections or when a breakpoint is set in the IDE, but have about 50% chance of succeeding when connecting from other machines on the LAN and there is no debugger breakpoints set.
This is so very weird (especially the debugger breakpoint thing), I'm wondering if the issue isn't with the domain controllers here: if one of the controllers were misconfigured in some fashion, it would explain the 50% success rate. Though it wouldn't explain the breakpoints thing or why we don't see the issue from Windows... (unless the OS were to silently fallback).
I guess if someone else tests and can't reproduce my issues, then that'll definitely point the finger at the domain here.
Offline
I added some test code to your project. Periodically in the log appears messages of wrong base64-encoded lines, and AcceptSecurityContext returns ERROR_INVALID_PARAMETER, so auth failed.
const
b64: array[0..64] of AnsiChar =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
...
if auth.User='' then begin
auth64:=request.Header('Authorization');
if auth64<>'' then
AuthText := StringToUTF8(StrAfterChar(auth64, ' '));
for i := 1 to Length(AuthText) do
begin
Found := False;
for j := Low(b64) to High(b64) do
begin
if AuthText[i] = b64[j] then
begin
Found := True;
break;
end;
end;
if not Found then
vLog.Add.Log(sllInfo, 'Invalid base 64 char % at %: %', [Ord(AuthText[i]), i, Copy(AuthText, i-2, 5)]);
end;
This is the first problem. The second problem is that in SynSSPIAuth.pas used byte ordering SECURITY_NETWORK_DREP (0), but browsers used SECURITY_NATIVE_DREP ($10). So it is necessary to correct the function calls.
Status := InitializeSecurityContextW(@aSecContext.CredHandle, LInCtxPtr, nil,
ISC_REQ_CONNECTION or ISC_REQ_ALLOCATE_MEMORY,
0, $10, InDescPtr, 0, @aSecContext.CtxHandle, @OutDesc, CtxAttr, Expiry);
Status := AcceptSecurityContext(@aSecContext.CredHandle, LInCtxPtr, @InDesc,
ASC_REQ_CONNECTION or ASC_REQ_ALLOCATE_MEMORY,
$10, @aSecContext.CtxHandle, @OutDesc, CtxAttr, Expiry);
P.S.
I modified code from post #9 and now it works fine for me.
if auth64<>'' then
//AuthText := StringToUTF8(StrAfterChar(auth64, ' '));
AuthText := FindIniNameValue(PUTF8Char(InHeaders), 'AUTHORIZATION: NTLM ');
Last edited by Chaa (2012-12-11 10:58:59)
Offline
I added some test code to your project. Periodically in the log appears messages of wrong base64-encoded lines [...]
Darn! Thanks for looking it up!
I found the issue: the headers parser was applying UrlDecode, which occasionally turned a '+' in the base64 encoding to a ' ', and since ' ' is allowed (and ignored) by base64 coders, there was no error thrown there... *feels like a fool* as I thought base64 was immune to UTF8 & encoding issues, which it just wasn't
@ab: any chance of surfacing the already parsed headers array in SynCrtSock? This would have allowed this mistake, and avoided the double-parsing
Offline
Pages: 1