UNPKG

@reclaimprotocol/tls

Version:

WebCrypto Based Cross Platform TLS

167 lines (166 loc) 7.48 kB
import { crypto } from "../crypto/index.js"; import { SUPPORTED_CIPHER_SUITE_MAP } from "./constants.js"; import { asciiToUint8Array, concatenateUint8Arrays, hkdfExpand, isSymmetricCipher, uint8ArrayToDataView } from "./generics.js"; import { packWithLength } from "./packets.js"; const TLS1_2_BASE_SEED = asciiToUint8Array('master secret'); const TLS1_2_KEY_EXPANSION_SEED = asciiToUint8Array('key expansion'); export async function computeSharedKeysTls12(opts) { const { clientRandom, serverRandom, cipherSuite, } = opts; const masterSecret = await generateMasterSecret(opts); // all key derivation in TLS 1.2 uses SHA-256 const hashAlgorithm = getPrfHashAlgorithm(cipherSuite); const { keyLength, cipher, hashLength, hashAlgorithm: cipherHashAlg, ivLength } = SUPPORTED_CIPHER_SUITE_MAP[cipherSuite]; const masterKey = await crypto .importKey(hashAlgorithm, masterSecret); const seed = concatenateUint8Arrays([ TLS1_2_KEY_EXPANSION_SEED, serverRandom, clientRandom, ]); const expandedSecretArr = []; let lastSeed = seed; for (let i = 0; i < 4; i++) { lastSeed = await crypto.hmac(hashAlgorithm, masterKey, lastSeed); const expandedSecret = await crypto.hmac(hashAlgorithm, masterKey, concatenateUint8Arrays([ lastSeed, seed ])); expandedSecretArr.push(expandedSecret); } let expandedSecret = concatenateUint8Arrays(expandedSecretArr); const needsMac = isSymmetricCipher(cipher); const clientMacKey = needsMac ? await crypto.importKey(cipherHashAlg, readExpandedSecret(hashLength)) : undefined; const serverMacKey = needsMac ? await crypto.importKey(cipherHashAlg, readExpandedSecret(hashLength)) : undefined; const clientEncKey = await crypto.importKey(cipher, readExpandedSecret(keyLength)); const serverEncKey = await crypto.importKey(cipher, readExpandedSecret(keyLength)); const clientIv = readExpandedSecret(ivLength); const serverIv = readExpandedSecret(ivLength); return { type: 'TLS1_2', masterSecret, clientMacKey, serverMacKey, clientEncKey, serverEncKey, clientIv, serverIv, serverSecret: masterSecret, clientSecret: masterSecret, }; function readExpandedSecret(len) { const returnVal = expandedSecret .slice(0, len); expandedSecret = expandedSecret .slice(len); return returnVal; } } async function generateMasterSecret({ preMasterSecret, clientRandom, serverRandom, cipherSuite }) { // all key derivation in TLS 1.2 uses SHA-256 const hashAlgorithm = getPrfHashAlgorithm(cipherSuite); const preMasterKey = await crypto .importKey(hashAlgorithm, preMasterSecret); const seed = concatenateUint8Arrays([ TLS1_2_BASE_SEED, clientRandom, serverRandom ]); const a1 = await crypto.hmac(hashAlgorithm, preMasterKey, seed); const a2 = await crypto.hmac(hashAlgorithm, preMasterKey, a1); const p1 = await crypto.hmac(hashAlgorithm, preMasterKey, concatenateUint8Arrays([ a1, seed ])); const p2 = await crypto.hmac(hashAlgorithm, preMasterKey, concatenateUint8Arrays([ a2, seed ])); return concatenateUint8Arrays([p1, p2]) .slice(0, 48); } export function computeUpdatedTrafficMasterSecret(masterSecret, cipherSuite) { const { hashAlgorithm, hashLength } = SUPPORTED_CIPHER_SUITE_MAP[cipherSuite]; return hkdfExtractAndExpandLabel(hashAlgorithm, masterSecret, 'traffic upd', new Uint8Array(), hashLength); } export async function computeSharedKeys({ hellos, masterSecret: masterKey, cipherSuite, secretType, earlySecret }) { const { hashAlgorithm, hashLength } = SUPPORTED_CIPHER_SUITE_MAP[cipherSuite]; const emptyHash = await crypto.hash(hashAlgorithm, new Uint8Array()); const zeros = new Uint8Array(hashLength); let handshakeTrafficSecret; if (secretType === 'hs') { // some hashes earlySecret = earlySecret || await crypto.extract(hashAlgorithm, hashLength, zeros, ''); const derivedSecret = await hkdfExtractAndExpandLabel(hashAlgorithm, earlySecret, 'derived', emptyHash, hashLength); handshakeTrafficSecret = await crypto.extract(hashAlgorithm, hashLength, masterKey, derivedSecret); } else { const derivedSecret = await hkdfExtractAndExpandLabel(hashAlgorithm, masterKey, 'derived', emptyHash, hashLength); handshakeTrafficSecret = await crypto.extract(hashAlgorithm, hashLength, zeros, derivedSecret); } return deriveTrafficKeys({ hellos, cipherSuite, masterSecret: handshakeTrafficSecret, secretType }); } export async function deriveTrafficKeys({ masterSecret, cipherSuite, hellos, secretType, }) { const { hashAlgorithm, hashLength } = SUPPORTED_CIPHER_SUITE_MAP[cipherSuite]; const handshakeHash = await getHash(hellos, cipherSuite); const clientSecret = await hkdfExtractAndExpandLabel(hashAlgorithm, masterSecret, `c ${secretType} traffic`, handshakeHash, hashLength); const serverSecret = await hkdfExtractAndExpandLabel(hashAlgorithm, masterSecret, `s ${secretType} traffic`, handshakeHash, hashLength); const { encKey: clientEncKey, iv: clientIv } = await deriveTrafficKeysForSide(clientSecret, cipherSuite); const { encKey: serverEncKey, iv: serverIv } = await deriveTrafficKeysForSide(serverSecret, cipherSuite); return { type: 'TLS1_3', masterSecret, clientSecret, serverSecret, clientEncKey, serverEncKey, clientIv, serverIv, }; } export async function deriveTrafficKeysForSide(masterSecret, cipherSuite) { const { hashAlgorithm, keyLength, cipher, ivLength } = SUPPORTED_CIPHER_SUITE_MAP[cipherSuite]; const encKey = await hkdfExtractAndExpandLabel(hashAlgorithm, masterSecret, 'key', new Uint8Array(), keyLength); const iv = await hkdfExtractAndExpandLabel(hashAlgorithm, masterSecret, 'iv', new Uint8Array(0), ivLength); return { masterSecret, encKey: await crypto.importKey(cipher, encKey), iv }; } export async function hkdfExtractAndExpandLabel(algorithm, secret, label, context, length) { const tmpLabel = `tls13 ${label}`; const lengthBuffer = new Uint8Array(2); const lengthBufferView = uint8ArrayToDataView(lengthBuffer); lengthBufferView.setUint16(0, length); const hkdfLabel = concatenateUint8Arrays([ lengthBuffer, packWithLength(asciiToUint8Array(tmpLabel)).slice(1), packWithLength(context).slice(1) ]); const key = await crypto.importKey(algorithm, secret); return hkdfExpand(algorithm, length, key, length, hkdfLabel, crypto); } export async function getHash(msgs, cipherSuite) { if (Array.isArray(msgs) && !(msgs instanceof Uint8Array)) { const { hashAlgorithm } = SUPPORTED_CIPHER_SUITE_MAP[cipherSuite]; return crypto.hash(hashAlgorithm, concatenateUint8Arrays(msgs)); } return msgs; } /** * Get the PRF algorithm for the given cipher suite * Relevant for TLS 1.2 */ export function getPrfHashAlgorithm(cipherSuite) { const opts = SUPPORTED_CIPHER_SUITE_MAP[cipherSuite]; // all key derivation in TLS 1.2 uses min SHA-256 return ('prfHashAlgorithm' in opts) ? opts.prfHashAlgorithm : opts.hashAlgorithm; }