From 82f7b2147ba12d63655831f5db65a38ea1594d48 Mon Sep 17 00:00:00 2001 From: Brian Botha Date: Fri, 15 Jul 2022 19:22:39 +1000 Subject: [PATCH] feat: added `--private-key` option to `CommandStart.ts` and `CommandBootstrap.ts` This should allow us to override the keypair generation with the provided private key. this will speed up agent starting. Still need to test this for `agent start` and `bootstrap`. Related #404 --- src/bin/agent/CommandStart.ts | 2 ++ src/bin/bootstrap/CommandBootstrap.ts | 2 ++ src/bin/utils/options.ts | 8 ++++++++ src/bin/utils/parsers.ts | 5 +++++ src/bootstrap/utils.ts | 3 ++- src/validation/utils.ts | 18 ++++++++++++++++++ tests/keys/KeyManager.test.ts | 13 +++++++++---- 7 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/bin/agent/CommandStart.ts b/src/bin/agent/CommandStart.ts index 6ccc4e9c04..27c361066e 100644 --- a/src/bin/agent/CommandStart.ts +++ b/src/bin/agent/CommandStart.ts @@ -37,6 +37,7 @@ class CommandStart extends CommandPolykey { this.addOption(binOptions.backgroundOutFile); this.addOption(binOptions.backgroundErrFile); this.addOption(binOptions.fresh); + this.addOption(binOptions.privateKey); this.action(async (options) => { options.clientHost = options.clientHost ?? config.defaults.networkConfig.clientHost; @@ -94,6 +95,7 @@ class CommandStart extends CommandPolykey { keysConfig: { rootKeyPairBits: options.rootKeyPairBits, recoveryCode: recoveryCodeIn, + privateKeyOverride: options.privateKey, }, proxyConfig: { connConnectTime: options.connectionTimeout, diff --git a/src/bin/bootstrap/CommandBootstrap.ts b/src/bin/bootstrap/CommandBootstrap.ts index 9842653c0a..acbf396c08 100644 --- a/src/bin/bootstrap/CommandBootstrap.ts +++ b/src/bin/bootstrap/CommandBootstrap.ts @@ -11,6 +11,7 @@ class CommandBootstrap extends CommandPolykey { this.addOption(binOptions.recoveryCodeFile); this.addOption(binOptions.rootKeyPairBits); this.addOption(binOptions.fresh); + this.addOption(binOptions.privateKey); this.action(async (options) => { const bootstrapUtils = await import('../../bootstrap/utils'); const password = await binProcessors.processNewPassword( @@ -27,6 +28,7 @@ class CommandBootstrap extends CommandPolykey { keysConfig: { rootKeyPairBits: options.rootKeyPairBits, recoveryCode: recoveryCodeIn, + privateKeyOverride: options.privateKey, }, fresh: options.fresh, fs: this.fs, diff --git a/src/bin/utils/options.ts b/src/bin/utils/options.ts index f2da17b8ca..4397603f8b 100644 --- a/src/bin/utils/options.ts +++ b/src/bin/utils/options.ts @@ -163,6 +163,13 @@ const noPing = new commander.Option('--no-ping', 'Skip ping step').default( true, ); +const privateKey = new commander.Option( + '--private-key ', + 'Override key generation with a private key Pem', +) + .argParser(binParsers.parsePrivateKeyPem) + .default(undefined); + export { nodePath, format, @@ -187,4 +194,5 @@ export { pullVault, forceNodeAdd, noPing, + privateKey, }; diff --git a/src/bin/utils/parsers.ts b/src/bin/utils/parsers.ts index bcab0e8d3c..7801514c51 100644 --- a/src/bin/utils/parsers.ts +++ b/src/bin/utils/parsers.ts @@ -92,6 +92,10 @@ function parseSecretPath(secretPath: string): [string, string, string?] { return [vaultName, directoryPath, undefined]; } +const parsePrivateKeyPem = validateParserToArgListParser( + validationUtils.parsePrivateKeyPem, +); + export { parseInteger, parseNumber, @@ -109,4 +113,5 @@ export { parseProviderIdList, parseCoreCount, parseSecretPath, + parsePrivateKeyPem, }; diff --git a/src/bootstrap/utils.ts b/src/bootstrap/utils.ts index 60844fc197..d79774a973 100644 --- a/src/bootstrap/utils.ts +++ b/src/bootstrap/utils.ts @@ -1,5 +1,5 @@ import type { FileSystem } from '../types'; -import type { RecoveryCode } from '../keys/types'; +import type { RecoveryCode, PrivateKey } from '../keys/types'; import path from 'path'; import Logger from '@matrixai/logger'; import { DB } from '@matrixai/db'; @@ -40,6 +40,7 @@ async function bootstrapState({ rootCertDuration?: number; dbKeyBits?: number; recoveryCode?: RecoveryCode; + privateKeyOverride?: PrivateKey; }; fresh?: boolean; fs?: FileSystem; diff --git a/src/validation/utils.ts b/src/validation/utils.ts index 8197348a98..753cf5eb6b 100644 --- a/src/validation/utils.ts +++ b/src/validation/utils.ts @@ -12,12 +12,14 @@ import type { GestaltAction, GestaltId } from '../gestalts/types'; import type { VaultAction, VaultId } from '../vaults/types'; import type { Host, Hostname, Port } from '../network/types'; import type { ClaimId } from '../claims/types'; +import type { PrivateKey } from '../keys/types'; import * as validationErrors from './errors'; import * as nodesUtils from '../nodes/utils'; import * as gestaltsUtils from '../gestalts/utils'; import * as vaultsUtils from '../vaults/utils'; import * as networkUtils from '../network/utils'; import * as claimsUtils from '../claims/utils'; +import * as keysUtils from '../keys/utils'; import config from '../config'; function parseInteger(data: any): number { @@ -259,6 +261,21 @@ function parseSeedNodes(data: any): [SeedNodes, boolean] { return [seedNodes, defaults]; } +function parsePrivateKeyPem(data: any): PrivateKey { + if (typeof data !== 'string') { + throw new validationErrors.ErrorParse('Private key Pem must be a string'); + } + let privateKey: PrivateKey; + try { + privateKey = keysUtils.privateKeyFromPem(data); + } catch (e) { + throw new validationErrors.ErrorParse( + 'Must provide a valid private key Pem', + ); + } + return privateKey; +} + export { parseInteger, parseNumber, @@ -276,4 +293,5 @@ export { parsePort, parseNetwork, parseSeedNodes, + parsePrivateKeyPem, }; diff --git a/tests/keys/KeyManager.test.ts b/tests/keys/KeyManager.test.ts index c2cbab1886..7c63be6a57 100644 --- a/tests/keys/KeyManager.test.ts +++ b/tests/keys/KeyManager.test.ts @@ -164,20 +164,25 @@ describe('KeyManager', () => { test('override key generation with privateKeyOverride', async () => { const keysPath = `${dataDir}/keys`; const keyPair = await keysUtils.generateKeyPair(4096); - const mockedGenerateKeyPair = jest.spyOn(keysUtils, 'generateDeterministicKeyPair'); + const mockedGenerateKeyPair = jest.spyOn( + keysUtils, + 'generateDeterministicKeyPair', + ); const keyManager = await KeyManager.createKeyManager({ keysPath, password, privateKeyOverride: keyPair.privateKey, logger, }); - expect(mockedGenerateKeyPair).not.toHaveBeenCalled() + expect(mockedGenerateKeyPair).not.toHaveBeenCalled(); const keysPathContents = await fs.promises.readdir(keysPath); expect(keysPathContents).toContain('root.pub'); expect(keysPathContents).toContain('root.key'); - expect(keysUtils.publicKeyToPem(keyManager.getRootKeyPair().publicKey)).toEqual(keysUtils.publicKeyToPem(keyPair.publicKey)); + expect( + keysUtils.publicKeyToPem(keyManager.getRootKeyPair().publicKey), + ).toEqual(keysUtils.publicKeyToPem(keyPair.publicKey)); await keyManager.stop(); - }) + }); test('uses WorkerManager for generating root key pair', async () => { const keysPath = `${dataDir}/keys`; const keyManager = await KeyManager.createKeyManager({