Skip to content

Commit

Permalink
feat(azure-signing-manager): add azure signing manager
Browse files Browse the repository at this point in the history
add azure signing manager that allows for keys to be stored in an Azure Key Vault and to be used
with the Polymesh SDK

DA-1322
  • Loading branch information
polymath-eric committed Oct 4, 2024
1 parent e9a191a commit 9e4212d
Show file tree
Hide file tree
Showing 24 changed files with 1,410 additions and 5 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"polywallet",
"Resonse",
"satoshi",
"secp",
"sonarcloud",
"tscpaths",
"Unsub"
Expand Down
1 change: 1 addition & 0 deletions commitlint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module.exports = {
'approval-signing-manager',
'fireblocks-signing-manager',
'walletconnect-signing-manager',
'azure-signing-manager',
],
],
},
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@babel/preset-env": "^7.16.11",
"@commitlint/cli": "^16.2.1",
"@commitlint/config-conventional": "^16.2.1",
"@golevelup/ts-jest": "^0.5.6",
"@ng-easy/builders": "^5.2.0",
"@nrwl/cli": "13.8.2",
"@nrwl/devkit": "13.8.2",
Expand All @@ -48,6 +49,8 @@
"@types/node": "16.11.7",
"@types/require-from-string": "^1.2.1",
"@typescript-eslint/eslint-plugin": "~5.10.0",
"@types/jest-when": "^3.5.2",
"jest-when": "^3.6.0",
"@typescript-eslint/parser": "~5.10.0",
"babel-loader": "^8.2.3",
"commitiquette": "^1.2.1",
Expand Down
3 changes: 3 additions & 0 deletions packages/azure-signing-manager/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": [["@nrwl/web/babel", { "useBuiltIns": "usage" }]]
}
18 changes: 18 additions & 0 deletions packages/azure-signing-manager/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
7 changes: 7 additions & 0 deletions packages/azure-signing-manager/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# azure-signing-manager

This library was generated with [Nx](https://nx.dev).

## Running unit tests

Run `nx test azure-signing-manager` to execute the unit tests via [Jest](https://jestjs.io).
12 changes: 12 additions & 0 deletions packages/azure-signing-manager/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module.exports = {
displayName: 'azure-signing-manager',
preset: '../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
testEnvironment: 'node',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/packages/azure-signing-manager',
};
25 changes: 25 additions & 0 deletions packages/azure-signing-manager/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@polymeshassociation/azure-signing-manager",
"version": "0.0.1",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@polymeshassociation/signing-manager-types": "^3.3.0",
"@azure/keyvault-keys": "4.8.0",
"@azure/identity": "4.4.1",
"secp256k1": "5.0.0"
},
"devDependencies": {
"@types/secp256k1": "4.0.6"
},
"peerDependencies": {
"@polymeshassociation/polymesh-sdk": ">=15.0.0"
},
"main": "./index.js",
"typings": "./index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/PolymeshAssociation/signing-managers.git"
}
}
51 changes: 51 additions & 0 deletions packages/azure-signing-manager/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"root": "packages/azure-signing-manager",
"sourceRoot": "packages/azure-signing-manager/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nrwl/node:package",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/packages/azure-signing-manager",
"tsConfig": "packages/azure-signing-manager/tsconfig.lib.json",
"packageJson": "packages/azure-signing-manager/package.json",
"main": "packages/azure-signing-manager/src/index.ts",
"assets": ["packages/azure-signing-manager/*.md"],
"srcRootForCompilationRoot": "packages/azure-signing-manager/src"
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["packages/azure-signing-manager/**/*.ts"]
}
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["coverage/packages/azure-signing-manager"],
"options": {
"jestConfig": "packages/azure-signing-manager/jest.config.js",
"passWithNoTests": true
}
},
"release": {
"executor": "@ng-easy/builders:semantic-release",
"configurations": {
"local": {
"force": true
}
}
},
"run-local": {
"executor": "./tools/executors/run-local:run-local",
"options": {
"runInBrowser": false,
"path": "packages/azure-signing-manager/sandbox/index.ts",
"port": 9000
}
}
},
"tags": []
}
2 changes: 2 additions & 0 deletions packages/azure-signing-manager/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './lib/azure-signing-manager';
export * from './types';
132 changes: 132 additions & 0 deletions packages/azure-signing-manager/src/lib/azure-hsm/azure-hsm.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import {
CryptographyClient,
KeyClient,
KeyProperties,
KeyVaultKey,
KnownSignatureAlgorithms,
} from '@azure/keyvault-keys';
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { when } from 'jest-when';

import { AzureHsm } from './azure-hsm';
import { AzureSignerError } from './types';
import { createPagedAsyncIterableIterator } from './util';

const mockKeyClient = createMock<KeyClient>();
const mockCryptographyClient = createMock<CryptographyClient>();
const mockKeyVaultKey = createMock<KeyVaultKey>();

jest.mock('@azure/keyvault-keys', () => ({
...jest.requireActual('@azure/keyvault-keys'),
KeyClient: jest.fn().mockImplementation(() => mockKeyClient),
CryptographyClient: jest.fn().mockImplementation(() => mockCryptographyClient),
KeyVaultKey: jest.fn().mockImplementation(() => mockKeyVaultKey),
}));

describe('AzureHsm', () => {
let azure: AzureHsm;
let mockKey: DeepMocked<KeyVaultKey>;
let mockNewKey: DeepMocked<KeyVaultKey>;
let mockPartialKey: DeepMocked<KeyVaultKey>;

const x = Buffer.from('d6eb9ae6b44331da8c92a0bd88850b94c7f32981aaf42d1c1345e05af810470a', 'hex');
const y = Buffer.from('7b4603950822dd8806494a3aff5d6bc2e437b71f7c7de64e9d84a60411dc1293', 'hex');
const publicKey = '0x63eb973f33ad5737a1ea2e4100c04e806c403b5994d986f0c71a2fd80dbbd179';

beforeEach(() => {
azure = new AzureHsm({ keyVaultUrl: 'https://example.com' });
mockKey = createMock<KeyVaultKey>({ key: { x, y } });
mockNewKey = createMock<KeyVaultKey>({ key: { x, y } });
mockPartialKey = createMock<KeyVaultKey>({ key: { x: undefined, y: undefined } });

const mockKeyProperties: KeyProperties[] = [
{ name: 'someKey', vaultUrl: 'http://example.com/keyOne' },
];

const mockIterator = createPagedAsyncIterableIterator(mockKeyProperties);

mockKeyClient.listPropertiesOfKeys.mockReturnValue(mockIterator);

mockKeyClient.getKey.mockRejectedValue({ code: 'KeyNotFound' });

mockKeyClient.createKey.mockImplementation(async name => {
when(mockKeyClient.getKey).calledWith(name).mockResolvedValue(mockKey);

return mockKey;
});
when(mockKeyClient.getKey).calledWith('someKey').mockResolvedValue(mockKey);

when(mockKeyClient.getKey).calledWith('partialKey').mockResolvedValue(mockPartialKey);

mockCryptographyClient.sign.mockResolvedValue({
result: Buffer.from(
'b4b24959854a6afdba21e1007955c6c728725009feab9886408d6dc4fb9712e3ab5f30be4f06d2f12f01a10ccd374278d44ab133ed8ee5998341034f9347f156',
'hex'
),
algorithm: KnownSignatureAlgorithms.ES256K,
});

mockKeyClient.getCryptographyClient.mockReturnValue(mockCryptographyClient);
});

describe('createKey', () => {
it('should create a key', async () => {
const result = await azure.createKey('newKey');

expect(result).toEqual(
expect.objectContaining({
name: 'newKey',
keyVaultKey: mockNewKey,
})
);
});
});

describe('fetchAllKeys', () => {
it('should return all of the key', async () => {
const result = await azure.fetchAllKeys();

expect(result).toEqual(
expect.arrayContaining([expect.objectContaining({ name: 'someKey', keyVaultKey: mockKey })])
);
});
});

describe('getAzureKeyByPubKey', () => {
it('should return the key', async () => {
const key = await azure.getAzureKeyByPubKey(publicKey);

expect(key).toEqual(expect.objectContaining({ name: 'someKey', keyVaultKey: mockKey }));
});
});

describe('getAzureKey', () => {
it('should return the azure key', async () => {
const result = await azure.getAzureKey('someKey');

expect(result).toEqual(expect.objectContaining({ name: 'someKey', keyVaultKey: mockKey }));
});

it('should return undefined if key is not found', () => {
return expect(azure.getAzureKey('unknownKey')).resolves.toEqual(undefined);
});

it('should throw an error if the key is missing details', async () => {
const expectedError = new AzureSignerError({ message: 'essential key details missing' });

return expect(azure.getAzureKey('partialKey')).rejects.toThrow(expectedError);
});
});

describe('signData', () => {
it('should sign the data', async () => {
const bytes = Buffer.from('00', 'hex');

const result = await azure.signData(publicKey, bytes);

expect(result).toEqual(
'0x02b4b24959854a6afdba21e1007955c6c728725009feab9886408d6dc4fb9712e3ab5f30be4f06d2f12f01a10ccd374278d44ab133ed8ee5998341034f9347f15601'
);
});
});
});
Loading

0 comments on commit 9e4212d

Please sign in to comment.