zkLogin 是 Web3 中第一個 Sui 原語,是一種真正無需信任、安全且用戶友好的身份驗證機制。藉助 zkLogin,開發人員可以創建無縫的入門體驗,讓用戶使用熟悉的 Web2 憑據(例如 Google 或 Facebook)登錄,輕鬆創建和管理 Sui 地址。

使用 zkLogin 爲地址簽署交易時,必須提供唯一的鹽值來表示與 Sui 地址關聯的 OAuth 或 Web2 憑據。此鹽值對於確保鏈上地址無法追溯到用戶的 Web2 憑據至關重要。鹽服務器負責在發起交易時可預測地生成、存儲和提供鹽。開發人員有多種選項來生成和存儲此鹽值,無論是在客戶端還是在服務器端。

在 Mysten Labs,我們運營一個鹽服務器,該服務器使用主種子與用戶的 JSON Web Token (JWT) 結合來爲每個應用的每個用戶派生一個可重現的鹽值。此過程包括根據提供商驗證 JWT。考慮到主種子的敏感性,在典型的計算環境中託管鹽服務器是不負責任的。保護主種子對於維護 Web2 身份與 Sui 地址的分離至關重要。

爲了保護我們的客戶和合作夥伴的用戶身份,我們的鹽服務器在安全的計算環境中運行,確保免受意外或惡意泄露。我們將在此概述我們的方法,以供其他希望實施類似解決方案的人蔘考。

zkLogin 和鹽服務器

如上所述,在使用 zkLogin 時,鹽服務器在維護用戶 Web2 憑證的隱私和安全方面發揮着重要作用。使用祕密主種子和用戶的 JWT,鹽服務器會爲該應用生成一個該用戶獨有的鹽值,但會隱藏用戶身份與其 Sui 活動的聯繫,以加密方式確保隱私。在生成 zkLogin 證明之前,因此在鏈上發出交易之前,鹽值是必需的。

當有人使用由 Mysten Labs 鹽服務器支持的應用程序時,他們會輸入自己的 Web2 憑據,然後應用程序會從身份驗證提供商處請求 JWT。然後,應用程序將 JWT 發送到鹽服務器以獲取鹽值。每次從用戶身份派生 Sui 地址時,包括計算原始證明以獲取用戶的 zkLogin 簽名時,鹽都會用於確保始終可以從用戶的令牌中確定性地計算出用戶的地址,而不會泄露兩者之間的綁定。

深入研究 Salt 服務器

保護鹽服務器使用的主種子有三個主要目標。它必須安全地生成,防止在 Mysten Labs 內部暴露給任何人,並防止通過服務或通過我們雲提供商的旁道攻擊暴露給外部互聯網。如果除了鹽服務器之外沒有任何人或系統看到密鑰,我們可以對其保密性充滿信心,因此可以確保從 JWT 到鏈上地址的哈希映射的有效性。

可信計算系統

託管可信計算基礎設施有多種選擇。在理想情況下,所有哈希計算都將在可信硬件模塊(如硬件安全模塊 (HSM) 或可信平臺模塊 (TPM))內進行。

但是,將主種子綁定到單個硬件意味着該硬件成爲系統中的單點故障——無法訪問 HSM 或 TPM 將意味着主種子永久丟失。在這種情況下,種子丟失的風險對於我們的鹽服務器來說太高了,所以我們需要一些東西來權衡可信硬件的絕對安全性和更大的靈活性。我們確定的解決方案是使用可信計算環境,我們可以在具有容器證明的隔離環境中運行服務器,並只允許通過 TCP 直接訪問服務的端點。

所有三大雲提供商都提供可信計算解決方案;Azure 機密計算、GCP 機密虛擬機和 AWS Nitro Enclaves 都是支持隔離計算的環境。我們選擇在 EC2 上使用 Nitro Enclaves。使用 Nitro Enclaves,我們可以帶來自己的容器映像和現有的構建工具,並在其上添加 Enclave 層。

種子生成

由於主種子是永久的且不可輪換,因此生成只會發生一次。種子可以是任何字節序列,因爲隨機性纔是最重要的。但是,例如,如果我自己生成了主種子,那麼它就會很脆弱。即使我沒有向任何其他人透露主種子,由於我知道主種子,我也會成爲 Mysten Labs zkLogin 實現及其上構建的任何應用程序的功能的根本漏洞。相反,我們更喜歡以自動化方式生成密鑰。

在非氣隙隔離的機器上生成種子會引入額外的漏洞,因爲它會暴露在系統的其餘部分甚至互聯網上。Nitro Enclaves 用於創建一個隔離環境,我們可以在其中安全地爲 zkLogin 的主密鑰生成密鑰。

在安全區內,我們隨機生成主種子。我們使用加密密鑰加密種子,並將密鑰存儲在機密存儲中。機密存儲的配置使得只有安全區可以獲取機密,甚至管理員也無法訪問明文機密。我們還拆分密鑰並存儲分片,如下文“種子恢復”部分所述。

Nitro Enclave 內部運行的 shell 腳本相對簡單。

./generate-random-seed > seed.json secrets-store put --name SEED --file seed.json

種子用途

主種子密鑰具有僅允許 enclave 身份訪問它的策略,這意味着即使是管理員也不能偶然讀取它。我們還爲鹽服務器密鑰維護一個單獨的雲提供商帳戶,以將管理員訪問權限與其他 Mysten Labs 項目分開,並限制具有訪問權限的人數。

當 Nitro Enclave 中的鹽服務器讀取密鑰時,它會以明文形式保存在內存中。我們依靠隔離環境的保護來防止在同一主機上進行訪問,因此對靜態種子進行加密並針對每個請求進行解密不會有任何好處。種子在每個請求中都會使用,因此常規流量會破壞靜態加密所提供的保護。

爲了讓服務仍然能夠接收流量,我們必須允許一小部分網絡訪問鹽服務器安全區環境。我們使用 vsock 代理來限制通過單個應用程序端口的流量,以及流向 OAuth 提供商進行 JWT 驗證和流向網關地址進行可觀察性發布的流量。這允許鹽服務器完成其驗證和鹽生成工作,並讓我們在禁止所有其他網絡訪問的同時監控服務。

這裏是一些示例代碼,演示瞭如何使用 Pulumi 配置 enclave 並初始化 enclave。

def get_enclave_instances(args:EnclaveInstanceArgs) -> List[aws.ec2.Instance]:instances = [] for index in range(args.instance_count):name = f“{pulumi.get_stack()}-salt-server-ec2-{index}”instance = 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)返回實例def get_startup_script(role_name:str,role_arn:Output[str],image_name:Output[str],index:int):aws_config = pulumi.Config(“aws”)返回Output.all(role_arn,image_name).apply(lambda args:f“”“#!/bin/bash#打印每個命令集-o xtrace yum install awscli aws-nitro-enclaves-cli-devel aws-nitro-enclaves-cli docker nano socat -y nitro-cli 終止enclave --all killall socat nitro-cli build-enclave --docker-uri'{args[1]}'--輸出文件 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,端口:443}} - {{address:secretsmanager.us-west-2.amazonaws.com,端口:443}} - {{address:kms.us-west-2.amazonaws.com,端口: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 實例發送到 enclave 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} >assuming-role.json # 解析 JSON 輸出並設置環境變量 catassuming-role.json | socat - VSOCK-CONNECT:$ENCLAVE_CID:7777 # 這是在 enclave 啓動後運行的,因此來自端口 8888 的流量 -> 可以重定向到 vsock # 監聽所有端口 8888 以從外部轉發到 ENCLAVE CID->Port socat TCP4-LISTEN:8888,reuseaddr,fork VSOCK-CONNECT:$ENCLAVE_CID:3000 & """ )

此 Python Pulumi 代碼爲我們服務的每個副本創建一個 EC2 實例,將其連接到我們設置的網絡,併爲其啓用 Nitro Enclaves。我們插入的 user_data 構建了 enclave,設置了 vsock 代理,並啓動了 enclave。

我們盡一切努力成功保護主種子的使用,但將我們的祕密留在單一服務的雲提供商那裏仍然存在風險。制定種子恢復計劃可確保我們在災難情況下有多種方法恢復主種子。

種子回收

我們最初的種子生成過程的一部分涉及將種子拆分成多個加密碎片,以便由參與設計鹽服務器系統的一組人員進行恢復。我們使用 Unit 410 的 Horcrux 實用程序來實現這一點。

Horcrux 使用 Shamir 的祕密共享來生成種子的多個加密部分,並允許使用指定的碎片子集進行解密。每個碎片都使用小組中每個人擁有的硬件密鑰進行加密,從而增加了地理分佈的安全級別。

加密碎片冗餘存儲在多個遠程服務器中。由於它們是加密的,我們將它們存儲在一起並增加冗餘以避免單點故障。如果密鑰從服務器丟失,可以快速恢復並通過 Horcrux 保持保密性。

權衡

我們的鹽服務器生產系統與我們運行的許多其他服務看起來截然不同。我們在 Nitro Enclaves 中運行該服務,因此對其設置了安全限制,這使得鹽服務器的運營本身就具有挑戰性。

我們在安全區中添加的每一個網絡代理都是主種子外泄的潛在面。只要我們繼續對每個新的 OAuth 提供商遵循相同的做法,我們預計攻擊面將保持不變。我們預計安全區本身不需要更多進出網絡流量。

鹽服務器必須保持簡單;與其他 Mysten Labs 服務的通信以及隨着時間的推移與新工具的集成可能需要單獨考慮所涉及的安全權衡。我們將在嚴格受限的環境中維護系統,以努力維護最終成爲基於我們的 zkLogin 實現構建的應用程序的系統關鍵部分。

安全第一

在 Mysten Labs,我們專注於解決基礎問題,zkLogin 就是這一重點的具體體現。隨着我們繼續構建與 zkLogin 類似並在其之上的其他新構造,我們將以加密可證明的方式使我們的系統達到最高的安全標準。我們的實施與 Nitro Enclaves 和 Horcrux 的強大功能相結合,展示了對這些標準的承諾,並將 Web3 的好處帶給了每個人。