aws / aws/aws-nitro-enclaves-cli
`build-enclave` with a cross-account KMS key silently produces an UNSIGNED EIF and exits 0 (`AWS GetPublicKey error: service error`)
- Dominant language
- Rust
- Stars
- 156
- Forks
- 99
- PR merge metrics
- No merged PRs in 30d
Description
### Summary
When signing an EIF at build time with a KMS key whose ARN belongs to a **different AWS account** than the calling IAM identity, `nitro-cli build-enclave` fails to read the signing key, prints a vague `Could not read signing info: "AWS GetPublicKey error: service error"`, and then **continues building an unsigned EIF while exiting 0**. `nitro-cli describe-eif` subsequently reports `"IsSigned": false`. The same KMS key/region/credentials work fine with `aws kms get-public-key`, and same-account signing works fine.
There are two distinct problems here:
1. Cross-account KMS keys can't be used for signing (the account is dropped from the key ARN before the `GetPublicKey` call), and the real SDK error is hidden behind a generic `service error`.
2. A signing failure silently degrades a *signed* build into an *unsigned* one with a success exit code.
### Environment
- `nitro-cli --version`: `Nitro CLI 1.4.4`
- OS: Amazon Linux 2023 (kernel `6.12.83-113.160.amzn2023.x86_64`)
- Region: `us-east-1`
- Build account (calling identity): ``
- KMS key account (different): ``
- KMS key: `` — asymmetric ECC signing key, with a cross-account key policy/grant allowing `kms:GetPublicKey` + `kms:Sign` to the build role.
### Steps to reproduce
1. Assume/run as an IAM role in `` that is granted `kms:GetPublicKey` and `kms:Sign` on a KMS key in a **different** account ``.
2. Verify credentials and cross-account KMS access from the exact same shell/environment:
```bash
aws sts get-caller-identity # shows the expected assumed role in
aws kms get-public-key --key-id --region us-east-1 # SUCCEEDS, returns KeyId
```
3. Build a signed EIF with the same ARN:
```bash
nitro-cli build-enclave \
--docker-uri \
--private-key \
--signing-certificate \
--output-file app.eif
```
4. Inspect the result:
```bash
nitro-cli describe-eif --eif-path app.eif # "IsSigned": false
```
### Expected behavior
- A cross-account KMS key ARN (already usable via aws-cli in the same environment) should be usable for signing.
- If signing info cannot be obtained, `build-enclave` should **fail with a non-zero exit code** rather than silently emitting an unsigned EIF (or at minimum require an explicit opt-out to downgrade to unsigned).
- The error message should surface the **underlying SDK error** (e.g. `NotFoundException`/`AccessDeniedException`) instead of a generic `service error`.
### Actual behavior
`build-enclave` prints the following and exits 0, producing an unsigned EIF:
```
Start building the Enclave Image...
Could not read signing info: "AWS GetPublicKey error: service error"
Using the locally available Docker image...
Enclave Image successfully created.
```
Then:
```
nitro-cli describe-eif --eif-path app.eif -> "IsSigned": false
```
Evidence that this is **not** an IAM/permissions or networking issue:
- `aws sts get-caller-identity` (same env) shows the expected assumed role.
- `aws kms get-public-key --key-id --region us-east-1` (same env, immediately before) **succeeds** and returns the KeyId.
- No `AccessDenied`, no endpoint/timeout/DNS errors anywhere.
- **Same-account** signing (identity and key in the same account) works fine with the identical command shape.
So aws-cli's `GetPublicKey` works cross-account, but nitro-cli's embedded SDK `GetPublicKey` fails with a vague `service error`.
### Likely root cause (from public source)
- In `aws-nitro-enclaves-image-format` `src/utils/eif_signer.rs`, `parse_kms_arn` captures **only** the region and key-id from the ARN and discards the 12-digit account. `SignKeyInfo::KmsKeyInfo { id, .. }` therefore holds just the bare key-id, which is passed to `KmsKey::new_with_public_key(client, id, None)`.
- In `awslabs/aws-nitro-enclaves-cose` `src/crypto/kms.rs`, that results in `client.get_public_key().key_id()`. A **bare key-id (no account) resolves against the caller's own account**, so a cross-account key is not found — while `aws kms get-public-key --key-id ` succeeds because the ARN pins the owning account.
- The failure is `CoseError::AwsGetPublicKeyError(SdkError)`, whose `Display` collapses the `ServiceError` variant to `service error`, hiding the modeled error.
- The silent-unsigned behavior comes from `aws-nitro-enclaves-cli` `enclave_build/src/lib.rs`, where a `SignKeyData::new(...)` error is handled with `eprintln!("Could not read signing info: {e:?}")` and `sign_info` set to `None`, after which the build proceeds and returns success.
### Impact
A build that requested KMS signing can silently ship an **unsigned** EIF with a success exit code. In CI/CD this can propagate unsigned enclaves to attestation-gated environments unless a separate guard catches it, undermining the security guarantee that signing is supposed to provide.
### Workaround
1. `aws sts assume-role` into a role **in the same account as the KMS key** and run `nitro-cli build-enclave` under those scoped temporary credentials, so the signing identity and key are same-account.
2. Add a post-build guard: run `nitro-cli describe-eif`, parse `IsSigned`, and **fail the build (non-zero)** if a KMS key was requested but `IsSigned` is `false`:
```bash
signed=$(nitro-cli describe-eif --eif-path app.eif | jq -r '.IsSigned')
if [ "$signed" != "true" ]; then
echo "ERROR: KMS signing was requested but EIF is unsigned" >&2
exit 1
fi
```
### Suggested fixes
1. **Surface the real SDK error** instead of `service error` (include the modeled `GetPublicKeyError`/service message and request ID) so failures are diagnosable.
2. **Support cross-account key ARNs**: pass the full ARN (preserving the account) to `GetPublicKey`/`Sign` rather than stripping to the bare key-id in `parse_kms_arn`.
3. **Fail non-zero on signing failure**: when `--private-key`/`--signing-certificate` are supplied, a signing-info error must abort the build (or require an explicit `--allow-unsigned`-style opt-out) rather than silently emitting an unsigned EIF and exiting 0.
Contributor guide
Assessment
This issue has not been assessed yet.