diff --git a/keyserver/src/utils/aes-crypto-utils.js b/keyserver/src/utils/aes-crypto-utils.js new file mode 100644 --- /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 copy from web/flow-typed/web-crypto.js copy to lib/flow-typed/web-crypto-common.js --- a/web/flow-typed/web-crypto.js +++ b/lib/flow-typed/web-crypto-common.js @@ -279,7 +279,3 @@ | '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 --- /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 --- 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;