Rutoken EDSおよびRutoken Sを使用したAstra Linuxでの認証のためのPAMモジュールの開発とアプリケーション





この記事では、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つの主要な実装があります。

  1. Linux-PAMは、この記事で説明するPAMアーキテクチャの主要な実装です。
  2. OpenPAMは、BSDシステムおよびMac OS Xで使用されるPAMの代替実装です
  3. Java PAM-Linux-PAM上のJavaラッパー


PAM構造



まず、「PAMモジュール」とは何かを考えましょう。 モジュールは、PAM自体がモジュールに指示できる操作ハンドラーを含むライブラリです。 たとえば、標準のpam_unixモジュールは次のことを実行できます。



以下は、PAMの仕組みの一般的な概要です。



PAMを使用するアプリケーションでの非常に単純化された認証スキームは次のとおりです。

  1. アプリケーションはPAMライブラリ(libpam.so)を初期化します
  2. アプリケーションの構成ファイルに従ってPAMが必要なモジュールにアクセスします
  3. モジュールはそれらに割り当てられたアクションを実行します
  4. 操作の結果がアプリケーションに返されます。


もちろん、PAMは認証のみを許可しません。 PAM機能はモジュールタイプによって分類されます。 括弧内は、構成ファイル内のモジュールの指定です。



これで認証にのみ関心があるため、残りの好奇心は読者に任せます。



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
      
      





たとえば、ログインアプリケーションの抽象構成ファイルを見てください。

 # 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
      
      





構成の各行は次のように記述されます

 < > < > <  > <>
      
      







したがって、各モジュールが独自のアクションを実行するモジュールのスタックを取得します。 PAMは、スタックを必要に応じて同時に解析します(上から下)。 制御フラグに従って、操作が成功するための次の要件が設定されます。



モジュール構成ファイルは、/ usr / share / pam-configs / <モジュール名>に保存されます。 各ファイルは、モジュールのフルネーム、デフォルトで有効になっているかどうか、モジュールの優先度、および認証パラメーターを示します。



PAMの認証モジュールの開発



このセクションでは、pam_p11モジュールのソースコードを分析し、独自のモジュールを作成する際に注意すべき主なポイントを検討します。



pam_p11


このモジュールは、非対称暗号化を使用したスマートカードまたはUSBトークンを使用したユーザーの2要素認証を可能にします。 その仕事の一般的なスキームを考えてみましょう:



認証は次のとおりです。

  1. ユーザー証明書のトークンが検索されます
  2. PAMを介して、トークンのPINコードが要求されます
  3. トークンの認証が成功した場合、ランダムデータはトークンの秘密キーを使用して署名されます。 署名自体はハードウェアで実行されます。
  4. 受信したデジタル署名は、ユーザー証明書を使用して検証されます。


その結果、署名の検証が成功した場合、モジュールはすべてが正常であると外側に言います。

このスキームでは、2048ビットのRSAキーペアが使用され、トークン上でハードウェアで生成されます。



実際にモジュール開発


モジュールの機能に応じて、PAMは次の機能を必要とする場合があります。



モジュールを認証できるようにするには、関数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);
      
      





ここで重要なパラメーターのうち、注目に値するものは次のとおりです。



関数は、次の値のいずれかを返す必要があります。



モジュール内では、PKCS#11 APIを操作するためにlibp11ライブラリを使用し、証明書を操作するために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; }
      
      





これにより、認証の第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);
      
      





次に、証明書に対応する秘密キーを取得し、ランダムなデータに署名します

 //      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ですべてがうまく機能することを考慮して、Rutoken EDSのUSBトークンを使用するAstra Linux Special EditionオペレーティングシステムのSmolenskリリースで認証を構成するために、一般的な原因に認証を使用することを決定しましたルートケンS.



追加のパッケージをインストールする


まず、いくつかのパッケージをインストールする必要がありました。 Rutoken Sの操作には、古いバージョンのOpenSCが必要です:0.11.13、およびRutoken EDSの操作には、新しいバージョンが必要です:0.12.2。 OpenCTバージョン0.6.20は、両方のトークンのミドルウェアとして使用されます。

その結果、配布キットの開発者によって配信されたパッケージが配信されました。



Rutoken Sの場​​合




Rutoken EDSの場合




openscの新しいバージョンをインストールするとき、パッケージの依存関係を満たす必要がありました。 このため、次のパッケージがDebian squeezeリポジトリから取得されました。



PAMモジュールとその依存関係


トークンによる認証を実装するために、次のパッケージがインストールされました。



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ディストリビューションの指示として使用できます。



All Articles