Разработка и применение модуля PAM для аутентификации в Astra Linux с использованием Рутокен ЭЦП и Рутокен S



    В этой статье мне бы хотелось рассказать о том, как приложения в Linux могут использовать систему Подключаемых Модулей Безопасности (Pluggable Authentication Modules) для прозрачной аутентификации пользователей. Мы немного покопаемся в истории развития механизмов аутентификации в Linux, разберемся с системой настроек PAM и разберем исходный код модуля аутентификации pam_p11, который позволяет проводить аутентификацию пользователей по смарт-картам.
    В конце статьи мы рассмотрим на практике настройку и работу модуля аутентификации в сертифицированном по 3 классу защищенности СВТ и 2 уровню контроля отсутствия недекларированных возможностей дистрибутиве Astra Linux для аутентификации по USB-токенам Рутокен ЭЦП и Рутокен S. Учитывая то, что Рутокен S имеет сертификаты ФСТЭК по НДВ 3, а Рутокен ЭЦП по НДВ 4, это решение может применяться в информационных системах, обрабатывающих конфиденциальную информацию, вплоть до информации с грифом «С».

    Немного истории


    В старые добрые времена если приложению в Linux требовалось запросить аутентификацию пользователя, то ему приходилось обращаться к файлам /etc/passwd и /etc/shadow. Такой подход был прост как пробка, но при этом разработчикам приходилось думать не только о работе с файлами, но и о вопросах безопасности. В связи с этим возникла необходимость разработки прозрачного механизма аутентификации пользователей, не зависящего от способа хранения информации об их учетных записях.
    Решением тому стал проект Linux-PAM. К слову сказать, сама архитектура PAM была впервые предложена компанией Sun в октябре 1995 года, а в августе 1996 года инфраструктура Linux-PAM была включена в дистрибутив Red Hat Linux. В настоящее время существуют три основных реализации PAM:
    1. Linux-PAM – основная реализация архитектуры PAM, рассматривается нами в этой статье
    2. OpenPAM – альтернативная реализация PAM, используемая в BSD-системах и Mac OS X
    3. Java PAM – Java-обертка над Linux-PAM

    Структура PAM


    Для начала разберемся, что же такое «Модуль PAM». Модули представляют собой библиотеки, в которых прописаны обработчики операций, которые может направлять к ним сам PAM. Для примера, стандартный модуль pam_unix умеет следующее:
    • Запросить пароль у пользователя и проверить введенное значение с хранимым в системе
    • Проверить, удовлетворяет ли пароль требованиям безопасности и не истек ли он

    Ниже представлена общая схема работы PAM

    Сильно упрощенная схема аутентификации в приложении, использующем PAM, выглядит следующим образом:
    1. Приложение инициализирует библиотеку PAM (libpam.so)
    2. PAM в соответствии с конфигурационным файлом для приложения обращается к требуемым модулям
    3. Модули выполняют возложенные на них действия
    4. Приложению возвращается результат операции

    Конечно, PAM позволяет проводить не только аутентификацию. Функции PAM классифицируются по типу модулей. В скобках указаны обозначения модулей в конфигурационных файлах:
    • Аутентификация (auth)
    • Управление учетными записями (account)
    • Управление сеансами (session)
    • Управление паролями (passwd)

    Сейчас нам интересна только аутентификация, поэтому рассмотрение остальных функций оставим любопытству читателя.

    Конфигурация PAM


    Если приложению требуется аутентификация, то оно должно создать файл со своим именем в каталоге /etc/pam.d, в котором должны быть указаны модули, с помощью которых производится аутентификация и прочие действия. Посмотрим, что лежит в каталоге /etc/pam.d в Ubuntu 11.10
    $ ls /etc/pam.d/
    atd common-account common-session-noninteractive lightdm other samba vmtoolsd chfn common-auth cron lightdm-autologin passwd sshd chpasswd common-password cups login polkit-1 su chsh common-session gnome-screensaver newusers ppp sudo
    

    Для примера, посмотрим на абстрактный файл конфигурации для приложения login
    # PAM configuration for login
    auth requisite pam_securetty.so
    auth required pam_nologin.so
    auth required pam_env.so
    auth required pam_unix.so nullok
    account required pam_unix.so
    session required pam_unix.so
    session optional pam_lastlog.so
    password required pam_unix.so nullok obscure min=4 max=8
    

    Каждая строчка конфига записывается в виде
    <тип модуля>	<управляющий флаг> <путь к библиотеке> <параметры> 
    

    • Тип модуля соответствует обозначениям самих модулей (т.е. auth/account/session/passwd)
    • Управляющий флаг указывает критичность модуля для успешного выполнения операции. Флаг может принимать следующие значения: requisite (необходимый), required (требуемый), sufficient (достаточный) и optional (необязательный).
    • Путь к библиотеке задает собственно путь до файла модуля. По умолчанию они ищутся в /lib/security/
    • Параметры задают список аргументов, которые будут переданы модулю. Аргументы передаются аналогично принципу argc/argv в функции main(), за исключением того, что argv[0] содержит не имя модуля, а конкретный аргумент.

    Таким образом, мы получаем стек модулей, каждый из которых выполняет свое действие. PAM при этом разбирает стек как и положено – сверху вниз. В соответствии с управляющим флагом задаются следующие требования к успешности операции:
    • requisite (необходимый): если модуль стека вернет отрицательный ответ, то запрос сразу же отвергается. Другие модули при этом не будут выполнены.
    • required (требуемый): если один или несколько модулей стека вернут отрицательный ответ, все остальные модули будут выполнены, но запрос приложения будет отвергнут.
    • sufficient (достаточный): если модуль помечен как достаточный и перед ним ни один из необходимых или достаточных модулей не возвратил отрицательного ответа, то все оставшиеся модули в стеке игнорируются, и возвращается положительный ответ.
    • optional (дополнительный): если в стеке нет требуемых модулей, и если ни один из достаточных модулей не возвратил положительного ответа, то хотя бы один из дополнительных модулей приложения или службы должен вернуть положительный ответ

    Конфигурационные файлы модулей хранятся в /usr/share/pam-configs/<имя модуля>. В каждом файле указывается полное имя модуля, включен ли он по умолчанию, приоритет модуля и параметры аутентификации.

    Разработка модуля аутентификации для PAM


    В этом разделе мы разберем исходный код модуля pam_p11 и рассмотрим основные моменты, которым стоит уделить внимание при написании своего собственного модуля.

    pam_p11

    Данный модуль позволяет осуществить двухфакторную аутентификацию пользователей по смарт-картам или USB-токенам с помощью ассиметричной криптографии. Рассмотрим общую схему его работы:
    • На токене хранится сертификат пользователя и его закрытый ключ
    • Сертификат также сохранен в домашнем каталоге пользователя как доверенный

    Аутентификация происходит следующим образом:
    1. На токене выполняется поиск сертификата пользователя
    2. Через PAM производится запрос PIN-кода к токену
    3. Если аутентификация на токене прошла успешно, то производится подпись случайных данных с помощью закрытого ключа с токена. Сама подпись выполняется аппаратно.
    4. Полученная ЭЦП проверяется с помощью сертификата пользователя

    Если в итоге проверка подписи осуществлена успешно, то модуль говорит наружу, что все хорошо.
    В данной схеме используются ключевая пара RSA длины 2048 бит, сгенерированная аппаратно на токене.

    Собственно разработка модуля

    В зависимости от функционала модуля, PAM может требовать от него наличия следующих функций:
    • pam_sm_authenticate, pam_sm_setcred – аутентификация
    • pam_sm_acct_mgmt – управление учетными записями
    • pam_sm_chauthtok – управление паролями
    • pam_sm_open_session, pam_sm_close_session — управление сеансами

    Для того чтобы модуль мог выполнять аутентификацию, нам необходимо реализовать в нем функции pam_sm_authenticate и pam_sm_setcred. В остальных функциях достаточно просто добавить заглушки, чтобы наш модуль нельзя было использовать для других операций.
    Для работы с PAM необходимо определить специальные константы, а только затем подключить заголовочные файлы:
    #define PAM_SM_AUTH
    #define PAM_SM_ACCOUNT
    #define PAM_SM_SESSION
    #define PAM_SM_PASSWORD
    #include <security/pam_appl.h>
    #include <security/pam_modules.h>
    

    Эти константы необходимы, чтобы PAM знал, что наш модуль может выполнять все функции, описанные выше. Конечно, если мы реализуем только аутентификацию, то остальные функции можно отбросить, но разработчики pam_p11 решили, что надежнее будет поставить заглушки вместо неиспользуемых функций.
    Приступим к написанию функции pam_sm_authenticate. Она имеет следующую сигнатуру:
    PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv);
    

    Из важных параметров тут стоит отметить:
    • pamh – хендл к PAM, полученный приложением
    • argc, argv – аргументы, указанные в конфигурационном файле. Наш модуль принимает один аргумент – путь к библиотеке PKCS#11

    Функция должна вернуть одно из следующих значений:
    • PAM_AUTH_ERR – ошибка аутентификации
    • PAM_CRED_INSUFFICIENT – у приложения недостаточно прав для выполнения аутентификации
    • PAM_AUTHINFO_UNAVAIL – модулю не удалось получить информацию для выполнения аутентификации. Это может случиться из-за проблем в сети или другого отказа оборудования
    • PAM_SUCCESS – аутентификация прошла успешно
    • PAM_USER_UNKNOWN – пользователь с переданным именем не существует
    • PAM_MAXTRIES – один или несколько модулей аутентификации превысили допустимый предел попыток.

    Внутри нашего модуля мы будем пользоваться библиотекой libp11 для работы с API PKCS#11 и OpenSSL для работы с сертификатами.
    Первым делом определим переменные, которые нам потребуются:
    int i, rv;
    const char *user; // имя пользователя
    char *password; // вводимый пользователем пароль
    char password_prompt[64]; // запрос на ввод пароля, показываемый приложением
    // структуры PAM
    struct pam_conv *conv; // функция диалога PAM
    struct pam_message msg; // сообщения диалога PAM
    struct pam_message *(msgp[1]);
    struct pam_response *resp; // ответ PAM
    
    // структуры lib_p11:
    PKCS11_CTX *ctx; // контекст PKCS#11
    PKCS11_SLOT *slot, *slots; // слоты
    PKCS11_CERT *certs; // сертификаты
    unsigned int nslots, ncerts;
    PKCS11_KEY *authkey; // закрытый ключ
    PKCS11_CERT *authcert; // сертификат
    
    EVP_PKEY *pubkey; // открытый ключ OpenSSL для проверки подписи
    
    unsigned char rand_bytes[RANDOM_SIZE];
    unsigned char signature[MAX_SIGSIZE];
    int fd;
    unsigned siglen;
    

    Затем проверим, передали ли нам путь к библиотеке PKCS#11
    if (argc != 1) {
    	pam_syslog(pamh, LOG_ERR, "need pkcs11 module as argument");
    	return PAM_ABORT;
    }
    

    После чего инициализируем OpenSSL и контекст PKCS#11
    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();
    ctx = PKCS11_CTX_new();
    

    Запросим у PAM имя пользователя
    rv = pam_get_user(pamh, &user, NULL);
    if (rv != PAM_SUCCESS) {
    	pam_syslog(pamh, LOG_ERR, "pam_get_user() failed %s", pam_strerror(pamh, rv));
    	return PAM_USER_UNKNOWN;
    }
    

    Теперь загрузим библиотеку PKCS#11, найдем первый доступный токен и получим с него сертификаты
    rv = PKCS11_CTX_load(ctx, argv[0]);
    if (rv) {
    	pam_syslog(pamh, LOG_ERR, "loading pkcs11 engine failed");
    	return PAM_AUTHINFO_UNAVAIL;
    }
    // получим все доступные слоты PKCS#11
    rv = PKCS11_enumerate_slots(ctx, &slots, &nslots);
    if (rv) {
    	pam_syslog(pamh, LOG_ERR, "listing slots failed");
    	return PAM_AUTHINFO_UNAVAIL;
    }
    // найдем первый слот с токеном
    slot = PKCS11_find_token(ctx, slots, nslots);
    if (!slot || !slot->token) {
    	pam_syslog(pamh, LOG_ERR, "no token available");
    	rv = PAM_AUTHINFO_UNAVAIL;
    	goto out;
    }
    // получим сертификаты с токена
    rv = PKCS11_enumerate_certs(slot->token, &certs, &ncerts);
    if (rv) {
    	pam_syslog(pamh, LOG_ERR, "PKCS11_enumerate_certs failed");
    	rv = PAM_AUTHINFO_UNAVAIL;
    	goto out;
    }
    if (ncerts <= 0) {
    	pam_syslog(pamh, LOG_ERR, "no certificates found");
    	rv = PAM_AUTHINFO_UNAVAIL;
    	goto out;
    }
    

    Теперь среди сертификатов на токене найдем тот, что лежит у нас в ~/.eid/authorized_certificates:
    for (i = 0; i < ncerts; i++) {
    	authcert = &certs[i];
    	if (authcert != NULL) {
    		/* проверим, совпадает ли сертификат с введенным именем пользователя */
    		rv = match_user(authcert->x509, user);
    		if (rv < 0) {
    			pam_syslog(pamh, LOG_ERR, "match_user() failed");
    			rv = PAM_AUTHINFO_UNAVAIL;
    			goto out;
    		} else if (rv == 0) {
    			/* this is not the cert we are looking for */
    			authcert = NULL;
    		} else {
    			break;
    		}
    	}
    }
    if (!authcert) {
    	pam_syslog(pamh, LOG_ERR, "no matching certificates found");
    	rv = PAM_AUTHINFO_UNAVAIL;
    	goto out;
    }
    

    А сейчас самое интересное – нам нужно запросить через PAM пароль пользователя (который в нашем случае будет PIN-кодом к токену), а затем выполнить аутентификацию на токене
    // для начала проверим, не сохранил ли PAM пароль при выполнении других модулей
    rv = pam_get_item(pamh, PAM_AUTHTOK, (void *)&password);
    if (rv == PAM_SUCCESS && password) {
    	password = strdup(password);
    } else {
    	// если пароль не сохранен, то спросим его у пользователя
    	sprintf(password_prompt, "Password for token %.32s: ", slot->token->label);
    	// задаем параметры диалога PAM: запрос пароля без рисования "звездочек"
    	msg.msg_style = PAM_PROMPT_ECHO_OFF;
    	msg.msg = password_prompt;
    	// получаем указатель на структуру диалога PAM
    	rv = pam_get_item(pamh, PAM_CONV, (const void **)&conv);
    	if (rv != PAM_SUCCESS) {
    		rv = PAM_AUTHINFO_UNAVAIL;
    		goto out;
    	}
    	if ((conv == NULL) || (conv->conv == NULL)) {
    		rv = PAM_AUTHINFO_UNAVAIL;
    		goto out;
    	}
    	// вызываем функцию диалога, введенный пароль будет записан в resp
    	rv = conv->conv(1, (const struct pam_message **)msgp, &resp, conv->appdata_ptr);
    	if (rv != PAM_SUCCESS) {
    		rv = PAM_AUTHINFO_UNAVAIL;
    		goto out;
    	}
    	if ((resp == NULL) || (resp[0].resp == NULL)) {
    		rv = PAM_AUTHINFO_UNAVAIL;
    		goto out;
    	}
    	// запоминаем пароль и очищаем память ответа
    	password = strdup(resp[0].resp);
    	memset(resp[0].resp, 0, strlen(resp[0].resp));
    	free(&resp[0]);
    }
    

    Теперь мы можем выполнить аутентификацию на токене:
    rv = PKCS11_login(slot, 0, password);
    // не забываем очистить память, выделенную под пароль
    memset(password, 0, strlen(password));
    free(password);
    if (rv != 0) {
    	pam_syslog(pamh, LOG_ERR, "PKCS11_login failed");
    	rv = PAM_AUTHINFO_UNAVAIL;
    	goto out;
    }
    

    На этом завершается первый этап аутентификации. Теперь нам нужно проверить, обладает ли владелец токена закрытым ключом. Для этого вычислим ЭЦП для произвольного блока данных и проверим ее с помощью доверенного сертификата.
    Для начала считаем 128 байт из /dev/random
    fd = open(RANDOM_SOURCE, O_RDONLY);
    if (fd < 0) {
    	pam_syslog(pamh, LOG_ERR, "fatal: cannot open RANDOM_SOURCE: ");
    	rv = PAM_AUTHINFO_UNAVAIL;
    	goto out;
    }
    rv = read(fd, rand_bytes, RANDOM_SIZE);
    if (rv < 0) {
    	pam_syslog(pamh, LOG_ERR, "fatal: read from random source failed: ");
    	close(fd);
    	rv = PAM_AUTHINFO_UNAVAIL;
    	goto out;
    }
    
    if (rv < RANDOM_SIZE) {
    	pam_syslog(pamh, LOG_ERR, "fatal: read returned less than %d<%d bytes\n", rv, RANDOM_SIZE);
    	close(fd);
    	rv = PAM_AUTHINFO_UNAVAIL;
    	goto out;
    }
    close(fd);
    

    Затем получим закрытый ключ, соответствующий сертификату и подпишем на нем случайные данные
    // поиск закрытого ключа по сертификату
    authkey = PKCS11_find_key(authcert);
    if (!authkey) {
    	pam_syslog(pamh, LOG_ERR, "no key matching certificate available");
    	rv = PAM_AUTHINFO_UNAVAIL;
    	goto out;
    }
    // аппаратное вычисление ЭЦП
    siglen = MAX_SIGSIZE;
    rv = PKCS11_sign(NID_sha1, rand_bytes, RANDOM_SIZE, signature, &siglen, authkey);
    if (rv != 1) {
    	pam_syslog(pamh, LOG_ERR, "fatal: pkcs11_sign failed\n");
    	rv = PAM_AUTHINFO_UNAVAIL;
    	goto out;
    }
    

    Проверим подпись. Для этого сначала средствами OpenSSL получим открытый ключ из сертификата, а затем выполним проверку ЭЦП
    pubkey = X509_get_pubkey(authcert->x509);
    if (pubkey == NULL) {
    	pam_syslog(pamh, LOG_ERR, "could not extract public key");
    	rv = PAM_AUTHINFO_UNAVAIL;
    	goto out;
    }
    // программно проверяем ЭЦП с помощью OpenSSL
    rv = RSA_verify(NID_sha1, rand_bytes, RANDOM_SIZE, signature, siglen, pubkey->pkey.rsa);
    if (rv != 1) {
    	pam_syslog(pamh, LOG_ERR, "fatal: RSA_verify failed\n");
    	rv = PAM_AUTHINFO_UNAVAIL;
    	goto out;
    }
    

    Если проверка подписи прошла успешно, то мы можем завершить работу с библиотекой PKCS#11 и вернуть PAM_SUCCESS.
    	rv = PAM_SUCCESS;
    out:
    	PKCS11_release_all_slots(ctx, slots, nslots);
    	PKCS11_CTX_unload(ctx);
    	PKCS11_CTX_free(ctx);
    	return rv;
    

    Вместо остальных функций оставим никому не интересные заглушки, соберем модуль и приступим к его настройке и использованию.

    Практическое использование


    В качестве подопытного дистрибутива можно было бы взять свежую Ubuntu, но учитывая то, что в 12.04 все слишком хорошо работает, мы решили с пользой для общего дела настроить аутентификацию в релизе «Смоленск» операционной системы Astra Linux Special Edition по USB-токенам Рутокен ЭЦП и Рутокен S.

    Установка дополнительных пакетов

    Для начала пришлось установить некоторые пакеты. Для работы Рутокен S необходима старая версия OpenSC: 0.11.13, а для работы Рутокен ЭЦП – более новая: 0.12.2. В качестве middleware для обоих токенов используется OpenCT версии 0.6.20.
    В итоге были поставлены пакеты, переданные разработчиками дистрибутива:
    • libopenct1 (0.6.20-1.2): libopenct1_0.6.20-1.2_amd64.deb
    • openct (0.6.20-1.2): openct_0.6.20-1.2_amd64.deb

    Для Рутокен S

    • libopensc2_0.11.13-1.1_amd64.deb
    • opensc_0.11.13-1.1_amd64.deb
    • mozilla-opensc_0.11.13-1.1_amd64.deb

    Для Рутокен ЭЦП

    • opensc (0.12.2-2): opensc_0.12.2-2_amd64.deb

    При установке новой версии opensc потребовалось удовлетворить зависимости пакетов. Для этого были взяты следующие пакеты из репозитория Debian squeeze:
    • libltdl7 (>= 2.2.6b): libltdl7_2.2.6b-2_amd64.deb
    • libssl0.9.8 (>= 0.9.8m-1): libssl0.9.8_0.9.8o-4squeeze11_amd64.deb

    Модуль PAM и его зависимости

    Для осуществления аутентификации по токену были установлены пакеты:
    • libp11-1 (0.2.7-2): libp11-1_0.2.7-2_amd64.deb
    • libpam-p11 (0.1.5-1): libpam-p11_0.1.5-1+b1_amd64.deb
    • libengine-pkcs11-openssl (0.1.8-2): libengine-pkcs11-openssl_0.1.8-2_amd64.deb

    Настройка pam_p11

    К счастью, нам не почти не придется править конфиги руками. Достаточно только создать файл /usr/share/pam-configs/p11 со следующим содержанием:
    Name: Pam_p11 
    Default: yes 
    Priority: 800 
    Auth-Type: Primary 
    Auth: sufficient pam_p11_opensc.so /usr/lib/opensc-pkcs11.so
    

    Интерес предоставляет последняя строчка конфига, в которой мы указываем тип модуля, имя библиотеки и параметры, передаваемые модулю. Наш модуль принимает в качестве параметра путь к библиотеке PKCS#11.
    Теперь нам осталось только выполнить команду
    $ pam-auth-update
    

    В появившемся диалоге необходимо выбрать pam_p11. Если вы хотите отключить аутентификацию по паролям, то можно отключить Unix authentication. Поскольку в конфигурационном файле профиля было указано, что модуль будет «sufficient», то при получении от нашего модуля ответа «PAM_SUCCESS» весь процесс аутентификации будет считаться успешным.

    Создание ключа и сертификата

    Для начала создаем ключевую пару RSA длины 2048 бит c ID «45» (id стоит запомнить, он понадобится при создании сертификата).
    $ pkcs15-init --generate-key rsa/2048 --auth-id 02 --id 45
     <вводим PIN пользователя>
    

    Проверим сгенерированный ключ:
    $ pkcs15-tool --list-keys
    Using reader with a card: Aktiv Rutoken ECP 00 00 
    Private RSA Key [Private Key] 
    Object Flags : [0x3], private, modifiable 
    Usage : [0x4], sign Access Flags : [0x1D], sensitive, alwaysSensitive, neverExtract, local
    ModLength : 2048 
    Key ref : 1 (0x1) 
    Native : yes 
    Path : 3f001000100060020001 
    Auth ID : 02 
    ID : 45
    

    Теперь с помощью OpenSSL создадим самоподписанный сертификат. Запускаем openssl и подгружаем модуль поддержки pkcs11:
    $ openssl
    OpenSSL> engine dynamic -pre SO_PATH:/usr/lib/engines/engine_pkcs11.so -pre ID:pkcs11 -pre LIST_ADD:1 -pre LOAD -pre MODULE_PATH:opensc-pkcs11.so
    (dynamic) Dynamic engine loading support
    [Success]: SO_PATH:/usr/lib/engines/engine_pkcs11.so
    [Success]: ID:pkcs11
    [Success]: LIST_ADD:1
    [Success]: LOAD
    Loaded: (pkcs11) pkcs11 engine
    

    Создаем сертификат в PEM-формате:
    OpenSSL> req -engine pkcs11 -new -key 1:45 -keyform engine -x509 -out cert.pem –text
    

    В последней команде 1:45 — это пара :<id ключа>. Таким образом, мы создали сертификат на базе ключевой пары, хранящейся на токене. При этом в текущем каталоге должен создаться файл сертификата с именем cert.pem.
    Теперь сохраним сертификат на токен:
    $ pkcs15-init --store-certificate cert.pem --auth-id 02 --id 45 --format pem 
    <Вводим PIN пользователя>
    


    Занесение сертификата в список доверенных

    На данном этапе нам осталось только прочитать с токена сертификат с нужным ID и записать его в файл доверенных сертификатов:
    $ mkdir ~/.eid
    $ chmod 0755 ~/.eid
    $ pkcs15-tool -r <certificate_id> > ~/.eid/authorized_certificates
    $ chmod 0644 ~/.eid/authorized_certificates
    


    Заключение


    В статье я постарался рассмотреть механизм работы PAM, особо не углубляясь в специфику работы его внутренних функций. В связи с этим остались без особого внимания такие вещи, как механизм диалогов PAM, функции для работы со структурами PAM и некоторые тонкости настройки всей системы. Сами по себе они претендуют на отдельную статью, поэтому, если будет интерес, то могу их описать в новой статье.
    Описанные шаги по настройке системы аутентификации можно использовать как инструкцию в любом современном дистрибутиве Linux.
    • +19
    • 24,3k
    • 6
    «Актив» 102,83
    Компания
    Поделиться публикацией
    Похожие публикации
    Комментарии 6
    • 0
      Как модуль себя ведёт в многпоточном окружении?
      Я получал красивый шар с волосами при массивом su/sudo запуске в параллель на разных сторонних PAM модулях. Не сталкивались или не тестировали?
      Как с перехватом авторизации во втором окне? Например в момент авторизации на экране на фоне вредоносный процесс запустит в сабшелле su — он сможет перехватить авторизацию?
      • +3
        Насколько я помню, PAM гарантирует корректную работу нескольких приложений с модулем, если каждое их них использует свой хендл к libpam
        The libpam interfaces are only thread-safe if each thread within the multithreaded application uses its own PAM handle.

        В связи с этим мне казалось, модулям не надо думать о многопоточности, PAM сам должен это разруливать. По логике построения модуля конечно есть шанс, что кто-то из компании {модуль, pkcs11-библиотека, OpenSSL, токен} может умереть при множественном вызове. Надо бы это проверить, спасибо за совет.

        На счет второго вопроса я не совсем понимаю, как это возможно. Опять же, по логике работы с хендлами, PAM не должен дать доступ другому приложению.
        • +1
          интерфейс, а не сами .so'шки — является ли thread-safe сам код модуля? все ли используемые структуры — только динамические и только на хендл завязанные?

          по параллельному запросу — на фоне запустился su и запросил пин. на экране запустился визуальный su и тоже пин запросил. на токен ушло два запроса — 1й и 2й в очереди. есть ли гарантия что они придут именно в том порядке в каком ушли в токен?

          каков шанс перехвата протокола работы с токеном — только ли ограничено pcscd?
          • +1
            Да, код завязан только на хендл. В коде есть отдельный условный блок, который компилируется, если объявлен макрос PAM_STATIC. Как я понимаю, это нужно для статической линковки модуля в libpam. По умолчанию этого, естественно, не происходит.
            Полный исходный код модуля можно глянуть здесь: www.opensc-project.org/pam_p11/browser/trunk/src
            Там его довольно мало, большая часть отведена функции аутентификации и разобрана в статье.

            Также я только что проверил множественные логины — все работает корректно, запросы на токен приходят именно в том порядке, в котором и были посланы.

            Запрос к токену идет по следующему пути: модуль -> opensc-pkcs11.so -> pcscd -> токен. Теоретически, можно просмотреть APDU-команды, посылаемые токену со стороны pcscd, откуда получить PIN-код. Данное действие будет аналогично установке в систему любого кейлоггера. Плюс использования токена в том, что во-первых, закрытый ключ неизвлекаем, а, во-вторых, токен можно отключать на время, когда не требуется производить аутентификацию.
      • 0
        Для работы Рутокен S необходима старая версия OpenSC: 0.11.13, а для работы Рутокен ЭЦП – более новая: 0.12.2

        Можно пояснить, почему так?
        • +2
          В старых версиях OpenSC (до 0.12.х) криптоалгоритмы, не поддерживаемые токеном аппаратно, реализованы программно. В новых OpenSС по понятным причинам это убрали.
          Рутокен S в данной статье стоит рассматривать только с точки зрения обратной совместимости. Для дистрибутивов Linux мы сейчас предлагаем в основном только Рутокен ЭЦП.

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое