[Server Login] OpenSSHでのU2F/FIDO2認証を検証してみる

Introduction for SSH with U2F/FIDO2

サーバログインのスタンダードであるOpenSSHに関して、U2F/FIDO2認証のサポートがコミットされました。

ということで早速どんな感じなのか試してみたのでまとめてみますよ。

はじめに

OpenSSHでのU2F/FIDO2認証サポートがコミットされたとの報告がコミュニティで上がりました。

U2F/FIDO2自体については以前も簡単に扱ったのでそちらを参照ってことでここでは割愛しますが、 セキュリティキーを利用した認証ログインの仕組みです。

参考:[FIDO2] Webauthn APIと戯れてみる

WebブラウザベースでのU2F/FIDO2(Webauthn)認証とは結構雰囲気が異なっていたので注意です。 どちらかというと使い勝手的にはPKCS#11(スマートカード)に近い印象でした。

ここではどんな感じか試したのでまとめてみます。

先に結論を言っておくと、一応セキュリティキーを利用して登録・検証通して再現できはしましたが、 ところどころ条件によってはうまく行かないところもあり、まだ安定はしてない印象でした。

検証したのは2019年12月末で、ここでの検証ではその段階での最新のOpenSSH, ならびに依存ライブラリ(libfido2)を利用しています。

※ 挙動は今後リファクタリングによってこれからどんどん変わっていく(安定していく)ものと思われますのでその点留意ください。1

構成概要

OpenSSHでのFIDOサポートでは、FIDO2もU2F(CTAP1)を包括していることから、 U2F/FIDO2どちらでも汎用的に利用できるように、基本的にはU2Fの機能のみで実現できます。

詳細な仕様についてはOpenSSHのPROTOCOL.u2f を参照するとおもしろいかもしれません。

鍵の登録から認証までのフローのざっくりとした概略は以下のようになります。

ssh_u2f_abst_flow

WebブラウザベースでのU2F/FIDO2フローとは異なり、登場人物はログイン元(SRC)ログイン先(Dest)のみ、FIDOサーバは登場しません。

順にフローを追ってみます。

  • 登録フロー
  1. セキュリティキー(device-secret)を用いてキーペア(key-handle + pubkey)を生成します。
  2. 生成したキーペアのうち、公開鍵(pubkey)ログイン先(Dest)に何らかの方法でexportします。
  • 認証フロー
  1. キーペアのうち、key-handleからセキュリティキー(device-secret)を用いて秘密鍵(privkey)を導出します。
  2. 導出した秘密鍵(privkey)を用いて公開鍵認証を行います。

※ ここではセキュリティキーから生成されたキーペアのpriv部分をkey-handleと書いておきます。 勘違いしやすいですがYubikeyなどのセキュリティキー自体には認証時に利用するprivkeyはストアしません。

詳細は以下がわかりやすいです。

Yubico U2F key generation

特に難しいことはないと思いますが、Webブラウザベースフローではkey-handleの管理はFIDOサーバが担っていましたが、 それが存在しないため、その管理はログインユーザ(SRC)側に委ねられるようです。。 つまり、セキュリティキーとは別にkey-handleもSRCのホストにコピーして回らなければならいぽい。。 (この辺りの不都合さは後述するFIDO2でのresident keyの仕組みでカバーすると言うことのようです。)

概要を確認したところで、それでは実際に試してみます。

検証環境

USBインタフェースに挿したセキュリティキーを介した認証となるため、リモートのサーバからではセキュリティキーとU2F(CTAP)通信ができません。

なのでLinux(今回はUbuntu 18.04.3 LTSLocalマシン環境で行います。詳細は以下となります。

$ cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.3 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.3 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic

また、セキュリティキーとして今回はYubico社がリリースしている以下を用いました。

SSHでのFIDO認証を検証してみる

準備

まず検証にあたって必要なソフトフェアをインストールします。

middleware (libfido2) インストール

OpenSSHでは、U2F/FIDO2サポートにあたってセキュリティキーとの通信プロトコル部は自身に盛り込まず、外部のミドルウェアライブラリに委譲する仕様としたようです。(PKCS#11と同様)

ということでその外部のミドルウェアライブラリとしてYubicoが公開している Yubico/libfido2をまずインストールします。

ビルド・インストール手順は公式のとおりですが、依存など含めて一応記載しておきますよ。

# install dependencies
$ sudo apt install cmake libcbor-dev libssl-dev libudev-dev

# install libfido2
$ git clone https://github.com/Yubico/libfido2.git && cd libfido2

$ mkdir build && cd build

$ cmake ..

$ cd .. && make -C build
$ sudo make -C build install

# confirm
$ ls /usr/local/lib/ | grep fido
libfido2.a
libfido2.so
libfido2.so.1
libfido2.so.1.3.0
libsk-libfido2.so

OpenSSH Latest インストール

U2F/FIDO2サポートした最新の master OpenSSH をインストールします。

これもビルド・インストール手順は公式のとおりですが、依存・オプションなど含めて一応記載しておきますよ。

# install dependencies
$ sudo apt install autoconf gcc g++ make

$ git clone https://github.com/openssh/openssh-portable && cd openssh-portable
$ autoreconf

# 既存のopensshとの競合を避けるため、`--prefix`でインストール先を適当なところに変更します
# 上でインストールした`libfido2`をincludeするため`--with-security-key-builtin`オプションを付与
$ ./configure --prefix=$HOME/.local --with-security-key-builtin

# それっぽいリンクのチェックが行われるのを確認
checking if /usr/bin/pkg-config knows about libfido2... yes
checking for fido_init in -lfido2... yes

OpenSSH has been configured with the following options:
                     User binaries: /home/nicopun/.local/bin
                   System binaries: /home/nicopun/.local/sbin
               Configuration files: /home/nicopun/.local/etc
                   Askpass program: /home/nicopun/.local/libexec/ssh-askpass
                      Manual pages: /home/nicopun/.local/share/man/manX
                          PID file: /var/run
  Privilege separation chroot path: /var/empty
            sshd default user PATH: /usr/bin:/bin:/usr/sbin:/sbin:/home/nicopun/.local/bin
                    Manpage format: doc
                    ...
           Translate v4 in v6 hack: yes
                  BSD Auth support: no
              Random number source: OpenSSL internal ONLY
             Privsep sandbox style: seccomp_filter
                   PKCS#11 support: yes
                  U2F/FIDO support: built-in

# 上記構成でインストール
$ make && sudo make install

# 確認
$ cd $HOME/.local/bin/ && ls
scp  sftp  ssh  ssh-add  ssh-agent  ssh-keygen  ssh-keyscan

--with-security-key-builtinオプションを付与したため、./configure結果でU2F/FIDO support: built-in となっているのを確認します。

鍵生成・認証

もろもろ下準備は完了したので試してみます。以下でのSSH系のコマンドは上記でインストールした$HOME/.local/配下のものを利用します。

キーペアの生成

U2F/FIDO用のキータイプとしてecdsa-sk,ed25519-skが追加されています。 今回はecdsa-skを指定してキーペアを生成してみます。

(※ FIDO/U2F機能を利用するにはセキュリティキーが少なくともECDSA-P256をサポートしている必要があります。ED25519はオプション。今回の検証で用いたキーはそもそもED25519をサポートしてなさそうなため利用できませんでした。)

USBにPIN設定済みのSecurity Key By Yubico(FIDO2対応のもの)を挿入して以下を実行していきます。

$ ./ssh-keygen -t ecdsa-sk
Generating public/private ecdsa-sk key pair.
You may need to touch your security key to authorize key generation.
Enter PIN for security key:
Enter file in which to save the key (/home/nicopun/.ssh/id_ecdsa_sk):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/nicopun/.ssh/id_ecdsa_sk.
Your public key has been saved in /home/nicopun/.ssh/id_ecdsa_sk.pub.
The key fingerprint is:
SHA256:ACNdkdpt/2YFO52YHjFlJAKCBfT9k4tt0sjGK2VHiq4 nicopun@nicopun-laptop
The key's randomart image is:
+-[ECDSA-SK 256]--+
|  .o===o... ...  |
|   .o+.o   . .o  |
|     oo..    o   |
|    . ..o...+    |
|       oS++  O . |
|      .o+=oo* +  |
|     . oB.=o +   |
|      o. +  =    |
|    E. ..  o     |
+----[SHA256]-----+

$ cd $HOME/.ssh

$ cat id_ecdsa_sk
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAfwAAACJzay1lY2
RzYS1zaGEyLW5pc3RwMjU2QG9wZW5zc2guY29tAAAACG5pc3RwMjU2AAAAQQSQ1QYr3P3m
VOdZYNwurDEuLjetAq0n2dBUWeZujvv+Vps05YbuLNiGzU8K4HQvqTZsZXcHyNsItSVsbA
goJzgvAAAABHNzaDoAAADwy9G+esvRvnoAAAAic2stZWNkc2Etc2hhMi1uaXN0cDI1NkBv
cGVuc3NoLmNvbQAAAAhuaXN0cDI1NgAAAEEEkNUGK9z95lTnWWDcLqwxLi43rQKtJ9nQVF
nmbo77/labNOWG7izYhs1PCuB0L6k2bGV3B8jbCLUlbGwIKCc4LwAAAARzc2g6AQAAAEBs
hiZwb2AhzwaH1C8OrQDKnniGnrAFuPmWFbbLEAi2MoCz3hv9i5dGU4OcNqhnV2aBZn7QiL
/2kPAOmJ84/VAUAAAAAAAAABZrb250YW5pQGtvbnRhbmktbGFwdG9wAQIDBAUG
-----END OPENSSH PRIVATE KEY-----

$ cat id_ecdsa_sk.pub
sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBJDVBivc/eZU51lg3C6sMS4uN60CrSfZ0FRZ5m6O+/5WmzTlhu4s2IbNTwrgdC+pNmxldwfI2wi1JWxsCCgnOC8AAAAEc3NoOg== nicopun@nicopun-laptop

以下の出力メッセージのあと、PINの入力セキュリティキーへのタップ(UP) が要求され、一通り操作を終えるとキーペアが生成されました。

You may need to touch your security key to authorize key generation.
Enter PIN for security key:

これらのキーペアを用いて認証を行います。

(ちなみに、PINが設定されていないU2Fキーの場合はPINは要求されませんでした。)

SSHD (dest) サーバ起動

今回はsource, destともにlocalhostで検証を行います。(localhost -> localhost)

まず、上で生成したキーペアのうち、pubauthorized_keysに追加しておきます。

$ cat id_ecdsa_sk.pub > $HOME/.ssh/authorized_keys

クライアントでの認証を行う前に、SSHDサーバ側も上記のキータイプ(*-sk)を解釈できるようにするため最新にします。 既存のSSHDプロセスを先程インストールした最新のものに挿げ替えます。

# 既存プロセスの停止
$ sudo systemctl stop sshd

# latestのSSHDを起動
$ sudo $HOME/.local/sbin/sshd -D

認証メソッドとしてはpublickey扱いなので、とくにsshd_configの変更は不要なはずです。

認証(ログイン)

最後にログインしてみます。 今回生成したprivkey(厳密にはkeyhandle)はデフォルト名(id_ecdsa_sk)で自動参照してくれるため、そのままsshしてみます。

$ ssh -A localhost
Confirm user presence for key ECDSA-SK SHA256:UHdGvOx7YajtnMtrOsN5V9lFZqBeteub9jMJlIWFncE
Last login: Sun Dec 28 22:42:25 2019 from 127.0.0.1

セキュリティキーへのタップUP)が要求され、タップすると無事認証されました。

また、sshの際、USBに対象のセキュリティキーが挿入されていない場合速攻でfailします。

加えて、複数セキュリティキーを挿入していた場合、どちらか1つしか反応しませんでした。 (※これはlibfido2の仕様上仕方なしらしい。)

ちなみにこのid_ecdsa_skですが、これ自体では意味を持たないため、 通常の公開鍵認証での秘密鍵ほどセンシティブなものではないことに留意です。

機能詳細

上記で一応通して検証はできましたが、もう少し関連する機能詳細について見てみますよ。

UP(セキュリティキーへのタップ)を制御

デフォルトセキュリティキーでの認証時、UPを満たすためセキュリティキーへのタップが要求されますが、これはキーペア生成時にオプションで指定していてかつDestサーバ側で設定することで制御可能なようです。

ssh-keygenでのUP不要指定

-O no-touch-required

参考:https://man.openbsd.org/ssh-keygen.1#no-touch-required

sshd_configによる制御

PubkeyAuthOptions (none / touch-required)

参考:https://man.openbsd.org/sshd_config.5#PubkeyAuthOptions

PubkeyAuthOptionsによってsshdレベルでUPを制御可能です。デフォルトtouch-requiredなので、 もしセキュリティキーでの認証にUPが不要ならnone指定します。

authorized_keysによる制御

各セキュリティキーごとにより細かく制御したい場合、authorized_keysレベルでの制御も可能です。 対象の公開鍵のprefixに以下オプションを指定してあげます。

no-touch-required

参考:https://man.openbsd.org/sshd.8#no-touch-required

ざっとみた感じUV(User Verificaiton)はなさそうです。 (U2Fの時代はそんなフラグなかったもんね。)

Provider(middleware)の制御

今回の検証ではbuilt-inlibfido2を指定したため、特になにも指定しませんでしたが、これもオプションで色々制御できます。

ssh-agentでのWhite-listの設定

通常、ssh-agentで利用するshared-librarywhitelist制御されています。デフォルトのwhitelist/usr/lib/*, /usr/local/lib/* なので普通は特に指定なしで問題ない事が多いですが、もし別のpathにインストールされている場合は別途whitelist追加してあげる必要があります。

-P provider_whitelist

参考:https://man.openbsd.org/ssh-agent.1#P

ssh-addでのprovider指定

ssh-addでagentに追加するタイミングでproviderの指定ができます。これはオプション環境変数で可能。

  • オプション指定の場合

-S provider

参考:https://man.openbsd.org/ssh-add.1#S

  • 環境変数指定の場合

SSH_SK_PROVIDER

参考:https://man.openbsd.org/ssh-add.1#SSH_SK_PROVIDER

ssh-keygenでのprovider指定

これも上記と同じような感じでオプション環境変数で可能です。

  • オプション指定の場合

-w provider

参考:https://man.openbsd.org/ssh-keygen.1#w

  • 環境変数指定の場合

SSH_SK_PROVIDER

参考:https://man.openbsd.org/ssh-keygen.1#SSH_SK_PROVIDER

resident keyによるキーペアの導出

さて、U2F/FIDO認証にはセキュリティキーに加えてkey-handleも必要なわけですが、 それだと各ログイン元であるSRCにkey-handleを何らかの方法で逐一転送してやらないといけないくなります。

それだとせっかくのセキュリティキーのポータブルな旨味が目減りします。

そこでセキュリティキーのみでなんとかなるように、FIDO2でのresident keyを活用してキーペアを導出するためのseedをセキュリティキーへストア・利用する機構があるようです。

これにより、セキュリティキーのみで任意の環境からのSSHが可能となります。 (※ セキュリティキーのみで認証が完結できるため、セキュリティキー盗難時のリスクが大きくなる気もしますが、 residentkeyへのアクセスはPIN認証が必須であり対策がとられています。)

手順としてはまずssh-keygenでのキーペア生成時にオプションでresidentkey利用を指定してやります。

ssh-keygen -Oresident -t ecdsa-sk

これでセキュリティキーへストアされ、利用する際はssh-keygenでのexport, ssh-addでのエージェント追加などします。

ssh-keygen -K

参考:https://man.openbsd.org/ssh-keygen.1#K

ssh-add -O

これならまあまあ使い勝手も良いのではないでしょうか。

まあ自分の環境ではこれは再現しなかったんですけどね。。。。

おわりに

今回、SSHでのU2F認証を試してみました。

もちろんこのサポートのアプローチでなくともSSHにはPAMやらForceCommandやら を駆使することで別のやり方もできそうです。

個人的にはSRCのクライアントのsshクライアント更新はまだいいとして、 Destのsshdも最新に更新しないとけないのはなかなかハードル高い気がしてます。(PIVとかだとそんな必要なかったのに)

ただ、キーペアの生成から認証まで一貫してFIDOのプロトコルで面倒見れて手軽な印象だったので、 そのあたりは美味しいのかもしれません。

所々オプションがうまくいかなかったりWSLやMacでも試しましたが、挙動がいまいちだったので もう少し安定しないと使うのは厳しいそうな印象でした。

と言うことでもう少し様子見しまふ。という〆


  1. 2020-02-14、OpenSSH 8.2にて正式にFIDO/U2Fサポートがリリースされました。 詳細はOpenSSH-8.2リリースノートをご参照ください。 ↩︎


See also