こんにちは。GMOグローバルサイン・ホールディングスCTO室で分散型IDの研究開発をしている神沼@t_kanumaです。
この記事は以下の記事の続編です。
前編では、Node.js環境でAFJを利用して疑似的なHolder Agentを作り、VCモデルを一通り回すことができました。後編では同じようにAFJを利用しながら、React Native環境でAndroidアプリとしてHolder Agentを作りVCモデルを回す試みの様子をお届けしたいと思います。ただそこに進む前に、この中編でWalletを暗号化するマスターキーについて述べたいと思います。なぜかというと、このマスターキーはWalletの外で開発者が運用するモノであり、Agentの開発と運用において重要になると考えるためです。
Walletの概要
概略するとWalletは暗号化されたDBであり、データとしてPublic/Pairwise DIDの秘密鍵、Verifiable Credential(VC)、他Agentとのやり取り(VC発行やProof検証)の状態、Master Secret(後述します)などを持つと考えます。暗号処理を行うIndy SDKにラップされており、AriesのフレームワークはIndy SDKを通してWalletにアクセスします。Indy SDKはデフォルトでSQLiteをWalletのストレージとしますが、条件に合えば異なるストレージに差し替え可能で、ACA-PyとAFJではPostgreSQLをサポートしています。
詳細は以下のWalletに関するIndyとAriesのドキュメントをご参照ください。
- Aries RFC: concepts/0050 Wallets (x)
- Indy SDK: concepts/Default Wallet Implementation (y)
- Indy SDK: design/003-wallet-storage (z)
以下、上記ドキュメント(x)より抜粋したWalletの構造を表す図です。wallet coreの部分がIndy SDKに、pluggable storageの部分がPostgreSQLやSQLiteに該当すると考えます。
備考: Master Secret(Link Secret)
備考になりますが、前述のMaster SecretはIndyがサポートするVCのフォーマットであるIndy AnonCredsの仕組みの中で、HolderがVCのオーナーであること(そのHolderに発行されたVCであること)の証明(Verifierによる検証)に使われます。Holderに対する相関付け、名寄せが起きないようプライバシーを保護する目的があり、Wallet内に1つ存在するシークレットな値です。以下、Indy SDKの開発ガイドからの抜粋です。
A Master Secret is an item of Private Data used by a Prover to guarantee that a credential uniquely applies to them. The Master Secret is an input that combines data from multiple Credentials to prove that the Credentials have a common subject (the Prover). A Master Secret should be known only to the Prover.
以下のEvernymのブログ記事も参考になります。
また各種VCにおけるプライバシー保護機能については、以下のEvernymのコンテンツが参考になります。
- ブログ記事: Categorizing Verifiable Credentials
- ブログ記事: Why the Verifiable Credentials Community Should Converge on BBS+
- YouTube動画: What BBS+ Means For Verifiable Credentials
マスターキーとそれを導出するためのパスフレーズ
Indy SDKがWalletの操作で扱うキーについて平たくいうと、データ(VCやPeer DIDのキーペアなど)を暗号化するCEK(Content Encryption Key)と、それを暗号化する複数のKEK(Key Encryption Key)があり、さらにそれらをまとめて暗号化するKEKとしてマスターキーが存在するようです。(マスターキー以外はWallet内に格納される。)上記ドキュメント(x)からの以下の抜粋の通り、そうすることでマスターキーのローテーションに対し、全てのデータを再び暗号化させる必要がなくなります。
The 7 “column” keys are concatenated and encrypted with a wallet master key, then saved into the metadata of the wallet. This allows the master key to be rotated without re-encrypting all the items in the wallet.
ACA-PyでWalletの構成設定について責務を持つwallet_setup.py周辺のコードや、AFJのコード(type.ts、IndyWallet.ts)またAFJの開発ガイドのAgent構成設定に関するパートを読むと、ACA-PyおよびAFJの実装では、このマスターキーはIndy SDKの中でargo2により鍵導出(Key Derivation)されることがわかります。
ポイントとして、ドキュメント(y)からの以下の抜粋の通り、この鍵導出のパラメーターになるパスフレーズは、Wallet(Agent)の外で開発者が生成し管理します。
The passphrase to open the wallet is stored outside of indy-sdk and is left to the consumer’s security preference such as HSMs, TEEs, or offline methods.
開発者はどこでパスフレーズを指定するか
ACA-Py
ここから、「では実際Ariesフレームワークを基盤にAgentを作る開発者は、実装においてどこにどうマスターキーのパラメーターであるパスフレーズを指定すれば良いのか?」という問いに答えていきます。
ACA-Pyではその起動に際し、2つのコマンドラインパラメータ(もしくは環境変数)を用意しています。
- パラメーター名: wallet-key / 環境変数名: ACAPY_WALLET_KEY
- aca-py helpコマンドで取得できるdescriptionは以下の通りです。
Specifies the master key value to use to open the wallet.
- パラメーター名: wallet-rekey / 環境変数名: ACAPY_WALLET_REKEY
- (まだ検証はしていませんが)このパラメーターはマスターキーのローテーションに使えるようです。
- aca-py helpコマンドで取得できるdescriptionは以下の通りです。
Specifies a new master key value to which to rotate and to open the wallet next time.
AFJ
AFJでは、(前編の記事で登場したものの触れてなかったのですが)、Agentの初期化にてwalletConfig.keyプロパティで設定します。以下、前編の記事から抜粋したコードです。
const config: InitConfig = {
label: "nodejs-holder-agent",
walletConfig: {
id: "nodejs-holder-wallet",
key: "testkey0000000000000000000000000",
},
// omit...
};
const holder = new Agent(config, agentDependencies);
またACA-Pyと同様に、AFJでもマスターキーをローテートするメソッドが用意されています。(検証はしていませんが)以下、サンプルコードです。
const walletConfigRekey: WalletConfigRekey = {
id: "<agentが現在繋いでるwalletのid>",
key: "<agentが現在繋いでるwalletのパスフレーズ>",
rekey: "<置き換える新しいパスフレーズ>",
};
holder.wallet.rotateKey(walletConfigRekey);
パスフレーズ管理の設計
ここからCloud/EdgeそれぞれのAgentにおいて、どうパスフレーズを管理すればいいか、その手法について考えてみます。
Cloud Agent(ACA-Py)における設計”案”
AWSでACA-Pyを使いIssuerおよびVerifier Agentを構築するケースを考えてみます。具体的には以下の拙著で行った、ECSでACA-Pyコンテナを建てるケースについて考えてみます。
まずパスフレーズを2種類用意します。これらをパスフレーズ1(環境変数のACAPY_WALLET_KEYに設定する値)と、ローテーションで置き換えするパスフレーズ2(環境変数のACAPY_WALLET_REKEYに設定する値)とします。これらを、シークレットな値を保管するためのサービスであるSystems Manager Parameter StoreもしくはSecret Managerにデータとして保管します。
Parameter StoreおよびSecret Mangerは背後でAWS KMSと連携しています。AWS KMSは内部でHSMを保持しており、HSM内でCMK(Customer Master Key – AWS KMS固有の用語)を管理します。データとしてのパスフレーズ1および2は、データが変更されるたびに破棄と新規作成されるData Key(AWS KMS固有の用語)によって暗号化/復号されます。そしてData KeyはCMKによりHSM内部で暗号化/復号される仕組みです。
(CMKおよびData KeyはParameter Store/Secret Managerにより管理されます。詳細は以下をご参照ください。
- AWS KMS開発ガイド: AWS Systems Manager Parameter StoreがAWS KMSを使用する方法
- AWS Secret Managerユーザーガイド: シークレット暗号化と復号
)
ACA-Pyコンテナ起動/再起動の際は、ECSにてParameter StoreまたはSecret Manager内のパスフレーズ1と2のARNを参照させ、コンテナ環境変数に設定させる流れです。
ローテーションについては、Event BridgeでLambdaを定期実行させて、パスフレーズ1をパスフレーズ2の値で上書きます。そして新しいパスフレーズ2を生成し保管します。この処理の後にECSのタスクを再起動するように予めスケジューリングしておけば、ACA-Pyコンテナが再起動されてマスターキーがローテーションされます。
上記のような運用の自動化が、(まだ検証してはおりませんが)可能ではないかと考えます。
Edge Agent(AFJ)における設計”例”
見当がつかなかったため、参考になる例としてAries Mobile Agent React Nativeのコードを探りました。
Aries Mobile Agent React Nativeは以下の拙著でも述べた通り、AFJを基盤にしたモバイルアプリで、Hyperledger公式のGitHubリポジトリです。開発者から見ると参照実装的な位置付けとして捉えることができると考えます。
コードを読んだ結果として、Androidの場合の処理方式を以下に図化します。
ユーザー(Holder)がアプリの初期設定で入力するPINコードとversion4 UUIDを元にパスフレーズ(argon2で導出しているためパスフレーズというよりキー)を生成し、それをAndoroid Keystore Systemを使い暗号化した上でSharedPreferenesに保管する仕組みでした。(そしてアプリ起動時にパスフレーズをロードしてIndy SDKにて再度argon2でマスターキーを導出する流れです。)
以下に参考までに上図の処理を行うコード群を列挙します。
- core/App/navigators/RootStack.tsx
- core/App/contexts/auths.tsx
- core/App/services/keychain.ts
- core/App/utils/crypto.ts
- ライブラリ: react-native-keychain
後編で構築する環境
最後に、後編でHolder Agentを動作させる環境について簡単にご紹介して、この記事を終わりにします。
- Holder Agent以外の要素は前編と同様Dockerコンテナです。これらをAWS上に構築します。AWS環境はシンプルにEC2インスタンスを1つ建て、そこにDockerをインストールし、前編と同様に各コンテナを繋ぎます。
- Holder AgentはAndroidアプリであり、筆者のスマートフォンにインストールして動作させます。
- HolderからAWSへ、またAWSからHolderへの通信はインターネットを通ります。
おわりに
中編となる当記事では、AgentのWalletとそのマスターキーについて述べました。
どなたかのお役に立てたならば幸いです。
後編はこちらです。