diff --git a/web/flow-typed/WebCrypto.js b/web/flow-typed/WebCrypto.js new file mode 100644 index 000000000..7421100c4 --- /dev/null +++ b/web/flow-typed/WebCrypto.js @@ -0,0 +1,285 @@ +// @flow + +declare interface RandomSource { + getRandomValues(typedArray: T): T; + randomUUID(): string; +} + +declare interface Crypto extends RandomSource { + +subtle: SubtleCrypto; +} + +type CryptoKey$Type = 'secret' | 'public' | 'private'; +type CryptoKey$Usages = + | 'encrypt' + | 'decrypt' + | 'sign' + | 'verify' + | 'deriveKey' + | 'deriveBits' + | 'wrapKey' + | 'unwrapKey'; + +declare type CryptoKey = { + +algorithm: + | SubtleCrypto$AesKeyGenParams + | SubtleCrypto$RsaHashedKeyGenParams + | SubtleCrypto$EcKeyGenParams + | SubtleCrypto$HmacKeyGenParams, + +extractable: boolean, + +type: CryptoKey$Type, + +usages: $ReadOnlyArray, +}; + +type SubtleCrypto$KeyFormatWithoutJwk = 'pkcs8' | 'raw' | 'spki'; +type SubtleCrypto$KeyFormat = 'jwk' | SubtleCrypto$KeyFormatWithoutJwk; +type SubtleCrypto$HashAlgo = 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512'; +type SubtleCrypto$AesAlgo = 'AES-CBC' | 'AES-CTR' | 'AES-GCM' | 'AES-KW'; + +type SubtleCrypto$RsaOaepParams = { + +name: 'RSA-OAEP', + +label?: BufferSource, +}; + +type SubtleCrypto$AesCtrParams = { + +name: 'AES-CTR', + +counter: BufferSource, + +length: number, +}; + +type SubtleCrypto$AesCbcParams = { + +name: 'AES-CBC', + +iv: BufferSource, +}; + +type SubtleCrypto$AesGcmParams = { + +name: 'AES-GCM', + +iv: BufferSource, + +additionalData?: BufferSource, + +tagLength?: number, +}; + +type SubtleCrypto$EcdhKeyDeriveParams = { + +name: 'ECDH', + +public: CryptoKey, +}; + +type SubtleCrypto$HkdfParams = { + +name: 'HKDF', + +hash: 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512', + +info: BufferSource, + +salt: BufferSource, +}; + +type SubtleCrypto$Pbkdf2Params = { + +name: 'PBKDF2', + +hash: 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512', + +iterations: number, + +salt: BufferSource, +}; + +type SubtleCrypto$HmacImportParams = { + +name: 'HMAC', + +hash: 'SHA-256' | 'SHA-384' | 'SHA-512', + +length?: number, +}; + +type SubtleCrypto$RsaHashedKeyGenParams = { + +name: 'RSASSA-PKCS1-v1_5' | 'RSA-PSS' | 'RSA-OAEP', + +modulusLength: number, + +publicExponent: Uint8Array, + +hash: 'SHA-256' | 'SHA-384' | 'SHA-512', +}; + +type SubtleCrypto$HmacKeyGenParams = { + +name: 'HMAC', + +hash: SubtleCrypto$HashAlgo, + +length?: number, +}; + +type SubtleCrypto$RsaHashedImportParams = { + +name: 'RSASSA-PKCS1-v1_5' | 'RSA-PSS' | 'RSA-OAEP', + +hash: 'SHA-256' | 'SHA-384' | 'SHA-512', +}; + +type SubtleCrypto$EcKeyImportParams = { + +name: 'ECDSA' | 'ECDH', + +namedCurve: 'P-256' | 'P-384' | 'P-521', +}; + +type SubtleCrypto$EcKeyGenParams = { + +name: 'ECDSA' | 'ECDH', + +namedCurve: 'P-256' | 'P-384' | 'P-521', +}; + +type SubtleCrypto$RsaPssParams = { + +name: 'RSA-PSS', + +saltLength: number, +}; + +type SubtleCrypto$EcdsaParams = { + +name: 'ECDSA', + +hash: 'SHA-256' | 'SHA-384' | 'SHA-512', +}; + +type SubtleCrypto$AesKeyGenParams = { + +name: 'AES-CBC' | 'AES-CTR' | 'AES-GCM' | 'AES-KW', + +length: 128 | 192 | 256, +}; + +type SubtleCrypto$ImportKeyAlgo = + | SubtleCrypto$RsaHashedImportParams + | SubtleCrypto$EcKeyImportParams + | SubtleCrypto$HmacImportParams + | SubtleCrypto$AesAlgo + | 'PBKDF2' + | 'HKDF'; + +type SubtleCrypto$RsaOtherPrimesInfo = { + +d?: string, + +r?: string, + +t?: string, +}; + +type SubtleCrypto$JsonWebKey = { + +alg?: string, + +crv?: string, + +d?: string, + +dp?: string, + +dq?: string, + +e?: string, + +ext?: boolean, + +k?: string, + +key_ops?: $ReadOnlyArray, + +kty?: string, + +n?: string, + +oth?: $ReadOnlyArray, + +p?: string, + +q?: string, + +qi?: string, + +use?: string, + +x?: string, + +y?: string, +}; + +declare interface SubtleCrypto { + decrypt( + algorithm: + | SubtleCrypto$RsaOaepParams + | SubtleCrypto$AesCtrParams + | SubtleCrypto$AesCbcParams + | SubtleCrypto$AesGcmParams, + key: CryptoKey, + data: BufferSource, + ): Promise; + deriveBits( + algorithm: + | SubtleCrypto$EcdhKeyDeriveParams + | SubtleCrypto$HkdfParams + | SubtleCrypto$Pbkdf2Params, + baseKey: CryptoKey, + length: number, + ): Promise; + deriveKey( + algorithm: + | SubtleCrypto$EcdhKeyDeriveParams + | SubtleCrypto$HkdfParams + | SubtleCrypto$Pbkdf2Params, + baseKey: CryptoKey, + derivedKeyType: + | SubtleCrypto$HmacKeyGenParams + | SubtleCrypto$AesKeyGenParams, + extractable: boolean, + keyUsages: $ReadOnlyArray, + ): Promise; + digest( + algorithm: SubtleCrypto$HashAlgo | { +name: SubtleCrypto$HashAlgo }, + data: BufferSource, + ): Promise; + encrypt( + algorithm: + | SubtleCrypto$RsaOaepParams + | SubtleCrypto$AesCtrParams + | SubtleCrypto$AesCbcParams + | SubtleCrypto$AesGcmParams, + key: CryptoKey, + data: BufferSource, + ): Promise; + exportKey(format: 'jwk', key: CryptoKey): Promise; + exportKey( + format: SubtleCrypto$KeyFormatWithoutJwk, + key: CryptoKey, + ): Promise; + generateKey( + algorithm: + | SubtleCrypto$RsaHashedKeyGenParams + | SubtleCrypto$EcKeyGenParams + | SubtleCrypto$HmacKeyGenParams + | SubtleCrypto$AesKeyGenParams, + extractable: boolean, + keyUsages: $ReadOnlyArray, + ): Promise; + importKey( + format: SubtleCrypto$KeyFormatWithoutJwk, + keyData: BufferSource, + algorithm: SubtleCrypto$ImportKeyAlgo, + extractable: boolean, + keyUsages: $ReadOnlyArray, + ): Promise; + importKey( + format: 'jwk', + keyData: SubtleCrypto$JsonWebKey, + algorithm: SubtleCrypto$ImportKeyAlgo, + extractable: boolean, + keyUsages: $ReadOnlyArray, + ): Promise; + + sign( + algorithm: + | 'RSASSA-PKCS1-v1_5' + | 'HMAC' + | SubtleCrypto$RsaPssParams + | SubtleCrypto$EcdsaParams, + key: CryptoKey, + data: BufferSource, + ): Promise; + unwrapKey( + format: SubtleCrypto$KeyFormat, + wrappedKey: ArrayBuffer, + unwrappingKey: CryptoKey, + unwrapAlgorithm: + | SubtleCrypto$RsaOaepParams + | SubtleCrypto$AesCtrParams + | SubtleCrypto$AesCbcParams + | SubtleCrypto$AesGcmParams + | 'AES-KW', + unwrappedKeyAlgorithm: SubtleCrypto$ImportKeyAlgo, + extractable: boolean, + keyUsages: $ReadOnlyArray, + ): Promise; + verify( + algorithm: + | SubtleCrypto$RsaPssParams + | SubtleCrypto$EcdsaParams + | 'RSASSA-PKCS1-v1_5' + | 'HMAC', + key: CryptoKey, + signature: ArrayBuffer, + data: ArrayBuffer, + ): Promise; + wrapKey( + format: SubtleCrypto$KeyFormat, + key: CryptoKey, + wrappingKey: CryptoKey, + wrapAlgorithm: + | SubtleCrypto$RsaOaepParams + | SubtleCrypto$AesCtrParams + | SubtleCrypto$AesCbcParams + | SubtleCrypto$AesGcmParams + | 'AES-KW', + ): Promise; +} + +declare var crypto: Crypto; +declare var msCrypto: Crypto; +declare var webkitCrypto: Crypto; diff --git a/web/utils/text-utils.js b/web/utils/text-utils.js index 9366746db..4af9be9f8 100644 --- a/web/utils/text-utils.js +++ b/web/utils/text-utils.js @@ -1,66 +1,61 @@ // @flow import invariant from 'invariant'; let canvas; -declare var crypto: { - getRandomValues(typedArray: NumArray): NumArray, - ... -}; - function calculateMaxTextWidth( texts: $ReadOnlyArray, font: string, ): number { if (!canvas) { canvas = document.createElement('canvas'); } const context = canvas.getContext('2d'); context.font = font; const widths = texts.map(text => context.measureText(text).width); return Math.max(...widths); } const numberOfPossibleByteValues = 256; function generateRandomString(length: number, availableSigns: string): string { invariant(length >= 0, 'length must be non-negative'); invariant( availableSigns !== '' || length === 0, "cannot create a random string of non-zero length from availableSigns = ''", ); invariant( numberOfPossibleByteValues >= availableSigns.length, `The number of available signs must not exceed ${numberOfPossibleByteValues}`, ); const validByteUpperBound = availableSigns.length * Math.floor(numberOfPossibleByteValues / availableSigns.length); // Generating more bytes than the required length, // proportionally to how many values will be omitted // due to uniformness requirement, // to lower the chances of having to draw again const drawBytes = Math.floor( length * (1 + 2 * (1 - validByteUpperBound / numberOfPossibleByteValues)), ); let str = ''; while (str.length < length) { const rand = new Uint8Array(drawBytes); crypto.getRandomValues(rand); for (let i = 0; str.length < length && i < drawBytes; i++) { if (rand[i] < validByteUpperBound) { const index = rand[i] % availableSigns.length; str += availableSigns.charAt(index); } } } return str; } export { calculateMaxTextWidth, generateRandomString };