Работа С Https Без Компонент

Vadik(R)

Well-known member
12.12.2007
469
0
#1
Добрый вечер, форумчане!

Есть у меня такой вопрос. Как на низком уровне работать с https?
Мои попытки вот:
Код:
program TestSSL;

{$APPTYPE CONSOLE}

uses
Dialogs, SysUtils, Windows, WinSock;

procedure SendText(S: TSocket; Buffer: AnsiString);
begin
send(S, Buffer[1], Length(Buffer), 0);
end;

procedure RecvText(S: TSocket; var Content: AnsiString);
var
Buffer: AnsiString;
Bytes: Integer;
begin
Content := '';
repeat
SetLength(Buffer, 8192);
Bytes := recv(S, Buffer[1], Length(Buffer), 0);
SetLength(Buffer, Bytes);
Content := Content + Buffer;
until Bytes = 0;
end;

var
Content: AnsiString;
S: TSocket;
SockAddr: TSockAddr;
WSAData: TWSAData;

begin
WSAStartup($202, WSAData);
SockAddr.sin_family := AF_INET;
SockAddr.sin_port := htons(443);
SockAddr.sin_addr.S_addr := inet_addr('69.58.188.34');
S := socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
connect(S, SockAddr, SizeOf(TSockAddr));
Content := '';
Content := Content + 'GET / HTTP/1.1' + #13#10;
Content := Content + 'Host: bitly.com' + #13#10;
Content := Content + 'Connection: close' + #13#10;
Content := Content + 'User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17' + #13#10;
Content := Content + 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' + #13#10;
Content := Content + 'Accept-Encoding: gzip,deflate,sdch' + #13#10;
Content := Content + 'Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4' + #13#10;
Content := Content + 'Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.3' + #13#10;
Content := Content + '' + #13#10;
SendText(S, Content);
RecvText(S, Content);
closesocket(S);
WSACleanup;
ShowMessage(Content);
end.
Хочу получить код
Для просмотра контента необходимо: Войти или зарегистрироваться

Но сервер выдаёт такую ошибку: 400 The plain HTTP request was sent to HTTPS port
Предполагаю, что дело в том, что https-соединение защищено и прежде, чем делать запросы на получение кода, надо данные как-то зашифровать. И заранее об этом договориться с сервером, алгоритм шифрования, ключи...
Но я не знаю, какие именно надо прежде запросы делать.
Обычно смотрю, какие запросы надо делать через панель разработчика в Google Chrome. Но он ничего толкового в этот раз не подсказал, а с обычным http его всегда хватает.
Помогите, пожалуйста.
 

Vadik(R)

Well-known member
12.12.2007
469
0
#2
Нашёл пример работы с https здесь http://synapse.ararat.cz/doku.php/download
Вроде бы можно было и его использовать, но ведь интерес!
Целый день дебажу прогу, но никак не могу понять, в каком моменте используется этот SSL :(
Вероятно из-за того, что пока происходит выполнение по шагам сервер уже успевает разрывать соединение из-за тайм аута.
Голова взрывается, а гугл не помогает. Неужели никто раньше этой проблемой не интересовался?

P.S. WireShark чтоль подключить?
Никак не пойму, то ли за защищённым соединением должна сама виндовс следить, а мне стоит просто передать в какую-нибудь WinAPI дополнительный параметр о защите...
Толи я просто никак не могу отследить тот момент, где получают/передают какие-либо ключи и шифруется информация.
 

Vadik(R)

Well-known member
12.12.2007
469
0
#4
Прошу помощи. Вот полная версия кода из последней ссылки: http://www.everfall.com/paste/id.php?qhdubuy2o9id

Нужна помощь в этой части кода, что происходит до неё - понятно:
C++:
bool Handshake(SOCKET s, wchar_t * pwzHostname, PCredHandle phCredential, PCtxtHandle phContext)
{
SecBufferDesc  InBufferDesc;
SecBuffer	  InBuffers[2];
InBufferDesc.cBuffers = _countof(InBuffers);
InBufferDesc.pBuffers = InBuffers;
InBufferDesc.ulVersion = SECBUFFER_VERSION;
SecBufferDesc  OutBufferDesc;
SecBuffer	  OutBuffers[1];
OutBufferDesc.cBuffers = _countof(OutBuffers);
OutBufferDesc.pBuffers = OutBuffers;
OutBufferDesc.ulVersion = SECBUFFER_VERSION;
char *		 InSecBuffer = NULL;
char *		 OutSecBuffer = NULL;
TimeStamp	  tsExpiry;
ULONG m_byteOrder = SECURITY_NETWORK_DREP;
ULONG requiredSecurity =
ISC_REQ_SEQUENCE_DETECT  |
ISC_REQ_REPLAY_DETECT	 |
ISC_REQ_CONFIDENTIALITY  |
ISC_RET_EXTENDED_ERROR	|
ISC_REQ_STREAM;
ULONG actualSecurity;

SECURITY_STATUS scRet;
bool			success = false;

// Figure out how large of a buffer to allocate for a token.
PSecPkgInfo pPackageInfo;
scRet = QuerySecurityPackageInfo(SecurityPackage, &pPackageInfo);
ULONG cbMaxTokenSize = pPackageInfo->cbMaxToken;
FreeContextBuffer(pPackageInfo);

// Allocate a buffer large enough to hold the largest token.
InSecBuffer = (char *) LocalAlloc(LMEM_FIXED, cbMaxTokenSize);
OutSecBuffer = (char *) LocalAlloc(LMEM_FIXED, cbMaxTokenSize);
if (!InSecBuffer || !OutSecBuffer)
{
oom;
goto cleanup;
}
ULONG& cbInSecBuffer = InBuffers[0].cbBuffer;
ULONG& cbOutSecBuffer = OutBuffers[0].cbBuffer;

//Do some one-time initialization of the buffers
InBuffers[0].BufferType = SECBUFFER_TOKEN;
InBuffers[0].pvBuffer = InSecBuffer;
InBuffers[1].BufferType = SECBUFFER_EMPTY;
InBuffers[1].pvBuffer = NULL;
InBuffers[1].cbBuffer = 0;

SecInvalidateHandle(phContext);
do
{
cbInSecBuffer = 0;
BOOL bFirstTime = !SecIsValidHandle(phContext);
int cbData;

do
{
if (InBuffers[1].BufferType == SECBUFFER_EXTRA)
{
// The server sent more to the token than could be processed in a single
// call to InitializeSecurityContext. We move this extra data into the token
// buffer and prepare to call the function again.
InBuffers[1].pvBuffer = (byte*)InBuffers[0].pvBuffer + cbData - InBuffers[1].cbBuffer;
MoveMemory(InSecBuffer, InBuffers[1].pvBuffer, InBuffers[1].cbBuffer);
InBuffers[0].pvBuffer = InSecBuffer;
InBuffers[0].cbBuffer = InBuffers[1].cbBuffer;
InBuffers[0].BufferType = SECBUFFER_TOKEN;

InBuffers[1].BufferType = SECBUFFER_EMPTY;
InBuffers[1].pvBuffer = NULL;
InBuffers[1].cbBuffer = 0;
}
else // don't try to read more data from the server (yet) if there was data in the extra buffer
{
// Read in response from server if we already said something to it.
if (!bFirstTime)
{
cbData = recv(s, InSecBuffer + cbInSecBuffer, cbMaxTokenSize - cbInSecBuffer, 0);
if(cbData == SOCKET_ERROR || cbData == 0)
{
error("failure sending data to server 0x%x\n", WSAGetLastError());
goto cleanup;
}
verbose("%5d bytes of handshake data received.\n", cbData);
cbInSecBuffer += cbData;

InBuffers[0].BufferType = SECBUFFER_TOKEN;
InBuffers[0].pvBuffer = InSecBuffer;
}
}

// Prepare client token.
OutBuffers[0].BufferType = SECBUFFER_TOKEN;
OutBuffers[0].pvBuffer = OutSecBuffer;
OutBuffers[0].cbBuffer = cbMaxTokenSize;

// Prepare client token.
scRet = InitializeSecurityContextW(
phCredential,
bFirstTime ? NULL : phContext,
bFirstTime ? pwzHostname : NULL,
requiredSecurity,
0,
m_byteOrder,
bFirstTime ? NULL : &InBufferDesc,
0,
bFirstTime ? phContext : NULL,
&OutBufferDesc,
&actualSecurity,
&tsExpiry);
} while (scRet == SEC_E_INCOMPLETE_MESSAGE);

// Send token to server if there is a token.
if(OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != NULL)
{
cbData = send(s,
(const char*)OutBuffers[0].pvBuffer,
OutBuffers[0].cbBuffer,
0);

if(cbData == SOCKET_ERROR || cbData == 0)
{
error("failure sending data to server 0x%x\n", WSAGetLastError());
goto cleanup;
}

verbose("%5d bytes of handshake data sent.\n", cbData);
}
} while (scRet == SEC_I_CONTINUE_NEEDED);

if (scRet != SEC_E_OK)
{
error("server handshake failed with error code 0x%x\n", scRet);
goto cleanup;
}

success = true;

cleanup:

if (InSecBuffer) LocalFree(InSecBuffer);
if (OutSecBuffer) LocalFree(OutSecBuffer);

if (!success)
{
if (SecIsValidHandle(phContext))
{
DeleteSecurityContext(phContext);
SecInvalidateHandle(phContext);
}
}

return success;
}
Из названия понятно, что здесь происходит рукопожатие. Причём двойное.
Но в целом, тут мало что понятно. Куча каких-то непонятных структур безопасности, которые я смотрел на MSDN, но запутался. Не понятно, почему структуры заполняются именно такими параметрами, а не какими-либо другими.
Хотелось бы найти объяснение этого участка кода в следующем виде:
Для установки https-соединения необходим процесс рукопожатия. Вначале мы должны отправить серверу такие-то данные о себе (поддерживаемую версию SSL и поддерживаемые алгоритмы шифрования? (источник - http://www.securitylab.ru/analytics/216405.php)). Для того, чтобы получить эти данные, необходимо вызвать такую-то функцию. Эта функция принимает дофига параметров:
Первый параметр такой-то, он отвечает за то-то, поэтому мы присваиваем ему такое-то значение...
К сожалению, подобного разбора в интернете нигде не находил. Сам бы написал хорошую статью на эту тему, да вот пока не понимаю, как это всё работает... Кто-нибудь может помочь, кто силён в этой теме?
Заранее спасибо!
 

sinkopa

Well-known member
17.06.2009
344
4
#5
Из названия понятно, что здесь происходит рукопожатие. Причём двойное.
Но в целом, тут мало что понятно. Куча каких-то непонятных структур безопасности, которые я смотрел на MSDN, но запутался. Не понятно, почему структуры заполняются именно такими параметрами, а не какими-либо другими.
Хотелось бы найти объяснение этого участка кода в следующем виде:К сожалению, подобного разбора в интернете нигде не находил. Сам бы написал хорошую статью на эту тему, да вот пока не понимаю, как это всё работает... Кто-нибудь может помочь, кто силён в этой теме?
Заранее спасибо!
Ну... на пальцах в "два плевка" не объяснишь... :rolleyes:
Короче... Есть один парень, звать Владислав Баженов (Vlad) - болшой фанат библиотеки Synapse (библиотека бесплатная, с открытым кодом).
На его сайте, вот тут http://www.webdelphi.ru/tag/synapse/
больше 50 статей посвященных библиотеке, с подробнейщим описанием, разбором "что к чему", примерами и демо (ссылки для скачивания).
На вскидку, по Вашей теме:
Synapse. Авторизация на сайте. Работа с HTTPS.
Библиотека Synapse. Работа с модулем HTTPSend.pas.
Думаю, есть и еще...
Почитайте. Даже если он Вас "не уговорит" использовать Synapse, там подробно описан порядок "рукопожатия"... :)
 

Vadik(R)

Well-known member
12.12.2007
469
0
#6
sinkopa, за статьи спасибо, правда на тот момент, когда я хотел их почитать - сайт не открывался, лежал.
Но от модуля Synapse решил отказаться, так как так и не смог разобраться, что именно происходит при установки https-соединения.
Стал искать эту тему в интернете ещё, в итоге наткнулся на какое-то описание SSL (уже точно не помню, но вроде как раз я наткнулся именно на стандарт, что я и искал), на поддомене mozilla.org.
Там было очень много текста, да ещё и на английском (было бы хотя бы что-то одно из этих двух), и я решил, что ответ на свой вопрос я нашёл, но он слишком большой, время всё-таки жалко было на его изучение. В итоге я просто разобрался в коде, который был выложен на сайте Microsoft, и перекодил его на Delphi (код, кстати не идеальный (например, вызывается функция ntohs на том месте, где должна быть htons, просто для двухбайтового числа работа этих функций одинакова), но рабочий, разобрался, пока транслировал программу на Delphi).
http://www.everfall.com/paste/id.php?gtadnp23tdv2 - SSPI
http://www.everfall.com/paste/id.php?yqfbd38lzjl0 - SSL
http://www.everfall.com/paste/id.php?hbltmky7i1s3 - Unit1
SSPI - это модуль Security Support Provider Interface, в Visual Studio он уже был готовый, на Delphi я такого не обнаружил, хотя видел в интернете частично переписанным его для Delphi. По сути, я тоже часть модуля и переписал, там только те функции, которые нужны для установки защищённого соединения из библиотеки security.dll.
SSL - это тот код, который выложен на Microsoft. Просто захотелось его как-то отделить.
Кого заинтересует, он нуждается в небольших доработках. В плане, что функция ReceiveResponse должна возвращать строку - ответ от сервера, а SendRequest логично бы было принимать строку, которую необходимо только зашифровать и передать. Собственно, это не сложно дописать, и я, возможно, выложу сюда дальнейшую версию этого модуля. Сразу переделывать эти функции я не стал, я просто был рад, что код и на Delphi стал рабочий, и прям "не дышать" хотелось :)
Unit1 - пример использования. Вызываются функции из модуля SSL.

Для себя интересную работу сделал, узнал что-то новое, вполне доволен. Хотя и времени она отняла достаточно.
Выкладываю результаты сюда, вдруг пригодятся такому же извращенцу, как я, не принимающему готовых решений :)