Skip to content

Add AWS KMS support for account keys - #232

Merged
vishalchangrani merged 2 commits into
mainfrom
kan/aws-kms
Oct 23, 2026
Merged

vishalchangrani merged 2 commits into
mainfrom
kan/aws-kms

Conversation

@Kay-Zee

@Kay-Zee Kay-Zee commented Oct 22, 2026

Copy link
Copy Markdown
Member

What

Adds an aws-kms account key type, mirroring the existing google-kms key type. The key is declared in flow.json with a resourceID, and signing is delegated to AWS KMS.

"accounts": {
  "my-account": {
    "address": "0xf8d6e0586b0a20c7",
    "key": {
      "type": "aws-kms",
      "resourceID": "arn:aws:kms:us-west-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab"
    }
  }
}
  • config.KeyTypeAWSKMS ("aws-kms")
  • accounts.AWSKMSKey signs through github.com/onflow/flow-go-sdk/crypto/awskms
  • the region is read from the key ARN, so it does not have to be configured separately
  • credentials come from the default AWS configuration (env, ~/.aws, SSO)
  • Validate returns an actionable error when credentials cannot be resolved, rather than starting an interactive login

Notes

The hash algorithm is fixed to SHA2_256. AWS KMS signs ECDSA keys with ECDSA_SHA_256 only: awskms sets SigningAlgorithmSpecEcdsaSha256, and its parseHashAlgorithm returns SHA2_256 for both supported key specs. flowkit defaults to SHA3_256, so the hash algorithm is now derived from the key type instead of the configuration, and a configuration asking for anything else is rejected. Without this, HashAlgo() reports a value that no verifier can reproduce.

Only the canonical key ARN is accepted (arn:aws:kms:<region>:<account-id>:key/<key-id>). Key aliases and other KMS resource types cannot be reconstructed from a parsed key, and the signing key has to be addressed by its ARN.

Verification

  • go test ./... passes; gofmt, go vet, check-headers, check-schema, and go mod tidy are clean
  • a flow binary built against this branch loads a flow.json holding both an aws-kms and a google-kms account
  • signing reaches the AWS KMS API with the region taken from the ARN, and surfaces the API error when credentials are absent or expired
  • the aws-kms hash algorithm survives a flow.json write-back as SHA2_256
  • a successful signature is untested: it needs live credentials and a real KMS key

Adds an `aws-kms` key type that mirrors the existing `google-kms` key
type: the key is declared in flow.json with a `resourceID`, and signing
is delegated to the KMS provider.

- config.KeyTypeAWSKMS ("aws-kms")
- AWSKMSKey signs through the Flow Go SDK awskms package
- the region is read from the key ARN, so it does not have to be
  configured separately
- credentials are resolved from the default AWS configuration
- Validate returns an actionable error when credentials cannot be
  resolved, instead of starting an interactive login

AWS KMS signs ECDSA keys with ECDSA_SHA_256 only, so the hash algorithm
is not a choice for this key type: it is fixed to SHA2_256, and a
configuration asking for anything else is rejected. Leaving it to the
default SHA3_256 would produce signatures that no verifier can check.

Only the canonical key ARN form is accepted
(arn:aws:kms:<region>:<account-id>:key/<key-id>), because key aliases and
other KMS resource types cannot be reconstructed from a parsed key and
the signing key has to be addressed by its ARN.
@Kay-Zee
Kay-Zee requested a review from a team as a code owner October 22, 2026 23:24
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 56.66667% with 26 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
accounts/keys.go 50.94% 25 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@vishalchangrani vishalchangrani left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looked through this carefully. Two strong correctness catches worth calling out:

Hash algorithm pin to SHA2_256. This one would have been easy to miss. AWS KMS only signs ECDSA with ECDSA_SHA_256, and awskms.parseHashAlgorithm in flow-go-sdk returns SHA2_256 for both supported key specs. If the config falls through to flowkit's default (SHA3_256), Key.HashAlgo() reports SHA3_256 but the signer actually produces SHA2_256 — every signature gets rejected on-chain. Pinning in both awsKMSKeyFromConfig and at JSON parse time (with the explicit error on non-SHA2_256) is the right call.

ARN regex guards an SDK crash. awskms.KeyFromResourceARN in flow-go-sdk does strings.Split(parts[5], "/")[1] without bounds-checking — give it arn:aws:kms:us-west-2:111122223333:key (no key ID) and it panics. Rejecting aliases, short account IDs, and missing region/account up front is a solid defensive layer. Might be worth a quick follow-up patch to the SDK itself.

A few non-blocking suggestions:

  1. Non-standard AWS partitions. The regex only accepts arn:aws:. China and GovCloud use arn:aws-cn: / arn:aws-us-gov:. Likely irrelevant to our migration, but easy to open up: ^arn:aws(-cn|-us-gov)?:kms:....

  2. Hash enforcement asymmetric. JSON path errors on SHA3_256; a Go caller constructing config.AccountKey{Type: KeyTypeAWSKMS, HashAlgo: crypto.SHA3_256} directly gets a silent override. Fine if intentional (strict at user-facing boundary, lenient internally), but worth a one-line comment on awsKMSKeyFromConfig noting the override so future readers aren't surprised.

  3. Validate() cost. awsCfg.Credentials.Retrieve() can trigger an IMDS roundtrip on EC2/ECS, and runs even for accounts not being used in the current command. The GCP equivalent is heavier (can pop a browser) so there's precedent, but it may add noticeable startup latency on short-lived commands with many AWS accounts in flow.json. Worth keeping in mind if someone later reports slow flow invocations.

Overall LGTM — this is a strict upgrade over a naive port of the GCP path.

- Note on awsKMSKeyFromConfig that overriding the configured hash algorithm is
  deliberate, and that the JSON layer is the strict boundary
- Say why aws-cn and aws-us-gov ARNs are rejected: the SDK rebuilds the ARN as
  arn:aws:kms:... and sends that as the KeyId, so the wrong partition reaches the
  service. Supporting them needs an SDK change.
- Pin the partition rejection in the ARN test table
@Kay-Zee

Kay-Zee commented Oct 23, 2026

Copy link
Copy Markdown
Member Author

Pushed 9a5cfc6 for the two code points.

Partitions. I kept the rejection rather than opening the regex up, because accepting them would only move the failure later. awskms.Key.ARN() rebuilds the ARN as arn:aws:kms:<region>:<account>:key/<id>, and the SDK passes that string as KeyId in both GetPublicKey and Sign. A GovCloud key would parse fine and then be sent to the service with an aws partition and rejected there, which is a worse error than the one we produce now. The regex comment says this, and the rejection message names the two partitions. Real support needs a change in the SDK.

Hash enforcement. Added the comment on awsKMSKeyFromConfig noting the override is deliberate and that the JSON layer is the strict boundary.

Validate() cost. Checked all three call sites of Key.Validate(). Two matter: transactions.SetSigner — the account actually signing — and the emulator service-account path in flow-cli. The third is BIP44Key.PrivateKey() lazily validating itself, which does not touch this key type. So it does not run for every account in flow.json. The IMDS roundtrip happens once per signing, on top of the LoadDefaultConfig that Signer() does anyway; two config loads per signature. If startup latency ever shows up, that duplication is the first thing I would look at, not a per-account cost.

SDK crash. Agreed the bounds check belongs upstream — KeyFromResourceARN panics on ...:key with no key ID. Happy to send that as a separate patch if it is useful.

@Kay-Zee

Kay-Zee commented Oct 23, 2026

Copy link
Copy Markdown
Member Author

Verified end-to-end against AWS KMS on Flow testnet.

CI: lint and test both pass.

Setup

KMS key arn:aws:kms:us-west-2:908768512294:key/9f617527-d0ef-4e93-ade0-bf1131cafb5d (ECC_NIST_P256, SIGN_VERIFY, staging)
testnet account 0x987326f263cad683, key index 0, weight 1000, ECDSA_P256, SHA2_256
transaction 221daa307a24f0680218c56426999e671ad545034a4f3eab69d4a6441221bfbf

The account's key is the public half of that KMS key. The transaction has the KMS account as proposer and authorizer, with a separate account as payer.

Result: Sealed, no error. Read back from the access node API rather than from the CLI:

proposal_key     {address: 987326f263cad683, key_index: 0}
payload_sigs     [{address: 987326f263cad683, key_index: 0, signature: AqSO0mIGis4m...}]
envelope_sigs    [{address: 547f177b243b4d80, key_index: 0, signature: Zfj3yDKpHKCU...}]

987326f263cad683 is the KMS-backed account. Its payload signature was produced by AWS KMS and verified by the network.

On the hash algorithm. The account had to be registered with SHA2_256 explicitly, because flow accounts create defaults --hash-algo to SHA3_256. Registering the key with the default would have produced an account that can never verify a KMS signature, which is the failure mode this change fixes at the configuration layer.

@vishalchangrani
vishalchangrani merged commit 0b233ad into main Oct 23, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants