Показать сообщение отдельно

  #2  
Старый 06.06.2009, 22:15
lytgeygen
Новичок
Регистрация: 12.04.2009
Сообщений: 23
Провел на форуме:
34915

Репутация: 13
Отправить сообщение для lytgeygen с помощью ICQ
По умолчанию

Из этой строки нам понадобится значение Nonce для последующего построения ответа серверу, после чего мы подготавливаем строку ответа, которую мы передадим на сервер в ответном пакете, предварительно закодировав ее в Base64. Итак, ответная строка будет иметь следующий вид:

Код:
username="delphi-test",
realm="jabber.ru",
nonce="22647748",
cnonce="2313e069649daa0ca2b76363525059ebd",
nc=00000001,
qop=auth,
digest-uri="xmpp/jabber.ru"
,charset=utf-8,
response=16351f86cc5591312e20b4ccd880eadb
где:

username - JID-node пользователя

realm - JID-domain пользователя

nonce - Уникальный код сессии, присланный нам ранее сервером

cnonce
- Уникальный код ответной клиентской сессии, сгенерированный клиентом

nc - Так называемый once count - сколько раз был использован текущий nonce. Обычно значение параметра равно 00000001, его и будем использовать. На самом деле параметр довольно интересный и стоит отдельного рассмотрения и изучения в RFC, но как показала практика его смело можно игнорировать.

digest-uri - Протокол подключения, для XMPP сервера он состоит из соединения строк "xmpp/" + JID Domain

charset - поддержка кодировки пароля и имени, в нашем случае UTF-8

И самый важный параметр response в котором заключен ключ ответа серверу, включающий в себя пароль и ответные данные в формате MD5 строящийся по определенному алгоритму.

Алгоритм построения строки ответа и параметра Response более подробно мы рассмотрим далее в подразделе "RFC 2831 использование MD5-Digest аутенфикации в SASL". Пока примем к сведению, что текущее и следующие два действие относится уже к данному алгоритму.

Итак, строку ответа, мы сформировали, закодировали в Base64 и отправляем обратно серверу (всё это должно быть в одну строчку, но, чтобы страница не расползалась, разбито на несколько):

Код:
<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
dXNlcm5hbWU9ImRlbHBoaS10ZXN0IixyZWFsbT0iamFiYmVyLnJ1Iixub25jZT0iMjI2ND
c3NDgiLGNub25jZT0iMjMxM2UwNjk2NDlkYWEwY2EyYjc2MzYzNTI1MDU5ZWJkIixu
Yz0wMDAwMDAwMSxxb3A9YXV0aCxkaWdlc3QtdXJpPSJ4bXBwL2phYmJlci5ydSIsY2
hhcnNldD11dGYtOCxyZXNwb25zZT0xNjM1MWY4NmNjNTU5MTMxMmUyMGI0Y2Nk
ODgwZWFkYg==
</response>
Если все нормально мы получим следующий ответ:

Код:
<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
cnNwYXV0aD1lOTg5NjZjZjUxNjliZWUzOTYzNGU5Zjk5ZTIzZDZhYg==
</challenge>
Тут нам особо ничего не нужно, подтверждаем принятие его, отправив со стороны клиента:

Код:
<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>
И если все прошло успешно, то получаем со стороны сервера пакет, говорящий нам о том, что аутенфикация прошла успешно:

Код:
<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>
Далее мы снова посылаем пакет рукопожатия:

Код:
<?xml version='1.0' encoding='UTF-8'?>
      <stream:stream to='jabber.ru'
           xmlns='jabber:client'
           xmlns:stream='http://etherx.jabber.org/streams'
           xml:l='ru' version='1.0'>
Получаем ответ:
Код:
<?xml version='1.0'?>
      <stream:stream xmlns='jabber:client'
            xmlns:stream='http://etherx.jabber.org/streams'
            id='4096919146'
            from='jabber.ru'
            version='1.0'
            xml:lang='en'>
После чего по стандарту мы должны связать нашего клиента с JID-ресурсом, что мы и делаем, посылая строку в формате UTF-8:

Код:
<iq type='set' id='bund_2'>
      <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
            <resource>тестовая</resource>
      </bind>
</iq>
Примечание: Все листинги будут представлены в ASCII формате, хотя на самом деле прием и посылка пакетов ведется в UTF-8. Однако что бы Вам не читать крякозаблы в листингах примеров, кодировка будет в показана в ASCII.

Сервер подтверждает связывание ресурса с данным клиентом:

Код:
<iq id='bund_2' type='result'>
      <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
           <jid>delphi-test@jabber.ru/тестовая</jid>
      </bind>
</iq>
Клиент посылает пакет присутствия в сети

Код:
<presence><show></show></presence>
и все, аутенфикация пройдена, значок клиента в контакт-листе становится зелененьким, теперь он может посылать и принимать сообщения.

RFC 2831 использование MD5-Digest аутенфикации в SASL

Итак, аутенфикация решает следующие задачи: Передача пароля на сервер, в закрытом виде, защиту от повторяющихся атак (monitoring nc value), защиту (monitoring nonce) в определённый промежуток времени от определённого клиента. Для того, что бы понять, как работает данный стандарт, разберем основы SASL.

Общие принципы работы SASL

Метод SASL (Simple Authentication and Security Layer) используется для добавления поддержки аутентификации в различные протоколы соединения. Для аутентификации могут быть использованы различные механизмы.

Имя требуемого механизма задаётся клиентом в команде аутентификации. Если сервер поддерживает указанный механизм, он посылает клиенту последовательность окликов (challenges), на которые клиент посылает ответы (responses), чтобы удостоверить себя. Содержимое окликов и ответов определяется используемым механизмом и может представлять собой двоичные последовательности произвольной длины. Кодировка последовательностей определяется прикладным протоколом. Вместо очередного оклика сервер может послать подтверждение аутентификации или отказ. Кодировка также определяется протоколом. Вместо ответа клиент может послать отказ от аутентификации. Кодировка опять определяется протоколом. В результате обменов откликам и ответами должна произойти аутентификация (или отказ), передача идентификатора клиента (пустой идентификатор влечёт получение идентификатора из аутентификации) серверу и, возможно, договорённость об использовании безопасного транспортного протокола (security layer), включая максимальный размер буфера шифрования.

Идентификатор клиента может отличаться от идентификатора, определяемого из аутентификации, для обеспечения возможности работы прокси.

Реализация на примере механизма MD5-Digest

Схема работы SASL для нашего клиента основана на использовании механизма MD-Digest и имеет следующий алгоритм работы:

Сервер посылает случайную строку nonce, наличие поддержки utf-8 в параметре charset для имени и пароля, алгоритм аутентификации (обязательно md5-sess) в параметре algorithm.

То есть те данные, что мы раскодировали ранее из пакета challenge:

Код:
nonce="22647748",qop="auth",charset=utf-8,algorithm=md5-sess
Клиент отвечает строкой, содержащей: идентификатор клиента username, идентификатор домена realm, полученную от сервера случайную строку nonce, случайную строку клиента cnonce, номер запроса (позволяет серверу заметить попытку replay attack) nc. параметр digest-uri (сочетание имени сервиса, имени сервера т.е. 'xmpp/' + JID Domain), строку responce подтверждающею знание пароля и ответ на оклик (MD5 от имени пользователя, realm, пароля, случайной строки сервера, случайной строки клиента, идентификатора клиента, номера запроса, уровня защиты, digest-uri; некоторые компоненты берутся в виде MD5, некоторые в исходном виде, некоторые в обоих видах), использование utf-8 для имени и пароля, принятый алгоритм шифрования и идентификатор клиента.

То есть, как вы догадались эта та строка, которую мы формируем в ответ:

Код:
username="delphi-test",
realm="jabber.ru",
nonce="22647748",
cnonce="2313e069649daa0ca2b76363525059ebd",
nc=00000001,
qop=auth,
digest-uri="xmpp/jabber.ru",
charset=utf-8,
response=16351f86cc5591312e20b4ccd880eadb
Сервер проверяет ответ на оклик и посылает ответ на ответ в похожем формате (но всё же отличающемся, чтобы клиент мог убедиться в подлинности сервера). Данный механизм слабее системы с открытыми ключами, но лучше простой CRAM-MD5.

Примечание: Стоит отметить, что может предусматриваться упрощённый протокол повторной аутентификации (начинается сразу с посылки клиентом ответа с увеличенным на 1 номером запроса).

Алгоритм вычисления строки ответа response

Алгоритм вычисления строки ответа response имеет следующую формулу:

Код:
response-value  =
         HEX( KD ( HEX(H(A1)),
                 { nonce-value, ":" nc-value, ":",
                   cnonce-value, ":", qop-value, ":", HEX(H(A2)) }))
      A1 = { H( { username-value, ":", realm-value, ":", passwd } ),
           ":", nonce-value, ":", cnonce-value }
      A2 = { "AUTHENTICATE:", digest-uri-value }
Где:

Выражение { a, b, ... } - означает сложение строк a, b

HEX(n) - 16-байтовый MD5-хеш n, приведенный в 32 байтовую Hex-строку в нижнем регистре. Фактически строковое представление дайджеста MD5.

H(s) - 16-байтовый MD5-хеш строки s

KD(k, s) - объединение данных (строк) k, s

H({k, ":", s}) - 16-байтовый MD5-хеш, полученный в результате сложения строки k, ":", S

Как видите, особо ничего сложного нет. Вот алгоритм расчета реализованный мной на Delphi:

Код:
function GenResponse(UserName, realm, digest_uri, Pass, nonce, cnonce : String) : string;
const
  nc         = '00000001';
  gop        = 'auth';
var
  A2, HA1, HA2,
  sJID : String;
  Razdel    : Byte;
  Context   : TMD5Context;
  DigestJID : TMD5Digest;
  DigestHA1 : TMD5Digest;
  DigestHA2 : TMD5Digest;
  DigestResponse : TMD5Digest;
begin
  Razdel := Ord(':');
  // ВЫЧИСЛЯЕМ А1 по формуле RFC 2831
  //  A1 = { H( { username-value, ":", realm-value, ":", passwd } ),
  //           ":", nonce-value, ":", cnonce-value, ":", authzid-value }
  sJID  := format('%S:%S:%S', [username, realm, Pass]);
  MD5Init(Context);
  MD5UpdateBuffer(Context, PByteArray(@sJID[1])  , Length(sJID));
  MD5Final(DigestJID, Context);

  MD5Init(Context);
  MD5UpdateBuffer(Context, PByteArray(@DigestJID),SizeOf(TMD5Digest));
  MD5UpdateBuffer(Context, @Razdel , SizeOf(Razdel));
  MD5UpdateBuffer(Context, PByteArray(@nonce[1])  , Length(nonce));
  MD5UpdateBuffer(Context, @Razdel , SizeOf(Razdel));
  MD5UpdateBuffer(Context, PByteArray(@cnonce[1])  , Length(cnonce));
  MD5Final(DigestHA1, Context);

  // ВЫЧИСЛЯЕМ А2 по формуле RFC 2831
  //  A2       = { "AUTHENTICATE:", digest-uri-value }
  A2   := format('AUTHENTICATE:%S', [digest_uri]);
  MD5Init(Context);
  MD5UpdateBuffer(Context, PByteArray(@A2[1])  , Length(A2));
  MD5Final(DigestHA2, Context);

  // ВЫЧИСЛЯЕМ RESPONSE по формуле RFC 2831
  //  HEX( KD ( HEX(H(A1)),
  //                 { nonce-value, ":" nc-value, ":",
  //                   cnonce-value, ":", qop-value, ":", HEX(H(A2)) }))
  HA1 := LowerCase( PacketToHex(@DigestHA1, SizeOf(TMD5Digest)));
  HA2 := LowerCase( PacketToHex(@DigestHA2, SizeOf(TMD5Digest)));
  MD5Init(Context);
  MD5UpdateBuffer(Context, PByteArray(@HA1[1]),Length(HA1));
  MD5UpdateBuffer(Context, @Razdel , SizeOf(Razdel));
  MD5UpdateBuffer(Context, PByteArray(@nonce[1])  , Length(nonce));
  MD5UpdateBuffer(Context, @Razdel , SizeOf(Razdel));
  MD5UpdateBuffer(Context, PByteArray(@nc[1])  , Length(nc));
  MD5UpdateBuffer(Context, @Razdel , SizeOf(Razdel));
  MD5UpdateBuffer(Context, PByteArray(@cnonce[1])  , Length(cnonce));
  MD5UpdateBuffer(Context, @Razdel , SizeOf(Razdel));
  MD5UpdateBuffer(Context, PByteArray(@gop[1])  , Length(gop));
  MD5UpdateBuffer(Context, @Razdel , SizeOf(Razdel));
  MD5UpdateBuffer(Context, PByteArray(@HA2[1]),Length(HA2));
  MD5Final(DigestResponse, Context);
  Result := LowerCase( PacketToHex(@DigestResponse, SizeOf(TMD5Digest)) )
 end;
На входе функция получает параметры рассмотренные нами ранее.
 
Ответить с цитированием