Ориентация главы

После KEX соединение шифровано, но личность ещё не доказана. Аутентификация SSH (RFC 4252) имеет форму меню методов: клиент перебирает методы, разрешённые сервером, — password (имя пользователя и пароль), publickey (подпись закрытым ключом — проб без подписи есть её жемчужина), keyboard-interactive (много раундов вызовов — носитель OTP/PAM), hostbased (поручительство хоста). Расслоение xssh: модули методов (четыре контрактные карточки password/publickey/keyboard/hostkey — каждая ведает только построением и разбором собственной нагрузки, не хранит учётные данные и не ведает раундами) поверх общего слоя сообщений (ssh_auth_message — кодирование/декодирование USERAUTH_REQUEST/FAILURE/SUCCESS/BANNER); оркестровка сессии (ssh_auth_session — запрос service, проверка стадий, бюджеты, согласованность направлений) сшивает их с транспортным ядром (глава 121); слой бюджета (ssh_auth_guard — чисто ресурсные бюджеты, общие для клиента и сервера) ограничивает попытки/раунды/байты/время. Неизвестные сообщения методов (60..79) прозрачно пропускаются — новый метод аутентификации компонует существующую сессию напрямую, машину состояний не трогая.

126.1引入

Механизм пробы метода publickey заслуживает открытия: клиент сперва шлёт без подписи вопрос «принимаете этот ключ?» (AuthPublicKeyWrite) — сервер отвечает PK_OK (ключ принят) или FAILURE; только после подтверждения отправляется подписанный запрос (экономия одной дорогой подписи и утечки подписываемого материала серверам, которые его не поддерживают). Подписываемый материал порождается AuthPublicKeySignDataWrite как string(session_id) || USERAUTH_REQUEST — неразрывный результат уходит подписанту вызывающего: аппаратные ключи, ssh-agent, криптография XRT или своя реализация. Ещё один случай паттерна «детерминированное ядро + внешний подписант» главы 83.

Многораундовая форма keyboard-interactive — вторая достопримечательность: сервер шлёт вызовы (список prompt — «пароль?» «третья цифра OTP?»), клиент отвечает, сервер может спрашивать снова — протокольный носитель многофакторной аутентификации. Ограничения: максимум один незавершённый вызов одновременно; суммарные попытки/раунды/байты/дедлайн ограничиваются слоем бюджета — «защита от перебора — это бюджет, а не удача».

126.2概念

Оркестровка сессии: расслоение и транзакции

1Модули методовпишут сообщения в буфер нагрузки вызывающего (без выделений, атомарно при отказе)
2Слой сессииWritePrepare (проверка стадии + резерв бюджета) -> transport строит линейный пакет -> сеть надёжно принимает
3Направление чтенияядро проверяет пакет -> ReadPrepare (проверка стадии + выдача представлений) -> верхний слой принимает

Ответственность четверная: модули методов ведают нагрузкой, сессия — стадиями и бюджетами, ядро — рамкой/шифрованием/порядком, драйвер — сетью — ни один слой не переходит границу. banner (приветственный/предупреждающий текст сервера) не меняет главного события; failure разрешает новый запрос («следующий» из меню методов); success фиксируется только направлением сервера (согласованность — клиент не может «объявить себя успешным»). Во время rekey драйвер сперва маршрутизирует управляющие сообщения транспорта (KEXINIT/NEWKEYS/DISCONNECT) в машины состояний KEX/transport, после завершения продолжает ту же auth session — аутентификация переживает rekey.

Четыре карточки методов

  • password (ssh_auth_password): USERAUTH_REQUEST + пароль открытым текстом (соединение уже шифровано — контраст с Basic главы 106: внутри шифрованного туннеля это приемлемо); PasswordWrite/Read строит и разбирает напрямую.
  • publickey: проб (без подписи) → PK_OK → подписанный (материал SignDataWrite → внешний подписант → подпись в запрос); алгоритм blob открытого ключа может отличаться от алгоритма запроса (запрос RSA SHA-2 с blob ssh-rsa — реальная деталь совместимости протокола), но алгоритм blob подписи обязан совпадать с запросом.
  • keyboard-interactive (RFC 4256): запрос (рекомендуется каноничный пустой language) → вызов (список name/instruction/prompt) → ответ; интерфейс «полная предварительная проверка + заимствованная итерация» (prompt непустой UTF-8, response может быть пустой; количество определяется полями протокола и буферами вызывающего, без фиксированного потолка в библиотеке и без выделения массивов объектов); много раундов гарантирует один вызов машина состояний + общий лимит раундов бюджета.
  • hostbased (ssh_auth_hostbased): подпись ключа хоста клиента + поручительство именем хоста — машинное доверие в кластерных средах; формат из семейства publickey.

Слой бюджета: auth guard

ssh_auth_guard — чисто ресурсный слой бюджета: не разбирает сообщения и не хранит имена пользователей: число попыток (подсчёт USERAUTH_REQUEST), интерактивные раунды (сообщения метода сервера считают раунды, ответы клиента — сообщения), бюджет байтов, дедлайн. Общий для обеих сторон: сервер защищается от перебора (лимит попыток клиента), клиент — от проволочек сервера (лимит времени, которое его могут мучить) — бюджет есть двусторонняя оборона.

Дисциплина представлений (в очередной раз)

Представления Request/Failure/Banner/Method заимствуют пакет и действительны только до фиксации чтения — асинхронные аутентификационные бэкенды (запрос OTP требует времени) обязаны копировать поля, сохраняемые через ожидание. Та же дисциплина, что в главах 95/103/109 — четвёртое появление в SSH-слое; вы встречали её в каждом сетевом слое.

126.3示例

Первая полная программа: построение сообщений publickey

Программа ниже из examples/auth_publickey — проб и форматы открытых ключей вместе:

extlibs/xssh/examples/auth_publickey/main.c源码 ↗
#include <stdio.h>

#include <xssh.h>



/* 构建 Ed25519 publickey 探测并读取算法。 */
int main(void)
{
	unsigned char arrRawKey[XSSH_ED25519_PUBLIC_SIZE] = { 0u };
	unsigned char arrKey[64];
	unsigned char arrPayload[160];
	xsshwriter KeyWriter;
	xsshwriter Writer;
	xsshauthpublickey PublicKey;

	if ( !xrtSshWriterInit(&KeyWriter, arrKey, sizeof(arrKey)) ||
		(xrtSshEd25519PublicKeyWrite(
			&KeyWriter,
			(xbytesview){ arrRawKey, sizeof(arrRawKey) }
		) != XSSH_OK) || !xrtSshWriterInit(
			&Writer,
			arrPayload,
			sizeof(arrPayload)
		) || (xrtSshAuthPublicKeyWrite(
			&Writer,
			XRT_STR_LITERAL("alice"),
			XRT_STR_LITERAL(XSSH_HOSTKEY_ED25519),
			(xbytesview){ arrKey, KeyWriter.Size }
		) != XSSH_OK) || (xrtSshAuthPublicKeyRead(
			(xbytesview){ arrPayload, Writer.Size },
			&PublicKey
		) != XSSH_OK) ) {
		return 1;
	}
	printf("algorithm=%.*s bytes=%zu\n",
		(int)PublicKey.Algorithm.Size,
		PublicKey.Algorithm.Data,
		Writer.Size);
	return 0;
}
$ gcc -O1 -DXRT_MODULE_ALL -DXSSH_MODULE_ALL -I single -I single/extlibs -include xrt.h -include xssh.h impl.c extlibs/xssh/examples/auth_publickey/main.c -lws2_32 -lbcrypt -ladvapi32 -liphlpapi(输出 algorithm=ssh-ed25519 与报文尺寸的自检结果)

Что только что произошло. (1) blob открытого ключа из образца hostkey главы 123 становится здесь материалом аутентификации — одна кодировка проходит рукопожатие (K_S), хранение (known_hosts) и аутентификацию (blob publickey) — переиспользование слоя форматов xssh. (2) xrtSshAuthPublicKeyWrite строит проб без подписи — печатаемые algorithm и bytes и есть сообщение-вопрос «можно ли этот ключ». (3) Каждый шаг реальной последовательности (проб→PK_OK→SignData→подписант→подписанный запрос) — независимый вход; внешний подписант вставляет между SignData и подписанным (гнездо HSM/агента). Семья: auth_password (сообщения пароля), auth_keyboard (итерационный интерфейс вызова/ответа), auth_hostbased (форма поручительства хоста), auth_message (общий слой), auth_guard (четыре бюджетные линии).

Вторая полная программа: старт сессии и запрос service

Вторая программа из examples/auth_session — отправная точка слоя оркестровки:

extlibs/xssh/examples/auth_session/main.c源码 ↗
#include <stdio.h>

#include <xssh.h>



/* 展示认证编排对象与直接写入最终 payload 的 service 起点。 */
int main(void)
{
	unsigned char arrPayload[64];
	xsshauthsession Session;
	xsshwriter Writer;

	if ( !xrtSshAuthSessionInit(&Session, XSSH_ROLE_CLIENT) ||
		!xrtSshWriterInit(&Writer, arrPayload, sizeof(arrPayload)) ||
		(xrtSshServiceRequestWrite(
			&Writer,
			XRT_STR_LITERAL(XSSH_SERVICE_USERAUTH)
		) != XSSH_OK) ) {
		return 1;
	}
	printf(
		"session=%zu service-request=%zu event=%d\n",
		sizeof(Session),
		Writer.Size,
		(int)xrtSshAuthSessionEvent(&Session)
	);
	return 0;
}
$ gcc -O1 -DXRT_MODULE_ALL -DXSSH_MODULE_ALL -I single -I single/extlibs -include xrt.h -include xssh.h impl.c extlibs/xssh/examples/auth_session/main.c -lws2_32 -lbcrypt -ladvapi32 -liphlpapi(输出会话结构尺寸、service 请求报文尺寸与初始事件的自检结果)

Что только что произошло. (1) xrtSshAuthSessionInit(&Session, XSSH_ROLE_CLIENT) — сессия на стеке; вывод sizeof (то же заявление о нулевой обузе, что в главах 117/118). (2) xrtSshServiceRequestWrite пишет запрос service ssh-userauth — первое сообщение аутентификации (сперва объявить сервис, потом говорить о методах); Writer.Size — размер сообщения. (3) xrtSshAuthSessionEvent запрашивает текущее главное событие — сессия ведётся событиями (что отправлять/ждать сейчас); слой драйвера диспетчеризирует модули методов по событию — рантайм главы 127 и есть полная форма этой диспетчеризации. (4) Предусловие Begin: принимается только транспорт той же роли, завершивший первый раунд KEX — насильственный порядок расслоения (нет шифрования — нет аутентификации).

126.4契约

  • Ответственность четверная: методы ведают нагрузкой, сессия — стадиями + бюджетом, ядро — рамкой, драйвер — сетью; никто не переходит границ.
  • Транзакция записи: метод пишет нагрузку → SessionPrepare (стадия + бюджет) → ядро оборачивает → сеть принимает → двойная фиксация; отмена сперва отбрасывает ядро, потом Abort (бюджет не расходуется).
  • Транзакция чтения: ядро проверяет → SessionPrepare (выдача представлений) → принятие двойной фиксацией; отказ — ReadAbort + закрыть transport.
  • Дисциплина представлений: Request/Failure/Banner/Method заимствуют пакет, действительны до фиксации чтения — асинхронные бэкенды копируют нужное.
  • banner/failure/success: banner не меняет главного события; failure разрешает новые запросы; success фиксируется только направлением сервера.
  • publickey: проб без подписи (подписывать только после PK_OK); материал подписи session_id||REQUEST уходит внешнему подписанту; алгоритм blob может отличаться от алгоритма запроса, алгоритм подписи обязан совпадать.
  • keyboard: один незавершённый вызов; prompt непустой UTF-8 / response может быть пустой; без потолка в библиотеке и без выделения массивов; много раундов ведают машина состояний + бюджет.
  • Неизвестные методы (60..79): прозрачный проход как PACKET_METHOD + Method взаймы — новые методы компонуют существующую сессию.
  • Бюджеты в обе стороны: попытки/раунды/байты/дедлайн, четыре линии; сервер против перебора, клиент против проволочек.
  • Сосуществование с rekey: управляющие сообщения маршрутизируются в KEX/transport; после завершения та же auth session продолжается.

Типичные ошибки

Ловушка 1: пропуск пробы и сразу подписанный запрос:Ловушка 1: пропуск пробы и сразу подписанный запрос

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

Причина: дизайн пробы — «сначала спроси, потом подписывай» — подписывать стоит только после PK_OK. Пропуск экономит один круг и платит стоимостью подписи плюс поверхностью безопасности.

错误写法
/* каждый раз сразу подписанный запрос */
sign_and_send(Signer, Key);   /* подписано, даже если сервер откажет */
正确写法
xrtSshAuthPublicKeyWrite(&Writer, Key, ...);    /* проб: без подписи */
send_and_wait();
if ( got_pk_ok() ) {
	xrtSshAuthPublicKeySignDataWrite(&Sig, ...);  /* материал */
	Signature = signer_sign(Signer, &Sig);        /* внешний подписант */
	xrtSshAuthPublicKeySignedWrite(&Writer, Key, Signature, ...);
}
Ловушка 2: представления prompt вызова keyboard сохранены до следующего раунда:Ловушка 2: представления prompt вызова keyboard сохранены до следующего раунда

Симптом: в многораундовой аутентификации второй раунд читает мусорные prompt — представления первого раунда умерли с освобождённым пакетом.

Причина: представления вызова/ответа заимствуют пакет; сохранение между раундами (между ожиданиями) требует копирования. Асинхронные OTP-бэкенды — зона высокого риска.

错误写法
on_challenge(Prompt) {
	g_Prompts = Prompt;   /* сохранены представления: пакет следующего раунда уже другой */
}
正确写法
on_challenge(xsshauthkeyboard* pAuth) {
	copy_prompts(pCh, &g_State);   /* копировать всё, что пересекает ожидание */
}
Ловушка 3: клиент без бюджета, которого тянет злой сервер:Ловушка 3: клиент без бюджета, которого тянет злой сервер

Симптом: подключились к злому серверу — аутентификация вечно «ещё раунд» — клиента тянут в бесконечных вызовах.

Причина: бюджет нужен не только серверу — дедлайн и потолок раундов клиента есть самозащита.

错误写法
/* клиент без guard - сервер получает столько раундов, сколько попросит */
正确写法
xsshauthguardpolicy Policy;
xrtSshAuthGuardPolicyInit(&Policy);   /* рекомендуемые RFC умолчания */
Policy.MaxAttempts = 3;      /* максимум 3 попытки */
Policy.MaxRounds = 5;        /* максимум 5 интерактивных раундов */
xrtSshAuthGuardInit(&Guard, &Policy, DeadlineMs);
/* при превышении разорвать активно - стоп-лосс против атак проволочек */

Практические задания

Базовое: обход сообщений четырёх методов

Прогнать образцы auth_password/publickey/keyboard/hostbased, аннотируя тип сообщения и поля каждого. Критерии приёмки: вы можете нарисовать различия полей метода в USERAUTH_REQUEST (password открытый текст / publickey blob + подпись / keyboard submethods).

Продвинутое: полная последовательность проб-подпись

Детерминированно прокрутить: проб→имитация PK_OK→SignDataWrite→поддельный подписант (фиксированная подпись)→SignedWrite→имитация SUCCESS. Сверить счёт бюджета на всём пути. Критерии приёмки: каждое сообщение последовательности читается обратно; перед SUCCESS расход бюджета — ровно два запроса.

Вызов: двухфакторная аутентификация end-to-end

keyboard в три раунда: пароль→OTP→подтверждение нового пароля (имитация PAM); бюджет клиента (3 раунда, 30 секунд); бюджет сервера (антиперебор). Пути верного и неверного OTP. Критерии приёмки: верный путь проходит SUCCESS за три раунда; неверный — FAILURE с корректным вычетом бюджета; путь тайм-аута клиента разрывает соединение активно.

Итоги

ТемаКратко
Расслоениеметоды (нагрузка) → общий слой сообщений → оркестровка сессии (стадия + бюджет) → транспортное ядро → сеть
Транзакция записиметод пишет → SessionPrepare → ядро оборачивает → приём → двойная фиксация; отмена бюджет не расходует
Транзакция чтенияядро проверяет → SessionPrepare (выдача представлений) → двойная фиксация / Abort + закрытие
Четыре методаpassword (открытый текст в туннеле) / publickey (проб + внешний подписант) / keyboard (много раундов) / hostbased
пробвопрос без подписи → подписывать после PK_OK; материал подписи session_id‖REQUEST
Детали алгоритмовалгоритм blob может отличаться от запроса (RSA SHA-2 с blob ssh-rsa); алгоритм подписи обязан совпадать
keyboardодин незавершённый вызов; prompt непустой / response может быть пустой; без потолка и выделений; бюджет лимитирует раунды
Неизвестные методы60..79 прозрачный проход + Method взаймы — новые методы не трогают машину состояний
Бюджеты в обе стороныпопытки/раунды/байты/дедлайн; антиперебор (сервер) + антипроволочка (клиент)
Дисциплина представленийзаимствуют пакет, действительны до фиксации — асинхронные бэкенды копируют

📖 Справочники API в этой главе:xssh-ssh_auth_session (18 записей)xssh-ssh_auth_publickey (6 записей)xssh-ssh_auth_keyboard (9 записей)

Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки