Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TIDFTP with SChannel? session reuse required #10

Open
jdredd87 opened this issue Jan 14, 2024 · 4 comments
Open

TIDFTP with SChannel? session reuse required #10

jdredd87 opened this issue Jan 14, 2024 · 4 comments

Comments

@jdredd87
Copy link

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;
`

@rlebeau
Copy link

rlebeau commented Jan 14, 2024

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.

@tothpaul
Copy link
Owner

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;

@rlebeau
Copy link

rlebeau commented Jan 15, 2024

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.

@tothpaul
Copy link
Owner

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 :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants