본문으로 건너뛰기

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로는 테스트되지 않았다.

Components

Seal-Nautilus pattern을 보여주는 다음 예시는 다음 구성 요소를 사용한다:

  1. AWS Nitro Enclave 내부에서 실행되는 Nautilus server(src/nautilus-server/src/apps/seal-example): 이곳은 policy에 따라 Seal secret를 복호화할 수 있는 유일한 위치이다. 인터넷에는 /get_attestation/process_data endpoint로 port 3000을 노출한다. 또한 enclave가 실행되는 host instance에서 key load 단계를 초기화하고 완료할 때만 사용할 수 있는 3개의 /admin endpoint를 localhost에 port 3001로 노출한다.

  2. Seal CLI: 특히 이 예시에서는 encrypt and fetch-keys를 사용한다.

  3. Move contract move/seal-policy/seal_policy.move: enclave ephemeral key를 사용해 wallet public key에 commit된 signature를 검증하는 seal_approve policy를 정의한다.

View the example's full source code.

Phase 1: Start and register the server

정보

이 문맥에서 admin은 EC2 instance에 접근 권한이 있는 사람을 뜻한다. admin은 그 위에서 enclave binary를 빌드하고 실행할 수 있으며, EC2 instance의 localhost를 통해 admin 전용 enclave endpoint를 호출할 수 있다.

  1. admin은 게시된 Seal policy package ID와 Seal configuration으로 seal_config.yaml을 지정한다.

admin은 노출된 /get_attestation endpoint와 함께 enclave를 빌드하고 실행한다.

Click to open
src/nautilus-server/src/apps/seal-example/endpoints.rs
  1. admin은 attestation response를 사용해 PCR과 enclave public key를 등록한다. 이 시점에는 SEAL_API_KEY가 아직 초기화되지 않았으므로 /process_data endpoint가 error를 반환한다.

  2. admin은 enclave를 온체인에 등록하고 enclave object ID와 initial shared version을 가져온다.

Phease 2: Initialize and complete key load

  1. admin은 enclave object로 /admin/init_seal_key_load를 호출한다. enclave는 encoded FetchKeyRequest를 반환한다.

  2. admin은 FetchKeyRequest를 사용해 CLI를 호출하고 enclave의 encryption public key 아래에서 암호화된 Seal response를 가져온다.

  3. admin은 Seal response로 /admin/complete_seal_key_load를 호출한다. enclave는 모든 Seal key를 복호화해 이후 사용을 위해 memory에 cache한다.

Phase 3: Provision application secrets

  1. 이제 Seal key가 cache되었으므로 encrypted object를 cache된 key를 사용해 필요할 때 복호화할 수 있다. 이 예시에서는 admin이 encrypted weather API key object로 /admin/provision_weather_api_key를 호출한다. enclave는 cache된 key를 사용해 이를 복호화하고 SEAL_API_KEY로 저장한다.

  2. enclave는 이제 /process_data request를 처리할 수 있다.

Security guarantees

enclave는 시작 시 3개의 key를 생성하며, 모두 enclave memory에만 유지된다:

  1. Enclave ephemeral key pair(state.eph_kp): Ed25519 key pair이다. /process_data response에 서명하고 seal_approve PTB에서 signature argument를 만드는 데 사용한다. 해당 public key는 Enclave object에 온체인으로 등록된다.

  2. Seal wallet(WALLET_BYTES): Ed25519 key pair이다. Seal certificate 서명과 seal_approve transaction sender로 사용한다.

  3. 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는 다음을 검증한다:

  1. signature는 enclave의 ephemeral public key(enclave.pk()에서 가져옴)와 wallet public key 및 timestamp에 대한 WalletPK scope의 intent message를 사용해 검증된다.

  2. key ID는 vector[0]의 고정값이다.

  3. 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뿐이다.

Why is a 2-step key load needed?

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가 변조되지 않았음을 검증할 수 있다.

Example deployment steps

Step 0: Build, run, and register enclave

# 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: Encrypt secret

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: Initialize 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: Fetch keys from Seal servers

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: Complete 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: Provision weather API key

이 단계는 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: Use the 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"}

Handle multiple secrets

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든 복호화한다.

Multiple enclaves

여러 enclave가 같은 Seal encrypted secret에 접근할 수 있다. 대안으로는 한 enclave를 사용해 Seal에서 key를 가져올 필요 없이 다른 attested enclave에 직접 provision하는 방법도 있다.

Troubleshooting

  1. Step 3의 Certificate expired error: FetchKeyRequest의 certificate는 30분(TTL) 후 만료된다. 새 certificate가 있는 fresh request를 생성하려면 Step 2를 다시 실행하고, 그 뒤 Step 3을 다시 시도한다.

  2. Enclave restarts: enclave가 다시 시작되면 모든 ephemeral key(여기에는 cache된 Seal key도 포함됨)를 잃는다. secret로 enclave를 다시 초기화하려면 Step 2–5를 다시 실행해야 한다.