亚马逊AWS官方博客

基于 Nitro Enclaves + AWS EKS 的加密钱包应用

一、概述

在 Web3 的世界里,钱包是一种管理数字资产的基础软件或应用程序,而软件始终是网络攻击者“容易”攻击的目标,并且钱包中将包含用户的私钥,即使密钥被加密后存储,但它们仍必须在内存中解密才能用于签署交易,因此我们需要确保钱包应用处于最安全的环境中。通过 TEE(可信执行环境)技术,我们能够以高度安全和可信的方式保护钱包应用,而无需担心钱包成为各种黑客活动的牺牲品。

AWS Nitro Enclaves 是亚马逊云科技提供的 TEE 解决方案,可同时兼容 Intel,AMD x86 处理器 以及 Graviton arm 架构处理器,并支持 EC2 实例和 Amazon EKS(Kubernetes)计算服务。在这篇文章中,我们将介绍如何实现基于 Nitro Enclaves TEE 技术的钱包应用,并在 AWS EKS 容器平台上部署。

二、方案概述

本方案构建一个基于 Amazon EKS 部署的 Nitro Enclaves 加密钱包示例应用程序,实现钱包账户生成,交易签名等主要功能。

下图展示了钱包应用的部署架构,使用包括: Nitro Enclaves,Amazon EKS,AWS KMS,DynamoDB 等服务。

利用 Nitro Enclaves 技术在 EKS worker 节点上提供一个隔离的计算环境(Enclave)来保护和安全地处理高度敏感的数据,例如区块链操作的私钥。Enclave 和 EKS Pods 以及外部 AWS 服务(如 AWS KMS 或 Secrets Manager)之间的通信仅限于安全的本地 vsocks 通道。

生成账户

通过调用 generateAccount API,在 Enclave 里生成钱包账户,并使用 KMS 进行信封加密(envelope encryption),并将私钥加密后存储到 DynamoDB。下图描述了这一过程通信流程:

生成账户工作流程说明:

  1. 父实例客户端接收 generateAccount API 调用
  2. 调用 getlAMToken() 函数获取 IAM Role 的凭证
  3. 通过 vsock 发送凭证并请求 generateAccount API 调用
  4. 在 Nitro Enclave 中生成加密钱包帐户
  5. 使用凭据和证明(Attestation)请求 KMS generateDataKey API 调用
  6. 使用 datakey 对账户信息进行加密
  7. 将加密后的账户信息通过 vsock 发送给父实例
  8. 调用 API 将加密后的账户信息保存到 DynamoDB

私钥签名

在 Enclave 里对钱包私钥进行解密,并使用私钥对交易签名。下图描述了这一过程通信流程:

交易签名工作流程说明:

  1. 父实例客户端接收 sign API 调用
  2. 调用 getlAMToken() 函数获取 IAM Role 的凭证
  3. 通过 vsock 发送凭据并签署 API 调用
  4. 使用 KMS API 对加密后的 datakey 进行解密
  5. 用 datakey 对加密的钱包私钥进行解密
  6. 利用解密后的钱包私钥对消息进行签名
  7. 通过 vsock 将签名发回给父实例

信封加密

Amazon KMS 支持使用信封加密技术保护数据,参考这个文档查看具体的细节。在方案中我们使用了 KMS 生成的 Data Key 对钱包私钥进行加密保护。在本文讨论的方案中我们采用了 AES 的方案对数据进行加密,代码片段如下:

func encrypt(key []byte, message string) string {
    //Create byte array from the input string
    plainText := []byte(message)
    //Create a new AES cipher using the key
    block, err := aes.NewCipher(key)
    //IF NewCipher failed, exit:
    if err != nil {
        log.Fatal(err)
    }
    //Make the cipher text a byte array of size BlockSize + the length of the message
    cipherText := make([]byte, aes.BlockSize+len(plainText))
    //iv is the ciphertext up to the blocksize (16)
    iv := cipherText[:aes.BlockSize]
    if _, err = io.ReadFull(rand.Reader, iv); err != nil {
        log.Fatal(err)
    }
    //Encrypt the data:
    stream := cipher.NewCFBEncrypter(block, iv)
    stream.XORKeyStream(cipherText[aes.BlockSize:], plainText)
    //Return string encoded in base64
    return base64.RawStdEncoding.EncodeToString(cipherText)
}
func decrypt(key []byte, secure string) string {
    //Remove base64 encoding:
    cipherText, err := base64.RawStdEncoding.DecodeString(secure)
    //IF DecodeString failed, exit:
    if err != nil {
        log.Fatal(err)
    }
    //Create a new AES cipher with the key and encrypted message
    block, err := aes.NewCipher(key)
    //IF NewCipher failed, exit:
    if err != nil {
        log.Fatal(err)
    }
    //IF the length of the cipherText is less than 16 Bytes:
    if len(cipherText) < aes.BlockSize {
        fmt.Println("Ciphertext block size is too short!")
    }
    iv := cipherText[:aes.BlockSize]
    cipherText = cipherText[aes.BlockSize:]
    //Decrypt the message
    stream := cipher.NewCFBDecrypter(block, iv)
    stream.XORKeyStream(cipherText, cipherText)
    return string(cipherText)
}

三、应用部署

1. 准备工作

参考另一篇博客《在 AWS EKS 容器部署 Nitro Enclaves 可信计算应用程序》 ,我们假定您已经拥有了一个安装好环境的 EKS 集群,并提前安装好所需的软件或工具,包括:

集群配置

在接下来的讨论中我们会使用手动部署的方式展示如何部署钱包应用。博客演示使用的集群配置关键数据如下:

  • 机型:m5.2xlarge
  • /etc/nitro_enclaves/allocator.yaml 配置: CPU_COUNT=2;MEMORY_MIB=16384
  • Hugepages 配置: vm.nr_hugepages=2048
  • 部署 Region:ap-northeast-1

IAM 配置

创建一个 IAM Role(EC2 service role),命名如:“EKSNodeRoleForNitroEnclaveApplication”,并附加到集群的每个 worker 节点,该 Role 必须拥有以下权限。注意替换 Resource 中的 KMS ARN(在下一步会创建),为了方便,在测试环境中可以使用*代替。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "dynamodb:CreateTable",
                "dynamodb:PutItem",
                "dynamodb:DescribeTable",
                "dynamodb:GetItem"
            ],
            "Resource": "*"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": [
                "kms:Decrypt",
                "kms:GenerateDataKey"
            ],
            "Resource": "arn:aws:kms:ap-northeast-1:774209043150:key/c314a998-b78f-44f8-8c12-6f426ccf89fd"
        }
    ]
}

KMS 配置

创建一个 KMS 用户管理对称密钥(Customer managed key),用于数据的加解密,步骤如下:

  • 第一步: 选择对称密钥
  • 第二步: 为 Key 添加别名

  • 第三步: 选择 Key 的管理员用户,如您账户下的一个某个管理员用户(只有 Key 管理员可以删除或修改 Key 的权限)

  • 第四步: 密钥使用权限,选择上一步创建的 IAM Role

  • 第五步:查看密钥详情,并记录下该密钥的 ARN 替换 IAM 策略 Resource 中的 KMS ARN

2. 部署钱包应用

在这里我们演示一下如何手动部署演示代码。演示代码使用了默认 region 是 ap-northeast-1,可以根据实际情况修改 Dockerfile 中的 REGION 参数,以及下载的代码中的 REGION 参数。

1. 编译 Nitro Enclave 需要的镜像文件

# get code
git clone https://github.com/forhead/nitro-examples.git
# go to enclave folder and build source code
cd nitro-examples/nitro-golang-example/enclave
go build
# build eif file
docker build -t enclave_server_go:latest .

nitro-cli build-enclave --docker-uri enclave_server_go:latest \
 --output-file enclave_server_go.eif > enclave_server_go.log

(可选)如果你是使用 Amazon Linux 以外的操作系统,放在基于 amazonlinux:2 容器里 build 成 .eif 后导出

docker run --name ne-builder -v /var/run/docker.sock:/var/run/docker.sock -v$(pwd)/datadir:/output -it ne-example-builder:latest /bin/bash
# then run commands to build and copy it out
nitro-cli build-enclave --docker-uri enclave_server_go:latest --output-file /output/enclave_server_go.eif

2. 部署 EKS 运行需要的 docker 镜像并推送到 ECR,注意替换下面命令中的数字 ID 为自己的 ID

# copy eif file to enclavek8s folder
cp enclave_server_go.eif ../enclavek8s/
# go to enclavek8s folder
cd ../enclavek8s 
 # build local image 
docker build -t enclave_server_k8s .
# login ECR
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS \
 --password-stdin 774209043150.dkr.ecr.ap-northeast-1.amazonaws.com
# create ECR repositry at first time, copy the repositoryUri from the output
aws ecr create-repository --repository-name enclaveserverk8s 
# tag 
docker tag enclave_server_k8s:latest 774209043150.dkr.ecr.ap-northeast-1.amazonaws.com/enclaveserverk8s:latest
# push to ECR 
docker push 774209043150.dkr.ecr.ap-northeast-1.amazonaws.com/enclaveserverk8s:latest

3. 编写 kms_server_deployment.yaml 文件,请替换其中的 image 的值为你自己的 ECR 的 URI

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kmsserver-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: kmsserver
  template:
    metadata:
      labels:
        app: kmsserver
    spec:
      containers:
      - name: kmsserver
        image: 774209043150.dkr.ecr.ap-northeast-1.amazonaws.com/enclaveclientk8s:latest
        command: ["/home/run.sh"]
        imagePullPolicy: Always
        volumeMounts:
        # - mountPath: /hugepages-2Mi
        #   name: hugepage-2mi
        #   readOnly: false
        # Enable if 1Gi pages are required
        - mountPath: /hugepages-1Gi
          name: hugepage-1gi
          readOnly: false
        resources:
          limits:
            aws.ec2.nitro/nitro_enclaves: "1"
            hugepages-1Gi: 1Gi
            cpu: 2000m
          requests:
            aws.ec2.nitro/nitro_enclaves: "1"
            hugepages-1Gi: 1Gi
      volumes:
      # - name: hugepage-2mi
      #   emptyDir:
      #     medium: HugePages-2Mi
      - name: hugepage-1gi
        emptyDir:
          medium: HugePages-1Gi
      tolerations:
      - effect: NoSchedule
        operator: Exists
      - effect: NoExecute
        operator: Exists

4. 部署 deployment

kubectl apply -f kms_server_deployment.yaml 

5. 部署过程可以通过 kubectl 工具查看进度,如果成功完成将会显示 Running 状态

kubectl get pods 

NAME   READY   STATUS    RESTARTS   AGE
kmsserver-deployment-56d985586c-vlwfv   1/1     Running   0          7s

6. 编译客户端程序镜像,并推送到 ECR 注意替换下面命令中的数字 ID 为自己的 ID

# go to client folder
cd nitro-golang-example/parentk8s
# build the source code
go build appClient.go
# build local docker image
docker build -t enclaveclientk8s -f Dockerfile .
# login the ecr, you should replace the account with yours
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 774209043150.dkr.ecr.ap-northeast-1.amazonaws.com
# create the ecr repo
aws ecr create-repository --repository-name enclaveclientk8s 
# tag local image 
docker tag enclaveclientk8s:latest 774209043150.dkr.ecr.ap-northeast-1.amazonaws.com/enclaveclientk8s:latest
# push image to ecr repo
docker push 774209043150.dkr.ecr.ap-northeast-1.amazonaws.com/enclaveclientk8s:latest

7. 编写 appclient-deployment.yaml 文件,请替换其中的 image 的值为你自己的 ECR 的 URI

apiVersion: apps/v1
kind: Deployment
metadata:
  name: appclient
  labels:
    app: appclient
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: appclient
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: appclient
    spec:
      containers:
      - image: 774209043150.dkr.ecr.ap-northeast-1.amazonaws.com/enclaveclientk8s:latest
        imagePullPolicy: Always
        name: appclient
        ports:
        - containerPort: 5000
          protocol: TCP

8. 部署 deployment

kubectl apply -f appclient-deployment.yaml

3. 环境清理

完成以上部署及功能验证后,为避免持续产生费用,请按照以下步骤停止应用或者删除所有已部署的组件:

# 删除pod
kubectl get pods
kubectl delete pod <your-pod-name>

# 清理未完成的部署
kubectl delete deployment appclient

# 删除由 enclavectl 工具创建的eks集群
enclavectl cleanup

四、功能演示

生成钱包

钱包生成的逻辑在 Enclave 中实现,最终会存储到 Dynamodb 中,在上面步骤中部署完成 parentk8s 的 pod 之后,程序中的代码自动执行,最终会将生成的钱包数据存储到 dynamodb。表 AccountTable 设计如下:

  • KeyId:用来生成 DataKey 的 KMS ID
  • Name:钱包的名称,用来区分其他生成的钱包
  • EncryptedPrivateKey:钱包被加密的私钥
  • Address:钱包地址
  • EncryptedDataKey:加密后的 DataKey,其中 DataKey 被用来加密钱包私钥

可以在 Dynamodb 界面查看 AccountTable 的输入数据:

交易签名

交易签名的逻辑在 Enclave 中实现,在演示中,代码会将相关数据存储到 Dynamodb 中,表 SignedValueTable 的设计如下:

  • Name:钱包的名称,用来区分其他生成的钱包
  • Transaction:Transaction 原始数据
  • SignedValue:签名结果

五、原理解析

本方案实现了基于 Nitro Enclaves 的基本钱包功能, 这里对过程进行详细分解,以便您在了解其中的技术原理后也可以实现自己的钱包或其它 Enclave 应用。

生成钱包

  1. 运行在 EKS 上的 API 后端服务接收来自客户端的生成钱包账户 API 请求后,从 STS 请求访问 Enclave 需要的临时安全凭证(IAM role) ;
  2. 后端服务通过 vsock 发送凭证,并向 Enclave 里运行的钱包应用发出服务请求;
  3. 钱包应用收到请求后在 Enclave 环境生成明文的钱包账户;

  1. 应用程序从 Enclave 调用 kms:GenerateDataKey API 命令生成数据密钥(data key),并从 Nitro 系统获取已签名的证明文件(attestation document),其中包括 Enclave 的独特信息(PCRs),以证明其身份并与 KMS 服务建立信任;
  2. Enclave 应用通过 vsock 将证明文件发送给 EKS,并经过 Pod 上运行的 KMS proxy 将证明文件发送给 AWS KMS;
  3. KMS 验证证明文件由 Nitro Hypervisor 签署,且 KMS 密钥策略定义的 PCR 与证明文件上的 PCR 匹配后执行 GenerateDataKey ,并使用 Enclave 的公钥(在证明文件中提供)加密数据密钥(data key)后通过 vsock 发回给 Enclave 应用;

  1. Enclave 使用它的私钥对加密的数据密钥(data key)进行解密,并使用数据密钥(data key)对账户信息进行加密;
  2. Enclave 应用将加密后的账户信息通过 vsock 发送给运行在 EKS 上后端程序;
  3. 后端程序调用 API 将加密后的账户信息保存到 DynamoDB。

交易签名

  1. 运行在 EKS 上的 API 后端服务接收来自客户端的签名 API 请求后,从 STS 请求访问 DynamoDB 的临时安全凭证(IAM role);
  2. 后端服务根据请求信息调用 API 从 DynamoDB 取出所需的加密后的账户信息(Encrypted private key 和 Encrypted Datakey);
  3. 后端服务将交易信息、加密后的账户信息以及加密后的 Data Key 通过 vsock 发送给 Enclave;

  1. Enclave 内的应用程序调用 kms:Decrypt API 命令,并从 Nitro 系统获取已签名的证明文件(attestation document),其中包括 Enclave 的独特信息(PCRs),以证明其身份并与 KMS 等外部服务建立信任;
  2. 应用程序通过 vsock 将证明文件和加密后的 Datakey 发送给 EKS,并经过 KMS proxy 将证明文件发送给 AWS KMS;
  3. KMS 验证证明文件由 Nitro Hypervisor 签署,且 KMS 密钥策略定义的 PCR 与证明文件上的 PCR 匹配后执行 Decrypt 操作,并使用 Enclave 的公钥(在证明文件中提供)重新加密 Datakey(Re-encrypted Datakey)后通过 vsock 发回给 Enclave 应用;

  1. Enclave 使用它的私钥对重新加密的账户信息进行解密得到明文的 Datakey,并使用 Datakey 解密获得明文的钱包私钥;
  2. Enclave 应用利用解密后的私钥对交易信息进行签名操作;
  3. 最后通过 vsock 将签名发回给运行在 EKS 容器里的后端程序。

kmstool-enclave-cli

kmstool-enclave-cli 是由亚马逊云提供的一个命令行工具,它提供了两个指令,分别是 genkey 和 decrypt。同时这两个指令已经自动的将上面介绍的 Nitro Enclave 的 Attestation 相关逻辑封装。具体的使用文档可以参考此文档。下面简单介绍两个指令的功能。

genkey

该指令调用 KMS 服务的 generateDataKey 函数,生成的数据密钥被用来加密钱包的密钥。

decrypt

decrypt 函数调用 KMS 的 decrypt 函数,用来将存储好的加密的 DataKey 解密,并用 DataKey 在 Enclave 中解密钱包私钥。

六、总结

本文主要介绍和演示了基于 Nitro Enclaves 实现的钱包应用,通过在隔离的安全环境中创建和使用私钥,确保密钥免受黑客攻击和窃取。在 Web3 领域,除钱包之外,任何涉及数字资产管理的场景都可以利用 Nitro Enclaves 技术保障加密资产的安全。

同时 Nitro Enclaves 也适用于敏感数据处理的场景,在隔离的安全区域内运行应用程序,对个人身份,信用卡号,资产信息等 PII 敏感数据进行处理,这点对于任何无许可去中心化网络节点也非常重要。

本篇作者

倪赛龙

AWS 解决方案架构师,负责基于 AWS 云计算方案的架构咨询和设计实现,同时致力于无服务计算的应用和推广。

肖学嵩

亚马逊云科技初创生态解决方案架构师,15 年 IT 从业经验,现主要负责云计算方案架构的咨询和设计,以技术赋能助力初创企业成长。拥有多年企业级数据中心系统架构设计和实施经验,曾任职于 Sinodata, IBM, Lenovo 等厂商,并先后在私有云和大数据领域有从 0 到 1 的创业经历。

郝志煜

亚马逊云科技解决方案架构师, 他与客户合作构建各类创新解决方案,帮助客户解决业务问题并快速使用 AWS 服务。在工作之余,他还热爱各种极限运动,滑翔伞、极限飞盘和自由潜。