I'm developing a MITM proxy with SSL support in delphi 10.3, using indy. I use IdHttpServer component, which does work in CommandOther event. I managed to make it to decrypt and dump data on the fly, and reencrypt and send it to browser, but I need to change idhttpserver certificate for each domain. I can generate them and install my own CA, but i cant find a way to change them while my proxy is working. I will greatly appreciate if someone will show me how!
procedure TForm3.IdHTTPServer1CommandOther(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
S: string;
LClient: TIdtcpClient;
newsize:int64;
LBuf: TIdBytes;
Len: Integer;
var response:integer;
s3: TStringDynArray;
var cmd:string;
bytes:tidbytes;
oldstr,newstr:string;
ResponseCode, ResponseText: string;
Size: Int64;
ssl:tIdServerIOHandlerSSLopenssl;
begin
if not TextIsSame(ARequestInfo.Command, 'CONNECT') then Exit;
LClient := TIdtcpClient.Create(nil);
try
S := ARequestInfo.URI;
LClient.Host := Fetch(S, ':', True);
LClient.Port := StrToIntDef(S, 443);
LClient.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(LClient);
LClient.ConnectTimeout := 5000;
// connect and activate SSL between this proxy and the target server
LClient.Connect;
try
AResponseInfo.ResponseNo := 200;
AResponseInfo.ResponseText := 'Connection established';
AResponseInfo.WriteHeader;
// activate SSL between this proxy and the client
TIdSSLIOHandlerSocketOpenSSL(AContext.Connection.Socket).PassThrough:=false;
// pass data between AContext.Connection.IOHandler and LClient.IOHandler
//as needed.
// received data will be decrypted, and sent data will be encryted...
while AContext.Connection.Connected and lclient.Connected do
begin
//mitm traffic modification routine
end;
finally
LClient.Disconnect;
end;
finally
LClient.Free;
end;
end;
This is cert switching code:
procedure TForm3.IdHTTPServer1Connect(AContext: TIdContext);
var
SSL: TIdSSLIOHandlerSocketOpenSSL;
begin
if AContext.Connection.Socket.Binding.Port = 443 then
begin
sslh:=tIdSSLIOHandlerSocketOpenSSL(AContext.Connection.IOHandler);
sslh.SSLOptions.CertFile:='Certificate.pem';
sslh.SSLOptions.keyfile:='PrivateKey.pem';
sslh.SSLOptions.RootCertFile:='certificateAuthorityCertificate.pem';
sslh.SSLOptions.SSLVersions:=[sslvSSLv23];
sslh.ssloptions.mode:=sslmBoth;
sslh.OnGetPassword:= IdServerIOHandlerSSLOpenSSL1GetPassword;
sslh.PassThrough:=false;
TIdSSLIOHandlerSocketOpenSSL(AContext.Connection).PassThrough:=false;
//memo2.Text:=AContext.Connection.IOHandler.ReadLn();
end;
end;
On a form I have a tidhttpserver and TIdServerIOHandlerSSLOpenSSL as iohandler for it.
Assign on OnQuerySSLPort event handler that unconditionally sets the VUseSSL parameter to False regardless of the APort requested. Then, in the OnConnect event, if the AContext.Connection.Socket.Binding.Port property is 443 (or whatever port you want to use HTTPS on), you can typecast the AContext.Connection.IOHandler property to TIdSSLIOHandlerSocketBase (or descendant, like TIdSSLIOHandlerSocketOpenSSL if using OpenSSL), configure its certificates as needed, and then set its PassThrough property to False to complete the SSL/TLS handshake.
Related
I am using Delphi 10.0 Seattle.
I'd like to send requests to a UDP server and then read the server response, which is a simple string:
Client side:send('12345')
server side(onread event or whatever):if received string = ('12345') then
send ('jhon|zack|randy')
else disconnect;
The length of the response string is variable.
The server is running on a well opened network with open connection (dedicated vps).
The client is not the same, it is behind routers and secure networks (not forwarded).
So far, I can only send the request from the client:
(uc=idUDPclient)
procedure TForm1.Button1Click(Sender: TObject);
var
s:string;
begin
if uc.Connected =False then
Uc.Connect;
uc.Send('12345');
uc.ReceiveTimeout := 2000;
s:=uc.ReceiveString() ;
ShowMessage(s);
uc.Disconnect
end;
Server side (us=idUDPserver)
procedure TForm1.usUDPRead(AThread:TIdUDPListenerThread;const AData: TIdBytes;ABinding: TIdSocketHandle);
begin
ShowMessage(us.ReceiveString());
if us.ReceiveString() = '12345' then
begin
ShowMessage(us.ReceiveString());
//respond with a string to the client immediately (behind a routers) how ?
end;
I don't know if TCP is better, and how to use it.
Android will be involved.
You are not using the TIdUDPServer.OnUDPRead event correctly. You need to get rid of the calls to ReceiveString(), they do not belong in there. Use the AData parameter instead, it contains the raw bytes of the client's request. TIdUDPServer has already read the client's data before firing the event handler.
If you need the bytes in a string, you can use Indy's BytesToString() function, or IIdTextEncoding.GetString() method.
To send a response back to the client, use the ABindingparameter.
Try this:
procedure TForm1.usUDPRead(AThread: TIdUDPListenerThread;
const AData: TIdBytes; ABinding: TIdSocketHandle);
var
s: string;
begin
s := BytesToString(AData);
//ShowMessage(s);
if s = '12345' then begin
ABinding.SendTo(ABinding.PeerIP, ABinding.PeerPort, 'jhon|zack|randy', ABinding.IPVersion);
end;
end;
I have a website I post to that currently supports TLS v1.1 and TLS 1.2. They will soon only allow TLS ver 1.2 connections. I upgraded Delphi 5 to Indy 10 for this reason.
Currently, I create my components in code and everything works great running 3 threads at a time:
HTTp := TIdHttp.Create(nil);
HTTP.OnSelectAuthorization := HTTPSelectAuthorization;
HTTP.HTTPOptions := [hoInProcessAuth,hoForceEncodeParams,hoKeepOrigProtocol];
HTTP.OnStatus := HTTPStatus;
HTTP.OnWorkEnd := HTTPWorkEnd;
HTTP.Request.ContentType := 'application/x-www-form-urlencoded';
HTTP.ProxyParams.ProxyPort := ProxyPort;
HTTP.ProxyParams.ProxyUsername := ProxyUserName;
HTTP.ProxyParams.ProxyPassword := ProxyPassword;
HTTP.ProxyParams.BasicAuthentication := ProxyBasicAuth;
end;
If UseSSL and (SSL = nil) then
Begin
SSL := TIDSSLIOHandlerSocketOpenSSL.Create(nil);
SSL.SSLOptions.Mode := sslmClient;
SSL.OnGetPassword := SSLGetPassword;
SSL.SSLOptions.Method := sslvTLSv1_2;
HTTP.IOHandler := SSL;
end;
Is there an event that I would tell me exactly what TLS version I am current actually connecting with when sending a post? I don't want there to be a surprise when they finally stop accepting TLS v1.1 connections.
Thanks.
There is no event specifically for that purpose. You would have to query the underlying SSL object directly, such as in the OnStatus event, using the SSL_get_version() function.
However, you are setting the Method to TLS 1.2 exclusively, so that is all Indy will use (as long as you use a version of OpenSSL that supports 1.2, otherwise Indy will silently fallback to 1.0).
On a side note, your UseSSL if block should look more like this:
If UseSSL then
Begin
If (SSL = nil) then
Begin
SSL := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
SSL.SSLOptions.Mode := sslmClient;
SSL.OnGetPassword := SSLGetPassword;
SSL.SSLOptions.Method := sslvTLSv1_2;
End;
HTTP.IOHandler := SSL;
end;
Here is an example how you can get info about SSL version.
(may need some update as I don't use latest Indy)
Declaration
procedure IdSSLIOHandlerSocketOpenSSLStatusInfoEx(ASender: TObject;
const AsslSocket: PSSL; const AWhere, Aret: Integer; const AType,
AMsg: string);
Assign
SSL.OnStatusInfoEx:=IdSSLIOHandlerSocketOpenSSLStatusInfoEx;
Usage
procedure THttpThread.IdSSLIOHandlerSocketOpenSSLStatusInfoEx(ASender: TObject;
const AsslSocket: PSSL; const AWhere, Aret: Integer; const AType,
AMsg: string);
begin
if AsslSocket.version = TLS1_VERSION then
...
end;
Hi i need help on how to retrieved the parameters and data using IdHttpServer from indy.
many of my application uses TIdMultiPartFormDataStream to send data over the php. I would like to use the TIdHTTPServer to verify parameters for some reason and forward the request to its destination.
i created a short example for you to see.
uses
IdContext, IdMultipartFormData;
// Server Side------------------------------------------------
IdHTTPServer1.Defaultport := 88;
IdHTTPServer1.active := True;
procedure TForm1.IdHTTPServer1CommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
// the request will be pass through its destination by POST/GET
// and send the result back to the client apps.
AResponseInfo.ContentText := ARequestInfo.Params.Text;
end;
// Client Side------------------------------------------------
// This will work using the standard Post or Get
procedure TForm1.btnPost1Click(Sender: TObject);
var
sl: TStringList;
res: String;
begin
sl := TStringList.Create;
try
sl.Add('Param1=Data1');
sl.Add('Param2=Data1');
sl.Add('Param3=Data2');
sl.Add('Param4=Data3');
res := IdHTTP1.Post('http://localhost:88/some.php', sl);
ShowMessage(res);
finally
sl.Free;
end;
end;
//how can i get the parameters and value for this code in my IdHttpServer
procedure TForm1.btnPost2Click(Sender: TObject);
var
mfd: TIdMultiPartFormDataStream;
res: String;
begin
mfd := TIdMultiPartFormDataStream.Create;
try
mfd.AddFormField('Param1', 'Data1');
mfd.AddFormField('Param2', 'Data1');
mfd.AddFormField('Param3', 'Data2');
mfd.AddFormField('Param4', 'Data3');
res := IdHTTP1.Post('http://localhost:88/some.php', mfd);
ShowMessage(res);
finally
mfd.Free;
end;
end;
and how would i know if the Client apps pass a TIdMultiPartFormDataStream type of parameter?
This has been asked and answered many times before in the Embarcadero and Indy forums. Please search through their archives, as well as other archives, like Google Groups, to find code examples.
In a nutshell, when the TIdHTTPServer.OnCommandGet event is triggered, if the AResponseInfo.ContentType property says multipart/form-data (the version of TIdHTTP.Post() you are using will send application/x-www-form-urlencoded instead), the AResponseInfo.PostStream property will contain the raw MIME data that the client posted. You can use the TIdMessageDecoderMIME class to parse it. However, that class was never intended to be used server-side, so it is not very intuitive to use, but it is possible nontheless.
In Indy 11, I am planning on implementing native multipart/form-data parsing directly into TIdHTTPServer itself, but there is no ETA on that yet as we have not started work on Indy 11 yet.
I think this is an easy question for someone familiar with Indy. I'm using Delphi 2010 and Indy 10. I am trying to get off the ground accessing an SSL web service. I think it will be a lot easier if I can get Fiddler to see my HTTP traffic. I have seen posts on StackOverflow that indicate it's no big thing to get Fiddler to see your Indy traffic, that you just have to configure the port to make it work. My question is how do you do that?
Here is my code so far:
procedure TForm1.Button1Click(Sender: TObject);
var slRequest: TStringList;
sResponse,
sFileName: String;
lHTTP: TIdHTTP;
lIOHandler: TIdSSLIOHandlerSocketOpenSSL;
begin
sFileName := 'Ping.xml';
slRequest := TStringList.Create;
try
slRequest.LoadFromFile(sFileName);
lHTTP := TIdHTTP.Create(nil);
lHTTP.Intercept := IdLogDebug1;
lIOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
try
lHTTP.IOHandler := lIOHandler;
sResponse := lHTTP.Post('https://FSETTESTPROD.EDD.CA.GOV/fsetservice', slRequest);
Memo1.Lines.Text := sResponse;
finally
lIOHandler.Free;
end;
finally
slRequest.Free;
end;
end;
Edit: If I don't use the proxy for Fiddler and click the button while Wireshark is running, I get this traffic in Wireshark.
You can set Indy to use the proxy fiddler provides easily by setting the ProxyParams:
try
lHTTP.IOHandler := lIOHandler;
lHTTP.ProxyParams.ProxyServer := '127.0.0.1';
lHTTP.ProxyParams.ProxyPort := 8888;
sResponse := lHTTP.Post('<URL>', slRequest);
Memo1.Lines.Text := sResponse;
finally
lIOHandler.Free;
end;
You should be able to see all traffic in Fiddler then.
Edit: If that does not work you can add a TIdLogDebug component and add it as interceptor (like you did in your question).
The OnReceive and OnSend events contain the complete headers sent and received aswell as the reply data:
procedure TForm10.captureTraffic(ASender: TIdConnectionIntercept;
var ABuffer: TArray<Byte>);
var
i: Integer;
s: String;
begin
s := '';
for i := Low(ABuffer) to High(ABuffer) do
s := s + chr(ABuffer[i]);
Memo1.Lines.Add(s);
end;
When i send a message from TCPClient to a TCPServer it will be handled using OnExecute event in the server . Now i want to handle the received messages in the Client but TCPClient doesn't have any event for this. So i have to make a thread to handle them manually. how can i do it ?
As others said in response to your question, TCP is not a message oriented protocol, but a stream one. I'll show you how to write and read to a very simple echo server (this is a slightly modified version of a server I did this week to answer other question):
The server OnExecute method looks like this:
procedure TForm2.IdTCPServer1Execute(AContext: TIdContext);
var
aByte: Byte;
begin
AContext.Connection.IOHandler.Writeln('Write anything, but A to exit');
repeat
aByte := AContext.Connection.IOHandler.ReadByte;
AContext.Connection.IOHandler.Write(aByte);
until aByte = 65;
AContext.Connection.IOHandler.Writeln('Good Bye');
AContext.Connection.Disconnect;
end;
This server starts with a welcome message, then just reads the connection byte per byte. The server replies the same byte, until the received byte is 65 (the disconnect command) 65 = 0x41 or $41. The server then end with a good bye message.
You can do this in a client:
procedure TForm3.Button1Click(Sender: TObject);
var
AByte: Byte;
begin
IdTCPClient1.Connect;
Memo1.Lines.Add(IdTCPClient1.IOHandler.ReadLn); //we know there must be a welcome message!
Memo1.Lines.Add('');// a new line to write in!
AByte := 0;
while (IdTCPClient1.Connected) and (AByte <> 65) do
begin
AByte := NextByte;
IdTCPClient1.IOHandler.Write(AByte);
AByte := IdTCPClient1.IOHandler.ReadByte;
Memo1.Lines[Memo1.Lines.Count - 1] := Memo1.Lines[Memo1.Lines.Count - 1] + Chr(AByte);
end;
Memo1.Lines.Add(IdTCPClient1.IOHandler.ReadLn); //we know there must be a goodbye message!
IdTCPClient1.Disconnect;
end;
The next byte procedure can be anything you want to provide a byte. For example, to get input from the user, you can turn the KeyPreview of your form to true and write a OnKeyPress event handler and the NextByte function like this:
procedure TForm3.FormKeyPress(Sender: TObject; var Key: Char);
begin
FCharBuffer := FCharBuffer + Key;
end;
function TForm3.NextByte: Byte;
begin
Application.ProcessMessages;
while FCharBuffer = '' do //if there is no input pending, just waint until the user adds input
begin
Sleep(10);
//this will allow the user to write the next char and the application to notice that
Application.ProcessMessages;
end;
Result := Byte(AnsiString(FCharBuffer[1])[1]); //just a byte, no UnicodeChars support
Delete(FCharBuffer, 1, 1);
end;
Anything the user writes in the form will be sent to the server and then read from there and added to memo1. If the input focus is already in Memo1 you'll see each character twice, one from the keyboard and the other form the server.
So, in order to write a simple client that gets info from a server, you have to know what to expect from the server. Is it a string? multiple strings? Integer? array? a binary file? encoded file? Is there a mark for the end of the connection? This things are usually defined at the protocol or by you, if you're creating a custom server/client pair.
To write a generic TCP without prior known of what to get from the server is possible, but complex due to the fact that there's no generic message abstraction at this level in the protocol.
Don't get confused by the fact there's transport messages, but a single server response can be split into several transport messages, and then re-assembled client side, your application don't control this. From an application point of view, the socket is a flow (stream) of incoming bytes. The way you interpret this as a message, a command or any kind of response from the server is up to you. The same is applicable server side... for example the onExecute event is a white sheet where you don't have a message abstraction too.
Maybe you're mixing the messages abstraction with the command abstraction... on a command based protocol the client sends strings containing commands and the server replies with strings containing responses (then probably more data). Take a look at the TIdCmdTCPServer/Client components.
EDIT
In comments OP states s/he wants to make this work on a thread, I'm not sure about what's the problem s/he is having with this, but I'm adding a thread example. The server is the same as shown before, just the client part for this simple server:
First, the thread class I'm using:
type
TCommThread = class(TThread)
private
FText: string;
protected
procedure Execute; override;
//this will hold the result of the communication
property Text: string read FText;
end;
procedure TCommThread.Execute;
const
//this is the message to be sent. I removed the A because the server will close
//the connection on the first A sent. I'm adding a final A to close the channel.
Str: AnsiString = 'HELLO, THIS IS _ THRE_DED CLIENT!A';
var
AByte: Byte;
I: Integer;
Client: TIdTCPClient;
Txt: TStringList;
begin
try
Client := TIdTCPClient.Create(nil);
try
Client.Host := 'localhost';
Client.Port := 1025;
Client.Connect;
Txt := TStringList.Create;
try
Txt.Add(Client.IOHandler.ReadLn); //we know there must be a welcome message!
Txt.Add('');// a new line to write in!
AByte := 0;
I := 0;
while (Client.Connected) and (AByte <> 65) do
begin
Inc(I);
AByte := Ord(Str[I]);
Client.IOHandler.Write(AByte);
AByte := Client.IOHandler.ReadByte;
Txt[Txt.Count - 1] := Txt[Txt.Count - 1] + Chr(AByte);
end;
Txt.Add(Client.IOHandler.ReadLn); //we know there must be a goodbye message!
FText := Txt.Text;
finally
Txt.Free;
end;
Client.Disconnect;
finally
Client.Free;
end;
except
on E:Exception do
FText := 'Error! ' + E.ClassName + '||' + E.Message;
end;
end;
Then, I'm adding this two methods to the form:
//this will collect the result of the thread execution on the Memo1 component.
procedure TForm3.AThreadTerminate(Sender: TObject);
begin
Memo1.Lines.Text := (Sender as TCommThread).Text;
end;
//this will spawn a new thread on a Create and forget basis.
//The OnTerminate event will fire the result collect.
procedure TForm3.Button2Click(Sender: TObject);
var
AThread: TCommThread;
begin
AThread := TCommThread.Create(True);
AThread.FreeOnTerminate := True;
AThread.OnTerminate := AThreadTerminate;
AThread.Start;
end;
TCP doesn't operate with messages. That is stream-based interface. Consequently don't expect that you will get a "message" on the receiver. Instead you read incoming data stream from the socket and parse it according to your high-level protocol.
Here is my code to Read / Write with Delphi 7. Using the Tcp Event Read.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ScktComp;
type
TForm1 = class(TForm)
ClientSocket1: TClientSocket;
Button1: TButton;
ListBox1: TListBox;
Edit1: TEdit;
Edit2: TEdit;
procedure Button1Click(Sender: TObject);
procedure ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
procedure ClientSocket1Error(Sender: TObject; Socket: TCustomWinSocket;
ErrorEvent: TErrorEvent; var ErrorCode: Integer);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
UsePort: Integer;
UseHost: String;
begin
UseHost := Edit1.Text;
UsePort := STRTOINT(Edit2.Text);
ClientSocket1.Port := UsePort;
ClientSocket1.Host := UseHost;
ClientSocket1.Active := true;
end;
procedure TForm1.ClientSocket1Read(Sender: TObject;
Socket: TCustomWinSocket);
begin
ListBox1.Items.Add(ClientSocket1.Socket.ReceiveText);
end;
procedure TForm1.ClientSocket1Error(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
var ErrorCode: Integer);
begin
ErrorCode:=0;
ClientSocket1.Active := False;
end;
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
ClientSocket1.Socket.SendText(Edit1.Text);
end;
end.
If you need the Indy client to handle incoming "messages" (definition of "message" depends on the protocol used), I recommend to take a look at the implementation of TIdTelnet in the protocols\IdTelnet unit.
This component uses a receiving thread, based on a TIdThread, which asynchronously receives messages from the Telnet server, and passes them to a message handler routine. If you have a similar protocol, this could be a good starting point.
Update: to be more specific, the procedure TIdTelnetReadThread.Run; in IdTelnet.pas is where the asynchronous client 'magic' happens, as you can see it uses Synchronize to run the data processing in the main thread - but of course your app could also do the data handling in the receiving thread, or pass it to a worker thread to keep the main thread untouched. The procedure does not use a loop, because looping / pausing / restarting is implemented in IdThread.
Add a TTimer.
Set its Interval to 1.
Write in OnTimer Event:
procedure TForm1.Timer1Timer(Sender: TObject);
var
s: string;
begin
if not IdTCPClient1.Connected then Exit;
if IdTCPClient1.IOHandler.InputBufferIsEmpty then Exit;
s := IdTCPClient1.IOHandler.InputBufferAsString;
Memo1.Lines.Add('Received: ' + s);
end;
Don't set Timer.Interval something else 1.
Because, the received data deletes after some milliseconds.