Skip to content

Commit

Permalink
feat(client): Configurable RSA key length (streamr-dev#1505)
Browse files Browse the repository at this point in the history
Added RSA key length as a new configuration option: `encryption.rsaKeyLength`

In tests we use shorter RSA key (640 bits instead of 4096 bits) as tests don't need very safe RSA keys. This change reduced the execution time of Integration tests significantly: 
- before ~50s
- after ~37s
  • Loading branch information
teogeb authored Jun 13, 2023
1 parent 20012ec commit 0aae953
Show file tree
Hide file tree
Showing 11 changed files with 59 additions and 28 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ found [here](packages/broker/CHANGELOG.md).

#### Added

- Add optional config option `encryption.rsaKeyLength` to control the strength of RSA encryption in key-exchange (https://github.com/streamr-dev/network/pull/1505)

#### Changed

#### Deprecated
Expand Down
14 changes: 10 additions & 4 deletions packages/client/src/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export interface TrackerRegistryContract {
contractAddress: string
}

export interface ChainConnectionInfo {
export interface ChainConnectionInfo {
rpcs: ConnectionInfo[]
chainId?: number
name?: string
Expand Down Expand Up @@ -145,6 +145,12 @@ export interface StreamrClientConfig {
* from overflowing.
*/
maxKeyRequestsPerSecond?: number

/**
* Defines how strong RSA key, in bits, is used when an encryption key is
* requested via the standard Streamr key-exchange.
*/
rsaKeyLength?: number
}

/**
Expand Down Expand Up @@ -286,9 +292,9 @@ export interface StreamrClientConfig {
* Used to assign a custom external IP address for the node.
* Useful in cases where the node has a public IP address but
* the hosts network interface does not know of it.
*
* Works only if the Full Cone NAT that the node is behind preserves local
* port mappings on the public side.
*
* Works only if the Full Cone NAT that the node is behind preserves local
* port mappings on the public side.
*/
externalIp?: ExternalIP
}
Expand Down
6 changes: 5 additions & 1 deletion packages/client/src/ConfigTest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { StreamrClientConfig } from './Config'
import { toEthereumAddress } from '@streamr/utils'
import { StreamrClientConfig } from './Config'
import { MIN_KEY_LENGTH } from './encryption/RSAKeyPair'

function toNumber(value: any): number | undefined {
return (value !== undefined) ? Number(value) : undefined
Expand Down Expand Up @@ -52,6 +53,9 @@ export const CONFIG_TEST: StreamrClientConfig = {
streamRegistryChainRPCs: sideChainConfig,
theGraphUrl: `http://${process.env.STREAMR_DOCKER_DEV_HOST || '127.0.0.1'}:8000/subgraphs/name/streamr-dev/network-contracts`,
},
encryption: {
rsaKeyLength: MIN_KEY_LENGTH
},
_timeouts: {
theGraph: {
indexTimeout: 10 * 1000,
Expand Down
4 changes: 4 additions & 0 deletions packages/client/src/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,10 @@
"maxKeyRequestsPerSecond": {
"type": "number",
"default": 20
},
"rsaKeyLength": {
"type": "number",
"default": 4096
}
},
"default": {}
Expand Down
22 changes: 15 additions & 7 deletions packages/client/src/encryption/RSAKeyPair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ import { promisify } from 'util'

const { webcrypto } = crypto

/**
* The length of encrypted data determines the minimum length. In StreamrClient we use RSA
* for encrypting 32 byte GroupKeys. In Node environment 585 bits is enough, but in
* browser environment we need 640.
* https://en.wikipedia.org/wiki/Optimal_asymmetric_encryption_padding
*/
export const MIN_KEY_LENGTH = 640

function getSubtle(): SubtleCrypto {
const subtle = typeof window !== 'undefined' ? window?.crypto?.subtle : webcrypto.subtle
if (!subtle) {
Expand Down Expand Up @@ -58,17 +66,17 @@ export class RSAKeyPair {
return this.privateKey
}

static async create(): Promise<RSAKeyPair> {
static async create(keyLength: number): Promise<RSAKeyPair> {
return (typeof window !== 'undefined')
? RSAKeyPair.create_browserEnvironment()
: RSAKeyPair.create_serverEnvironment()
? RSAKeyPair.create_browserEnvironment(keyLength)
: RSAKeyPair.create_serverEnvironment(keyLength)
}

private static async create_serverEnvironment(): Promise<RSAKeyPair> {
private static async create_serverEnvironment(keyLength: number): Promise<RSAKeyPair> {
// promisify here to work around browser/server packaging
const generateKeyPair = promisify(crypto.generateKeyPair)
const { publicKey, privateKey } = await generateKeyPair('rsa', {
modulusLength: 4096,
modulusLength: keyLength,
publicKeyEncoding: {
type: 'spki',
format: 'pem',
Expand All @@ -82,10 +90,10 @@ export class RSAKeyPair {
return new RSAKeyPair(privateKey, publicKey)
}

private static async create_browserEnvironment(): Promise<RSAKeyPair> {
private static async create_browserEnvironment(keyLength: number): Promise<RSAKeyPair> {
const { publicKey, privateKey } = await getSubtle().generateKey({
name: 'RSA-OAEP',
modulusLength: 4096,
modulusLength: keyLength,
publicExponent: new Uint8Array([1, 0, 1]), // 65537
hash: 'SHA-256'
}, true, ['encrypt', 'decrypt'])
Expand Down
3 changes: 2 additions & 1 deletion packages/client/src/encryption/SubscriberKeyExchange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ export class SubscriberKeyExchange {
this.authentication = authentication
this.logger = loggerFactory.createLogger(module)
this.ensureStarted = pOnce(async () => {
this.rsaKeyPair = await RSAKeyPair.create()
// eslint-disable-next-line no-underscore-dangle
this.rsaKeyPair = await RSAKeyPair.create(config.encryption.rsaKeyLength)
const node = await networkNodeFacade.getNode()
node.addMessageListener((msg: StreamMessage) => this.onMessage(msg))
this.logger.debug('Started')
Expand Down
24 changes: 14 additions & 10 deletions packages/client/test/test-utils/fake/FakeEnvironment.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
import { container, DependencyContainer } from 'tsyringe'
import merge from 'lodash/merge'
import { NodeId } from '@streamr/network-node'
import { fastPrivateKey, fastWallet } from '@streamr/test-utils'
import merge from 'lodash/merge'
import { DependencyContainer, container } from 'tsyringe'
import { StreamrClientConfig } from '../../../src/Config'
import { StorageNodeRegistry } from '../../../src/registry/StorageNodeRegistry'
import { NetworkNodeFactory } from '../../../src/NetworkNodeFacade'
import { StreamrClient } from '../../../src/StreamrClient'
import { MIN_KEY_LENGTH } from '../../../src/encryption/RSAKeyPair'
import { StorageNodeRegistry } from '../../../src/registry/StorageNodeRegistry'
import { StreamRegistry } from '../../../src/registry/StreamRegistry'
import { FakeStorageNodeRegistry } from './FakeStorageNodeRegistry'
import { FakeStreamRegistry } from './FakeStreamRegistry'
import { StreamStorageRegistry } from '../../../src/registry/StreamStorageRegistry'
import { FakeStreamStorageRegistry } from './FakeStreamStorageRegistry'
import { FakeNetworkNodeFactory, FakeNetworkNode } from './FakeNetworkNode'
import { NetworkNodeFactory } from '../../../src/NetworkNodeFacade'
import { LoggerFactory } from './../../../src/utils/LoggerFactory'
import { FakeNetwork } from './FakeNetwork'
import { FakeChain } from './FakeChain'
import { FakeLogger } from './FakeLogger'
import { FakeNetwork } from './FakeNetwork'
import { FakeNetworkNode, FakeNetworkNodeFactory } from './FakeNetworkNode'
import { FakeStorageNode } from './FakeStorageNode'
import { NodeId } from '@streamr/network-node'
import { FakeStorageNodeRegistry } from './FakeStorageNodeRegistry'
import { FakeStreamRegistry } from './FakeStreamRegistry'
import { FakeStreamStorageRegistry } from './FakeStreamStorageRegistry'

const DEFAULT_CLIENT_OPTIONS: StreamrClientConfig = {
network: {
trackers: [] // without this setting NetworkNodeFacade would query the tracker addresses from the contract
},
encryption: {
rsaKeyLength: MIN_KEY_LENGTH
},
metrics: false
}

Expand Down
4 changes: 3 additions & 1 deletion packages/client/test/test-utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,9 @@ export const createGroupKeyManager = (
litProtocolEnabled: false,
litProtocolLogging: false,
maxKeyRequestsPerSecond: 10,
keyRequestTimeout: 50
keyRequestTimeout: 50,
// eslint-disable-next-line no-underscore-dangle
rsaKeyLength: CONFIG_TEST.encryption!.rsaKeyLength!
}
},
authentication,
Expand Down
2 changes: 1 addition & 1 deletion packages/client/test/unit/GroupKeyManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('GroupKeyManager', () => {
litProtocolLogging: false,
maxKeyRequestsPerSecond: 10,
keyRequestTimeout: 100
}
} as any
},
createPrivateKeyAuthentication(wallet.privateKey, {} as any),
eventEmitter,
Expand Down
4 changes: 2 additions & 2 deletions packages/client/test/unit/RSAKeyPair.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ describe('RSAKeyPair', () => {
let rsaKeyPair: RSAKeyPair

beforeEach(async () => {
rsaKeyPair = await RSAKeyPair.create()
}, 10000)
rsaKeyPair = await RSAKeyPair.create(512)
})

it('rsa decryption after encryption equals the initial plaintext', () => {
const plaintext = 'some random text'
Expand Down
2 changes: 1 addition & 1 deletion packages/client/test/unit/messagePipeline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ describe('messagePipeline', () => {
litProtocolLogging: false,
keyRequestTimeout: 50,
maxKeyRequestsPerSecond: 0
}
} as any
}
streamRegistryCached = {
getStream: async () => stream,
Expand Down

0 comments on commit 0aae953

Please sign in to comment.