{"version":3,"file":"c_security_crypto.js","sources":["../../../../metaserver/static/js/security/crypto.ts"],"sourcesContent":["/*\n * This file is for cryptography on the client that does not depend on sjcl.\n *\n * NOTE: Client-side cryptography is generally discouraged as most cryptography should\n * be done server-side. However, there are some cases it is useful on client side,\n * so in those cases please consider running it by ask-security@.\n */\nimport * as Browser from 'js/browser/browser_detection';\nimport {concatenateArrayBuffers} from 'metaserver/static/js/security/util';\nimport type {CryptoWrapper} from 'metaserver/static/js/security/crypto_wrapper_types';\n\n// can't easily use a class to extend Error directly because of:\n// https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work\nexport interface BrowserNotSupportedError extends Error {\n isBrowserNotSupported?: boolean;\n}\n\nfunction makeBrowserNotSupportedError(message: string): BrowserNotSupportedError {\n const err: BrowserNotSupportedError = new Error(message);\n err.isBrowserNotSupported = true;\n return err;\n}\n\nasync function cryptoWrapper(): Promise\u003cCryptoWrapper\u003e {\n const isLegacyEdge = Browser.edge \u0026\u0026 !Browser.edgeChromium();\n if (\n window.crypto.subtle !== undefined \u0026\u0026\n // legacy edge has incompatibilities handled by a shim below\n !isLegacyEdge \u0026\u0026\n // chrome 40 and below has incompatibilities that we don't support\n !Browser.checkBrowserVersion(Browser.chrome, 40, false /* greaterThan=false */)\n ) {\n return window.crypto;\n }\n\n // shim for legacy Edge\n if (isLegacyEdge) {\n const {legacyEdgeWrapper} = await import('metaserver/static/js/security/legacy_edge_shim');\n const wrapper = legacyEdgeWrapper();\n if (wrapper !== undefined) {\n return wrapper;\n }\n }\n\n throw makeBrowserNotSupportedError('WebCrypto not supported');\n}\n\n// This function currently has minimal unit tests since jest does not support WebCrypto\nexport async function encryptWithPublicKey(\n publicKey: Uint8Array,\n message: Uint8Array,\n version: number\n): Promise\u003cUint8Array\u003e {\n // Encrypts a message with a public key using RSA-OAEP. Supports arbitrary length input\n // by using AES-GCM with a generated and wrapped key internally.\n // Uses SubtleCrypto web API for all low-level cryptography, and is compatible with the\n // Python server version.\n //\n //\n // Parameters:\n // - publicKey: the RSA-OAEP public key\n // - message: the message to encrypt\n // - version: the version of the RSA-OAEP public key\n return cryptoWrapper().then(async (crypto) =\u003e {\n const iv = crypto.getRandomValues(new Uint8Array(12));\n\n const aesGcmKey = await crypto.subtle.generateKey(\n {\n name: 'AES-GCM',\n length: 128,\n },\n true,\n ['encrypt', 'decrypt']\n );\n\n const encryptedMessage = await crypto.subtle.encrypt(\n {\n name: 'AES-GCM',\n iv: iv,\n },\n aesGcmKey,\n message\n );\n\n // \"raw\" format is the raw bytes of the key, which is what server Python decryption expects\n const aesGcmKeyBytes = await crypto.subtle.exportKey('raw', aesGcmKey);\n\n const publicKeyObj = await crypto.subtle.importKey(\n 'spki',\n publicKey,\n {\n name: 'RSA-OAEP',\n hash: 'SHA-256',\n },\n true,\n ['encrypt']\n );\n\n const encryptedAesGcmKey = await crypto.subtle.encrypt(\n {\n name: 'RSA-OAEP',\n },\n publicKeyObj,\n aesGcmKeyBytes\n );\n\n const versionBuffer = new ArrayBuffer(2);\n const versionView = new DataView(versionBuffer);\n // note: always big endian\n versionView.setInt16(0, version);\n return concatenateArrayBuffers(versionBuffer, encryptedAesGcmKey, iv, encryptedMessage);\n });\n}\n\n// This function currently has minimal unit tests since jest does not support WebCrypto\nexport async function hmacMessage(key: Uint8Array, message: Uint8Array): Promise\u003cUint8Array\u003e {\n // Authenticates a message with a key using HMAC-SHA256.\n // Uses SubtleCrypto web API for all low-level cryptography, and is compatible with the\n // Python server function \"csrf_hmac_urltoken\"\n //\n //\n // Parameters:\n // - key: the HMAC-SHA256 secret key\n // - message: the message to encrypt\n const crypto = await cryptoWrapper();\n const keyObj = await crypto.subtle.importKey(\n 'raw',\n key,\n {\n name: 'HMAC',\n hash: 'SHA-256',\n },\n true,\n ['sign']\n );\n\n const hmac = await crypto.subtle.sign(\n {\n name: 'HMAC',\n },\n keyObj,\n message\n );\n return new Uint8Array(hmac);\n}\n\nexport async function hashSHA256(data: ArrayBuffer): Promise\u003cUint8Array\u003e {\n const crypto = await cryptoWrapper();\n\n const encrypted = await crypto.subtle.digest('SHA-256', data);\n\n return new Uint8Array(encrypted);\n}\n\nexport async function verifyMessageHmac(\n key: Uint8Array,\n message: Uint8Array,\n signature: Uint8Array\n): Promise\u003cboolean\u003e {\n const crypto = await cryptoWrapper();\n\n const keyObj = await crypto.subtle.importKey('raw', key, {name: 'HMAC', hash: 'SHA-256'}, true, [\n 'verify',\n ]);\n\n return await crypto.subtle.verify({name: 'HMAC'}, keyObj, signature, message);\n}\n\nexport async function getRandomValues(buffer: Uint8Array): Promise\u003cUint8Array\u003e {\n const crypto = await cryptoWrapper();\n\n crypto.getRandomValues(buffer);\n\n return buffer;\n}\n"],"names":["async","cryptoWrapper","isLegacyEdge","Browser.edge","Browser.edgeChromium","undefined","window","crypto","subtle","Browser.checkBrowserVersion","Browser.chrome","legacyEdgeWrapper","Promise","resolve","reject","require","wrapper","message","err","Error","isBrowserNotSupported","makeBrowserNotSupportedError","publicKey","version","then","iv","getRandomValues","Uint8Array","aesGcmKey","generateKey","name","length","encryptedMessage","encrypt","aesGcmKeyBytes","exportKey","publicKeyObj","importKey","hash","encryptedAesGcmKey","versionBuffer","ArrayBuffer","DataView","setInt16","concatenateArrayBuffers","key","signature","keyObj","verify"],"mappings":";8GAuBAA,eAAeC,IACb,MAAMC,EAAeC,EAAAA,OAAiBC,EAAAA,eACtC,QAC2BC,IAAzBC,OAAOC,OAAOC,SAEbN,IAEAO,sBAA4BC,EAAAA,OAAgB,IAAI,GAEjD,OAAOJ,OAAOC,OAIhB,GAAIL,EAAc,CAChB,MAAMS,kBAACA,SAA2B,IAAAC,SAAA,SAAAC,EAAAC,GAAAC,EAAA,CAAO,iCAAgDF,EAAAC,EAAA,IACnFE,EAAUL,IAChB,QAAgBN,IAAZW,EACF,OAAOA,CAEV,CAED,MA3BF,SAAsCC,GACpC,MAAMC,EAAgC,IAAIC,MAAMF,GAEhD,OADAC,EAAIE,uBAAwB,EACrBF,CACT,CAuBQG,CAA6B,0BACrC,wBAGOrB,eACLsB,EACAL,EACAM,GAYA,OAAOtB,IAAgBuB,MAAKxB,MAAOO,IACjC,MAAMkB,EAAKlB,EAAOmB,gBAAgB,IAAIC,WAAW,KAE3CC,QAAkBrB,EAAOC,OAAOqB,YACpC,CACEC,KAAM,UACNC,OAAQ,MAEV,EACA,CAAC,UAAW,YAGRC,QAAyBzB,EAAOC,OAAOyB,QAC3C,CACEH,KAAM,UACNL,GAAIA,GAENG,EACAX,GAIIiB,QAAuB3B,EAAOC,OAAO2B,UAAU,MAAOP,GAEtDQ,QAAqB7B,EAAOC,OAAO6B,UACvC,OACAf,EACA,CACEQ,KAAM,WACNQ,KAAM,YAER,EACA,CAAC,YAGGC,QAA2BhC,EAAOC,OAAOyB,QAC7C,CACEH,KAAM,YAERM,EACAF,GAGIM,EAAgB,IAAIC,YAAY,GAItC,OAHoB,IAAIC,SAASF,GAErBG,SAAS,EAAGpB,GACjBqB,EAAuBA,wBAACJ,EAAeD,EAAoBd,EAAIO,EAAiB,GAE3F,sBA0COhC,eACL6C,EACA5B,EACA6B,GAEA,MAAMvC,QAAeN,IAEf8C,QAAexC,EAAOC,OAAO6B,UAAU,MAAOQ,EAAK,CAACf,KAAM,OAAQQ,KAAM,YAAY,EAAM,CAC9F,WAGF,aAAa/B,EAAOC,OAAOwC,OAAO,CAAClB,KAAM,QAASiB,EAAQD,EAAW7B,EACvE","debugId":"0bf6c664-62ac-34b6-8851-d2b4a9bf741c","debug_id":"0bf6c664-62ac-34b6-8851-d2b4a9bf741c"}