Linuxで顔認証するPAMモジュール chissu-pam を作った
これは何?
毎回 sudo やロックスクリーン解除でパスワードを打つのが面倒なので、顔認証の PAM モジュールを自作した話です。Linux Desktop の顔認証といえば Howdy が定番ですが、あえて chissu-pam を作った理由も含めてまとめました。名前の由来は Windows Hello → 日本語のちぃ〜っす → Chissu という流れです。
Mac なら Touch ID、Windows なら Windows Hello でパスワード入力から解放されている人が多いと思います。Linux Desktop でも同じ快適さを得たい!という勢いと、今流行りの Vibe Coding の実験を兼ねて作ってみました。
なんで作ったの?
まず、Linux において生体認証というと fprintd があります。Touch ID のような指紋認証で、Linux でサポートされている指紋リーダーは基本的にノート PC 内蔵です。外付け USB デバイスで Linux 対応のものを調べてメルカリで買いましたが、認識精度はいまひとつでした。
では次に試したのが Howdy です。Windows Hello のような顔認証を実現する PAM モジュールで、やりたいことはほぼ実現されています。実際これで 2 年ほど運用しましたが、Ubuntu を半年ごとに do-release-upgrade するたびに壊れるのがつらい。Howdy は正式な Python 3 対応がまだで、自分で手当てしながら動かしています(まだリリースされていないが最近バージョンアップしてこの問題は解消されました)。Python の dlib を入れるときに Deb パッケージがなく --break-system-packages が必要になる点もストレス。半年に一度の Ubuntu アップグレードで壊れないものがほしかった。
加えて、Vibe Coding の実験場としても使いたかった、というのがもうひとつの動機です。
ざっくりした要件は?
- Must: 環境に対する頑健性がほしいのでバイナリを生成すること。なので Go か Rust が有力候補で、調べた結果 Rust に。
- Should: non-root で動くこと。
- Should: 可能であればセキュアであること。自分の顔画像の特徴量ベクトルが生で見れるところにあるのはちょっと嫌。他のシステムは 644 で置かれていた。
ざっくりどう動くの?
詳しくは https://github.com/sett4/chissu-pam や https://deepwiki.com/sett4/chissu-pam を見てね。以下の 6 ファイルがあれば動きます。
- chissu-cli
- /etc/chissu-pam/config.toml
- /usr/lib/security/libpam_chissu.so
- /var/lib/chissu-pam/embeddings/$USER.json
- /var/lib/chissu-pam/dlib-models/shape_predictor_68_face_landmarks.dat
- /var/lib/chissu-pam/dlib-models/dlib_face_recognition_resnet_model_v1.dat
ざっくりした使い方は。
-
chissu-pam enrollで顔画像を登録する。ここでは sudo は必要ない。D-Bus Secret Service に暗号キーを登録し、それで /var/lib/chissu-pam/embeddings/$USER.json を暗号化しています。 - Linux 立ち上げ直後のログイン画面では顔認証は使えません。なぜならこのタイミングでは D-Bus Secret Service が初期化されていないので。ここでパスワードを使わずに立ち上げても、結局ログイン直後に Secret Service 初期化のためパスワードが必要です。
- デスクトップが立ち上がった後であれば sudo やロックスクリーンや 1Password のロック解除に顔認証を利用可能。
-
chissu-pam doctorで設定があっているかどうかを確認可能。
どう進めたの?
主要な言語・ライブラリの選定やどういう方針でやるかは Gemini と相談しながら。PAM モジュールの作り方とか LLM が無いと調べるだけで疲れちゃってたろうな。
OpenSpec?
実装そのものは Codex CLI に OpenSpec を入れて進めた。以前 SpecKit を試したが、Spec Driven とか言いながらみなさん最初から Spec を固めれます?Spec ってちょっとづつ育てるものじゃないです?という気持ちが強くあまり肌に合わなかった。あと SpecKit は出力される Markdown が大量すぎて見なきゃだめ?これならコード見せてもらったほうが早くない?という気持ちにもなった。
OpenSpec は changes と specs に分かれていて、まず/prompts:openspec-proposal $PROPOSALすると openspec/changes/$proposalId に proposal.md, tasks.md. spec.md が出力される。どれもそんなに大きくない。ざっとみて良さそうなら /prompts:openspec-apply すると実装が始まり、実装結果を見て良さそうなら /prompts:openspec-archive する。そうすると openspec/changes/$proposalId は openspec/changes/archive/yyyy-mm-dd-$proposalId に移動され、 変更点を含んだ openspec/specs/$spec-name/spec.md が作成される。specs 配下の spec.md は proposal で新しく作られることもあれば、既存のものが更新されることもある。アーカイブに行った proposal は基本的にはもう見なくても良い、なぜなら必要なものは specs に入っているから。 例: https://github.com/sett4/chissu-pam/tree/main/openspec
Spec-Driven Development: The Waterfall Strikes Back という記事がありぼくもこれに同意。みんな最初からいけてる仕様って書ける?仕様って育てるものだよね?という気持ちが強い。仕様を育てるやり方って SpecKit と相性良くないように見える。なので、細かい要件を proposal として追加して specs を育てるような流れの OpenSpec は肌にあった。
開発ステップ
まずはコマンドラインツールとして要素を実装
- chissu-cli capture: キャプチャして PNG ファイルに出力
- chissu-cli face extract: PNG から dlib で特徴量抽出
- Landmark Detection で顔の領域を抽出
- Encoding で 128 次元ベクトルの Embedding を生成(
dlib_face_recognition_resnet_model_v1.datを使用)。日本人中心ならtaguchi_face_recognition_resnet_model_v1.datも選択肢。詳細は Dlib の顔学習モデルの、若年日本人女性データセットにおける性能評価 などが参考になります。
- chissu-cli face compare: 特徴量同士の類似度を出力
- chissu-cli face enroll: face extract の結果(embedding)を所定ディレクトリへ配置
ここまでで顔認証に必要なパーツを少しずつ追加して cli 動作させて挙動の確認(要素技術フェーズ)。
- libchissu_pam.so: PAM モジュールを生成したら一発で動き、近いうちの失職を覚悟した。
- chissu-cli enroll(CLI 拡張): capture → face extract → face enroll を一括実行するコマンドを追加。
ここまでで一通り顔認証は可能に。
このあとに D-Bus Secret Service を使って embedding を暗号化したり、PAM モジュールが動く root から一般ユーザの Secret Service にアクセスできないので fork() して setuid() して権限降格したり、1Password が DISPLAY を渡してくれないので logind から取得したり細々した対応をした。
セキュリティ周り
セキュリティ周りはおそらく 2 点ある。
- 顔認識の精度
- embedding の保管方法
顔認識の制度に関しては dlib の dlib_face_recognition_resnet_model_v1.dat を使っていて、おそらくこれは十分な精度を持っているはず。
embedding の保管方法が一番気になる部分。howdy は /usr/local/etc/howdy/models/$USER.dat に root:root に 0644 でファイルをおいている。誰でも読め、中を見ると embedding の 128 次元ベクトルが平分でおいてある。embedding は秘匿すべき内容か?に関してはぼくは秘匿すべき内容かもしれないと思う。ターゲットとなる embedding が分かればそこから画像に戻すことは不可能ではなさそうに思える。人間が見て別物だとわかるが dlib を騙せるものは作れそうに思える。このあたり専門家ではないので断定はできないが、可能であれば embedding は秘匿しておきたい。
というわけで秘匿のために暗号化したい。chissu-pam では embedding を格納する /var/lib/chissu-pam/embedding を0666にして誰でも読み書き可能にしている。これによって non-root での動作を可能にしている。そして chissu-cil enroll すると /var/lib/chissu-pam/embedding/$USER.json に暗号化して配置する。暗号化キーは D-Bus Secret Service から取得するためそこはセキュアだといえる。
PAM モジュールは root から呼び出しされる。D-Bus Secret Manager にはログイン試行しているユーザとしてアクセスせねばならず、プロセスを fork()して setuid()して権限降格した上で暗号キーを取得している。fork 元のプロセスとは pipe でプロセス間通信をしている。
暗号鍵はユーザ単位で異なっており他の人は読めないし、他の人が無理やり書き込んだら今度は復号できずにエラーとなり壊れたことがわかる。
リポジトリとドキュメント
- コード: https://github.com/sett4/chissu-pam
- 実装メモ(DeepWiki): https://deepwiki.com/sett4/chissu-pam
- OpenSpec で育てた spec 一式:
openspec/specs/*(リポジトリ内)
ところで Webcam は何使っているの?
Elecom の UCAM-CF20FBBKを使っています。これは /dev/video2 が赤外モードのカメラになっていて、これでキャプチャすると補助光として赤外ライトが光ります。そのため映画のハッカーみたいに暗い部屋でも認識できます。
使い方
セットアップ手順と PAM の組み込み方法は README の Getting Started(https://github.com/sett4/chissu-pam#getting-started)にまとめています。依存パッケージの入れ方と chissu-pam enroll の流れだけ押さえれば試せます。
今後やりたいことは?
そもそも Rust をよくわかってないので現在のコードが良いかどうかわからない
cargo clippy の警告は出ないようにしているが、基本的に Vibe Coding でやっていて Rust コードの妥当性が何一つわからない。何一つわからないのに認証モジュールなんで作ってるんだから世も末です。
世も末すぎるので一応理解しておきたい。
セキュアなのか?
/var/lib/chissu-pam/embeddings 配下はだれでも書けるパーミッションだし、 /var/lib/chissu-pam/embeddings/$USER.json は 600。中身は暗号化されているが、これは十分セキュアなのか? このあたりは識者に聞きたい。Crowdworks などで依頼することも考えたが、Rust で Linux PAM とかセキュリティ周りに強そうな人が都合よく見つかるとは思えず、今のところ誰にもレビューしてもらえていない。どうせ使うのは自分なので、ひとまず GitHub に公開して奇特な人が見てくれるのを待っています。
インストーラーを作る
使うのは自分だしという言い訳と反するが、動くものになったので人に使ってもらえるようにインストーラーとか Deb パッケージを作りたい。Deb パッケージも作ったことないので LLM に頼ることになる。
さいごに
最低限動くような Linux PAM の顔認証モジュールを作りました。作ったけどセキュリティ周りや Rust に自信がないので、誰かコードを読んでみたり試してもらえると嬉しいなぁ。Issue や PR を歓迎します( https://github.com/sett4/chissu-pam )。
コードは 99% AI が書きましたが、この記事は 90% 人間が書きました。
Discussion