diff --git a/keyserver/src/utils/aes-crypto-utils.js b/keyserver/src/utils/aes-crypto-utils.js new file mode 100644 index 000000000..d00fb5c70 --- /dev/null +++ b/keyserver/src/utils/aes-crypto-utils.js @@ -0,0 +1,33 @@ +// @flow + +import crypto from 'crypto'; + +import { + generateKeyCommon, + encryptCommon, + decryptCommon, +} from 'lib/media/aes-crypto-utils-common.js'; + +// crypto.webcrypto was introduced in Node 15.10.0. +// It is not defined in Flow so we need a cast +const commonCrypto: Crypto = (crypto: any).webcrypto; + +function generateKey(): Promise { + return generateKeyCommon(commonCrypto); +} + +function encrypt( + keyBytes: Uint8Array, + plaintext: Uint8Array, +): Promise { + return encryptCommon(commonCrypto, keyBytes, plaintext); +} + +function decrypt( + keyBytes: Uint8Array, + sealedData: Uint8Array, +): Promise { + return decryptCommon(commonCrypto, keyBytes, sealedData); +} + +export { generateKey, encrypt, decrypt }; diff --git a/web/flow-typed/web-crypto.js b/lib/flow-typed/web-crypto-common.js similarity index 98% copy from web/flow-typed/web-crypto.js copy to lib/flow-typed/web-crypto-common.js index 7421100c4..70dd21d90 100644 --- a/web/flow-typed/web-crypto.js +++ b/lib/flow-typed/web-crypto-common.js @@ -1,285 +1,281 @@ // @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/lib/media/aes-crypto-utils-common.js b/lib/media/aes-crypto-utils-common.js new file mode 100644 index 000000000..3f2288caf --- /dev/null +++ b/lib/media/aes-crypto-utils-common.js @@ -0,0 +1,84 @@ +// @flow + +const KEY_SIZE = 32; // bytes +const IV_LENGTH = 12; // bytes - unique Initialization Vector (nonce) +const TAG_LENGTH = 16; // bytes - GCM auth tag + +async function generateKeyCommon(commonCrypto: Crypto): Promise { + const algorithm = { name: 'AES-GCM', length: 256 }; + const key = await commonCrypto.subtle.generateKey(algorithm, true, [ + 'encrypt', + 'decrypt', + ]); + const keyData = await commonCrypto.subtle.exportKey('raw', key); + return new Uint8Array(keyData); +} + +async function encryptCommon( + commonCrypto: Crypto, + keyBytes: Uint8Array, + plaintext: Uint8Array, +): Promise { + if (keyBytes.length !== KEY_SIZE) { + throw new Error('Invalid AES key size'); + } + + // we're creating the buffer now so we can avoid reallocating it later + const outputBuffer = new ArrayBuffer( + plaintext.length + IV_LENGTH + TAG_LENGTH, + ); + const ivBytes = new Uint8Array(outputBuffer, 0, IV_LENGTH); + const iv = commonCrypto.getRandomValues(ivBytes); + + const algorithm = { name: 'AES-GCM', iv: iv, tagLength: TAG_LENGTH * 8 }; + const key = await commonCrypto.subtle.importKey( + 'raw', + keyBytes, + 'AES-GCM', + false, + ['encrypt'], + ); + const ciphertextWithTag = await commonCrypto.subtle.encrypt( + algorithm, + key, + plaintext, + ); + + const result = new Uint8Array(outputBuffer); + result.set(new Uint8Array(ciphertextWithTag), iv.length); + return result; +} + +async function decryptCommon( + commonCrypto: Crypto, + keyBytes: Uint8Array, + sealedData: Uint8Array, +): Promise { + if (keyBytes.length !== KEY_SIZE) { + throw new Error('Invalid AES key size'); + } + if (sealedData.length < IV_LENGTH + TAG_LENGTH) { + throw new Error('Invalid ciphertext size'); + } + + const iv = sealedData.subarray(0, IV_LENGTH); + const ciphertextWithTag = sealedData.subarray(IV_LENGTH); + + const algorithm = { name: 'AES-GCM', iv, tagLength: TAG_LENGTH * 8 }; + const key = await commonCrypto.subtle.importKey( + 'raw', + keyBytes, + 'AES-GCM', + false, + ['decrypt'], + ); + + const plaintextBuffer = await commonCrypto.subtle.decrypt( + algorithm, + key, + ciphertextWithTag, + ); + return new Uint8Array(plaintextBuffer); +} + +export { generateKeyCommon, encryptCommon, decryptCommon }; diff --git a/web/flow-typed/web-crypto.js b/web/flow-typed/web-crypto.js index 7421100c4..e56224a6c 100644 --- a/web/flow-typed/web-crypto.js +++ b/web/flow-typed/web-crypto.js @@ -1,285 +1,5 @@ // @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;