Skip to content

Commit

Permalink
feat: AWS KMS for delegation (#1435)
Browse files Browse the repository at this point in the history
* feat: first commit

* feat: second commit

* feat: second commit

* feat: second commit

* feat: second commit

* feat: second commit

* feat: more changes

* feat: more changes

* feat: more changes

* feat: more changes

* feat: more changes

* feat: more changes

* feat: more changes

* feat: more changes

* feat: scaffolding for testing

* feat: scaffolding for testing

* feat: scaffolding for testing

* feat: added gitleaks exceptions

* feat: some more changes

* feat: some more changes

* feat: solo tests not working

* feat: first tests working

* feat: more changes

* feat: still delegation url to test

* feat: still delegation url to test

* feat: still delegation url to test

* feat: still delegation url to test

* feat: still delegation url to test

* feat: fixed delegated hash

* feat: more tests

* feat: added testnet tests

* feat: modified readme

* feat: modified readme

* feat: modified readme

* feat: modified readme

---------

Co-authored-by: Fabio Rigamonti <[email protected]>
  • Loading branch information
freemanzMrojo and fabiorigam authored Oct 29, 2024
1 parent f185f4b commit 03dfd27
Show file tree
Hide file tree
Showing 17 changed files with 1,237 additions and 333 deletions.
3 changes: 2 additions & 1 deletion .gitleaksignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
# Exceptions for gitleaks to be placed here
465a9c6b265ec49015fd7ff641506905cb93811c:packages/aws-kms-adapter/tests/test-aws-credentials.json:generic-api-key:3
465a9c6b265ec49015fd7ff641506905cb93811c:packages/aws-kms-adapter/tests/test-aws-credentials.json:generic-api-key:12
2 changes: 1 addition & 1 deletion docker-compose.localstack.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ services:
- CMD
- bash
- -c
- $$(awslocal kms list-keys | jq '.Keys | length | . == 1') || exit 1; # There is 1 key at the moment
- $$(awslocal kms list-keys | jq '.Keys | length | . == 2') || exit 1; # There are 2 keys at the moment
interval: 5s
timeout: 20s
start_period: 2s
Expand Down
14 changes: 10 additions & 4 deletions localstack/init/kms.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
#!/bin/bash

CUSTOM_KEY_MATERIAL="f5KQzETF/SuV/iHWrW/l+pwXfhzW87TJapexPgnqoVg="
CUSTOM_ID="bffb20d8-35ca-4408-9d54-f775b929b38d"
CUSTOM_ORIGIN_KEY_MATERIAL="f5KQzETF/SuV/iHWrW/l+pwXfhzW87TJapexPgnqoVg="
CUSTOM_ORIGIN_ID="bffb20d8-35ca-4408-9d54-f775b929b38d"
CUSTOM_DELEGATOR_ID="3e47cac8-de37-4f50-b591-57f525c1b05c"

# The command succeeds but the key is not created with the custom key material (should be fixed once this is clarified: https://github.com/localstack/localstack/issues/11678)
# awslocal kms create-key --key-usage SIGN_VERIFY --key-spec ECC_SECG_P256K1 --tags "[{\"TagKey\":\"_custom_key_material_\",\"TagValue\":\"$CUSTOM_KEY_MATERIAL\"},{\"TagKey\":\"_custom_id_\",\"TagValue\":\"$CUSTOM_ID\"}]"
awslocal kms create-key --key-usage SIGN_VERIFY --key-spec ECC_SECG_P256K1 --tags "[{\"TagKey\":\"_custom_id_\",\"TagValue\":\"$CUSTOM_ID\"}]"
# awslocal kms create-key --key-usage SIGN_VERIFY --key-spec ECC_SECG_P256K1 --tags "[{\"TagKey\":\"_custom_key_material_\",\"TagValue\":\"$CUSTOM_ORIGIN_KEY_MATERIAL\"},{\"TagKey\":\"_custom_id_\",\"TagValue\":\"$CUSTOM_ORIGIN_ID\"}]"

# Origin key
awslocal kms create-key --key-usage SIGN_VERIFY --key-spec ECC_SECG_P256K1 --tags "[{\"TagKey\":\"_custom_id_\",\"TagValue\":\"$CUSTOM_ORIGIN_ID\"}]"

# Delegator key
awslocal kms create-key --key-usage SIGN_VERIFY --key-spec ECC_SECG_P256K1 --tags "[{\"TagKey\":\"_custom_id_\",\"TagValue\":\"$CUSTOM_DELEGATOR_ID\"}]"
212 changes: 144 additions & 68 deletions packages/aws-kms-adapter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ The AWS KMS Adapter for VeChain SDK provides a secure way to sign transactions u
- **Secure Key Management**: Use AWS KMS to securely manage and protect your private keys.
- **Transaction Signing**: Sign VeChain transactions using keys stored in AWS KMS.
- **Integration with VeChain SDK**: Seamlessly integrate with the VeChain SDK for blockchain interactions.
- **[WIP] Sign and send transactions using a delegator key**: You can specify the key ID of a delegator key to leverage this VeChain feature for signing and sending transactions.
- **Sign and send transactions using a delegator key**: You can specify the key ID of a delegator key to leverage this VeChain feature for signing and sending transactions.

## Installation

Expand All @@ -19,68 +19,61 @@ yarn add @vechain/sdk-aws-kms-adapter

## Test

To run all the tests, including the ones relying on a local instance of Thor Solo + LocalStack, please run:
To run all the tests, including the ones relying on a local instance of Thor Solo + LocalStack and Testnet, please run:

```bash
yarn test:solo
yarn test:integration
```

## Usage

To integrate this into your code, depending on how you plan to manage your AWS credentials, you can choose one of the following examples.

Within this repo, you can create a credentials file called `aws-credentials.json` with your custom credentials under the `tests` folder in case you want to give it a try before integrating with your project. A valid format would be as follows:
Within this repo, you can create a credentials file called `aws-credentials.json` with your custom credentials under the `tests` folder in case you want to give it a try before integrating with your project. A valid format would be as follows (it is an array in case you want to include a delegator key, assumed to be the second one):

```json
{
// AWS KMS keyId (mandatory)
"keyId": "00000000-0000-0000-0000-000000000000",
// AWS region (mandatory)
"region": "eu-west-1",
// AWS credentials (optional)
"credentials": {
// AWS access key id (mandatory if credentials)
"accessKeyId": "test",
// AWS secret access key (mandatory if credentials)
"secretAccessKey": "test",
// AWS session token if SSO is configured (optional)
"sessionToken": "test"
},
// AWS endpoint (optional, to be used locally along with LocalStack)
"endpoint": "http://localhost:4599"
}
[
{
// AWS KMS keyId (mandatory)
"keyId": "00000000-0000-0000-0000-000000000000",
// AWS region (mandatory)
"region": "eu-west-1",
// AWS credentials (optional)
"credentials": {
// AWS access key id (mandatory if credentials)
"accessKeyId": "test",
// AWS secret access key (mandatory if credentials)
"secretAccessKey": "test",
// AWS session token if SSO is configured (optional)
"sessionToken": "test"
},
// AWS endpoint (optional, to be used locally along with LocalStack)
"endpoint": "http://localhost:4599"
}
]
```

### IAM roles

This is the preferred way. If you integrate this library in an app deployed in AWS following with IAM roles, you can just do as follows:

```ts
import { KMSVeChainProvider, KMSVeChainSigner } from '@vechain/sdk-aws-kms-adapter';
import { type KMSClientParameters, KMSVeChainProvider, KMSVeChainSigner } from '@vechain/sdk-aws-kms-adapter';
import {
THOR_SOLO_URL,
ThorClient
} from '@vechain/sdk-network';
...

interface AwsClientParameters {
keyId: string;
region: string;
credentials?: {
accessKeyId: string;
secretAccessKey: string;
sessionToken?: string;
};
endpoint?: string;
}

const awsClientParameters: KMSClientParameters = {
keyId: 'keyId',
region: 'region'
};
...

const thorClient = ThorClient.fromUrl(THOR_SOLO_URL);
const provider = new KMSVeChainProvider(
thorClient,
awsClientParameters.keyId,
awsClientParameters.region
awsClientParameters
);
const signer = new KMSVeChainSigner(provider);
// Signing typed data as per EIP712
Expand All @@ -96,33 +89,27 @@ import {
This way you can connect to your AWS account by using `accessKeyId`, `secretAccessKey` and `sessionToken` if SSO is enabled.

```ts
import { KMSVeChainProvider, KMSVeChainSigner } from '@vechain/sdk-aws-kms-adapter';
import { type KMSClientParameters, KMSVeChainProvider, KMSVeChainSigner } from '@vechain/sdk-aws-kms-adapter';
import {
signerUtils,
THOR_SOLO_URL,
ThorClient
} from '@vechain/sdk-network';
...

interface AwsClientParameters {
keyId: string;
region: string;
credentials?: {
accessKeyId: string;
secretAccessKey: string;
sessionToken?: string;
};
endpoint?: string;
}

const awsClientParameters: KMSClientParameters = {
keyId: 'keyId',
region: 'region',
credentials: {
accessKeyId: 'accessKeyId',
secretAccessKey: 'secretAccessKey'
}
};
...

const thorClient = ThorClient.fromUrl(THOR_SOLO_URL);
const provider = new KMSVeChainProvider(
thorClient,
awsClientParameters.keyId,
awsClientParameters.region
awsClientParameters.credentials
awsClientParameters
);
const signer = new KMSVeChainSigner(provider);
// Signing and sending a transaction
Expand All @@ -139,34 +126,123 @@ import {
You can also leverage LocalStack so you can try the library locally. Sample values are included in the file `tests/test-aws-credentials.json`.

```ts
import { KMSVeChainProvider, KMSVeChainSigner } from '@vechain/sdk-aws-kms-adapter';
import { type KMSClientParameters, KMSVeChainProvider, KMSVeChainSigner } from '@vechain/sdk-aws-kms-adapter';
import {
THOR_SOLO_URL,
ThorClient
} from '@vechain/sdk-network';
...

interface AwsClientParameters {
keyId: string;
region: string;
credentials?: {
accessKeyId: string;
secretAccessKey: string;
sessionToken?: string;
};
endpoint?: string;
}

const awsClientParameters: KMSClientParameters = {
keyId: 'keyId',
region: 'region',
credentials: {
accessKeyId: 'accessKeyId',
secretAccessKey: 'secretAccessKey'
},
endpoint: 'localstackEndpoint'
};
...

const thorClient = ThorClient.fromUrl(THOR_SOLO_URL);
const provider = new KMSVeChainProvider(
thorClient,
awsClientParameters.keyId,
awsClientParameters.region
awsClientParameters.credentials
awsClientParameters
);
const signer = new KMSVeChainSigner(provider);
// Returns the address related to the KMS key
const address = await signer.getAddress();
```

### Delegation (provider)

You can also use delegation to sign your transactions. In this example the source of the delegation is a delegator which key is in KMS so requires a `KMSVeChainProvider`.

```ts
import { type KMSClientParameters, KMSVeChainProvider, KMSVeChainSigner } from '@vechain/sdk-aws-kms-adapter';
import {
THOR_SOLO_URL,
ThorClient
} from '@vechain/sdk-network';
...
const awsClientParameters: KMSClientParameters = {
keyId: 'keyId',
region: 'region',
credentials: {
accessKeyId: 'accessKeyId',
secretAccessKey: 'secretAccessKey'
},
endpoint: 'localstackEndpoint'
};

const delegatorAwsClientParameters: KMSClientParameters = {
// Same format as awsClientParameters, changing values so we can connect
// to something different to LocalStack if we want (see examples above)
}
...

const thorClient = ThorClient.fromUrl(THOR_SOLO_URL);
const provider = new KMSVeChainProvider(
thorClient,
awsClientParameters
);

// Signer with delegator enabled
const delegatorProvider = new KMSVeChainProvider(
thorClient,
delegatorAwsClientParameters
);
const signerWithDelegator = new KMSVeChainSigner(
provider,
{
provider: delegatorProvider
}
);

// Returns the address related to the origin KMS key
const address = await signerWithDelegator.getAddress();
// Returns the address related to the delegator KMS key
const address = await signerWithDelegator.getAddress(true);
```

### Delegation (url)

You can also use delegation to sign your transactions. In this example the source of the delegation is a URL that returns the signature (for instance, `https://sponsor-testnet.vechain.energy/by/705`, more details on how to get yours [here](https://learn.vechain.energy/vechain.energy/FeeDelegation/Setup/)).

```ts
import { type KMSClientParameters, KMSVeChainProvider, KMSVeChainSigner } from '@vechain/sdk-aws-kms-adapter';
import {
THOR_SOLO_URL,
ThorClient
} from '@vechain/sdk-network';
...
const awsClientParameters: KMSClientParameters = {
keyId: 'keyId',
region: 'region',
credentials: {
accessKeyId: 'accessKeyId',
secretAccessKey: 'secretAccessKey'
},
endpoint: 'localstackEndpoint'
};
...

const thorClient = ThorClient.fromUrl(THOR_SOLO_URL);

// Signer with delegator enabled
const provider = new KMSVeChainProvider(
thorClient,
awsClientParameters
);
const signerWithDelegator = new KMSVeChainSigner(
provider,
{
url: 'https://sponsor-testnet.vechain.energy/by/705'
}
);

// Returns the address related to the origin KMS key
const address = await signerWithDelegator.getAddress();

// See /tests folder for more examples. This time we wont get the address
// of the delegator since there is no provider
```
2 changes: 1 addition & 1 deletion packages/aws-kms-adapter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"stop-thor-solo": "echo 'Stopping thor solo node ...' && docker compose -f ../../docker-compose.thor.yml -f ../../docker-compose.localstack.yml --profile thor-solo down && echo 'Thor solo node and localstack stopped ...'",
"test": "docker compose -f ../../docker-compose.localstack.yml up -d && sleep 10 && yarn test:all; ret=$?; docker compose -f ../../docker-compose.localstack.yml down; exit $ret",
"test:all": "rm -rf ./coverage && jest --coverage --coverageDirectory=coverage --group=integration --group=unit",
"test:solo": "(yarn start-thor-solo && yarn test:all && yarn stop-thor-solo) || yarn stop-thor-solo",
"test:integration": "(yarn start-thor-solo && yarn test:all && yarn stop-thor-solo) || yarn stop-thor-solo",
"test:unit": "rm -rf ./coverageUnit && UNIT=true jest --coverage --coverageDirectory=coverageUnit --group=unit"
},
"dependencies": {
Expand Down
Loading

1 comment on commit 03dfd27

@github-actions
Copy link

Choose a reason for hiding this comment

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

Test Coverage

Summary

Lines Statements Branches Functions
Coverage: 99%
99.06% (4338/4379) 97.71% (1413/1446) 99.11% (895/903)
Title Tests Skipped Failures Errors Time
core 808 0 💤 0 ❌ 0 🔥 2m 14s ⏱️
network 734 0 💤 0 ❌ 0 🔥 5m 3s ⏱️
errors 42 0 💤 0 ❌ 0 🔥 16.404s ⏱️

Please sign in to comment.