diff --git a/package-lock.json b/package-lock.json index b7c10b5c..a26b3f4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@ethersphere/bee-js": "^6.2.0", "@fairdatasociety/fdp-contracts-js": "^3.8.0", - "crypto-js": "^4.1.1", + "crypto-js": "^4.2.0", "ethers": "^5.5.2", "js-sha3": "^0.8.0" }, @@ -27,7 +27,7 @@ "@jest/test-sequencer": "^29.3.0", "@jest/types": "^29.3.1", "@types/content-disposition": "^0.5.3", - "@types/crypto-js": "^4.1.1", + "@types/crypto-js": "^4.1.3", "@types/debug": "^4.1.5", "@types/elliptic": "^6.4.12", "@types/expect-puppeteer": "^4.4.5", @@ -4105,9 +4105,9 @@ "dev": true }, "node_modules/@types/crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.3.tgz", + "integrity": "sha512-YP1sYYayLe7Eg5oXyLLvOLfxBfZ5Fgpz6sVWkpB18wDMywCLPWmqzRz+9gyuOoLF0fzDTTFwlyNbx7koONUwqA==", "dev": true }, "node_modules/@types/debug": { @@ -5961,9 +5961,9 @@ } }, "node_modules/crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, "node_modules/cwd": { "version": "0.10.0", @@ -16254,9 +16254,9 @@ "dev": true }, "@types/crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.3.tgz", + "integrity": "sha512-YP1sYYayLe7Eg5oXyLLvOLfxBfZ5Fgpz6sVWkpB18wDMywCLPWmqzRz+9gyuOoLF0fzDTTFwlyNbx7koONUwqA==", "dev": true }, "@types/debug": { @@ -17699,9 +17699,9 @@ } }, "crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, "cwd": { "version": "0.10.0", diff --git a/package.json b/package.json index d770327b..5416a03e 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "dependencies": { "@ethersphere/bee-js": "^6.2.0", "@fairdatasociety/fdp-contracts-js": "^3.8.0", - "crypto-js": "^4.1.1", + "crypto-js": "^4.2.0", "ethers": "^5.5.2", "js-sha3": "^0.8.0" }, @@ -71,7 +71,7 @@ "@jest/test-sequencer": "^29.3.0", "@jest/types": "^29.3.1", "@types/content-disposition": "^0.5.3", - "@types/crypto-js": "^4.1.1", + "@types/crypto-js": "^4.1.3", "@types/debug": "^4.1.5", "@types/elliptic": "^6.4.12", "@types/expect-puppeteer": "^4.4.5", diff --git a/src/pod/utils.ts b/src/pod/utils.ts index ce75190b..252fc947 100644 --- a/src/pod/utils.ts +++ b/src/pod/utils.ts @@ -10,7 +10,14 @@ import { PodsList, } from './types' import { Bee, Data, Utils } from '@ethersphere/bee-js' -import { assertMaxLength, bytesToString, stringToBytes, wordArrayToBytes } from '../utils/bytes' +import { + assertAllowedZeroBytes, + assertMaxLength, + bytesToString, + MAX_ZEROS_PERCENTAGE_ALLOWED, + stringToBytes, + wordArrayToBytes, +} from '../utils/bytes' import { utils } from 'ethers' import { getRawDirectoryMetadataBytes } from '../directory/adapter' import { @@ -364,7 +371,10 @@ export function assertPodShareInfo(value: unknown): asserts value is PodShareInf * Generates random password for a pod */ export function getRandomPodPassword(): PodPasswordBytes { - return wordArrayToBytes(CryptoJS.lib.WordArray.random(POD_PASSWORD_LENGTH)) as PodPasswordBytes + const password = wordArrayToBytes(CryptoJS.lib.WordArray.random(POD_PASSWORD_LENGTH)) as PodPasswordBytes + assertAllowedZeroBytes(password, MAX_ZEROS_PERCENTAGE_ALLOWED) + + return password } /** diff --git a/src/shim/crypto.ts b/src/shim/crypto.ts index 8e5869f0..59fba1dc 100644 --- a/src/shim/crypto.ts +++ b/src/shim/crypto.ts @@ -2,7 +2,9 @@ import crypto from 'crypto' import { isNode } from './utils' const getRandomValuesNode = (array: T): T => { - if (!(array instanceof Uint8Array || array instanceof Uint32Array)) { + const isUint32Array = array instanceof Uint32Array + + if (!(array instanceof Uint8Array || isUint32Array)) { throw new TypeError('Expected Uint8Array or Uint32Array') } @@ -13,8 +15,11 @@ const getRandomValuesNode = (array: T): T => { throw e } - const bytes = crypto.randomBytes(array.length) - array.set(bytes) + if (isUint32Array) { + array.set(new Uint32Array(crypto.randomBytes(array.byteLength).buffer)) + } else { + array.set(crypto.randomBytes(array.length)) + } return array } diff --git a/src/utils/bytes.ts b/src/utils/bytes.ts index e18aa3b4..ead2ddd9 100644 --- a/src/utils/bytes.ts +++ b/src/utils/bytes.ts @@ -12,6 +12,11 @@ import CryptoJS from 'crypto-js' export const SPAN_SIZE = 8 +/** + * Max percentage of zero bytes allowed in the array + */ +export const MAX_ZEROS_PERCENTAGE_ALLOWED = 20 + // we limit the maximum span size in 32 bits to avoid BigInt compatibility issues const MAX_SPAN_LENGTH = 2 ** 32 - 1 @@ -135,3 +140,34 @@ export function assertMaxLength(currentLength: number, maxLength: number, custom throw new Error(customMessage ? customMessage : `length ${currentLength} exceeds max length ${maxLength}`) } } + +/** + * Asserts if the percentage of zero bytes in the array is less or equal than allowed + * @param bytes + * @param allowedPercentage + */ +export function assertAllowedZeroBytes(bytes: Uint8Array, allowedPercentage: number): void { + if (!isAllowedZeroBytes(bytes, allowedPercentage)) { + throw new Error( + `bytes contain more than ${allowedPercentage}% of zero bytes. The reason could be a poor source of random numbers.`, + ) + } +} + +/** + * Checks if the percentage of zero bytes in the array is less or equal than allowed + * @param bytes bytes to check + * @param allowedPercentage allowed percentage of zero bytes + */ +export function isAllowedZeroBytes(bytes: Uint8Array, allowedPercentage: number): boolean { + const allowedZeroBytes = Math.floor((bytes.length * allowedPercentage) / 100) + let zeroBytes = 0 + + for (const byte of bytes) { + if (byte === 0) { + zeroBytes++ + } + } + + return zeroBytes <= allowedZeroBytes +} diff --git a/test/unit/pod/utils.spec.ts b/test/unit/pod/utils.spec.ts index fde1aa9e..2d204415 100644 --- a/test/unit/pod/utils.spec.ts +++ b/test/unit/pod/utils.spec.ts @@ -1,6 +1,7 @@ -import { isPod, isSharedPod, MAX_POD_NAME_LENGTH } from '../../../src/pod/utils' +import { getRandomPodPassword, isPod, isSharedPod, MAX_POD_NAME_LENGTH } from '../../../src/pod/utils' import { Utils } from '@ethersphere/bee-js' import { POD_PASSWORD_LENGTH } from '../../../src/utils/encryption' +import { isAllowedZeroBytes } from '../../../src/utils/bytes' describe('pod/utils', () => { it('isSharedPod', () => { @@ -127,4 +128,16 @@ describe('pod/utils', () => { expect(isPod('string')).toBeFalsy() }) }) + + it('getRandomPodPassword', () => { + // created to check https://github.com/fairDataSociety/fdp-storage/issues/212 + expect(isAllowedZeroBytes(new Uint8Array([0, 1, 1, 1, 1, 1, 1, 1, 1, 1]), 10)).toBeTruthy() + expect(isAllowedZeroBytes(new Uint8Array([0, 0, 1, 1, 1, 1, 1, 1, 1, 1]), 10)).toBeFalsy() + expect(isAllowedZeroBytes(new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), 10)).toBeFalsy() + + for (let i = 0; i < 100; i++) { + const password = getRandomPodPassword() + expect(password).toHaveLength(POD_PASSWORD_LENGTH) + } + }) })