この記事では、LinuxアプリケーションがPluggable Authentication Modulesシステムを使用してユーザーを透過的に認証する方法について説明します。 Linuxでの認証メカニズムの開発の歴史を少し掘り下げ、PAM設定システムを理解し、ユーザーがスマートカードを使用して認証できるpam_p11認証モジュールのソースコードを分析します。
記事の最後で、セキュリティクラス3 SVTに準拠した認証モジュールの認証モジュールの構成と操作、およびUSBトークンRutoken EDSおよびRutoken Sを使用した認証のためのAstra Linuxディストリビューションの未宣言の機能のレベル2制御を実際に調べます。 NDV 3の場合、およびNDV 4のRutoken EDSの場合、このソリューションは、「C」でマークされた情報まで、機密情報を処理する情報システムで使用できます。
ちょっとした歴史
古き良き時代、Linux上のアプリケーションがユーザー認証を要求する必要がある場合、/ etc / passwdおよび/ etc / shadowファイルにアクセスする必要がありました。 このアプローチはコルクのように単純でしたが、同時に、開発者はファイルの操作だけでなく、セキュリティの問題についても考えなければなりませんでした。 この点で、アカウントに関する情報を保存する方法とは関係なく、ユーザーを認証するための透過的なメカニズムを開発する必要が生じました。
これに対する解決策は、Linux-PAMプロジェクトでした。 ところで、PAMアーキテクチャ自体は1995年10月にSunによって最初に提案され、1996年8月にLinux-PAMインフラストラクチャがRed Hat Linuxディストリビューションに含まれました。 現在、PAMには3つの主要な実装があります。
- Linux-PAMは、この記事で説明するPAMアーキテクチャの主要な実装です。
- OpenPAMは、BSDシステムおよびMac OS Xで使用されるPAMの代替実装です
- Java PAM-Linux-PAM上のJavaラッパー
PAM構造
まず、「PAMモジュール」とは何かを考えましょう。 モジュールは、PAM自体がモジュールに指示できる操作ハンドラーを含むライブラリです。 たとえば、標準のpam_unixモジュールは次のことを実行できます。
- ユーザーにパスワードを要求し、システムに保存されている入力値を確認します
- パスワードがセキュリティ要件を満たしているか、有効期限が切れているかを確認します
以下は、PAMの仕組みの一般的な概要です。
PAMを使用するアプリケーションでの非常に単純化された認証スキームは次のとおりです。
- アプリケーションはPAMライブラリ(libpam.so)を初期化します
- アプリケーションの構成ファイルに従ってPAMが必要なモジュールにアクセスします
- モジュールはそれらに割り当てられたアクションを実行します
- 操作の結果がアプリケーションに返されます。
もちろん、PAMは認証のみを許可しません。 PAM機能はモジュールタイプによって分類されます。 括弧内は、構成ファイル内のモジュールの指定です。
- 認証(auth)
- アカウント管理
- セッション管理
- パスワード管理(passwd)
これで認証にのみ関心があるため、残りの好奇心は読者に任せます。
PAM設定
アプリケーションが認証を必要とする場合、/ etc / pam.dディレクトリーにその名前でファイルを作成する必要があります。このファイルには、認証およびその他のアクションが実行されるモジュールが示されている必要があります。 Ubuntu 11.10の/etc/pam.dディレクトリにあるものを見てみましょう
$ 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
たとえば、ログインアプリケーションの抽象構成ファイルを見てください。
構成の各行は次のように記述されます
< > < > < > <>
- モジュールのタイプは、モジュール自体の表記に対応しています(つまり、auth / account / session / passwd)
- 制御フラグは、正常に動作するためのモジュールの重要度を示します。 フラグは次の値を取ることができます:必須(必要)、必須(必須)、十分(十分)、オプション(オプション)。
- ライブラリへのパスは、モジュールファイルへの実際のパスを設定します。 デフォルトでは、それらは/ lib / security /で検索されます
- パラメーターは、モジュールに渡される引数のリストを指定します。 引数はmain()関数のargc / argv原則と同じ方法で渡されますが、argv [0]にはモジュール名ではなく特定の引数が含まれます。
したがって、各モジュールが独自のアクションを実行するモジュールのスタックを取得します。 PAMは、スタックを必要に応じて同時に解析します(上から下)。 制御フラグに従って、操作が成功するための次の要件が設定されます。
- 必要条件(必須):スタックモジュールが否定応答を返す場合、要求はすぐに拒否されます。 他のモジュールは実行されません。
- 必須:1つ以上のスタックモジュールが否定応答を返した場合、他のすべてのモジュールが実行されますが、アプリケーションリクエストは拒否されます。
- 十分:モジュールが十分とマークされ、その前に必要または十分なモジュールのいずれも否定応答を返さない場合、スタック内の残りのモジュールはすべて無視され、肯定応答が返されます。
- オプション:スタックに必要なモジュールがなく、十分なモジュールのいずれも肯定応答を返さない場合、アプリケーションまたはサービスの追加モジュールの少なくとも1つが肯定応答を返す必要があります
モジュール構成ファイルは、/ usr / share / pam-configs / <モジュール名>に保存されます。 各ファイルは、モジュールのフルネーム、デフォルトで有効になっているかどうか、モジュールの優先度、および認証パラメーターを示します。
PAMの認証モジュールの開発
このセクションでは、pam_p11モジュールのソースコードを分析し、独自のモジュールを作成する際に注意すべき主なポイントを検討します。
pam_p11
このモジュールは、非対称暗号化を使用したスマートカードまたはUSBトークンを使用したユーザーの2要素認証を可能にします。 その仕事の一般的なスキームを考えてみましょう:
- ユーザー証明書とその秘密鍵はトークンに保存されます
- 証明書は、信頼できるものとしてユーザーのホームディレクトリにも保存されます。
認証は次のとおりです。
- ユーザー証明書のトークンが検索されます
- PAMを介して、トークンのPINコードが要求されます
- トークンの認証が成功した場合、ランダムデータはトークンの秘密キーを使用して署名されます。 署名自体はハードウェアで実行されます。
- 受信したデジタル署名は、ユーザー証明書を使用して検証されます。
その結果、署名の検証が成功した場合、モジュールはすべてが正常であると外側に言います。
このスキームでは、2048ビットのRSAキーペアが使用され、トークン上でハードウェアで生成されます。
実際にモジュール開発
モジュールの機能に応じて、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ライブラリへのパスという1つの引数を受け入れます
関数は、次の値のいずれかを返す必要があります。
- PAM_AUTH_ERR-認証エラー
- PAM_CRED_INSUFFICIENT-アプリケーションには認証を実行するための十分な権限がありません
- PAM_AUTHINFO_UNAVAIL-モジュールは認証のための情報を取得できませんでした。 これは、ネットワークの問題またはその他のハードウェア障害が原因で発生する可能性があります。
- PAM_SUCCESS-認証成功
- PAM_USER_UNKNOWN-指定された名前のユーザーは存在しません
- PAM_MAXTRIES-1つ以上の認証モジュールが試行の許可された制限を超えました。
モジュール内では、PKCS#11 APIを操作するためにlibp11ライブラリを使用し、証明書を操作するためにOpenSSLを使用します。
まず、必要な変数を定義します。
int i, rv; const char *user;
次に、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; }
トークン上の証明書の中から、〜/ .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) { authcert = NULL; } else { break; } } } if (!authcert) { pam_syslog(pamh, LOG_ERR, "no matching certificates found"); rv = PAM_AUTHINFO_UNAVAIL; goto out; }
そして今、最も興味深いのは、PAM(この場合はトークンのPINコード)を介してユーザーパスワードを要求し、トークンで認証する必要があることです。
これで、トークンで認証を実行できます。
rv = PKCS11_login(slot, 0, password);
これにより、認証の第1段階が完了します。 次に、トークンの所有者が秘密鍵を持っているかどうかを確認する必要があります。 これを行うには、任意のデータブロックのデジタル署名を計算し、信頼できる証明書を使用して検証します。
最初に、/ dev / randomからの128バイトを考慮します
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);
次に、証明書に対応する秘密キーを取得し、ランダムなデータに署名します
署名を検証します。 これを行うには、まず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; }
署名の検証が成功した場合、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ですべてがうまく機能することを考慮して、Rutoken EDSのUSBトークンを使用するAstra Linux Special EditionオペレーティングシステムのSmolenskリリースで認証を構成するために、一般的な原因に認証を使用することを決定しましたルートケンS.
追加のパッケージをインストールする
まず、いくつかのパッケージをインストールする必要がありました。 Rutoken Sの操作には、古いバージョンのOpenSCが必要です:0.11.13、およびRutoken EDSの操作には、新しいバージョンが必要です:0.12.2。 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
Rutoken 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
Rutoken EDSの場合
- 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認証を無効にすることができます。 モジュールが「十分」であることがプロファイル構成ファイルで示されているため、モジュールから応答「PAM_SUCCESS」を受信すると、認証プロセス全体が成功したと見なされます。
キーと証明書を作成する
最初に、IDが「45」の2048ビット長のRSAキーペアを作成します(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はペアです:<key 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ダイアログメカニズム、PAM構造を操作する機能、およびシステム全体の微調整のようなものは、特に注意を払うことなく残されました。 自分で別の記事を主張しているので、興味があれば、新しい記事で説明できます。
説明されている認証システムの構成手順は、最新のLinuxディストリビューションの指示として使用できます。