# Linux系统上QingTian Enclave应用的开发
#### QingTian Enclave SDK
QingTian Enclave SDK由一系列开源库组成，以便您开发自己的QingTian Enclave应用程序。其中包括QingTian安全模块（QingTian Security Module，QTSM）提供的qtsm-lib函数库。此外，SDK集成了KMS接口，该接口内置了获取证明文档及调用KMS相关服务的功能。在典型使用案例里，我们描述了在QingTian Enclave调用KMS解密接口的示例。
表1接口介绍 
| 类型       | 接口                                      | 接口描述               |
|:---|:---|:---|
| libqtsm接口 | qtsm_describe_pcr                         | 查询指定index的PCR数据信息 |
| libqtsm接口 | qtsm_extend_pcr                         | 扩展指定index对应的PCR值    |
| libqtsm接口 | qtsm_lock_pcr                           | 锁定指定index的PCR数据信息  |
| libqtsm接口 | qtsm_lock_pcrs                        | 批量锁定指定index对应的PCR值 |
| libqtsm接口 | qtsm_get_describe                       | 获取qtsm信息           |
| libqtsm接口 | qtsm_get_attestation                     | 获取Attestation Doc |
| libqtsm接口 | qtsm_get_random                          | 获取硬件随机数           |
| KMS接口  | kms_generate_datakey_blocking         | 生成新的密钥对，获取公钥与私钥     |
| KMS接口  | kms_generate_datakey_blocking_with_proxy | 集成qtproxy代理的密钥对获取接口 |
| KMS接口  | kms_gen_random_blocking                | 获取随机数              |
| KMS接口  | kms_gen_random_blocking_with_proxy     | 集成qtproxy代理的随机数获取接口 |
| KMS接口  | kms_decrypt_data_blocking              | 解密数据                 |
| KMS接口  | kms_decrypt_data_blocking_with_proxy   | 集成qtproxy代理的解密数据接口  |
   
源码可以在开源仓库<https://gitee.com/HuaweiCloudDeveloper/huawei-qingtian/tree/master/enclave>免费获取，您可以基于测试示例开发自己的QingTian Enclave应用程序。
#### Vsock通信示例
本节主要通过vsock示例来介绍如何在Linux环境下开发QingTian Enclave应用程序。本节中提供的vsock程序只支持在Linux环境下运行。
通过该vsock程序，可以帮助开发者了解到父虚拟机和QingTian Enclave间如何进行消息传递从而实现双方交互。该vsock程序带有Server和Client参数，通过指定该参数可以指定父虚拟机或者QingTian Enclave虚拟机扮演相应角色。在该vsock程序中，客户端程序会发送一段简单的文本信息通过vsock通道传递给服务端程序，同时服务端程序会监听相应的vsock通道，一旦收到来自客户端的消息后，会将其收到的消息打印在终端界面上。
在下面介绍中，我们将说明如何让QingTian Enclave扮演服务端等待并接收来自客户端父虚拟机的hello world信息：
1. 编写一个SocketCommunication.py程序：
   ```
   #!/usr/local/env python3
   import argparse
   import socket
   import sys
   CID_DEFAULT = 3
   PORT_DEFAULT = 9999
   TIMEOUT = 5
   BLACKLOG_DEFAULT = 5
   class Client:
       def __init__(self, cid, port):
           self.clientAddr = (cid, port)
           self.connect()
       def connect(self):
           self.socket = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM)
           self.socket.settimeout(TIMEOUT)
           print("connecting to the server")
           try:
               self.socket.connect(self.clientAddr)
           except socket.error:
               print("client's socket connection err")
               sys.exit(1)
       def send(self, msg):
           print("client sends hello to the server")
           self.socket.sendall(msg)
       def disconnect(self):
           self.socket.close()
       def receiveData(self):
           while True:
               try:
                   message = self.socket.recv().decode()
               except (socket.error, UnicodeDecodeError):
                   break
               if message:
                   print(message, end = " ", flush = True)
           print()
   def clientHandler(args):
       client = Client(args.cid, args.port)
       message = "Hello world"
       client.send(message.encode())
       client.disconnect()
   class Server:
       def __init__(self, port):
           self.socket = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM)
           self.serverAddr = (socket.VMADDR_CID_ANY, port)
           self.socket.bind(self.serverAddr)
           self.socket.listen(BLACKLOG_DEFAULT)
       def receiveData(self):
           while True:
               print("waiting for a connection")
               (conn, clientAddr) = self.socket.accept()
               try:
                   print("connection from ", clientAddr)
                   while True:
                       try: 
                           data = conn.recv(256).decode()
                       except (socket.error, UnicodeDecodeError):
                           break
                       if data:
                           print("data: ", data)
                       else:
                           print("connection close")
                           break
               finally:
                   conn.close()
   def serverHandler(args):
       server = Server(args.port)
       server.receiveData()
   def main():
       parser = argparse.ArgumentParser(description = "Hello world demo", prog='SocketCommunication')
       subparsers = parser.add_subparsers(description = "Communication roles")
       parserClient = subparsers.add_parser("Client", description = "Client",
                                           help = "Communicate with server using a given cid and port.")
       parserClient.add_argument("-c", "--cid", default = CID_DEFAULT, type = int, help = "Client's Cid")
       parserClient.add_argument("-p", "--port", default = PORT_DEFAULT, type = int, help = "Client's port")
       parserClient.set_defaults(func = clientHandler)
       parserServer = subparsers.add_parser("Server", description = "Server", help = "Listen on a given port")
       parserServer.add_argument("-p", "--port", default = PORT_DEFAULT, type = int, help = "Server's Port")
       parserServer.set_defaults(func = serverHandler)
       if len(sys.argv) < 2:
           parser.print_usage()
           sys.exit(1)
       args = parser.parse_args()
       args.func(args)
   if __name__ == "__main__":
       main()
   ```
   

2. 创建一个Dockerfile文件，内容如下：
   ```
   #start the Docker image from ubuntu
   FROM ubuntu:22.04
   WORKDIR /home/builder
   # COPY vsocket example
   COPY . vsocket
   # install relative dependencies
   RUN apt-get update && \
       apt-get install python3 -y && \
       apt-get install gcc -y && \
       apt-get install gawk -y
   # Launch a client
   CMD ["python3", "/home/builder/vsocket/SocketCommunication.py","Server","-p 9999"]
   ```
   

3. 构建docker镜像：
   ```
   sudo docker build -t vsock-sample-client -f Dockerfile .
   ```
   

4. 将docker镜像转化为QingTian Enclave镜像：
   ```
   qt enclave make-img --docker-uri vsock-sample-client --eif vsock_sample.eif
   ```
   

5. 使用vsock_sample.eif以debug模式启动QingTian Enclave:
   ```
   qt enclave start --cpus 2 --mem 4096 --eif vsock_sample.eif --debug-mode --cid 4
   ```
   然后使用qt enclave console命令查看QingTian Enclave内只读终端输出：
   ```
   qt enclave console --enclave-id 0
   waiting for a connection
   ```
   

6. 然后另起一个父虚拟机终端，启动客户端程序：
   ```
   python3 SocketCommunication.py Client -c 4 -p 9999
   ```
   

7. 当服务端程序收到来着vsock的消息后，会打印如下消息到终端上：
   ```
   connection from  (3, 4180219645)
   data:  Hello world
   connection close
   waiting for a connection
   ```
   
 
#### KMS接口调用和权限配置示例
本节主要基于开源示例代码来介绍如何在QingTian Enclave应用程序中调用KMS接口并配置权限。本节中提供的示例程序只支持在Linux环境下运行。
在配置前，请先购买一台C7t规格的弹性云服务器作为QingTian Enclave父虚拟机并完成相关配置，详细内容，请参见[快速入门](https://support.huaweicloud.com/usermanual-ecs/ecs_03_1404.html)。
1. 从gitee仓库下载huawei-qingtian-enclave代码。 本示例以qtsm-java-sdk为例进行介绍。
   1. 执行以下命令，下载huawei-qingtian-enclave代码。 **cd /home**
      **git clone https://gitee.com/heathjay/huawei-qingtian.git**
      执行以下命令，切到newjay-java-sdk分支。
      **cd /home/huawei-qingtian**
      **git checkout newjay-java-sdk**
      
    
2. 根据用例，获取相关参数。 测试可参考：<https://gitee.com/HuaweiCloudDeveloper/huawei-qingtian/pulls/21/files>。
   [表2]中获取的参数需填入测试文件文件中。
    表2KMS接口参数 
   | 参数名称     | 含义                 | 获取方式                                                                                   |
   |:---|:---|:---|
   | ak        | Access Key ID        | 详细内容，请参见[API签名指南](https://support.huaweicloud.com/devg-apisign/api-sign-provide.html)。 |
   | sk         | Secret Access Key | 详细内容，请参见[API签名指南](https://support.huaweicloud.com/devg-apisign/api-sign-provide.html)。 |
   | project_id | 项目ID               | 获取方法请参见[获取项目ID](https://support.huaweicloud.com/api-ecs/ecs_01_0016.html)。             |
   | key_id    | KMS中的keyID        | 无                                                                                       |
   | host       | KMS endpoint        | 无                                                                                       |
      
   

3. 通过IAM创建自定义身份策略。 使用管理员权限账号创建自定义身份策略"enclave-test-kms-api"，详细操作，请参见[创建自定义策略](https://support.huaweicloud.com/usermanual-iam/iam_01_0605.html)。
   需要配置的自定义身份策略内容如下：
   ```
   {
     "Version": "5.0",
     "Statement": [
       {
         "Effect": "Allow",
         "Action": [
           "kms::generateRandom",
           "kms:cmk:createDataKey",
           "kms:cmk:decryptData",
           "kms:cmk:decryptDataKey"
         ],
         "Condition": {
           "StringEqualsIgnoreCase": {
             "kms:RecipientAttestation/PCR0": [
               "8f2cbfb3930e59c6de5c4caff0a3f4c0457e8956bfb4556a7ca1f5f4614a741eeee39ae10447eb5baee48d49e6c1cb6c",
               "ff7ba807a385b49fc1c3346bb47215aef503dee6df22d32f733e22b90a9bc4b22424ca7de1a3537ac9608d7ebe461d67",
               "a28e765550d6ad1188860d30167b1fdb9e29c8da825543861bc76ef1e8427fac6b444ec6a1847fc2c22deae8170c2e67"
             ],
             "kms:RecipientAttestation/PCR8": [
               "a9add94b0ecbbd992baded2176370ecf3bfed2cb39b2ec547512b5174279799f2036fa0b8577bdaf503836178bd11ee2"
             ]
           }
         }
       },
       {
         "Effect": "Allow",
         "Action": [
           "kms:cmk:encryptData",
           "kms:cmk:encryptDataKey"
         ]
       }
     ]
   }
   ```
   其中，PCR0和PCR8的值需要在完成QingTian Enclave镜像制作后，进行配置。
   
4. 创建IAM用户并授予权限。
   1. 在IAM控制台，单击"体验新版控制台"。
   
   2. 在"用户"页面创建IAM用户"enclave-test-kms-uers"并授予"enclave-test-kms-api"权限。
      图1创建IAM用户（1）   
      ![](https://support.huaweicloud.com/usermanual-ecs/zh-cn_image_0000002322566821.png "点击放大")
      图2创建IAM用户（2）   
      ![](https://support.huaweicloud.com/usermanual-ecs/zh-cn_image_0000002322644141.png "点击放大")
      图3为用户授权   
      ![](https://support.huaweicloud.com/usermanual-ecs/zh-cn_image_0000002288038134.png "点击放大") 
   
   3. 获取"enclave-test-kms-uers"用户的AK、SK。 在用户的"安全设置"页签可新增并下载访问密钥，请参见[创建IAM用户的访问密钥](https://support.huaweicloud.com/usermanual-iam/iam_02_0003.html#section1)。
      从下载的访问密钥文件中，可获取用户的AK、SK取值。
      
   
   4. 将AK、SK赋值到测试程序对应参数。 测试程序：/home/huawei-qingtian/enclave/qtsm-sdk-java/kms-cms-java/com/huawei/src/test/TestKmsCmsProxy.java
      图4赋值AK、SK   
      ![](https://support.huaweicloud.com/usermanual-ecs/zh-cn_image_0000002313171805.png "点击放大") 
    
5. 获取KMS的Endpoint节点。
   1. 通过[地区和终端节点](https://console.huaweicloud.com/apiexplorer/#/endpoint/KMS)获取KMS在不同区域的终端节点。
   
   2. 将KMS的Endpoint赋值到文件huawei-qingtian/enclave/qtsm-sdk-java/kms-cms-java/com/huawei/src/test/TestKmsCmsProxy.java中对应的host字符串变量：
      图5赋值Endpoint   
      ![](https://support.huaweicloud.com/usermanual-ecs/zh-cn_image_0000002322741361.png "点击放大") 
    
6. 获取项目ID。
   1. 通过控制台获取项目ID，详细操作，请参见[获取项目ID](https://support.huaweicloud.com/api-ecs/ecs_01_0016.html)。
   
   2. 将获取的项目ID赋值到文件/home/huawei-qingtian/enclave/qtsm-sdk-java/kms-cms-java/com/huawei/src/test/TestKmsCmsProxy.java中对应的uriPrefix字符串变量：
      图6赋值项目ID   
      ![](https://support.huaweicloud.com/usermanual-ecs/zh-cn_image_0000002322658257.png "点击放大") 
    
7. 获取密钥ID。
   1. 通过数据加密控制台，创建密钥并获取ID。详细操作，请参见[创建自定义密钥](https://support.huaweicloud.com/usermanual-dew/dew_01_0178.html)。
   
   2. 将获取的密钥ID赋值到文件/home/huawei-qingtian/enclave/qtsm-sdk-java/kms-cms-java/com/huawei/src/test/TestKmsCmsProxy.java中对应的uriPrefix字符串变量：
      图7赋值密钥ID   
      ![](https://support.huaweicloud.com/usermanual-ecs/zh-cn_image_0000002322753109.png "点击放大") 
   
   3. 使用密钥加密一段明文，例如"hello world！"，详细操作，请参见[使用自定义密钥在线加解密小数据](https://support.huaweicloud.com/usermanual-dew/dew_01_0022.html)。
   
   4. 将加密后的密文填写到文件/home/huawei-qingtian/enclave/qtsm-sdk-java/kms-cms-java/com/huawei/src/test/TestKmsCmsProxy.java中对应的dataInputStr中：
      图8填写密文   
      ![](https://support.huaweicloud.com/usermanual-ecs/zh-cn_image_0000002288063496.png "点击放大") 
    
8. 进入到/home/huawei-qingtian/enclave/qtsm-sdk-java/kms-cms-java/scripts目录，执行sh build_image.sh脚本构建QingTian Enclave镜像。
   图9执行脚本   
   ![](https://support.huaweicloud.com/usermanual-ecs/zh-cn_image_0000002288287850.png "点击放大")
   在该目录下有文件kms-demo.eif，包含pcr0和pcr8的值。
   
9. 生成镜像签名密钥和公钥。
   ```
   openssl ecparam -out private-key.pem -name secp384r1 -genkey
   openssl req -new -key private-key.pem -out ssl.csr
   openssl x509 -req -days 365 -in ssl.csr -signkey private-key.pem -out server.pem
   ```
   
10. 启动proxy工具。
    ```
    /usr/local/bin/qingtian/enclave/qt_proxy -l 8000 -a kms.ap-southeast-3.myhuaweicloud.com -p 443 &
    ```
    
11. 使用nc-vsock工具登录Enclave，进行调试。
    1. 由于该用例的QingTian Enclave中启动了一个vsock的服务端（监听9999端口），所以可以从QingTian Enclave父虚拟机内通过vsock客户端登录到Enclave进行调试。
       ```
       huawei-qingtian/enclave/qtsm-sdk-java/kms-cms-java/scripts/build_kms_demo.sh
       ```
       
    
    2. 使用nc-vsock进入Enclave。
       ```
       /home/huawei-qingtian/nc-vsock/nc-vsock 4 9999
       ```
       图10进入Enclave   
       ![](https://support.huaweicloud.com/usermanual-ecs/zh-cn_image_0000002288316244.png "点击放大") 
    
    3. 在enclave中执行如下命令：
       ```
       cd /home/builder/enclave/qtsm-sdk-java/kms-cms-java/target
       # We can perform it manually
       java -cp .:../lib/lombok-1.18.26.jar:../lib/junit-4.13.1.jar -Djava.library.path=./lib com.huawei.src.test.TestKmsCmsProxy
       ```
       图11报错信息   
       ![](https://support.huaweicloud.com/usermanual-ecs/zh-cn_image_0000002322886081.png "点击放大")
       - 若命令执行失败，提示用户无权限，如[图11]所示。需根据Enclave镜像的PCR0和PCR8为用户修改定义身份策略，执行步骤[11.d]。
       
       - 若命令执行成功，执行步骤[11.e]。
        
    
    4. （可选）修改自定义策略，大概30秒\~90秒生效。
       执行命令后，若发现该用户无权限，如[图11]所示。需根据Enclave镜像的PCR0和PCR8修改自定义策略策略。
       图12修改自定义策略   
       ![](https://support.huaweicloud.com/usermanual-ecs/zh-cn_image_0000002288245140.png "点击放大") 
    
    5. 在enclave中重新执行如下命令，调试KMS接口：
       ```
       cd /home/builder/enclave/qtsm-sdk-java/kms-cms-java/target
       # We can perform it manually
       java -cp .:../lib/lombok-1.18.26.jar:../lib/junit-4.13.1.jar -Djava.library.path=./lib com.huawei.src.test.TestKmsCmsProxy
       ```
       
    
    6. KMS接口调试成功，查看调试结果。
       图13解密接口   
       ![](https://support.huaweicloud.com/usermanual-ecs/zh-cn_image_0000002323022313.png "点击放大")
       图14数据密钥接口   
       ![](https://support.huaweicloud.com/usermanual-ecs/zh-cn_image_0000002322935425.png "点击放大")
       图15随机数接口   
       ![](https://support.huaweicloud.com/usermanual-ecs/zh-cn_image_0000002288423126.png "点击放大") 
     
 
