zkLogin は、Web3 で初めての Sui プリミティブであり、真に信頼不要で、安全で、ユーザーフレンドリーな認証メカニズムです。zkLogin を使用すると、開発者はシームレスなオンボーディング エクスペリエンスを作成し、ユーザーが Google や Facebook などの使い慣れた Web2 認証情報でサインオンして、Sui アドレスを簡単に作成および管理できるようになります。
zkLogin を使用してアドレスのトランザクションに署名する場合、Sui アドレスに関連付けられた OAuth または Web2 認証情報を表す一意のソルト値を提供する必要があります。このソルト値は、オンチェーン アドレスがユーザーの Web2 認証情報にさかのぼって追跡されないようにするために重要です。ソルト サーバーは、トランザクションが開始されるたびに、ソルトを予測どおりに生成、保存、および提供する役割を担います。開発者は、クライアント側でもサーバー側でも、このソルト値を生成して保存するためのいくつかのオプションを利用できます。
Mysten Labs では、マスター シードとユーザーの JSON Web Token (JWT) を組み合わせて、ユーザーごと、アプリごとに再現可能なソルト値を生成するソルト サーバーを運用しています。このプロセスには、プロバイダーに対する JWT の検証が含まれます。マスター シードの機密性を考えると、一般的なコンピューティング環境でソルト サーバーをホストするのは無責任です。マスター シードを保護することは、Web2 ID と Sui アドレスの分離を維持するために不可欠です。
お客様とパートナーのユーザー ID を保護するために、当社のソルト サーバーは安全なコンピューティング環境で動作し、偶発的または悪意のある露出から保護します。同様のソリューションの実装を検討している他のユーザーの参考として、ここで当社のアプローチの概要を説明します。
zkLoginとソルトサーバー
上で説明したように、ソルト サーバーは、zkLogin を使用する際にユーザーの Web2 認証情報のプライバシーとセキュリティを維持する上で重要な役割を果たします。ソルト サーバーは、秘密のマスター シードとユーザーの JWT を使用して、そのアプリのそのユーザーに固有のソルト値を生成しますが、ユーザーの ID から Sui アクティビティへの接続を隠し、暗号的にプライバシーを確保します。ソルト値は、zkLogin 証明を生成する前、つまりチェーン上でトランザクションを発行する前に必要です。
Mysten Labs のソルト サーバーでサポートされているアプリを使用する場合、ユーザーは Web2 認証情報を入力し、アプリケーションは認証プロバイダーから JWT を要求します。次に、アプリは JWT をソルト サーバーに送信してソルト値を取得します。ユーザーの ID から Sui アドレスが派生するたびに (ユーザーの zkLogin 署名を取得するための元の証明の計算を含む)、ソルトは、2 つの間のバインディングを明らかにすることなく、ユーザーのアドレスが常にトークンから決定論的に計算できることを保証するために使用されます。
ソルトサーバーに潜入
ソルト サーバーが使用するマスター シードを保護する主な目的は 3 つあります。マスター シードは安全に生成され、Mysten Labs 内部の誰にも公開されないように保護され、サービス経由またはクラウド プロバイダーのサイド チャネル攻撃によるインターネットへの外部公開から保護される必要があります。ソルト サーバー以外の人物やシステムがキーを見ることができない場合、その秘密性、つまり JWT からオンチェーン アドレスへのハッシュ マッピングの有効性に自信を持つことができます。
信頼できるコンピューティングシステム
信頼できるコンピューティング インフラストラクチャをホストするためのさまざまなオプションが存在します。理想的な世界では、すべてのハッシュ計算は、ハードウェア セキュリティ モジュール (HSM) やトラステッド プラットフォーム モジュール (TPM) などの信頼できるハードウェア モジュール内で実行されます。
ただし、マスター シードを単一のハードウェアにバインドすると、そのハードウェアがシステムの単一障害点になります。つまり、HSM または TPM にアクセスできなくなると、マスター シードが永久に失われることになります。このような状況下でのシード損失のリスクは、ソルト サーバーにとって高すぎるため、信頼できるハードウェアの絶対的なセキュリティと引き換えに柔軟性を高めるものが必要でした。私たちがたどり着いた解決策は、信頼できるコンピューティング環境を使用することです。この環境では、コンテナー認証を使用してサーバーを分離された環境で実行し、サービスのエンドポイントに直接 TCP 経由でのみアクセスを許可できます。
3 つの主要なクラウド プロバイダーはすべて、信頼できるコンピューティング ソリューションを提供しています。Azure Confidential Computing、GCP Confidential VM、AWS Nitro Enclaves はすべて、分離されたコンピューティングを可能にする環境です。私たちは、EC2 で Nitro Enclaves を使用することを選択しました。Nitro Enclaves を使用すると、独自のコンテナ イメージと既存のビルド ツールを持ち込み、その上に Enclave レイヤーを追加できます。
種子生成
マスター シードは永続的でローテーションできないため、生成は 1 回だけ行われます。シードはランダム性がすべてであるため、任意のバイト シーケンスにすることができます。ただし、たとえば、マスター シードを自分で生成した場合、脆弱になります。マスター シードを他の人に公開しなくても、マスター シードを知っているため、Mysten Labs zkLogin 実装とそれに基づいて構築されたすべてのアプリの機能に対する根本的な脆弱性になります。代わりに、キーを自動的に生成することを好みます。
エアギャップのないマシンでシードを生成すると、システムの残りの部分やインターネットに公開されるため、追加の脆弱性が生じます。Nitro Enclaves は、zkLogin のマスター キーのキー生成を安全に実行できる隔離された環境を作成するために使用されました。
エンクレーブ内では、ランダム性からマスター シードを生成します。シードを暗号化キーで暗号化し、そのキーをシークレット ストアに保存します。シークレット ストアは、エンクレーブのみがシークレットを取得できるように構成されており、管理者でさえプレーンテキストのシークレットにアクセスすることはできません。また、以下の「シードの回復」セクションで説明されているように、キーを分割してシャードを保存します。
Nitro Enclave 内で実行されるシェル スクリプトは比較的単純です。
./generate-random-seed > seed.json secrets-store put --name SEED --file seed.json
種子の使用
マスター シード シークレットには、エンクレーブ ID のみがアクセスできるポリシーがあるため、管理者であっても偶然に読み取ることはできません。また、ソルト サーバー シークレット用に別のクラウド プロバイダー アカウントも維持し、管理者のアクセスを他の Mysten Labs プロジェクトから分離し、アクセス権を持つユーザーの数を制限しています。
Nitro Enclave のソルト サーバーによってキーが読み取られると、キーはプレーンテキストでメモリに保存されます。同じホストへのアクセスを防止するために、隔離された環境の保護に依存しているため、保存時にシードを暗号化し、リクエストごとに復号化してもメリットはありません。シードはすべてのリクエストで使用されるため、通常のトラフィックは保存時の暗号化が提供する保護を損ないます。
サービスが引き続きトラフィックを受信できるようにするには、ソルト サーバーのエンクレーブ環境へのネットワーク アクセスの狭いサブセットを許可する必要がありました。vsock プロキシを使用して、単一のアプリケーション ポートを経由するトラフィックと、JWT 検証用の OAuth プロバイダーへのトラフィック、および可観測性公開用のゲートウェイ アドレスへのトラフィックを制限します。これにより、ソルト サーバーは検証とソルト生成のジョブを実行でき、他のすべてのネットワーク アクセスを禁止しながらサービスを監視できます。
以下は、Pulumi を使用してエンクレーブをプロビジョニングし、エンクレーブを初期化する方法を示すサンプル コードです。
def get_enclave_instances(args: EnclaveInstanceArgs) -> List[aws.ec2.Instance]: インスタンス = [] 範囲(args.instance_count)内のインデックスの場合: name = f"{pulumi.get_stack()}-salt-server-ec2-{index}" インスタンス = aws.ec2.Instance( name, tags={ "Name": name, }, instance_type="m5.xlarge", subnet_id=args.network.subnets[0].id, vpc_security_group_ids=[ args.security_group.id ], ami="ami-mysten123", user_data=get_startup_script( args.role_name, args.role_arn, args.image_name, index ), user_data_replace_on_change=True, iam_instance_profile=args.instance_profile.name、enclave_options=aws.ec2.InstanceEnclaveOptionsArgs(enabled=True)、opts=ResourceOptions(depends_on=[args.ecr_image])、) pulumi.export(f"publicIp-{index}", instance.public_ip) pulumi.export(f"publicHostName-{index}", instance.public_dns) instances.append(instance) return instances def get_startup_script( role_name: str、 role_arn: Output[str]、 image_name: Output[str]、 index: int ): aws_config = pulumi.Config("aws") return Output.all(role_arn, image_name).apply( lambda args: f"""#!/bin/bash # 各コマンドを出力 set -o xtrace yum install awscli aws-nitro-enclaves-cli-devel aws-nitro-enclaves-cli docker nano socat -y nitro-cli exit-enclave --all killall socat nitro-cli build-enclave --docker-uri '{args[1]}' --output-file salt.eif EIF_SIZE=$(du -b --block-size=1M "salt.eif" | cut -f 1) ENCLAVE_MEMORY_SIZE=$(((($EIF_SIZE * 4 + 1024 - 1)/1024) * 1024)) cat >> /etc/nitro_enclaves/vsock-proxy.yaml <<EOF - {{address: accounts.google.com, port: 443}} - {{address: secretsmanager.us-west-2.amazonaws.com, port: 443}} - {{address: kms.us-west-2.amazonaws.com, port: 443}} EOF # プロバイダー vsock-proxy 8001 accounts.google.com 443 --config /etc/nitro_enclaves/vsock-proxy.yaml & # kms vsock-proxy 8101 secretsmanager.us-west-2.amazonaws.com 443 --config /etc/nitro_enclaves/vsock-proxy.yaml および vsock-proxy 8102 kms.us-west-2.amazonaws.com 443 --config /etc/nitro_enclaves/vsock-proxy.yaml および nitro-cli run-enclave --cpu-count 2 --memory $ENCLAVE_MEMORY_SIZE --eif-path salt.eif ENCLAVE_ID=$(nitro-cli describe-enclaves | jq -r ".[0].EnclaveID") ENCLAVE_CID=$(nitro-cli describe-enclaves | jq -r ".[0].EnclaveCID") sleep 5 unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN # ec2 インスタンスからエンクレーブに認証情報を送信します TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"` \ && curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/{role_name} > unused-role.json # JSON 出力を解析し、環境変数を設定します cat unused-role.json | socat - VSOCK-CONNECT:$ENCLAVE_CID:7777 # これは、エンクレーブが起動した後に実行されるため、ポート 8888 からのトラフィックは vsock にリダイレクトされます # すべてのポート 8888 をリッスンして、外部から ENCLAVE CID-> ポートに転送します socat TCP4-LISTEN:8888,reuseaddr,fork VSOCK-CONNECT:$ENCLAVE_CID:3000 & """ )
この Python Pulumi コードは、サービスのレプリカごとに EC2 インスタンスを作成し、それを設定したネットワークに接続し、Nitro Enclaves を有効にします。挿入した user_data は、エンクレーブを構築し、vsock プロキシを設定し、エンクレーブを起動します。
当社はマスター シードの使用を確実に保護するためにあらゆる努力をしていますが、単一のサービスでクラウド プロバイダーに秘密を預けることにはリスクが残ります。シード回復計画を実施することで、災害シナリオでマスター シードを回復する複数の方法を確保できます。
種子回収
初期のシード生成プロセスでは、ソルト サーバー システムの設計に携わったグループがシードを復元できるように、シードを複数の暗号化されたシャードに分割しました。これを実現するために、Unit 410 の Horcrux ユーティリティを使用しました。
Horcrux は、シャミアの秘密分散法を使用してシードの複数の暗号化部分を生成し、指定されたシャードのサブセットで復号化できるようにします。各シャードは、グループ内の各個人が所有するハードウェア キーで暗号化されており、地理的に分散されたレベルのセキュリティが追加されています。
暗号化されたシャードは、複数のリモート サーバーに冗長的に保存されます。暗号化されているため、シャードをまとめて保存し、冗長性を追加して、単一障害点を回避します。サーバーからキーが失われた場合でも、Horcrux を通じて迅速に回復され、機密性が維持されます。
トレードオフ
当社のソルト サーバー実稼働システムは、当社が運営する他の多くのサービスとはかなり異なります。Nitro Enclaves で実行することでサービスに課せられたセキュリティ上の制約により、ソルト サーバーの運用化自体が課題となっています。
エンクレーブにネットワーク プロキシを追加するたびに、マスター シードが流出する可能性のある領域が増えます。新しい OAuth プロバイダーごとに同じプラクティスに従い続ける限り、攻撃対象領域は同じままであると予想されます。エンクレーブ自体に出入りするネットワーク トラフィックが増えることは予想していません。
ソルト サーバーは必然的にシンプルである必要があります。Mysten Labs の他のサービスとの通信や、時間の経過とともに新しいツールとの統合が行われる場合、セキュリティのトレードオフについて個別に考慮する必要がある可能性があります。zkLogin 実装に基づいて構築されたアプリケーションにとって最終的にシステムの重要な部分を維持するために、厳しく制約された環境でシステムを維持します。
セキュリティ第一
Mysten Labs では、基礎的な問題の解決に注力しており、zkLogin は、その注力の成果です。zkLogin に類似した、または zkLogin をベースにした新しい構造の構築を継続する中で、暗号的に証明可能な方法で、システムのセキュリティを最高水準に保ちます。Nitro Enclaves と Horcrux のパワーと組み合わせた当社の実装は、これらの基準への取り組みを示し、Web3 のメリットをすべての人に提供します。