TIDFTP with SChannel? session reuse required
Using OpenSSL , my program works to connect to a FTP server running TLS 1.2.
Snippet of code from a stand alone example...
Gets A "session reuse required" error.
If i switch to the openSSL IOHandler, it all works.
` IdFTP1 := tidftp.Create(nil); ssl := TIdSSLIOHandlerSocketSChannel.Create(nil);
IdFTP1.OnStatus := FTPStatus; IdFTP1.OnTLSNotAvailable := TLSNotAvailable; IdFTP1.OnTLSHandShakeFailed := TLSHandShakeFailed; IdFTP1.OnTLSNegCmdFailed := TLSNegCmdFailed;
IdFTP1.IOHandler := ssl; IdFTP1.UseTLS := utUseExplicitTLS; IdFTP1.Passive := True;
IdFTP1.Host := 'some-sever.com'; IdFTP1.Username := 'user'; IdFTP1.Password := 'pass';
IdFTP1.Connect;
IdFTP1.DataPortProtection := ftpdpsPrivate;
Memo1.lines.add(''); if IdFTP1.SupportsTLS then Memo1.lines.add('TLS IS SUPPORTED') else Memo1.lines.add('TLS IS NOT SUPPORTED'); Memo1.lines.add('');
IdFTP1.list; /// < ---- FAILS RIGHT HERE. session reuse required
for i := 0 to IdFTP1.DirectoryListing.Count - 1 do begin Memo1.lines.add(IdFTP1.DirectoryListing.Items[i].FileName); end; IdFTP1.TransferType := ftBinary;
if fileexists('test.txt') then IdFTP1.Put('test.txt');
IdFTP1.Disconnect; IdFTP1.Free; ssl.Free; `
The problem appears to be in TIdSSLIOHandlerSocketSChannel.Clone(), which TIdFTP calls when setting up a new data connection. TIdFTP clones the SSLIOHandler of the control connection, expecting the clone to reuse the same TLS session as the object it is cloned from. But TIdSSLIOHandlerSocketSChannel is not doing that, ie it is not sharing a credential handle across multiple instances, each instance is obtaining a new credential handle for itself.
Hi @rlebeau what it the purpose of the Clone method ? the TLS session is bound to a specific socket, so does the Clone() method create a new instance to work with the same socket ?
if it is the case, then I can add an SSLClone method with an automatic reference counteur in TSSLInfo..
something like
function TIdSSLIOHandlerSocketSChannel.Clone: TIdSSLIOHandlerSocketBase;
begin
{$IFDEF LOG_EVENTS}System.WriteLn('TIdSSLIOHandlerSocketSChannel.Clone');{$ENDIF}
Result := TIdSSLIOHandlerSocketSChannel.Create(nil);
Result.FSSL := SSLClone(FSSL);
end;
with
function SSLClone(SSL: THandle): Handle;
var
Info: PSSLInfo absolute SSL;
begin
Result := SSL;
if SSL <> 0 then
Inc(Info.RefCount); // set to 0 in SSLStart
end;
and
function SSLClose(SSL: THandle): Integer;
var
Info: PSSLInfo absolute SSL;
begin
Result := 0;
if SSL = 0 then
Exit;
{$IFDEF TRACE}
WriteLn(Trace, '-------------');
CloseFile(Trace);
{$ENDIF}
if Info.RefCount > 0 then
begin
Dec(Info.RefCount);
end else begin
Info.Clean;
Dispose(Info);
end;
end;
what it the purpose of the Clone method ?
To create a new SSLIOHandler object that shares a TLS session with an existing SSLIOHandler object.
the TLS session is bound to a specific socket
A TLS session is not bound to a specific socket. Not in OpenSSL, not in SChannel. Multiple sockets can share a TLS session across connections. For instance, in this situation, an FTPS data connection can (and on many servers, must) share a TLS session with the FTP control connection, to avoid MITM hijacking of data connections. HTTP requests across non-persistent connections can also share TLS sessions, too.
Basically, any time a peer wants to setup authentication+encryption 1 time with another peer and then reuse that session over and over regardless of how many connections are involved.
In Indy's case, that currently only happens in TIdFTP when the DataPortProtection property is ftpdpsPrivate. But there could be other uses for it in the future.
does the Clone() method create a new instance to work with the same socket ?
Not with the same socket, no. It should create a new instance that will share the same TLS session with a new socket.
if it is the case, then I can add an SSLClone method with an automatic reference counteur in TSSLInfo..
I don't think cloning the whole TSSLInfo will work. The only thing I can find that describes the technical requirement for reusing sessions in the SChannel SSPI API is this:
https://stackoverflow.com/questions/905851/ssl-session-reuse-with-schannel-windows
I think that means AcquireCredentialsHandle() can be called once per shared session, and then that credential handle can be reused for multiple calls to InitializeSecurityContext() (outbound) and AcceptSecurityContext() (inbound) socket connections.
So, you will likely still need a separate TSSLInfo object for each Socket+Context, but the Credentials: TCredHandle; member will have to be sharable across multiple TSSLInfo instances.
thank you @rlebeau you mean the TLS session at the TLS protocol level.
Sorry @jdredd87 I have no time to spend on SChannel nor Indy to handle that.
I spent some time studying the TLS protocol on another project, and now I know that SChannel prohibits quite a few things (like select cipher suites) and is quite poorly documented, so it's not easy to get it to work properly :)