Ориентация главы
После 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概念
Оркестровка сессии: расслоение и транзакции
Ответственность четверная: модули методов ведают нагрузкой, сессия — стадиями и бюджетами, ядро — рамкой/шифрованием/порядком, драйвер — сетью — ни один слой не переходит границу. 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 — проб и форматы открытых ключей вместе:
#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; }
Что только что произошло. (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 — отправная точка слоя оркестровки:
#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; }
Что только что произошло. (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 продолжается.
Типичные ошибки
Симптом: каждая аутентификация делает дорогую подпись — задержка 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, ...); }
Симптом: в многораундовой аутентификации второй раунд читает мусорные prompt — представления первого раунда умерли с освобождённым пакетом.
Причина: представления вызова/ответа заимствуют пакет; сохранение между раундами (между ожиданиями) требует копирования. Асинхронные OTP-бэкенды — зона высокого риска.
on_challenge(Prompt) { g_Prompts = Prompt; /* сохранены представления: пакет следующего раунда уже другой */ }
on_challenge(xsshauthkeyboard* pAuth) { copy_prompts(pCh, &g_State); /* копировать всё, что пересекает ожидание */ }
Симптом: подключились к злому серверу — аутентификация вечно «ещё раунд» — клиента тянут в бесконечных вызовах.
Причина: бюджет нужен не только серверу — дедлайн и потолок раундов клиента есть самозащита.
/* клиент без 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 расход бюджета — ровно два запроса.
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 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки