lemon-tls
Version:
JavaScript TLS 1.3/1.2 implementation for Node.js, with full control over cryptographic keys and record layer
391 lines (342 loc) • 11 kB
JavaScript
import {
concatUint8Arrays
} from './utils.js';
import { hmac as nobleHmac } from '@noble/hashes/hmac.js';
import { hkdf, extract as hkdf_extract_noble, expand as hkdf_expand_noble } from '@noble/hashes/hkdf.js';
import { sha256, sha384 } from '@noble/hashes/sha2.js';
import { p256 } from '@noble/curves/nist.js';
import { ed25519, x25519 } from '@noble/curves/ed25519.js';
var nobleHashes = {
hmac: nobleHmac,
hkdf: hkdf,
hkdf_extract: hkdf_extract_noble,
hkdf_expand: hkdf_expand_noble,
sha256: sha256,
};
var TLS_CIPHER_SUITES = {
// ----------------------
// TLS 1.3 (RFC 8446)
// ----------------------
0x1301: { // TLS_AES_128_GCM_SHA256
tls: 13,
kex: 'TLS13',
sig: 'TLS13',
cipher: 'AES_128_GCM',
aead: true,
keylen: 16,
ivlen: 12,
hash: 'sha256'
},
0x1302: { // TLS_AES_256_GCM_SHA384
tls: 13,
kex: 'TLS13',
sig: 'TLS13',
cipher: 'AES_256_GCM',
aead: true,
keylen: 32,
ivlen: 12,
hash: 'sha384'
},
0x1303: { // TLS_CHACHA20_POLY1305_SHA256
tls: 13,
kex: 'TLS13',
sig: 'TLS13',
cipher: 'CHACHA20_POLY1305',
aead: true,
keylen: 32,
ivlen: 12,
hash: 'sha256'
},
// ----------------------
// TLS 1.2 AEAD (GCM / CHACHA20)
// ----------------------
0xC02F: { // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
tls: 12,
kex: 'ECDHE_RSA',
sig: 'RSA',
cipher: 'AES_128_GCM',
aead: true,
keylen: 16,
fixed_ivlen: 4,
record_ivlen: 8,
ivlen: 12,
hash: 'sha256'
},
0xC030: { // TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
tls: 12,
kex: 'ECDHE_RSA',
sig: 'RSA',
cipher: 'AES_256_GCM',
aead: true,
keylen: 32,
fixed_ivlen: 4,
record_ivlen: 8,
ivlen: 12,
hash: 'sha384'
},
0xC02B: { // TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
tls: 12,
kex: 'ECDHE_ECDSA',
sig: 'ECDSA',
cipher: 'AES_128_GCM',
aead: true,
keylen: 16,
fixed_ivlen: 4,
record_ivlen: 8,
ivlen: 12,
hash: 'sha256'
},
0xC02C: { // TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
tls: 12,
kex: 'ECDHE_ECDSA',
sig: 'ECDSA',
cipher: 'AES_256_GCM',
aead: true,
keylen: 32,
fixed_ivlen: 4,
record_ivlen: 8,
ivlen: 12,
hash: 'sha384'
},
0xCCA8: { // TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
tls: 12,
kex: 'ECDHE_RSA',
sig: 'RSA',
cipher: 'CHACHA20_POLY1305',
aead: true,
keylen: 32,
fixed_ivlen: 4,
record_ivlen: 8,
ivlen: 12,
hash: 'sha256'
},
0xCCA9: { // TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
tls: 12,
kex: 'ECDHE_ECDSA',
sig: 'ECDSA',
cipher: 'CHACHA20_POLY1305',
aead: true,
keylen: 32,
fixed_ivlen: 4,
record_ivlen: 8,
ivlen: 12,
hash: 'sha256'
},
0xCCAA: { // TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256
tls: 12,
kex: 'DHE_RSA',
sig: 'RSA',
cipher: 'CHACHA20_POLY1305',
aead: true,
keylen: 32,
fixed_ivlen: 4,
record_ivlen: 8,
ivlen: 12,
hash: 'sha256'
},
0x009C: { // TLS_RSA_WITH_AES_128_GCM_SHA256
tls: 12,
kex: 'RSA',
sig: 'RSA',
cipher: 'AES_128_GCM',
aead: true,
keylen: 16,
fixed_ivlen: 4,
record_ivlen: 8,
ivlen: 12,
hash: 'sha256'
},
0x009D: { // TLS_RSA_WITH_AES_256_GCM_SHA384
tls: 12,
kex: 'RSA',
sig: 'RSA',
cipher: 'AES_256_GCM',
aead: true,
keylen: 32,
fixed_ivlen: 4,
record_ivlen: 8,
ivlen: 12,
hash: 'sha384'
},
// ----------------------
// TLS 1.2 CBC (Legacy)
// ----------------------
0xC013: { // TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
tls: 12,
kex: 'ECDHE_RSA',
sig: 'RSA',
cipher: 'AES_128_CBC',
aead: false,
keylen: 16,
ivlen: 16,
mac: 'sha1',
maclen: 20,
hash: 'sha256'
},
0xC014: { // TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
tls: 12,
kex: 'ECDHE_RSA',
sig: 'RSA',
cipher: 'AES_256_CBC',
aead: false,
keylen: 32,
ivlen: 16,
mac: 'sha1',
maclen: 20,
hash: 'sha256'
},
0x003C: { // TLS_RSA_WITH_AES_128_CBC_SHA256
tls: 12,
kex: 'RSA',
sig: 'RSA',
cipher: 'AES_128_CBC',
aead: false,
keylen: 16,
ivlen: 16,
mac: 'sha256',
maclen: 32,
hash: 'sha256'
},
0x003D: { // TLS_RSA_WITH_AES_256_CBC_SHA256
tls: 12,
kex: 'RSA',
sig: 'RSA',
cipher: 'AES_256_CBC',
aead: false,
keylen: 32,
ivlen: 16,
mac: 'sha256',
maclen: 32,
hash: 'sha256'
}
};
// --- Hash helpers: מקבלים מחרוזת ומחזירים פונקציית hash + outputLen ---
function getHashFn(hashName) {
if (hashName === 'sha256') return sha256;
if (hashName === 'sha384') return sha384;
throw new Error('Unsupported hash: ' + hashName);
}
function getHashLen(hashName) {
var fn = getHashFn(hashName);
return fn.outputLen|0;
}
// --- HMAC (עם noble) ---
function hmac(hashName, keyU8, dataU8) {
var hashFn = getHashFn(hashName);
return nobleHmac(hashFn, keyU8, dataU8); // Uint8Array
}
// --- HKDF wrappers (hash כמחרוזת) ---
function hkdf_extract(hashName, saltU8, ikmU8) {
var hashFn = getHashFn(hashName);
// extract(hash, ikm, salt?)
return hkdf_extract_noble(hashFn, ikmU8, saltU8);
}
function hkdf_expand(hashName, prkU8, infoU8, length) {
var hashFn = getHashFn(hashName);
// expand(hash, prk, info, length)
return hkdf_expand_noble(hashFn, prkU8, infoU8, length|0);
}
// --- TLS 1.3 label builder ---
function build_hkdf_label(label, context, length) {
var prefix = 'tls13 ';
var enc = new TextEncoder();
var full = enc.encode(prefix + label);
var info = new Uint8Array(2 + 1 + full.length + 1 + context.length);
// length (2 bytes BE)
info[0] = (length >>> 8) & 0xff;
info[1] = (length ) & 0xff;
// label
info[2] = full.length;
info.set(full, 3);
// context
var ofs = 3 + full.length;
info[ofs] = context.length;
info.set(context, ofs + 1);
return info;
}
function hkdf_expand_label(hashName, secret, label, context, length) {
var info = build_hkdf_label(label, context, length|0);
return hkdf_expand(hashName, secret, info, length|0);
}
// --- TLS 1.3: derive handshake secrets ---
function derive_handshake_traffic_secrets(hashName, shared_secret, transcript) {
var hashFn = getHashFn(hashName);
var hashLen = hashFn.outputLen|0;
var empty = new Uint8Array(0);
var zeros = new Uint8Array(hashLen); // "zeros" כ־salt בגודל hashLen
// early_secret = HKDF-Extract(zeros, PSK=empty) כשאין PSK
var early_secret = hkdf_extract(hashName, empty, zeros);
// derived_secret = HKDF-Expand-Label(early_secret, "derived", Hash(""), Hash.length)
var h_empty = hashFn(empty);
var derived_secret = hkdf_expand_label(hashName, early_secret, 'derived', h_empty, hashLen);
// handshake_secret = HKDF-Extract(derived_secret, shared_secret)
var handshake_secret = hkdf_extract(hashName, derived_secret, shared_secret);
// transcript_hash עד הנקודה הנוכחית
var transcript_hash = hashFn(transcript);
// תנועת handshake
var client_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 'c hs traffic', transcript_hash, hashLen);
var server_handshake_traffic_secret = hkdf_expand_label(hashName, handshake_secret, 's hs traffic', transcript_hash, hashLen);
return {
handshake_secret: handshake_secret,
client_handshake_traffic_secret: client_handshake_traffic_secret,
server_handshake_traffic_secret: server_handshake_traffic_secret,
};
}
// --- TLS 1.3: derive application secrets ---
function derive_app_traffic_secrets(hashName, handshake_secret, transcript) {
var hashFn = getHashFn(hashName);
var hashLen = hashFn.outputLen|0;
var empty = new Uint8Array(0);
var zeros = new Uint8Array(hashLen);
// derived_secret = HKDF-Expand-Label(handshake_secret, "derived", Hash(""), Hash.length)
var h_empty = hashFn(empty);
var derived_secret = hkdf_expand_label(hashName, handshake_secret, 'derived', h_empty, hashLen);
// master_secret = HKDF-Extract(derived_secret, zeros)
var master_secret = hkdf_extract(hashName, derived_secret, zeros);
// hash של ה־transcript (עד Finished של ה־server בד"כ)
var transcript_hash = hashFn(transcript);
// תנועת application
var client_app_traffic_secret = hkdf_expand_label(hashName, master_secret, 'c ap traffic', transcript_hash, hashLen);
var server_app_traffic_secret = hkdf_expand_label(hashName, master_secret, 's ap traffic', transcript_hash, hashLen);
return {
client_app_traffic_secret: client_app_traffic_secret,
server_app_traffic_secret: server_app_traffic_secret,
master_secret: master_secret
};
}
function build_cert_verify_tbs(hashName, isServer, transcript){
if(isServer){
var label = new TextEncoder().encode("TLS 1.3, server CertificateVerify");
}else{
var label = new TextEncoder().encode("TLS 1.3, client CertificateVerify");
}
var separator = new Uint8Array([0x00]);
var padding = new Uint8Array(64).fill(0x20);
var hashFn = getHashFn(hashName);
var transcript_hash = hashFn(transcript);
return concatUint8Arrays([padding, label, separator, transcript_hash]);
}
function get_handshake_finished(hashName, traffic_secret, transcript) {
var hashFn = getHashFn(hashName);
var hashLen = hashFn.outputLen|0;
var empty = new Uint8Array(0);
var finished_key = hkdf_expand_label(hashName, traffic_secret, 'finished', empty, hashLen);
var transcript_hash = hashFn(transcript);
var verify_data=hmac(hashName, finished_key, transcript_hash);
return verify_data;
}
// --- Exports ---
export {
TLS_CIPHER_SUITES,
getHashFn,
getHashLen,
hmac,
hkdf_extract,
hkdf_expand,
build_hkdf_label,
hkdf_expand_label,
derive_handshake_traffic_secrets,
derive_app_traffic_secrets,
build_cert_verify_tbs,
get_handshake_finished
};