Skip to content

threen134/signing_server

Repository files navigation

1. 签名服务器

签名服务器展示Hyper Protect Service 的使用场景

1.1. 环境介绍

  • 通过类似多方合约的方式部署签名服务器到可信执行环境HPVS
  • Client 通过RestAPI与签名服务器通信 (生产环境还需要TLS证书验证)
  • 由于签名服务器是以黑盒子的方式部署到HPVS内的,这里log信息通过内网发送到logDNA对log进行收集与可视化检索。
  • 签名服务器通过GREP11 API 与HPCS 通信 (生产环境还需要有MTLS 双向证书验证)
  • 经过加密的密钥,持久化到HPDBaaS内
  • IAM 对访问做认证以及权限控制
  • VPC 的security group 和Network ACL 对网络做内网通信控制
  • 所有通信都在内网内
  • 支持IPsec 与 专线的内网打通,或者通过Floating IP+ firewall的方式对外暴露服务

1.2. Client 通过下列endpoint 与签名服务器通信

export SIGN_HOST=<ip-address>
export SIGNING_PORT=8080

# 测试连通性
curl ${SIGN_HOST}:${SIGNING_PORT}/v1/grep11/get_mechanismsc

# 产生椭圆曲线Key pair
curl ${SIGN_HOST}:${SIGNING_PORT}/v1/grep11/key/secp256k1/generate_key_pair -X POST -s | jq

# 获取公钥
curl ${SIGN_HOST}:${SIGNING_PORT}/v1/grep11/key/secp256k1/public/${KEY_UUID} -s | jq

# 获取ethereum 格式的公钥
curl ${SIGN_HOST}:${SIGNING_PORT}/v1/grep11/key/secp256k1/get_ethereum_key/${KEY_UUID}  -s | jq

# 使用secp256k1类型的私钥在HPCS 上签名,签名后使用ethereum类型的公钥验证签名,
# 验证签名方法直接调用以太坊的库执行  crypto.VerifySignature
# 以太坊的签名摘要必须是32位
curl ${SIGN_HOST}:${SIGNING_PORT}/v1/grep11/key/secp256k1/verify_ethereum_pub_key/${KEY_UUID} -X POST  -s -d '{"data":"fad9c8855b740a0b7ed4c221dbad0f33","ethereum_pub_key":"0x0474618a3e3a8a7207c008d9a993b611b2f38f281c53cb8e1e67e5f2c9f0fd8fe572037924791385a203afe1c45149f3918b6df86918a020a822df3d1fc8508b3a"}' | jq

·# 获取被包裹的私钥
curl ${SIGN_HOST}:${SIGNING_PORT}/v1/grep11/key/secp256k1/private/${KEY_UUID} -s | jq

# 使用私钥签名数据
curl ${SIGN_HOST}:${SIGNING_PORT}/v1/grep11/key/secp256k1/sign/${KEY_UUID}  -s -X POST -d '{"data":"the text need to encrypted to verify kay."}' | jq

# 使用公钥验证签名
curl ${SIGN_HOST}:${SIGNING_PORT}/v1/grep11/key/secp256k1/verify/${KEY_UUID}  -s -X POST -d '{"data":"the text need to encrypted to verify kay.","signature":"Tw/Dk0NUNbklut31DQctitAFeFwkCtdRP7hAcMU84dYRkdXFlCB9mEFzaGpZ+dK/786k7iVQ8a8WRCNF0U7r/Q"}' |jq

# 使用master key包裹导入的AES,并持久化到HPDBaaS
curl ${SIGN_HOST}:${SIGNING_PORT}/v1/grep11/key/aes/import -X POST -s -d '{"key_content":"E5E9FA1BA31ECD1AE84F75CAAA474F3A"}' |jq

#使用导入到AESkey 加密数据与明文AES加密数据结果对比,如果一样就证明导入到key是正确的,并且被master key 包裹了
curl ${SIGN_HOST}:${SIGNING_PORT}/v1/grep11/key/aes/verify/${KEY_UUID}  -X POST -d '{"key_content":"E5E9FA1BA31ECD1AE84F75CAAA474F3A","data":"E5E9FA1BA31ECD1AE84F75CAAA474F3A"}'



####################### 导入外部密钥 (TBD: 需要调试)
# 产生EC 私钥 PEM 格式
openssl ecparam -genkey -name secp256k1 -noout -out secp256k1-key-pair.pem -param_enc explicit

# 提取公钥
openssl ec -in secp256k1-key-pair.pem -pubout > secp256k1-key-pub.pem

# 上传私钥并持久化
curl ${SIGN_HOST}:${SIGNING_PORT}/v1/grep11/key/import_ec -X POST -s  -F "file=@./secp256k1-key-pair.pem" | jq

# 签名ec
curl ${SIGN_HOST}:${SIGNING_PORT}/v1/grep11/key/sign/${KEY_UUID} -s -X POST -d '{"data":"the text need to encrypted to verify kay."}' | jq

# 使用公钥验证签名
curl ${SIGN_HOST}:${SIGNING_PORT}/v1/grep11/key/verify/${KEY_UUID} -s -X POST -d '{"data":"the text need to encrypted to verify kay.","signature":"vW3UVySThT4qQRmocPQiIus8gz1e5+Ch0XHs2YY7LlNN6HWfgWLtYcIjkZdsp0PTYYY73ffF1PnLQ1tTqmyaaQ"}'

#  签名ec 返回ANS1
curl ${SIGN_HOST}:${SIGNING_PORT}/v1/grep11/key/sign/${KEY_UUID}  -s -X POST -d '{"data":"the text need to encrypted to verify kay.","sig_format":"ans1"}' | jq

# 使用本地公钥验证签名
echo -n "the text need to encrypted to verify kay." > test.data
echo -n "MEUCIAgZXWc826mQ9ogdt6lVYiYYHp16rDyutc4Hb8OQdH3CAiEA3OOoTPtz9QW13+RlDTO8DCSOPv4M2Q1HKlf/xXJS6+c" |gbase64 --decode -w 0  > signature.sig
openssl pkeyutl -verify -in test.data -sigfile  signature.sig  -pubin  -inkey ec256-key-pub.pem

1.3. GREP11 API 使用举例时序图与说明

1.3.1. 步骤说明

1.4. 导入密钥流程

1.4.1. GREP11 AP1 导入私钥时序图与说明

1.4.2. 步骤说明

  • 说明: 所有HPCS产生的私钥都通过内部的master key进行包裹后返回的,明文密钥的生命周期不能离开HPCS内部的HSM加密卡。在密钥使用前,HPCS会对被包裹的密钥在内部进行解包裹(解密),密钥使用后,明文密钥在HSM内部被丢弃,这些步骤都是发生在HPCS内部的,客户是无感知的,所以下面不对这部分内容做特别说明。

  • 大致流程如下: 导入的密钥需要在导入之前对密钥进行加密,然后在HSM内部进行解密。在HSM 内部,master key对解密后的明文私钥进行包裹,HPCS返回被包裹的私钥给签名服务器,签名服务器用本地的KEK在HPCS内对被包裹的私钥进行二次加密,被KEK二次加密的私钥,持久化到HPDBaaS内。

  • 以上步骤对应的客户端请求为

     curl ${SIGN_HOST}:${SIGNING_PORT}/v1/grep11/key/import_ec -X POST -s  -F "file=@./ec256-key-pair.pem" | jq
  • 疑问1:HPCS 对私钥使用master key 包裹后,相当于密钥已经被master key加密了,为什么还需要用签名服务器内的KEK进行二次加密? 这一步是可选的,设计这一步的目的是签名服务器的KEK是保存在HPVS内的secure enclave内的安全边界内的可信执行环境里的,所以这可以保证所有的操作必须由签名服务器来发起,因为只有签名服务器内有KEK

  • 疑问2: 如何保证签名服务器内对KEK的安全与可持久性? 签名服务器本事运行在HPVS内的可信执行环境里(机密计算)并且通过多方合约的方式部署上去的。 签名服务器内的secure enclave 是可以做快照与跨区域备份的,而且是用多方部署的时候的产生的种子进行加密的,这意味着只有客户自己才能进场解密,因为种子的持有人是通过多方部署的形式由客户多个持有人控制的。

2. 部署签名服务器

2.1. HPVS 介绍

HPVS for VPC 基于机密计算的体系结构下,提供了一种安全可靠可信的部署应用到计算资源的方式,和传统的CICD与业务规范流程相比,IBM提供的部署流程能保证镜像与环境无法被篡改,同时提供了角色隔离,保证敏感信息的持有者或者服务端点无法接触到生产环境,同时可以保证部署人员无法接触到敏感信息。

  • 安全执行:通过技术而不是流程保证未经授权的用户(包括 IBM Cloud 管理员)无法访问应用程序。工作负载被单独的实例级安全边界锁定。
  • 多方合同和部署证明:从工作负载开发到部署应用零信任原则。随着多个角色和法人实体的协作,区分职责和访问权限至关重要。适用于 VPC 的 Hyper Protect Virtual Servers 基于加密合同概念,使每个角色都能够提供自己的贡献,同时通过加密确保其他角色都无法访问此数据或IP。部署可以由审计人员通过证明记录进行验证,该记录经过签名和加密,以确保只有审计人员具有这种洞察力。
  • 恶意软件保护:利用 Secure Build 设置验证过程,以确保只有授权代码在应用程序中运行。Hyper Protect Virtual Servers for VPC 仅部署容器版本,这些版本在部署时进行验证。
  • 自带 OCI 映像:使用任何开放容器倡议 (OCI) 映像并获得机密计算解决方案的好处,以提供额外级别的保护
  • 灵活的部署:从各种配置文件大小中进行选择,并根据需要进行扩展,以保护容器化应用程序并按小时付费。

2.2. 主要步骤描述与角色分离设计

byoi

2.2.1. 主要步骤概述

  • 1 开发人员push 代码,触发CICD流程,版本服务器构建版本镜像
  • 2 版本经理检视代码,检视通过,使用自己的私钥进行签名,只有经过版本经理签名的镜像才能够被部署
  • 3 签名后的镜像被推送到镜像仓库
  • 4 通过compose 生成部署模版,使用公钥对部署模版加密(加密为可选步骤)
  • 5 构造环境模版,使用公钥对环境变量进行加密(加密为可选步骤)
  • 6 合并模版与变量,使用私钥对模版进行摘要签名 (签名摘要为可选步骤)
  • 7 运维人员通过模版部署应用
  • 8 服务器获取到模版后,使用私钥进行解密,并提取环境变量里的公钥对模版进行指纹验证。
  • 9 服务器提取部署模版里的公钥对镜像进行验证
  • 验证通过后,应用成功部署

(只有运维人员需要接触生产环境,但是运维人员的权限是最低的,如果部署模版经过加密,那么运维人员获取不到任何信息。) (workload模版为一些部署镜像信息以及一些非敏感变量,env模版通常为一些环境变量的信息)

2.3. 角色定义

角色 工作内容 安全与隔离性
版本经理 检视代码与版本构建服务器的日志,签名镜像。通过CICD流程或者手动推送镜像,如果手动推送镜像,需要访问镜像仓库的权限 不需要接触生产环境
角色1 构建workload模版 不需要接触生产环境
角色2 构建环境变量模版 不需要接触生产环境
运维人员 从角色1和角色2拿到被加密的部署文件部署应用,但是无法读取部署文件的内容 不能接触敏感信息,只能部署应用

2.4. 准备工作

2.4.2. 安装IBM Container Registry

ibmcloud plugin repo-plugins -r 'IBM Cloud'  # list all of plugin
ibmcloud plugin install container-registry

2.5. 构建镜像

构建并上传镜像到IBM container registry
这一步通常由CICD工具或者构建服务器自动触发,我们这里手动模拟这个流程。

2.5.1. 克隆代码仓库

git clone https://github.com/threen134/signing_server.git

2.5.2. 构建镜像

# 创建镜像
cd ./signing_server
docker build -t signing_server:v3  .
# 如果是非s39x架构, 使用下面对命令
docker buildx build --platform=linux/s390x  -t signing_server:v1 .  

2.5.3. tag镜像

# tag 镜像
# au.icr.io 为上文中 IBM CR的endpoint 
# spark-demo 为上文创建的namespce
# s390x-signing-server:v3 为image:tag*  
docker tag signing_server:v1 au.icr.io/poc-demo/signing-server:v1

2.6. 版本经理检视代码后,执行签名操作。

2.6.1. 创建trust key

# 创建的密钥需要输入密码,后面签名镜像的时候需要使用这个密钥读取私钥进行签名
docker trust key generate poc-test-sign-key

2.6.2. 开启DCT(Docker Content Trust)

# DCT 环境变量的语法格式为https://notary.<region>.icr.io, 记得调整为自己对应的region
# 亚洲地区有au,北美区域有us支持 notary
export DOCKER_CONTENT_TRUST=1
export DOCKER_CONTENT_TRUST_SERVER=https://notary.au.icr.io

2.6.3. 上传镜像并签名

# 登陆ibm container registry 
ibmcloud login --apikey <your api key> -g Default -r jp-tok
# 设置区域
ibmcloud cr region-set ap-south
ibmcloud cr login 
#为IBM CR 创建namespce,名字必须全局唯一
ibmcloud cr namespace-add poc-demo
# 上载镜像, 上传的时候需要输入私钥的密钥,如果是第一次使用notary,还需要设置notary的密码
docker push au.icr.io/poc-demo/signing-server:v1
# 查看签名信息
docker trust inspect au.icr.io/poc-demo/signing-server:v1 

2.6.4. 获取签名的公钥

把这个公钥分享给角色1, 角色1需要把公钥加入到构建模版,后续镜像被部署的时候(拓扑图步骤9),需要这个公钥验证签名来保证镜像的完整性。

# cat ~/.docker/trust/tuf/au.icr.io/<username>/<imagename>/metadata/root.json
cat ~/.docker/trust/tuf/au.icr.io/poc-demo/signing-server/metadata/root.json  |jq

至此,版本经理或者CICD流程,构建了一个image,并且使用自己的私钥签名了这个image。

2.7. 角色1构建WORKLOAD模版

2.7.1. 把compose模版文件转码为base64

cd build
tar czvf - -C compose . | base64 -w0
H4sIAOG4/GIAA+3TTW+CMBwGcM77FD3syoui4k....

备注: 如果执行环境是MAC, 请使用 gnu版本的 tar 与 base64

brew install coreutils
brew install gnu-tar
alias tar=gtar
alias base64=gbase64

2.7.2. 构建workload模版

# workload.yaml
type: workload
auths:
  # au.icr.io 为container registry 的endpoint, 
  au.icr.io:
    # 指定用户CR的用户名和密钥,IBM CR的用户名为iamapikey
    password: xxxx
    username: iamapikey
compose:
  # archive 的内容为 1.7.1 把compose模版文件转码为base64的输出
  archive: H4sIAOG4/GIAA+3TTW....
images:
  dct:
      # 如果镜像有被签名,需要指定notary的信息,如果镜像没有被签名,那么compose里的镜像需要指定摘要信息
      # 对应compose里的image信息
    au.icr.io/spark-demo/s390x-signing-server:
      # notary endpoint
      notary: "https://notary.au.icr.io"
      # 步骤1.6.5 中获取到的公钥
      publicKey: LS0tLS1CRUdJTiBDRVJ....
env:
  # 设置一些非敏感环境变量,这些变量对应compose里的信息
  POSTGRESS_ADDRESS: "dbaas905.hyperp-dbaas.cloud.ibm.com"
  POSTGRESS_PORT: "30025"
  POSTGRESS_USERNAME: "admin"
  POSTGRESS_DBNAME: "admin"
  HPCS_ADDRESS: ep11.us-east.hs-crypto.cloud.ibm.com
  HPCS_PORT: "13412"
  HPCS_INSTANCE_ID: "4ad01aec-dc81-4158....."
  HPCS_IAM_ENDPOINT: "https://iam.cloud.ibm.com"
  SECURE_ENCLAVE_PATH: "/etc/secure_enclave"
#数据卷的加密是根据workload里的seed 和 env 字段里的seed 来共同产生的密钥。
#特别注意: boot volume 不可以用来做持久化。重启后boot volume 修改的内容被丢弃
#(boot volume 的任何改动都被视为代码被修改,从而破坏完整性,检测将不会被通过,计算实例将无法部署或者启动)
volumes:
    volume1:
        mount: /etc/secure_enclave
        seed: stsolutiontest
        filesystem: btrfs

2.7.3. 下载ibm的公钥

wget https://cloud.ibm.com/media/docs/downloads/hyper-protect-container-runtime/ibm-hyper-protect-container-runtime-1-0-s390x-3-encrypt.crt

2.7.4. 加密workload

# 设置变量
WORKLOAD=./workload.yaml
CONTRACT_KEY=./ibm-hyper-protect-container-runtime-1-0-s390x-3-encrypt.crt
# 随机产生一个32位密码
PASSWORD="$(openssl rand 32 | base64 -w0)"
# 使用证书加密密码
ENCRYPTED_PASSWORD="$(echo -n "$PASSWORD" | base64 -d | openssl rsautl -encrypt -inkey $CONTRACT_KEY -certin | base64 -w0 )"
#使用密码加密workload
ENCRYPTED_WORKLOAD="$(echo -n "$PASSWORD" | base64 -d | openssl enc -aes-256-cbc -pbkdf2 -pass stdin -in "$WORKLOAD" | base64 -w0)"
#把加密后的密码,与加密后对workload 组合起来
#把得到的输出交给部署人员
echo "hyper-protect-basic.${ENCRYPTED_PASSWORD}.${ENCRYPTED_WORKLOAD}"
#当HPVS第一次构建的时候它会用私钥对密码进行解密,得到明文的密码后,用密码对workload进行解密  

如果加密报这个错误 unknown option '-pbkdf2',需要升级openssh

备注: 如果执行环境是MAC, 使用openssl 代替默认的 LibreSSL

brew update
brew install openssl
# if it is already installed, update it:
brew upgrade [email protected]
echo 'export PATH="/opt/homebrew/opt/openssl@3/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc 

2.8. 角色2构建ENV模版

2.8.1. 创建env 模版

这里主要是设置logDNA的字段,可选的数据卷与环境变量

# env.yaml
type: env
logging:
  logDNA:
    hostname: syslog-a.private.jp-tok.logging.cloud.ibm.com
    ingestionKey: 7a98add3.....
    port: 6514
volumes:
  volume1:
    seed: thisisatest
env:
  # 设置一些敏感环境信息
  POSTGRESS_PASSWORD: xxxxx
  # PG 的证书base64 编码信息
  # echo ./cert.pem | base64 -w0
  POSTGRESS_SSLROOTCERT: "LS0tLS1CRUdJTiBDRVJUSU....."
  HPCS_IAM_KEY: "xxxx...."

部署的所有日志和后续应用程序的日志,可以通过logDNA来获取日志,请参考这个文档创建并获取LogDNA实例的信息,同时logDNA可以使用内网链接

2.8.2. 下载ibm的公钥

wget https://cloud.ibm.com/media/docs/downloads/hyper-protect-container-runtime/ibm-hyper-protect-container-runtime-1-0-s390x-3-encrypt.crt

2.8.3. 加密env 模版

# 设置变量
ENV=./env.yaml
CONTRACT_KEY=./ibm-hyper-protect-container-runtime-1-0-s390x-3-encrypt.crt
# 随机产生一个32位密码
PASSWORD="$(openssl rand 32 | base64 -w0)"
# 使用证书加密密码
ENCRYPTED_PASSWORD="$(echo -n "$PASSWORD" | base64 -d | openssl rsautl -encrypt -inkey $CONTRACT_KEY -certin | base64 -w0 )"
#使用密码加密workload
ENCRYPTED_ENV="$(echo -n "$PASSWORD" | base64 -d | openssl enc -aes-256-cbc -pbkdf2 -pass stdin -in "$ENV" | base64 -w0)"
#把加密后的密码,与加密后对env 组合起来
#把得到输出交给部署人员
echo "hyper-protect-basic.${ENCRYPTED_PASSWORD}.${ENCRYPTED_ENV}"
#当HPVS第一次构建的时候它会用私钥对密码进行解密,得到明文的密码后,用密码对env进行解密  

2.9. 运维人员部署应用

2.9.1. 构建user-data.yaml文件

从角色1与角色2拿到加密后对数据后构建类似下面的文件

  workload: hyper-protect-basic.js7TGt77EQ5bgTIKk5C0pViFTRHqWtn..............
  env: hyper-protect-basic.VWg/5/SWE+9jLfhr8q4i.........

2.9.2. 通过IBM console 创建实例

配置如下图
图片名称

2.9.3. 通过logDNA 查看部署情况

图片名称

2.9.4. 验证应用部署

挂载一个floating IP 到部署的签名服务器, 然后list 状态机·

curl ${SIGN_HOST}:${SIGNING_PORT}/v1/grep11/get_mechanismsc

2.10. 通过明文模版部署应用

如果你没有加密部署的需求,也可以通过明文模版的方式部署应用,这通常用于开发环境或者对部署进行测试时使用。

# cat user-data.yaml
workload: |
  type: workload
  auths:
    au.icr.io:
      password: Do0JROuGXO....
      username: iamapikey
  compose:
    archive: H4sIAOG4/GIAA+3TTW....
  images:
    dct:
      au.icr.io/spark-demo/s390x-signing-server:
        notary: "https://notary.au.icr.io"
        publicKey: LS0tLS1CRUdJTiBDRVJUSUZ.....
  env:
    POSTGRESS_ADDRESS: dbaas905.hyperp-dbaas.c....
    POSTGRESS_PORT: "30025"
    POSTGRESS_USERNAME: "admin"
    POSTGRESS_DBNAME: "admin"
    HPCS_ADDRESS: ep11.us-east.hs-crypto.cloud.ibm.com
    HPCS_PORT: "13412"
    HPCS_INSTANCE_ID: ad01aec-dc81.....
    HPCS_IAM_ENDPOINT: "https://iam.cloud.ibm.com"
    SECURE_ENCLAVE_PATH: "/etc/secure_enclave"
  volumes:
    volume1:
      mount: /etc/secure_enclave
      seed: stsolutiontest
      filesystem: btrfs
env: |
  type: env
  logging:
    logDNA:
      hostname: syslog-a.private.jp-tok.logging.cloud.ibm.com
      ingestionKey: 7a98add39999e9.....
      port: 6514
  volumes:
    volume1:
      seed: thisisatest
  env:
    POSTGRESS_PASSWORD: "......"
    POSTGRESS_SSLROOTCERT: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0t....."
    HPCS_IAM_KEY: "3lHSZqcuCh4b_....

3. 使用HPCS签名交易,并上测试链广播交易

3.1. 主要步骤说明

3.1.1. 通过HPCS 产生 钱包

export SIGN_HOST=localhost
export SIGNING_PORT=8080
curl ${SIGN_HOST}:${SIGNING_PORT}/v1/grep11/key/secp256k1/generate_key_pair -X POST -s | jq
# 获取钱吧的UUID并设置到环境变量
export KEY_UUID=c006f05e-002c-4fcf-b530-6e9820db03db
#获取 from_address 交易地址
curl ${SIGN_HOST}:${SIGNING_PORT}/v1/grep11/key/secp256k1/get_ethereum_key/${KEY_UUID}  -s | jq

3.1.2. 申请测试币

拿到上一步产生的地址,在水管上申请rinkeby测试币

3.1.3. 获取一个目标地址

获取一个目标交易地址, 或者通过上面的步骤生产一个新的钱包并获取 to address

3.1.4. 使用 ethereum-client 广播交易到 测试链 rinkeby

  • 加载环境变量
cd ./ethereum-client
cp ./env.sh.template ./env.sh
# 编辑env.sh
source ./ethereum-client/env.sh

3.1.5. 在测试链上签名交易

  go run ./... 
  # 得到输出
  https://rinkeby.etherscan.io/tx/0x71231d85bfa7497f09a54023535874779a3e73a2c0084fa8a1612f9cb709a7a1 

3.1.6. 查看交易结果

6

4. 参考文档