Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: enhance pod password generation with zero byte check and update crypto-js to 4.2.0 #281

Merged
merged 1 commit into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand All @@ -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",
Expand Down
14 changes: 12 additions & 2 deletions src/pod/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}

/**
Expand Down
11 changes: 8 additions & 3 deletions src/shim/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import crypto from 'crypto'
import { isNode } from './utils'

const getRandomValuesNode = <T extends ArrayBufferView | null>(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')
}

Expand All @@ -13,8 +15,11 @@ const getRandomValuesNode = <T extends ArrayBufferView | null>(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
}
Expand Down
36 changes: 36 additions & 0 deletions src/utils/bytes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
}
15 changes: 14 additions & 1 deletion test/unit/pod/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -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)
}
})
})
Loading