Глава 2: Более глубокий взгляд на Аутентификацию




Сайт создан в системе uCoz
Вернуться к содержанию

 

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

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

Этот документ не задается целью рассматривать детально преимущества и недостатки каждого метода аутентификации. Поэтому следует прочитать достаточно обширную документацию по этой теме для полного понимания преимуществ и рисков в безопасности, связанных с применения каждого из них.

Аутентификация с использованием публичного ключа

libssh полностью совместима с публичными и приватными ключами OpenSSH. Можно использовать метод аутентификации автоматического публичного ключа, поддерживаемый libssh, или ввернуть свой собственный с использованием функций публичного ключа.

Процесс аутентификации по публичному ключу состоит в следующем:

  • Производится перебор списка файлов, содержащие публичные ключи. Каждый ключ отправляется на SSH сервер, прежде чем сервер подтвердит его ( ключ ему известен, может быть использован для аутентификации пользователя).
  • Затем, обратно присылает приватный ключ на отправленный публичный ключ и отправляется подтверждающее сообщение, что приватный ключ известен.

Функция ssh_userauth_autopubkey() производит это, используя доступные ключи в "~/.ssh/". Она возвращает следующие значения:

  • SSH_AUTH_ERROR: В процессе аутентификации произошли серьезные проблемы;
  • SSH_AUTH_DENIED: ключ не совпал;
  • SSH_AUTH_SUCCESS: аутентификация произведена;
  • SSH_AUTH_PARTIAL: какие-то ключи совпали, но нужно ещё выполнить другие способы аутентификации (наподобие пароля).

Функция ssh_userauth_publickey_auto() так же пытается аутентифицировать используя SSH-агент, если таковой запущен, или в противном случае метод "none".

При желании использовать производить аутентификацию по своему собственному публичному ключу, следует выполнить следующие шаги:

  • Получить публичный ключ с использованием функции ssh_import_pubkey_file().
  • Опробовать публичный ключ на SSH-сервере используя ssh_userauth_try_publickey(). Если вернется значение SSH_AUTH_SUCCESS, SSH-сервер допускает аутентификацию с использованием публичного ключа и можно перейти к выполнению следующего шага.
  • Извлечь приватный ключ используя функцию ssh_pki_import_privkey_file(). Если потребуется кодовая фраза, она будет использована в качестве аргумента или в функции обратного вызова.
  • Произвести аутентификацию используя ssh_userauth_publickey() с данным приватным ключом.
  • В конце не забыть почистить память с использованием ssh_key_free()
  • .

Ниже приводится минимальный пример аутентификации по публичному ключу:

int authenticate_pubkey(ssh_session session)
{
int rc;
rc = ssh_userauth_publickey_auto(session, NULL);
if (rc == SSH_AUTH_ERROR)
{
fprintf(stderr, "Authentication failed: %s\n",
ssh_get_error(session));
return SSH_AUTH_ERROR;
}
return rc;
}
Смотри так же
ssh_userauth_publickey_auto()
ssh_userauth_try_publickey()
ssh_userauth_publickey()
ssh_pki_import_pubkey_file()
ssh_pki_import_privkey_file()
ssh_key_free()

Аутентификация по паролю

Функция ssh_userauth_password() служит для проведения аутентификации с использованием пароля. Она вернет SSH_AUTH_SUCCESS, если пароль принят, или в противном случае другие константы. Остается лишь запросить пароль и освободить его безопасным образом.

Если сервер посетовал, что пароль неправильный, можно еще аутентифицироваться с использованием клиента openssh (выдача пароля), так как возможно openssh признает только клавиатурно-интерактивный режим ввода пароля. Перейдите к этому режиму или попытайтесь настроить на стороне SSH-сервера ввод пароля в не зашифрованном виде.

Ниже проводится небольшой пример аутентификации по паролю

int authenticate_password(ssh_session session)
{
char *password;
int rc;
 
password = getpass("Enter your password: ");
rc = ssh_userauth_password(session, NULL, password);
if (rc == SSH_AUTH_ERROR)
{
fprintf(stderr, "Authentication failed: %s\n",
ssh_get_error(session));
return SSH_AUTH_ERROR;
}
 
return rc;
}
Смотри так же
ssh_userauth_password

Клавиатурно-интерактивный метод аутентификации

Клавиатурно-интерактивный метод, как это вытекает собственно из имени, подразумевает интерактивность. Сервер выдает один или более запросов, на которое пользователь должен ответить, прежде чем сервер примет решение об аутентификации.

ssh_userauth_kbdint() является основной клавиатурно-интерактивной функцией, которая возвращает значения SSH_AUTH_SUCCESS,SSH_AUTH_DENIED, SSH_AUTH_PARTIAL, SSH_AUTH_ERROR или SSH_AUTH_INFO, зависящие от результата выполнения запроса.

Клавиатурно-интерактивный метод аутентификации по SSH2 является возможность, которая позволяет серверу задавать клиенту определенное количество запросов интерактивно, прежде чем он решит разрешить или запретить вход в систему [прим.перевод. login переводится так, но никак учетное имя пользователя (account), так как происходит от фразового глагола log in].

Для начала, вызовем функцию ssh_userauth_kbdint() (имя пользователя и субметоды просто установим равным NULL-указателю) и сохраним ответ.

Если ответом будет SSH_AUTH_INFO, сервер указывает, что отправил несколько запросов, на которые должен ответить пользователь. Можно получить эти вопросы используя следующие функции:

ssh_userauth_kbdint_getnprompts(),
ssh_userauth_kbdint_getname(),
ssh_userauth_kbdint_getinstruction(),
ssh_userauth_kbdint_getprompt()

Ответить на каждый вопрос в запросе поможет функция ssh_userauth_kbdint_setanswer().

Затем, снова вызвать функцию ssh_userauth_kbdint() и запустить процесс, до тех пор пока эти функции возвращают нечто отличное от SSH_AUTH_INFO.

Ниже приводятся несколько замечаний:

  • Даже первый вызов может вернуть SSH_AUTH_DENIED или SSH_AUTH_SUCCESS.
  • Сервер может отправить пустой набор вопросов. Опять же может потребоваться разобрать ответы, которые могут содержать сообщения от сервера типа приветствия или что-то подобное. Просто нужно вызвать ssh_userauth_kbdint() до тех пор пока в этом есть необходимость
  • Понимание "name", "prompt", "instruction" может не много проводить к путанице, которую может разъяснить следующий фрагмент RFC.

Ниже приводится разъяснение того, как использовать информацию клавиатурно-интерактивной аутентификации, вытекающей собственно из RFC (rfc4256):

3.3 При получении пользовательским интерфейсом сообщения с запросом, клиент ДОЛЖЕН запросить пользователя следующее: в командной строке интерфейса набить имя и инструкцию(если та не пустая), снабдив новой строкой. Затем, в свою очередь, на каждый запрос, клиент ДОЛЖЕН отобразить запрос [прим.перевод имеется в виду приглашение для проверки подлинности пользователя] и прочитать, что ввел пользователь.
Клиент с графическим интерфейсом пользователя имеет разнообразные возможности того, как опросить пользователя. Одна из них состоит в использование поля имени (возможно предваряемое именем приложения) в качестве заголовка диалогового окна, в котором отображается один или несколько запросов. В этом же диалоговом окне, поля инструкций должны быть текстовыми сообщениям, а запрос — полями из меток для ввода текста. Все поля ДОЛЖНЫ быть видимы пользователю. Для примера реализация не должна скрывать поля имени, потому что в окнах отсутствуют названия; Вместо этого, НУЖНО найти другой способ отображения этой информации. Если запросы представлены в одном диалоговом окне, тогда клиент НЕ ДОЛЖЕН их размещать в отдельных окнах.
Все клиенты ОБЯЗАНЫ должным образом обрабатывать поле инструкции со встроенными новыми строками. Они так же должны отображать не менее 30 символов для имени или запросов. Если сервер предоставляет имена или запросы длиннее чем 30 символов, клиент МОЖЕТ обрезать их при отображении в полях. При этом, если он это сделал, ДОЛЖЕН непременно сообщить с чем связано отбрасывание символов.
Поле инструкции не должна обрезаться. Клиент должен использовать фильтрацию управляющих символов как обсуждалось в [SSH-ARCH] во-избежание атак через их отображение в поле управляющей формы.
Для каждого запроса соответствующее эхо-поле указывает должен ли или нет пользовательский ввод отображать печать символов. Клиенты ДОЛЖНЫ корректно отображать/маскировать пользовательский ввод независимо от других запросов в одном сообщении. Если клиент не принимает Эхо-поле по каким-то причинам, тогда клиент ОБЯЗАН сообщить об ошибке на стороне маскируемого ввода. Клиент GUI может иметь что-то похожее на кнопку-флажок, который переключает режим ввода маскирование/отображение символов. Клиент не должен добавлять никаких дополнительных символов к запросу таких как двоеточие ": ". Сервер является ответственным за весь текст, который будет отображен пользователю. Клиенты так же ОБЯЗАНЫ принимать пустые ответы от пользователей и принимать их в качестве пустых строк.

Следующий пример показывает как производить клавиатурно-интерактивную аутентификацию.

int authenticate_kbdint(ssh_session session)
{
int rc;
rc = ssh_userauth_kbdint(session, NULL, NULL);
while (rc == SSH_AUTH_INFO)
{
const char *name, *instruction;
int nprompts, iprompt;
 
name = ssh_userauth_kbdint_getname(session);
instruction = ssh_userauth_kbdint_getinstruction(session);
nprompts = ssh_userauth_kbdint_getnprompts(session);
 
if (strlen(name) > 0)
printf("%s\n", name);
 
if (strlen(instruction) > 0)
printf("%s\n", instruction);
 
for (iprompt = 0; iprompt < nprompts; iprompt++)
{
const char *prompt;
char echo;
 
prompt = ssh_userauth_kbdint_getprompt(session, iprompt, &echo);
if (echo)
{
char buffer[128], *ptr;
 
printf("%s", prompt);
if (fgets(buffer, sizeof(buffer), stdin) == NULL)
return SSH_AUTH_ERROR;
 
buffer[sizeof(buffer) - 1] = '\0';
if ((ptr = strchr(buffer, '\n')) != NULL)
*ptr = '\0';
 
if (ssh_userauth_kbdint_setanswer(session, iprompt, buffer) < 0)
return SSH_AUTH_ERROR;
 
memset(buffer, 0, strlen(buffer));
}
else
{
char *ptr;
 
ptr = getpass(prompt);
if (ssh_userauth_kbdint_setanswer(session, iprompt, ptr) < 0)
return SSH_AUTH_ERROR;
}
}
 
rc = ssh_userauth_kbdint(session, NULL, NULL);
}
return rc;
}

Смотри так же
ssh_userauth_kbdint()
ssh_userauth_kbdint_getnprompts()
ssh_userauth_kbdint_getname()
ssh_userauth_kbdint_getinstruction()
ssh_userauth_kbdint_getprompt()
ssh_userauth_kbdint_setanswer()

Аутентификация с методом "none"

      Основным назначением метода "none" является осуществление аутентификации без какой либо авторизации пользователя.Поэтому использовать этот метод нужно в том случае, если есть реальное желание или потребность разрешить анонимный доступ, но все таки лучше пользоваться одним из других методов аутентификации.

      Если учетная запись пользователя не имеет пароля и сервер настроен так, чтобы позволить вам войти без него, функция ssh_userauth_none() может вернуть SSH_AUTH_SUCCESS.

       Следующий пример показывает как осуществлять "none" аутентификацию:

int authenticate_kbdint(ssh_session session)
{
int rc;
rc = ssh_userauth_none(session, NULL, NULL);
return rc;
}

Получение списка поддерживаемых методов аутентификации

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

      Следующий пример показывает как получить список доступных методов аутентификации используя ssh_userauth_list() и как использовать результат её работы:

int test_several_auth_methods(ssh_session session)
{
int method, rc;
rc = ssh_userauth_none(session, NULL, NULL);
if (rc != SSH_AUTH_SUCCESS) {
return rc;
}
method = ssh_userauth_list(session, NULL);
if (method & SSH_AUTH_METHOD_NONE){
// см. соответствующий пример
// исходного кода функции authenticate_none()
rc = authenticate_none(session);
if (rc == SSH_AUTH_SUCCESS) return rc;
}
if (method & SSH_AUTH_METHOD_PUBLICKEY){
// см. соответствующий пример
// исходного кода функции authenticate_pubkey()
rc = authenticate_pubkey(session);
if (rc == SSH_AUTH_SUCCESS) return rc;
}
if (method & SSH_AUTH_METHOD_INTERACTIVE){
// см. соответствующий пример
// исходного кода функции authenticate_kbdint()
rc = authenticate_kbdint(session);
if (rc == SSH_AUTH_SUCCESS) return rc;
}
if (method & SSH_AUTH_METHOD_PASSWORD){
// см. соответствующий пример
// исходного кода функции authenticate_password()
rc = authenticate_password(session);
if (rc == SSH_AUTH_SUCCESS) return rc;
}
return SSH_AUTH_ERROR;
}

Получение баннера

     Сервер SSH может присылать баннер, который можно извлечь с ssh_get_issue_banner(), и затем отобразить пользователю.

      Следующий пример показывает как извлекать и выводить запрашиваемый баннер:

int display_banner(ssh_session session)
{
int rc;
char *banner;
/*
Does not work without calling ssh_userauth_none() first ***
That will be fixed ***/
 
rc = ssh_userauth_none(session, NULL);
if (rc == SSH_AUTH_ERROR)
return rc;
banner = ssh_get_issue_banner(session);
if (banner)
{
printf("%s\n", banner);
free(banner);
}
return rc;
}

 

Вернуться к содержанию