Seal로 enclave secret 암호화하기
Seal-Nautilus pattern은 enclave binary에 대한 어떤 secret이든 암호화할 수 있는 enclave 애플리케이션용 안전한 secret 관리를 제공한다.
enclave의 지정된 Platform Configuration Register(PCR)로 구성된 Seal policy를 정의할 수 있다. 그런 다음 고정된 ID로 Seal을 사용해 데이터를 암호화한다. 주어진 PCR을 가진 enclave만 이를 복호화할 수 있다.
이 pattern은 자체 관리되는 TEE를 사용해 테스트되었다. Marlin Oyster deployments로는 테스트되지 않았다.
component
Seal-Nautilus pattern을 보여주는 다음 예시는 다음 구성 요소를 사용한다:
-
AWS Nitro Enclave 내부에서 실행되는 Nautilus server(
src/nautilus-server/src/apps/seal-example): 이곳은 policy에 따라 Seal secret를 복호화할 수 있는 유일한 위치이다. 인터넷에는/get_attestation과/process_dataendpoint로 port 3000을 노출한다. 또한 enclave가 실행되는 host instance에서 key load 단계를 초기화하고 완료할 때만 사용할 수 있는 3개의/adminendpoint를 localhost에 port 3001로 노출한다. -
Seal CLI: 특히 이 예시에서는
encryptandfetch-keys를 사용한다. -
Move contract
move/seal-policy/seal_policy.move: enclave ephemeral key를 사용해 wallet public key에 commit된 signature를 검증하는seal_approvepolicy를 정의한다.
View the example's full source code.
Phase 1: server 시작 및 등록
이 문맥에서 admin은 EC2 instance에 접근 권한이 있는 사람을 뜻한다. admin은 그 위에서 enclave binary를 빌드하고 실행할 수 있으며, EC2 instance의 localhost를 통해 admin 전용 enclave endpoint를 호출할 수 있다.
- admin은 게시된 Seal policy package ID와 Seal configuration으로
seal_config.yaml을 지정한다.
src/nautilus-server/src/apps/seal-example/seal_config.yaml. You probably need to run `pnpm prebuild` and restart the site.admin은 노출된 /get_attestation endpoint와 함께 enclave를 빌드하고 실행한다.
src/nautilus-server/src/apps/seal-example/endpoints.rs
src/nautilus-server/src/apps/seal-example/endpoints.rs. You probably need to run `pnpm prebuild` and restart the site.-
admin은 attestation response를 사용해 PCR과 enclave public key를 등록한다. 이 시점에는
SEAL_API_KEY가 아직 초기화되지 않았으므로/process_dataendpoint가 error를 반환한다. -
admin은 enclave를 온체인에 등록하고 enclave object ID와 initial shared version을 가져온다.
Phease 2: Initialize and complete key load
-
admin은 enclave object로
/admin/init_seal_key_load를 호출한다. enclave는 encodedFetchKeyRequest를 반환한다. -
admin은
FetchKeyRequest를 사용해 CLI를 호출하고 enclave의 encryption public key 아래에서 암호화된 Seal response를 가져온다. -
admin은 Seal response로
/admin/complete_seal_key_load를 호출한다. enclave는 모든 Seal key를 복호화해 이후 사용을 위해 memory에 cache한다.
Phase 3: application secret provision
-
이제 Seal key가 cache되었으므로 encrypted object를 cache된 key를 사용해 필요할 때 복호화할 수 있다. 이 예시에서는 admin이 encrypted weather API key object로
/admin/provision_weather_api_key를 호출한다. enclave는 cache된 key를 사용해 이를 복호화하고SEAL_API_KEY로 저장한다. -
enclave는 이제
/process_datarequest를 처리할 수 있다.
security guarantee
enclave는 시작 시 3개의 key를 생성하며, 모두 enclave memory에만 유지된다:
-
Enclave ephemeral key pair(
state.eph_kp): Ed25519 key pair이다./process_dataresponse에 서명하고seal_approvePTB에서 signature argument를 만드는 데 사용한다. 해당 public key는 Enclave object에 온체인으로 등록된다. -
Seal wallet(
WALLET_BYTES): Ed25519 key pair이다. Seal certificate 서명과seal_approve의 transaction sender로 사용한다. -
ElGamal encryption key pair(
ENCRYPTION_KEYS): BLS group element이다. Seal response를 복호화하는 데 사용한다.
/init_seal_key_load 동안 wallet는 certificate를 위해 PersonalMessage에 서명한다. enclave는 또한 seal_approve용 programmable transaction block(PTB)을 생성하는데, 여기서 signature argument는 enclave ephemeral key pair가 wallet public key와 timestamp를 담은 intent message에 서명하여 만들어진다. Seal server가 transaction을 dry run하면 seal_approve는 다음을 검증한다:
-
signature는 enclave의 ephemeral public key(
enclave.pk()에서 가져옴)와 wallet public key 및 timestamp에 대한WalletPKscope의 intent message를 사용해 검증된다. -
key ID는
vector[0]의 고정값이다. -
transaction sender는 wallet public key와 일치한다.
이는 ephemeral key pair가 wallet public key에 commit하므로, wallet와 ephemeral key pair에 접근하는 enclave만이 유효하게 서명된 PTB를 만들 수 있음을 증명한다.
/init_seal_key_load 동안 enclave는 encryption key pair도 생성하고 FetchKeyRequest의 일부로 encryption public key를 반환한다. fetch key CLI는 enclave 밖에서 호출되지만, encryption secret key는 enclave만 가지고 있으므로 enclave 외에는 누구도 FetchKeyResponse를 복호화할 수 없다. 그런 다음 FetchKeyResponse는 /complete_seal_key_load에서 enclave로 전달되며, Seal key를 memory에서 검증하고 복호화할 수 있는 것도 enclave뿐이다.
2-step key load가 필요한 이유
enclave는 인터넷에 직접 접근할 수 없으 므로 Seal key server의 HTTP endpoint에서 secret를 직접 가져올 수 없다. 이 예시는 host를 중개자로 사용해 Seal server에서 암호화된 secret를 가져온다.
Seal response는 enclave의 encryption key 아래에서 암호화되므로 enclave만 가져온 Seal response를 복호화할 수 있어 이러한 위임은 안전하다. seal_config.yaml에 있는 Seal server의 public key는 admin이 enclave 안에서 정의하므로, enclave는 복호화된 Seal key가 변조되지 않았음을 검증할 수 있다.
deployment step 예시
Step 0: enclave build, 실행, 등록
# enclave package를 게시한다
cd move/enclave
sui move build && sui client publish
# 출력에서 이 값을 찾아 env var를 설정한다
ENCLAVE_PACKAGE_ID=0x8ecf22e78c90c3e32833d76d82415d7e4227ea370bec4efdad4c4830cbda9e49
# seal-policy 앱 package를 게시한다
cd ../seal-policy
sui move build && sui client publish
# 출력에서 이 값들을 찾아 env var를 설정한다
CAP_OBJECT_ID=0xbd6ad872040eddc08f20ff11e20a3dc030c3e30f4ab2303a0c42447006724262
ENCLAVE_CONFIG_OBJECT_ID=0x3ee612ffc17f29280b8479c36a1096a339e38c353a7662baa559ba4d879dd4de
APP_PACKAGE_ID=0x7f0171b76f82cd61ebb4ac3fd502deac6552e9becd38be28d5692b69b5fdb54e
# enclave 내부의 APP_PACKAGE_ID로 seal_config.yaml을 업데이트한다
# enclave용 ec2 instance를 구성한다. 자세한 내용은 메인 가이드의 UsingNautilus.md를 참조한다
# 구성된 차이점이 포함된 repo가 있는 ec2 instance에 ssh로 접속하여 docker를 빌드하고 실행하고 노출한다
make ENCLAVE_APP=seal-example && make run && sh expose_enclave.sh
# pcr을 찾아 env var를 설정한다
cat out/nitro.pcrs
PCR0=84db3309c8a06c31c1c0a44701fb6c47766244925d7c1d32d5e6589cbdea23aa1f619cddc62c7368ffe648a07df2feb8
PCR1=84db3309c8a06c31c1c0a44701fb6c47766244925d7c1d32d5e6589cbdea23aa1f619cddc62c7368ffe648a07df2feb8
PCR2=21b9efbc184807662e966d34f390821309eeac6802309798826296bf3e8bec7c10edb30948c90ba67310f7b964fc500a
# name과 url을 채운다
MODULE_NAME=weather
OTW_NAME=WEATHER
ENCLAVE_URL=http://<PUBLIC_IP>:3000
# pcr을 업데이트한다
sui client call --function update_pcrs --module enclave --package $ENCLAVE_PACKAGE_ID --type-args "$APP_PACKAGE_ID::$MODULE_NAME::$OTW_NAME" --args $ENCLAVE_CONFIG_OBJECT_ID $CAP_OBJECT_ID 0x$PCR0 0x$PCR1 0x$PCR2
# 선택 사항으로 name을 업데이트한다
sui client call --function update_name --module enclave --package $ENCLAVE_PACKAGE_ID --type-args "$APP_PACKAGE_ID::$MODULE_NAME::$OTW_NAME" --args $ENCLAVE_CONFIG_OBJECT_ID $CAP_OBJECT_ID "some name here"
# enclave를 온체인에 등록한다
sh register_enclave.sh $ENCLAVE_PACKAGE_ID $APP_PACKAGE_ID $ENCLAVE_CONFIG_OBJECT_ID $ENCLAVE_URL $MODULE_NAME $OTW_NAME
# 출력에서 생성된 enclave obj id를 읽고 그 initial shared version을 찾는다
ENCLAVE_OBJECT_ID=0x9b8bc44069abc9843bbd2f54b4e7732136cc7c615c34959f98ab2f7c74f002bd
ENCLAVE_OBJ_VERSION=722158400
이 시점에서 enclave는 실행 중이지만 SEAL_API_KEY가 없어 request를 처리할 수 없다.
curl -H 'Content-Type: application/json' -d '{"payload": { "location": "San Francisco"}}' -X POST http://<PUBLIC_IP>:3000/process_data
{"error":"API key not initialized. Please complete key load first."}%
Step 1: secret encrypt
Seal repo의 root directory에서 Seal CLI command를 실행할 수 있다. secret 값 이 안전한 어느 곳에서나 이 단계를 수행한다. 출력은 이후 Step 4에서 사용한다.
이 command는 주어진 network의 public full node를 사용해 지정된 key server ID의 public key를 조회한다. 그런 다음 identity id, threshold t, 지정한 key server -k, policy package -p를 사용해 secret를 암호화한다.
# seal repo에서 실행한다
# step 0의 package id를 설정한다
APP_PACKAGE_ID=0x2080f9c370ddb22c48d6377f8aa64883c3a1c61d3febbcc18b6bf70553ae45a0
cargo run --bin seal-cli encrypt --secret 303435613237383132646265343536333932393133323233323231333036 \
--id 0x00 \
-p $APP_PACKAGE_ID \
-t 2 \
-k 0x73d05d62c18d9374e3ea529e8e0ed6161da1a141a94d3f76ae3fe4e99356db75,0xf5d14a81a982144ae441cd7d64b09027f116a468bd36e7eca494f750591623c8 \
-n testnet
Encrypted object:
<ENCRYPTED_OBJECT>
--secret: 암호화하는 secret 값이며 hexadecimal format이어야 한다. enclave만 이를 복호화할 수 있다. 이 예시는 UTF-8에서 hexadecimal로 변환한 weather-api-key 값을 사용한다:
>>> '045a27812dbe456392913223221306'.encode('utf-8').hex()
'303435613237383132646265343536333932393133323233323231333036'
--id: 0x00의 고정값이다. 이것은 enclave로 어떤 데이터든 암호화할 때 사용하는 identity이다.
-p: Seal policy를 포함하는 package ID이다.
-k: key server object ID 목록이다. Mysten이 공개한 Testnet server 2개를 사용한다.
-t: 암호화에 사용하는 threshold이다.
-n: 사용하는 key server의 network이다.
Step 2: key load 초기화
이 단계는 enclave가 실행 중인 host에서 수행하며, 이 host는 port 3001을 통해 enclave와 통신할 수 있다.
이 호출에서 enclave는 wallet가 서명한 certificate를 생성하고 seal_approve를 호출하는 PTB를 구성한다. enclave ephemeral key pair는 wallet public key의 intent message에 서명한다. session key는 request에 서명하고 encoded FetchKeyRequest를 반환한다.
# step 0의 ENCLAVE_OBJECT_ID와 ENCLAVE_OBJ_VERSION을 사용한다
curl -X POST http://localhost:3001/admin/init_seal_key_load \
-H 'Content-Type: application/json' \
-d '{"enclave_object_id": "'$ENCLAVE_OBJECT_ID'", "initial_shared_version": '$ENCLAVE_OBJ_VERSION'}'
# 예상 response:
{"encoded_request":"<FETCH_KEY_REQUEST>"}
Step 3: Seal server에서 key fetch
Seal repo의 root에서 다음 command를 실행한다:
# seal repo에서 실행한다
cargo run --bin seal-cli fetch-keys --request <FETCH_KEY_REQUEST> \
-k 0x73d05d62c18d9374e3ea529e8e0ed6161da1a141a94d3f76ae3fe4e99356db75,0xf5d14a81a982144ae441cd7d64b09027f116a468bd36e7eca494f750591623c8 \
-t 2 \
-n testnet
Encoded seal responses:
<ENCODED_SEAL_RESPONSES>
--request: Step 2의 출력이다.
-k: key server object ID 목록이다. Mysten이 공개한 Testnet server 2개를 사용한다.
-t: 암호화에 사용하는 threshold이다.
-n: 사용하는 key server의 network이다.
이 command는 hexadecimal encoding된 BCS serialized FetchKeyRequest를 parsing하고, 주어진 network에 대해 지정된 key server object에서 key를 가져온다. 각 key server는 PTB와 signature를 검증한 뒤 Seal policy가 충족되면 enclave의 ephemeral ElGamal key로 암호화된 key share를 반환한다. CLI는 모든 response를 모아 Seal object ID 목록과 그 server response를 담은 hexadecimal encoded 값을 반환한다.
Step 4: key load 완료
이 단계는 enclave가 실행 중인 host에서 수행하며, 이 host는 port 3001을 통해 enclave와 통신할 수 있다. OK를 반환하면 enclave는 Seal key를 복호화해 memory에 cache한다. <ENCODED_SEAL_RESPONSES>를 Step 3의 출력으로 바꾼다.
curl -X POST http://localhost:3001/admin/complete_seal_key_load \
-H "Content-Type: application/json" \
-d '{
"seal_responses": "<ENCODED_SEAL_RESPONSES>"
}'
# 예상 response:
{"status":"OK"}
Step 5: weather API key provision
이 단계는 enclave가 실행 중인 host에서 수행하며, 이 host는 port 3001을 통해 enclave와 통신할 수 있다. <ENCRYPTED_OBJECT>를 Step 1의 출력으로 바꾼다.
이 호출에서 enclave는 Step 4에서 cache한 key를 사용해 암호화된 weather API key를 복호화한다. 이 endpoint는 애플리케이션별 endpoint이므로 필요에 따라 바꾸거나 더 추가한다. ID 값 0을 사용해 다른 데이터를 암호화하려면 Step 1을 반복하고, endpoint를 사용해 이를 enclave에 provision한다.
curl -X POST http://localhost:3001/admin/provision_weather_api_key \
-H "Content-Type: application/json" \
-d '{
"encrypted_object": "<ENCRYPTED_OBJECT>"
}'
# 예상 response:
{"status":"OK"}
Step 6: service 사용
enclave server는 이제 완전히 동작하며 데이터를 처리할 수 있다.
curl -H 'Content-Type: application/json' -d '{"payload": { "location": "San Francisco"}}' -X POST http://<PUBLIC_IP>:3000/process_data
# 예시 response:
{"response":{"intent":0,"timestamp_ms":1755805500000,"data":{"location":"San Francisco","temperature":18}},"signature":"4587c11eafe8e78c766c745c9f89b3bb7fd1a914d6381921e8d7d9822ddc9556966932df1c037e23bedc21f369f6edc66c1b8af019778eb6b1ec1ee7f324e801"}
여러 secret 처리
Seal은 public key encryption을 사용하므로 같은 고정 ID 값 0을 사용해 많은 secret를 암호화할 수 있다. 같은 package ID와 같은 ID 값 0을 사용해 어떤 데이터든 Step 1을 반복한다.
enclave용 Seal key를 cache하려면 Step 2–4를 한 번 실행한다.
key가 cache되면 Step 5와 비슷한 하나 이상의 provision endpoint를 구현하여 어떤 encrypted object든 복호화한다.
여러 enclave
여러 enclave가 같은 Seal encrypted secret에 접근할 수 있다. 대안으로는 한 enclave를 사용해 Seal에서 key를 가져올 필요 없이 다른 attested enclave에 직접 provision하는 방법도 있다.
문제 해결
-
Step 3의 Certificate expired error:
FetchKeyRequest의 certificate는 30분(TTL) 후 만료된다. 새 certificate가 있는 fresh request를 생성하려면 Step 2를 다시 실행하고, 그 뒤 Step 3을 다시 시도한다. -
Enclave restarts: enclave가 다시 시작되면 모든 ephemeral key(여기에는 cache된 Seal key도 포함됨)를 잃는다. secret로 enclave를 다시 초기화하려면 Step 2–5를 다시 실행해야 한다.