1
0
mirror of https://github.com/Ylianst/MeshCommander synced 2025-12-05 21:53:19 +00:00

Merge pull request #122 from EstiFeit/private/estifeit/add-new-cipher-suits

TLS: implement ECDHE/DHE key exchange and cipher suites
This commit is contained in:
Ylian Saint-Hilaire
2025-09-09 22:31:26 -07:00
committed by GitHub
8 changed files with 1203 additions and 66 deletions

View File

@@ -443,7 +443,7 @@ var CreateAmtRemoteIderIMR = function () {
});
} else {
// Open connection with TLS
if (obj.m.xtlsoptions == null) { obj.m.xtlsoptions = { secureProtocol: 'TLSv1_method', ciphers: 'RSA+AES:!aNULL:!MD5:!DSS', secureOptions: obj.constants.SSL_OP_NO_SSLv2 | obj.constants.SSL_OP_NO_SSLv3 | obj.constants.SSL_OP_NO_COMPRESSION | obj.constants.SSL_OP_CIPHER_SERVER_PREFERENCE, rejectUnauthorized: false }; }
if (obj.m.xtlsoptions == null) { obj.m.xtlsoptions = { secureProtocol: 'TLSv1_method', ciphers: 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:RSA+AES:!aNULL:!MD5:!DSS', secureOptions: obj.constants.SSL_OP_NO_SSLv2 | obj.constants.SSL_OP_NO_SSLv3 | obj.constants.SSL_OP_NO_COMPRESSION | obj.constants.SSL_OP_CIPHER_SERVER_PREFERENCE, rejectUnauthorized: false }; }
obj.m.client = _tls.connect(obj.m.port, obj.m.host, obj.m.xtlsoptions, function () {
//console.log('IDER Connected TLS, ' + obj.m.host + ':' + obj.m.port);

View File

@@ -64,7 +64,7 @@ var CreateAmtRedirect = function (module) {
obj.socket.on('close', obj.xxOnSocketClosed);
obj.socket.on('error', obj.xxOnSocketClosed);
} else {
if (obj.xtlsoptions == null) { obj.xtlsoptions = { secureProtocol: 'TLSv1_method', ciphers: 'RSA+AES:!aNULL:!MD5:!DSS', secureOptions: obj.constants.SSL_OP_NO_SSLv2 | obj.constants.SSL_OP_NO_SSLv3 | obj.constants.SSL_OP_NO_COMPRESSION | obj.constants.SSL_OP_CIPHER_SERVER_PREFERENCE, rejectUnauthorized: false }; }
if (obj.xtlsoptions == null) { obj.xtlsoptions = { secureProtocol: 'TLSv1_method', ciphers: 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:RSA+AES:!aNULL:!MD5:!DSS', secureOptions: obj.constants.SSL_OP_NO_SSLv2 | obj.constants.SSL_OP_NO_SSLv3 | obj.constants.SSL_OP_NO_COMPRESSION | obj.constants.SSL_OP_CIPHER_SERVER_PREFERENCE, rejectUnauthorized: false }; }
obj.socket = obj.tls.connect(port, host, obj.xtlsoptions, obj.xxOnSocketConnected);
//obj.socket.setEncoding('binary');
obj.socket.on('data', obj.xxOnSocketData);

View File

@@ -184,7 +184,7 @@ var CreateWsmanComm = function (host, port, user, pass, tls, tlsoptions) {
obj.socket.connect(obj.port, obj.host, obj.xxOnSocketConnected);
} else {
// Connect with TLS
var options = { secureProtocol: ((obj.xtlsMethod == 0) ? 'SSLv23_method' : 'TLSv1_method'), ciphers: 'RSA+AES:!aNULL:!MD5:!DSS', secureOptions: obj.constants.SSL_OP_NO_SSLv2 | obj.constants.SSL_OP_NO_SSLv3 | obj.constants.SSL_OP_NO_COMPRESSION | obj.constants.SSL_OP_CIPHER_SERVER_PREFERENCE, rejectUnauthorized: false };
var options = { secureProtocol: ((obj.xtlsMethod == 0) ? 'SSLv23_method' : 'TLSv1_method'), ciphers: 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:RSA+AES:!aNULL:!MD5:!DSS', secureOptions: obj.constants.SSL_OP_NO_SSLv2 | obj.constants.SSL_OP_NO_SSLv3 | obj.constants.SSL_OP_NO_COMPRESSION | obj.constants.SSL_OP_CIPHER_SERVER_PREFERENCE, rejectUnauthorized: false };
if (obj.xtlsoptions) {
if (obj.xtlsoptions.ca) options.ca = obj.xtlsoptions.ca;
if (obj.xtlsoptions.cert) options.cert = obj.xtlsoptions.cert;

View File

@@ -48,6 +48,155 @@ tls.CipherSuites['TLS_RSA_WITH_AES_256_CBC_SHA'] = {
initConnectionState: initConnectionState
};
// cipher suites with SHA-256 MAC
tls.CipherSuites['TLS_RSA_WITH_AES_128_CBC_SHA256'] = {
id: [0x00,0x3c],
name: 'TLS_RSA_WITH_AES_128_CBC_SHA256',
initSecurityParameters: function(sp) {
sp.bulk_cipher_algorithm = tls.BulkCipherAlgorithm.aes;
sp.cipher_type = tls.CipherType.block;
sp.enc_key_length = 16;
sp.block_length = 16;
sp.fixed_iv_length = 16;
sp.record_iv_length = 16;
sp.mac_algorithm = tls.MACAlgorithm.hmac_sha256;
sp.mac_length = 32;
sp.mac_key_length = 32;
sp.prf_algorithm = tls.PRFAlgorithm.tls_prf_sha256;
},
initConnectionState: initConnectionState_sha256
};
tls.CipherSuites['TLS_RSA_WITH_AES_256_CBC_SHA256'] = {
id: [0x00,0x3d],
name: 'TLS_RSA_WITH_AES_256_CBC_SHA256',
initSecurityParameters: function(sp) {
sp.bulk_cipher_algorithm = tls.BulkCipherAlgorithm.aes;
sp.cipher_type = tls.CipherType.block;
sp.enc_key_length = 32;
sp.block_length = 16;
sp.fixed_iv_length = 16;
sp.record_iv_length = 16;
sp.mac_algorithm = tls.MACAlgorithm.hmac_sha256;
sp.mac_length = 32;
sp.mac_key_length = 32;
sp.prf_algorithm = tls.PRFAlgorithm.tls_prf_sha256;
},
initConnectionState: initConnectionState_sha256
};
tls.CipherSuites['TLS_RSA_WITH_AES_128_GCM_SHA256'] = {
id: [0x00,0x9c],
name: 'TLS_RSA_WITH_AES_128_GCM_SHA256',
initSecurityParameters: function(sp) {
sp.bulk_cipher_algorithm = tls.BulkCipherAlgorithm.aes;
sp.cipher_type = tls.CipherType.aead;
sp.enc_key_length = 16;
sp.fixed_iv_length = 4;
sp.record_iv_length = 8;
sp.mac_algorithm = tls.MACAlgorithm.aead;
sp.mac_length = 16;
sp.mac_key_length = 0;
sp.auth_tag_length = 16;
sp.prf_algorithm = tls.PRFAlgorithm.tls_prf_sha256;
},
initConnectionState: initConnectionState_gcm
};
tls.CipherSuites['TLS_RSA_WITH_AES_256_GCM_SHA384'] = {
id: [0x00,0x9d],
name: 'TLS_RSA_WITH_AES_256_GCM_SHA384',
initSecurityParameters: function(sp) {
sp.bulk_cipher_algorithm = tls.BulkCipherAlgorithm.aes;
sp.cipher_type = tls.CipherType.aead;
sp.enc_key_length = 32;
sp.fixed_iv_length = 4;
sp.record_iv_length = 8;
sp.mac_algorithm = tls.MACAlgorithm.aead;
sp.mac_length = 16;
sp.mac_key_length = 0;
sp.auth_tag_length = 16;
sp.prf_algorithm = tls.PRFAlgorithm.tls_prf_sha384;
},
initConnectionState: initConnectionState_gcm
};
tls.CipherSuites['TLS_DHE_RSA_WITH_AES_256_GCM_SHA384'] = {
id: [0x00,0x9f],
name: 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384',
initSecurityParameters: function(sp) {
sp.bulk_cipher_algorithm = tls.BulkCipherAlgorithm.aes;
sp.cipher_type = tls.CipherType.aead;
sp.enc_key_length = 32;
sp.fixed_iv_length = 4;
sp.record_iv_length = 8;
sp.mac_algorithm = tls.MACAlgorithm.aead;
sp.mac_length = 16;
sp.mac_key_length = 0;
sp.auth_tag_length = 16;
sp.key_exchange_algorithm = 'dhe_rsa';
sp.prf_algorithm = tls.PRFAlgorithm.tls_prf_sha384;
},
initConnectionState: initConnectionState_gcm
};
tls.CipherSuites['TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384'] = {
id: [0xc0,0x30],
name: 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384',
initSecurityParameters: function(sp) {
sp.bulk_cipher_algorithm = tls.BulkCipherAlgorithm.aes;
sp.cipher_type = tls.CipherType.aead;
sp.enc_key_length = 32;
sp.fixed_iv_length = 4;
sp.record_iv_length = 8;
sp.mac_algorithm = tls.MACAlgorithm.aead;
sp.mac_length = 16;
sp.mac_key_length = 0;
sp.auth_tag_length = 16;
sp.key_exchange_algorithm = 'ecdhe_rsa';
sp.prf_algorithm = tls.PRFAlgorithm.tls_prf_sha384;
},
initConnectionState: initConnectionState_gcm
};
tls.CipherSuites['TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256'] = {
id: [0xc0,0x2f],
name: 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256',
initSecurityParameters: function(sp) {
sp.bulk_cipher_algorithm = tls.BulkCipherAlgorithm.aes;
sp.cipher_type = tls.CipherType.aead;
sp.enc_key_length = 16;
sp.fixed_iv_length = 4;
sp.record_iv_length = 8;
sp.mac_algorithm = tls.MACAlgorithm.aead;
sp.mac_length = 16;
sp.mac_key_length = 0;
sp.auth_tag_length = 16;
sp.key_exchange_algorithm = 'ecdhe_rsa';
sp.prf_algorithm = tls.PRFAlgorithm.tls_prf_sha256;
},
initConnectionState: initConnectionState_gcm
};
tls.CipherSuites['TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384'] = {
id: [0xc0,0x2c],
name: 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384',
initSecurityParameters: function(sp) {
sp.bulk_cipher_algorithm = tls.BulkCipherAlgorithm.aes;
sp.cipher_type = tls.CipherType.aead;
sp.enc_key_length = 32;
sp.fixed_iv_length = 4;
sp.record_iv_length = 8;
sp.mac_algorithm = tls.MACAlgorithm.aead;
sp.mac_length = 16;
sp.mac_key_length = 0;
sp.auth_tag_length = 16;
sp.key_exchange_algorithm = 'ecdhe_ecdsa';
sp.prf_algorithm = tls.PRFAlgorithm.tls_prf_sha384;
},
initConnectionState: initConnectionState_gcm
};
function initConnectionState(state, c, sp) {
var client = (c.entity === forge.tls.ConnectionEnd.client);
@@ -283,6 +432,321 @@ function compareMacs(key, mac1, mac2) {
return mac1 === mac2;
}
function initConnectionState_sha256(state, c, sp) {
var client = (c.entity === tls.ConnectionEnd.client);
// cipher setup
state.read.cipherState = {
init: false,
cipher: forge.cipher.createDecipher('AES-CBC', client ?
sp.keys.server_write_key : sp.keys.client_write_key),
iv: client ? sp.keys.server_write_IV : sp.keys.client_write_IV
};
state.write.cipherState = {
init: false,
cipher: forge.cipher.createCipher('AES-CBC', client ?
sp.keys.client_write_key : sp.keys.server_write_key),
iv: client ? sp.keys.client_write_IV : sp.keys.server_write_IV
};
state.read.cipherFunction = decrypt_aes_cbc_sha256;
state.write.cipherFunction = encrypt_aes_cbc_sha256;
// MAC setup
state.read.macLength = state.write.macLength = sp.mac_length;
state.read.macFunction = state.write.macFunction = tls.hmac_sha256;
}
function initConnectionState_gcm(state, c, sp) {
var client = (c.entity === tls.ConnectionEnd.client);
// cipher setup for AEAD (AES-GCM)
state.read.cipherState = {
init: false,
cipher: forge.cipher.createDecipher('AES-GCM', client ?
sp.keys.server_write_key : sp.keys.client_write_key),
iv: client ? sp.keys.server_write_IV : sp.keys.client_write_IV,
sequenceNumber: [0, 0]
};
state.write.cipherState = {
init: false,
cipher: forge.cipher.createCipher('AES-GCM', client ?
sp.keys.client_write_key : sp.keys.server_write_key),
iv: client ? sp.keys.client_write_IV : sp.keys.server_write_IV,
sequenceNumber: [0, 0]
};
state.read.cipherFunction = decrypt_aes_gcm;
state.write.cipherFunction = encrypt_aes_gcm;
// AEAD doesn't use separate MAC
state.read.macLength = state.write.macLength = 0;
state.read.macFunction = state.write.macFunction = null;
}
/**
* Updates a 64-bit sequence number represented as two 32-bit integers.
*
* @param seqNum the sequence number to update.
*/
function updateSequenceNumber(seqNum) {
if(seqNum[1] === 0xFFFFFFFF) {
seqNum[1] = 0;
++seqNum[0];
} else {
++seqNum[1];
}
}
/**
* Encrypts the TLSCompressed record into a TLSCipherText record using AES
* in CBC mode with SHA-256 MAC.
*
* @param record the TLSCompressed record to encrypt.
* @param s the ConnectionState to use.
*
* @return true on success, false on failure.
*/
function encrypt_aes_cbc_sha256(record, s) {
var rval = false;
// append MAC to fragment, update sequence number
var mac = s.macFunction(s.macKey, s.sequenceNumber, record);
record.fragment.putBytes(mac);
s.updateSequenceNumber();
// TLS 1.1+ use an explicit IV every time to protect against CBC attacks
var iv;
if(record.version.minor === tls.Versions.TLS_1_0.minor) {
// use the pre-generated IV when initializing for TLS 1.0, otherwise use
// the residue from the previous encryption
iv = s.cipherState.init ? null : s.cipherState.iv;
} else {
iv = forge.random.getBytesSync(16);
}
s.cipherState.init = true;
// start cipher
var cipher = s.cipherState.cipher;
cipher.start({iv: iv});
// TLS 1.1+ write IV into output
if(record.version.minor >= tls.Versions.TLS_1_1.minor) {
cipher.output.putBytes(iv);
}
// do encryption (default padding is appropriate)
cipher.update(record.fragment);
if(cipher.finish(encrypt_aes_cbc_sha256_padding)) {
// set record fragment to encrypted output
record.fragment = cipher.output;
record.length = record.fragment.length();
rval = true;
}
return rval;
}
/**
* Handles padding for aes_cbc_sha256 in encrypt mode.
*
* @param blockSize the block size.
* @param input the input buffer.
* @param decrypt true in decrypt mode, false in encrypt mode.
*
* @return true on success, false on failure.
*/
function encrypt_aes_cbc_sha256_padding(blockSize, input, decrypt) {
return encrypt_aes_cbc_sha1_padding(blockSize, input, decrypt);
}
/**
* Decrypts a TLSCipherText record into a TLSCompressed record using
* AES in CBC mode with SHA-256 MAC.
*
* @param record the TLSCipherText record to decrypt.
* @param s the ConnectionState to use.
*
* @return true on success, false on failure.
*/
function decrypt_aes_cbc_sha256(record, s) {
var rval = false;
var iv;
if(record.version.minor === tls.Versions.TLS_1_0.minor) {
// use pre-generated IV when initializing for TLS 1.0, otherwise use the
// residue from the previous decryption
iv = s.cipherState.init ? null : s.cipherState.iv;
} else {
// TLS 1.1+ use an explicit IV every time to protect against CBC attacks
// that is appended to the record fragment
iv = record.fragment.getBytes(16);
}
s.cipherState.init = true;
// start cipher
var cipher = s.cipherState.cipher;
cipher.start({iv: iv});
// do decryption
cipher.update(record.fragment);
rval = cipher.finish(decrypt_aes_cbc_sha256_padding);
// even if decryption fails, keep going to minimize timing attacks
// decrypted data:
// first (len - 32) bytes = application data
// last 32 bytes = MAC
var macLen = s.macLength;
// create a random MAC to check against should the mac length check fail
// Note: do this regardless of the failure to keep timing consistent
var mac = forge.random.getBytesSync(macLen);
// get fragment and mac
var len = cipher.output.length();
if(len >= macLen) {
record.fragment = cipher.output.getBytes(len - macLen);
mac = cipher.output.getBytes(macLen);
} else {
// bad data, but get bytes anyway to try to keep timing consistent
record.fragment = cipher.output.getBytes();
}
record.fragment = forge.util.createBuffer(record.fragment);
record.length = record.fragment.length();
// see if data integrity checks out, update sequence number
var mac2 = s.macFunction(s.macKey, s.sequenceNumber, record);
s.updateSequenceNumber();
rval = (mac2 === mac) && rval;
return rval;
}
/**
* Handles padding for aes_cbc_sha256 in decrypt mode.
*
* @param blockSize the block size.
* @param output the output buffer.
* @param decrypt true in decrypt mode, false in encrypt mode.
*
* @return true on success, false on failure.
*/
function decrypt_aes_cbc_sha256_padding(blockSize, output, decrypt) {
return decrypt_aes_cbc_sha1_padding(blockSize, output, decrypt);
}
/**
* Encrypts the TLSCompressed record into a TLSCipherText record using AES
* in GCM mode (AEAD).
*
* @param record the TLSCompressed record to encrypt.
* @param s the ConnectionState to use.
*
* @return true on success, false on failure.
*/
function encrypt_aes_gcm(record, s) {
var rval = false;
// construct explicit nonce (8 bytes)
var explicitNonce = forge.util.createBuffer();
explicitNonce.putInt32(s.cipherState.sequenceNumber[0]);
explicitNonce.putInt32(s.cipherState.sequenceNumber[1]);
// construct IV: fixed_iv + explicit_nonce
var iv = forge.util.createBuffer();
iv.putBytes(s.cipherState.iv);
iv.putBytes(explicitNonce.getBytes());
// create additional data for AEAD
var additionalData = forge.util.createBuffer();
additionalData.putInt32(s.cipherState.sequenceNumber[0]);
additionalData.putInt32(s.cipherState.sequenceNumber[1]);
additionalData.putByte(record.type);
additionalData.putByte(record.version.major);
additionalData.putByte(record.version.minor);
additionalData.putInt16(record.fragment.length());
// start cipher with IV and additional data
var cipher = s.cipherState.cipher;
cipher.start({
iv: iv.getBytes(),
additionalData: additionalData.getBytes()
});
// encrypt the fragment
cipher.update(record.fragment);
if(cipher.finish()) {
// prepend explicit nonce to ciphertext + auth tag
record.fragment = forge.util.createBuffer();
record.fragment.putBytes(explicitNonce.getBytes());
record.fragment.putBytes(cipher.output.getBytes());
record.fragment.putBytes(cipher.mode.tag.getBytes());
record.length = record.fragment.length();
rval = true;
// update sequence number
updateSequenceNumber(s.cipherState.sequenceNumber);
}
return rval;
}
/**
* Decrypts a TLSCipherText record into a TLSCompressed record using
* AES in GCM mode (AEAD).
*
* @param record the TLSCipherText record to decrypt.
* @param s the ConnectionState to use.
*
* @return true on success, false on failure.
*/
function decrypt_aes_gcm(record, s) {
var rval = false;
// extract explicit nonce (8 bytes)
var explicitNonce = record.fragment.getBytes(8);
// extract auth tag (16 bytes from end)
var ciphertext = record.fragment.getBytes(record.fragment.length() - 16);
var authTag = record.fragment.getBytes(16);
// construct IV: fixed_iv + explicit_nonce
var iv = forge.util.createBuffer();
iv.putBytes(s.cipherState.iv);
iv.putBytes(explicitNonce);
// create additional data for AEAD
var additionalData = forge.util.createBuffer();
additionalData.putInt32(s.cipherState.sequenceNumber[0]);
additionalData.putInt32(s.cipherState.sequenceNumber[1]);
additionalData.putByte(record.type);
additionalData.putByte(record.version.major);
additionalData.putByte(record.version.minor);
additionalData.putInt16(ciphertext.length);
// start cipher with IV, additional data, and auth tag
var cipher = s.cipherState.cipher;
cipher.start({
iv: iv.getBytes(),
additionalData: additionalData.getBytes(),
tag: authTag
});
// decrypt the ciphertext
cipher.update(forge.util.createBuffer(ciphertext));
if(cipher.finish()) {
record.fragment = cipher.output;
record.length = record.fragment.length();
rval = true;
// update sequence number
updateSequenceNumber(s.cipherState.sequenceNumber);
}
return rval;
}
} // end module implementation
/* ########## Begin module wrapper ########## */

118
forge.js/dhe.js Normal file
View File

@@ -0,0 +1,118 @@
/**
* DHE (Diffie-Hellman Ephemeral) implementation for Forge TLS.
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
var tls = forge.tls;
// Diffie-Hellman implementation for DHE support
tls.dh = {
// 2048-bit MODP Group 14 (RFC 3526) - well-known safe prime
p2048: new forge.jsbn.BigInteger(
'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1' +
'29024E088A67CC74020BBEA63B139B22514A08798E3404DD' +
'EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245' +
'E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' +
'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D' +
'C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F' +
'83655D23DCA3AD961C62F356208552BB9ED529077096966D' +
'670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' +
'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9' +
'DE2BCBF6955817183995497CEA956AE515D2261898FA0510' +
'15728E5A8AACAA68FFFFFFFFFFFFFFFF', 16),
g2048: new forge.jsbn.BigInteger('2', 16),
generateKeyPair: function(p, g) {
p = p || tls.dh.p2048;
g = g || tls.dh.g2048;
// Generate random private key (1 < x < p-1)
var pMinus1 = p.subtract(forge.jsbn.BigInteger.ONE);
var privateKey;
do {
// Generate random bytes for private key
var bytes = forge.random.getBytesSync(32); // 256 bits
privateKey = new forge.jsbn.BigInteger(forge.util.bytesToHex(bytes), 16);
} while (privateKey.compareTo(forge.jsbn.BigInteger.ONE) <= 0 ||
privateKey.compareTo(pMinus1) >= 0);
// Calculate public key: g^x mod p
var publicKey = g.modPow(privateKey, p);
return {
privateKey: privateKey,
publicKey: publicKey
};
},
computeSecret: function(privateKey, theirPublicKey, p) {
p = p || tls.dh.p2048;
// Validate that their public key is in valid range (1 < Y < p-1)
if(theirPublicKey.compareTo(forge.jsbn.BigInteger.ONE) <= 0 ||
theirPublicKey.compareTo(p) >= 0) {
throw new Error('Invalid DH public key');
}
// Compute shared secret: Y^x mod p
return theirPublicKey.modPow(privateKey, p);
}
};
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'dhe';
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
var nodeJS = true;
define = function(ids, factory) {
factory(require, module);
};
} else {
// <script>
if(typeof forge === 'undefined') {
forge = {};
}
return initModule(forge);
}
}
// AMD
var deps;
var defineFunc = function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
};
var tmpDefine = define;
define = function(ids, factory) {
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
if(nodeJS) {
delete define;
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
}
define = tmpDefine;
return define.apply(null, Array.prototype.slice.call(arguments, 0));
};
define(['require', 'module', './util', './jsbn', './random', './tls'], function() {
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
});
})();

216
forge.js/ecdh.js Normal file
View File

@@ -0,0 +1,216 @@
/**
* ECDH (Elliptic Curve Diffie-Hellman) implementation for Forge TLS.
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
var tls = forge.tls;
// Basic ECDH implementation for secp256r1 (P-256) and secp384r1
tls.ecdh = {};
tls.ecdh.curves = {
'secp256r1': {
p: new forge.jsbn.BigInteger('FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF', 16),
a: new forge.jsbn.BigInteger('FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC', 16),
b: new forge.jsbn.BigInteger('5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B', 16),
gx: new forge.jsbn.BigInteger('6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296', 16),
gy: new forge.jsbn.BigInteger('4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5', 16),
n: new forge.jsbn.BigInteger('FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551', 16),
fieldSize: 32
},
'secp384r1': {
p: new forge.jsbn.BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF', 16),
a: new forge.jsbn.BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC', 16),
b: new forge.jsbn.BigInteger('B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF', 16),
gx: new forge.jsbn.BigInteger('AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7', 16),
gy: new forge.jsbn.BigInteger('3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F', 16),
n: new forge.jsbn.BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973', 16),
fieldSize: 48
}
};
// Simple elliptic curve point implementation
tls.ecdh.ECPoint = function(curve, x, y) {
this.curve = curve;
this.x = x;
this.y = y;
this.isInfinity = (x === null && y === null);
};
tls.ecdh.ECPoint.prototype.add = function(other) {
if(this.isInfinity) return other;
if(other.isInfinity) return this;
var p = this.curve.p;
if(this.x.equals(other.x)) {
if(this.y.equals(other.y)) {
// Point doubling
var s = this.x.multiply(this.x).multiply(new forge.jsbn.BigInteger('3')).add(this.curve.a)
.multiply(this.y.multiply(new forge.jsbn.BigInteger('2')).modInverse(p)).mod(p);
var x3 = s.multiply(s).subtract(this.x.multiply(new forge.jsbn.BigInteger('2'))).mod(p);
var y3 = s.multiply(this.x.subtract(x3)).subtract(this.y).mod(p);
return new tls.ecdh.ECPoint(this.curve, x3, y3);
} else {
// Points are inverses
return new tls.ecdh.ECPoint(this.curve, null, null); // Point at infinity
}
} else {
// Point addition
var s = other.y.subtract(this.y).multiply(other.x.subtract(this.x).modInverse(p)).mod(p);
var x3 = s.multiply(s).subtract(this.x).subtract(other.x).mod(p);
var y3 = s.multiply(this.x.subtract(x3)).subtract(this.y).mod(p);
return new tls.ecdh.ECPoint(this.curve, x3, y3);
}
};
tls.ecdh.ECPoint.prototype.multiply = function(k) {
if(k.equals(forge.jsbn.BigInteger.ZERO)) {
return new tls.ecdh.ECPoint(this.curve, null, null); // Point at infinity
}
var result = new tls.ecdh.ECPoint(this.curve, null, null); // Start with point at infinity
var addend = this;
while(k.compareTo(forge.jsbn.BigInteger.ZERO) > 0) {
if(k.testBit(0)) {
result = result.add(addend);
}
addend = addend.add(addend); // Double
k = k.shiftRight(1);
}
return result;
};
// ECDH key generation and shared secret computation
tls.ecdh.generateKeyPair = function(curveName) {
var curve = tls.ecdh.curves[curveName];
if(!curve) {
throw new Error('Unsupported curve: ' + curveName);
}
// Generate random private key
var privateKey;
do {
var privateKeyBytes = forge.random.getBytes(curve.fieldSize);
privateKey = new forge.jsbn.BigInteger(forge.util.bytesToHex(privateKeyBytes), 16);
} while(privateKey.compareTo(curve.n) >= 0 || privateKey.equals(forge.jsbn.BigInteger.ZERO));
// Compute public key = private * G
var G = new tls.ecdh.ECPoint(curve, curve.gx, curve.gy);
var publicPoint = G.multiply(privateKey);
return {
privateKey: privateKey,
publicKey: publicPoint
};
};
tls.ecdh.computeSharedSecret = function(privateKey, publicPoint) {
var sharedPoint = publicPoint.multiply(privateKey);
// Convert x-coordinate to bytes (shared secret is the x-coordinate)
var sharedSecret = forge.util.hexToBytes(sharedPoint.x.toString(16));
// Pad to field size
var fieldSize = publicPoint.curve.fieldSize;
while(sharedSecret.length < fieldSize) {
sharedSecret = '\x00' + sharedSecret;
}
return sharedSecret;
};
tls.ecdh.encodePoint = function(point) {
if(point.isInfinity) {
return String.fromCharCode(0x00);
}
// Uncompressed point format: 0x04 || x || y
var x = forge.util.hexToBytes(point.x.toString(16));
var y = forge.util.hexToBytes(point.y.toString(16));
// Pad to field size
var fieldSize = point.curve.fieldSize;
while(x.length < fieldSize) x = '\x00' + x;
while(y.length < fieldSize) y = '\x00' + y;
return String.fromCharCode(0x04) + x + y;
};
tls.ecdh.decodePoint = function(curve, pointBytes) {
if(pointBytes.length === 0 || pointBytes.charCodeAt(0) === 0x00) {
return new tls.ecdh.ECPoint(curve, null, null); // Point at infinity
}
if(pointBytes.charCodeAt(0) !== 0x04) {
throw new Error('Only uncompressed points are supported');
}
var fieldSize = curve.fieldSize;
if(pointBytes.length !== 1 + 2 * fieldSize) {
throw new Error('Invalid point encoding length');
}
var x = new forge.jsbn.BigInteger(forge.util.bytesToHex(pointBytes.substr(1, fieldSize)), 16);
var y = new forge.jsbn.BigInteger(forge.util.bytesToHex(pointBytes.substr(1 + fieldSize, fieldSize)), 16);
return new tls.ecdh.ECPoint(curve, x, y);
};
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'ecdh';
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
var nodeJS = true;
define = function(ids, factory) {
factory(require, module);
};
} else {
// <script>
if(typeof forge === 'undefined') {
forge = {};
}
return initModule(forge);
}
}
// AMD
var deps;
var defineFunc = function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
};
var tmpDefine = define;
define = function(ids, factory) {
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
if(nodeJS) {
delete define;
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
}
define = tmpDefine;
return define.apply(null, Array.prototype.slice.call(arguments, 0));
};
define(['require', 'module', './util', './jsbn', './random', './tls'], function() {
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
});
})();

View File

@@ -66,6 +66,8 @@ define([
'./cipherModes',
'./debug',
'./des',
'./dhe',
'./ecdh',
'./hmac',
'./kem',
'./log',

View File

@@ -352,7 +352,67 @@ var prf_TLS1 = function(secret, label, seed, length) {
* @return the pseudo random bytes in a byte buffer.
*/
var prf_sha256 = function(secret, label, seed, length) {
// FIXME: implement me for TLS 1.2
var hmac = forge.hmac.create();
var seedData = forge.util.createBuffer();
seedData.putBytes(label);
seedData.putBytes(seed);
var A = seedData.getBytes();
var output = forge.util.createBuffer();
while(output.length() < length) {
// A(i) = HMAC_hash(secret, A(i-1))
hmac.start('SHA256', secret);
hmac.update(A);
A = hmac.digest().getBytes();
// P_hash output = HMAC_hash(secret, A(i) + seed)
hmac.start('SHA256', secret);
hmac.update(A);
hmac.update(seedData.getBytes());
output.putBytes(hmac.digest().getBytes());
}
// truncate to desired length
output.truncate(output.length() - length);
return output;
};
/**
* Generates pseudo random bytes using a SHA384 algorithm. For TLS 1.2.
*
* @param secret the secret to use.
* @param label the label to use.
* @param seed the seed value to use.
* @param length the number of bytes to generate.
*
* @return the pseudo random bytes in a byte buffer.
*/
var prf_sha384 = function(secret, label, seed, length) {
var hmac = forge.hmac.create();
var seedData = forge.util.createBuffer();
seedData.putBytes(label);
seedData.putBytes(seed);
var A = seedData.getBytes();
var output = forge.util.createBuffer();
while(output.length() < length) {
// A(i) = HMAC_hash(secret, A(i-1))
hmac.start('SHA384', secret);
hmac.update(A);
A = hmac.digest().getBytes();
// P_hash output = HMAC_hash(secret, A(i) + seed)
hmac.start('SHA384', secret);
hmac.update(A);
hmac.update(seedData.getBytes());
output.putBytes(hmac.digest().getBytes());
}
// truncate to desired length
output.truncate(output.length() - length);
return output;
};
/**
@@ -387,6 +447,30 @@ var hmac_sha1 = function(key, seqNum, record) {
return hmac.digest().getBytes();
};
/**
* Gets a MAC for a record using the SHA-256 hash algorithm.
*
* @param key the mac key.
* @param state the sequence number (array of two 32-bit integers).
* @param record the record.
*
* @return the sha-256 hash (32 bytes) for the given record.
*/
var hmac_sha256 = function(key, seqNum, record) {
var hmac = forge.hmac.create();
hmac.start('SHA256', key);
var b = forge.util.createBuffer();
b.putInt32(seqNum[0]);
b.putInt32(seqNum[1]);
b.putByte(record.type);
b.putByte(record.version.major);
b.putByte(record.version.minor);
b.putInt16(record.length);
b.putBytes(record.fragment.bytes());
hmac.update(b.getBytes());
return hmac.digest().getBytes();
};
/**
* Compresses the TLSPlaintext record into a TLSCompressed record using the
* deflate algorithm.
@@ -528,10 +612,11 @@ tls.ConnectionEnd = {
/**
* Pseudo-random function algorithm used to generate keys from the master
* secret.
* enum { tls_prf_sha256 } PRFAlgorithm;
* enum { tls_prf_sha256, tls_prf_sha384 } PRFAlgorithm;
*/
tls.PRFAlgorithm = {
tls_prf_sha256: 0
tls_prf_sha256: 0,
tls_prf_sha384: 1
};
/**
@@ -566,7 +651,8 @@ tls.MACAlgorithm = {
hmac_sha1: 1,
hmac_sha256: 2,
hmac_sha384: 3,
hmac_sha512: 4
hmac_sha512: 4,
aead: 5
};
/**
@@ -1333,17 +1419,139 @@ tls.handleCertificate = function(c, record, length) {
* @param length the length of the handshake message.
*/
tls.handleServerKeyExchange = function(c, record, length) {
// this implementation only supports RSA, no Diffie-Hellman support
// so any length > 0 is invalid
if(length > 0) {
return c.error(c, {
message: 'Invalid key parameters. Only RSA is supported.',
send: true,
alert: {
level: tls.Alert.Level.fatal,
description: tls.Alert.Description.unsupported_certificate
// check if cipher suite uses DHE key exchange
var cipherSuite = c.session.cipherSuite;
var sp = c.session.sp;
if(sp.key_exchange_algorithm === 'dhe_rsa') {
// DHE_RSA key exchange - parse DH parameters
if(length === 0) {
return c.error(c, {
message: 'DHE requires server key exchange parameters.',
send: true,
alert: {
level: tls.Alert.Level.fatal,
description: tls.Alert.Description.handshake_failure
}
});
}
try {
// Parse DH parameters (p, g, Ys) from server
var dh_p_length = record.fragment.getInt16();
var dh_p_bytes = record.fragment.getBytes(dh_p_length);
var dh_p = new forge.jsbn.BigInteger(forge.util.bytesToHex(dh_p_bytes), 16);
var dh_g_length = record.fragment.getInt16();
var dh_g_bytes = record.fragment.getBytes(dh_g_length);
var dh_g = new forge.jsbn.BigInteger(forge.util.bytesToHex(dh_g_bytes), 16);
var dh_Ys_length = record.fragment.getInt16();
var dh_Ys_bytes = record.fragment.getBytes(dh_Ys_length);
var dh_Ys = new forge.jsbn.BigInteger(forge.util.bytesToHex(dh_Ys_bytes), 16);
// Store DH parameters for client key exchange
c.session.dhParams = {
p: dh_p,
g: dh_g,
serverPublicKey: dh_Ys
};
// Consume and optionally verify the RSA signature over params
// Structure:
// TLS 1.2: signature: SignatureAndHashAlgorithm (2 bytes) + uint16 len + bytes
// TLS 1.0/1.1: signature: uint16 len + bytes
var isTls12 = (c.version.major === tls.Versions.TLS_1_2.major &&
c.version.minor === tls.Versions.TLS_1_2.minor);
var sigHashAlg = null, sigSigAlg = null;
if(isTls12) { sigHashAlg = record.fragment.getByte(); sigSigAlg = record.fragment.getByte(); }
var sigLen = record.fragment.getInt16();
var sigBytes = record.fragment.getBytes(sigLen);
// TODO: Verify signature using server certificate and transcript hash
} catch(ex) {
return c.error(c, {
message: 'Invalid DHE parameters: ' + ex.message,
send: true,
alert: {
level: tls.Alert.Level.fatal,
description: tls.Alert.Description.decode_error
}
});
}
} else if(sp.key_exchange_algorithm === 'ecdhe_rsa') {
// ECDHE_RSA key exchange - parse EC parameters
if(length === 0) {
return c.error(c, {
message: 'ECDHE requires server key exchange parameters.',
send: true,
alert: {
level: tls.Alert.Level.fatal,
description: tls.Alert.Description.handshake_failure
}
});
}
try {
// Parse ECDHE parameters from server
var curveType = record.fragment.getByte();
if(curveType !== 3) { // named_curve
throw new Error('Only named curves are supported');
}
});
var namedCurve = record.fragment.getInt16();
var curveName;
switch(namedCurve) {
case 23: curveName = 'secp256r1'; break; // 0x0017
case 24: curveName = 'secp384r1'; break; // 0x0018
default:
throw new Error('Unsupported named curve: ' + namedCurve);
}
var publicKeyLength = record.fragment.getByte();
var publicKeyBytes = record.fragment.getBytes(publicKeyLength);
// Store ECDHE parameters for client key exchange
c.session.ecdhParams = {
curveName: curveName,
namedCurve: namedCurve,
serverPublicKey: publicKeyBytes
};
// Consume and optionally verify the RSA/ECDSA signature over params
// Structure:
// TLS 1.2: signature: SignatureAndHashAlgorithm (2 bytes) + uint16 len + bytes
// TLS 1.0/1.1: signature: uint16 len + bytes
var isTls12 = (c.version.major === tls.Versions.TLS_1_2.major &&
c.version.minor === tls.Versions.TLS_1_2.minor);
var sigHashAlg = null, sigSigAlg = null;
if(isTls12) { sigHashAlg = record.fragment.getByte(); sigSigAlg = record.fragment.getByte(); }
var sigLen = record.fragment.getInt16();
var sigBytes = record.fragment.getBytes(sigLen);
// TODO: Verify signature using server certificate and transcript hash
} catch(ex) {
return c.error(c, {
message: 'Invalid ECDHE parameters: ' + ex.message,
send: true,
alert: {
level: tls.Alert.Level.fatal,
description: tls.Alert.Description.decode_error
}
});
}
} else {
// RSA key exchange - no server key exchange needed
if(length > 0) {
return c.error(c, {
message: 'Invalid key parameters. Only RSA is supported.',
send: true,
alert: {
level: tls.Alert.Level.fatal,
description: tls.Alert.Description.unsupported_certificate
}
});
}
}
// expect an optional CertificateRequest message next
@@ -1814,10 +2022,29 @@ tls.handleFinished = function(c, record, length) {
var client = (c.entity === tls.ConnectionEnd.client);
var label = client ? 'server finished' : 'client finished';
// TODO: determine prf function and verify length for TLS 1.2
// determine the PRF based on TLS version and cipher suite
var sp = c.session.sp;
var vdl = 12;
var prf = prf_TLS1;
var prf;
if(c.version.major === tls.Versions.TLS_1_2.major &&
c.version.minor === tls.Versions.TLS_1_2.minor &&
sp.prf_algorithm !== undefined) {
// TLS 1.2 - use cipher suite specified PRF
switch(sp.prf_algorithm) {
case tls.PRFAlgorithm.tls_prf_sha256:
prf = prf_sha256;
break;
case tls.PRFAlgorithm.tls_prf_sha384:
prf = prf_sha384;
break;
default:
// fallback to TLS 1.0 PRF for unknown algorithms
prf = prf_TLS1;
}
} else {
// TLS 1.0/1.1 implementation - use legacy PRF
prf = prf_TLS1;
}
b = prf(sp.master_secret, label, b.getBytes(), vdl);
if(b.getBytes() !== vd) {
return c.error(c, {
@@ -2352,22 +2579,26 @@ tls.generateKeys = function(c, sp) {
// TLS 1.0 but we don't care right now because AES is better and we have
// an implementation for it
// TODO: TLS 1.2 implementation
/*
// determine the PRF
// Choose PRF per TLS version and cipher suite
var isTls12 = (c.version.major === tls.Versions.TLS_1_2.major &&
c.version.minor === tls.Versions.TLS_1_2.minor);
var prf;
switch(sp.prf_algorithm) {
case tls.PRFAlgorithm.tls_prf_sha256:
prf = prf_sha256;
break;
default:
// should never happen
throw new Error('Invalid PRF');
if(isTls12 && sp.prf_algorithm !== undefined) {
switch(sp.prf_algorithm) {
case tls.PRFAlgorithm.tls_prf_sha256:
prf = prf_sha256;
break;
case tls.PRFAlgorithm.tls_prf_sha384:
prf = prf_sha384;
break;
default:
// fallback to TLS 1.0 PRF for unknown algorithms
prf = prf_TLS1;
}
} else {
// TLS 1.0/1.1 legacy PRF
prf = prf_TLS1;
}
*/
// TLS 1.0/1.1 implementation
var prf = prf_TLS1;
// concatenate server and client random
var random = sp.client_random + sp.server_random;
@@ -2989,40 +3220,124 @@ tls.createCertificate = function(c) {
* @return the ClientKeyExchange byte buffer.
*/
tls.createClientKeyExchange = function(c) {
// create buffer to encrypt
var b = forge.util.createBuffer();
// add highest client-supported protocol to help server avoid version
// rollback attacks
b.putByte(c.session.clientHelloVersion.major);
b.putByte(c.session.clientHelloVersion.minor);
// generate and add 46 random bytes
b.putBytes(forge.random.getBytes(46));
// save pre-master secret
var sp = c.session.sp;
sp.pre_master_secret = b.getBytes();
// RSA-encrypt the pre-master secret
var key = c.session.serverCertificate.publicKey;
b = key.encrypt(sp.pre_master_secret);
/* Note: The encrypted pre-master secret will be stored in a
public-key-encrypted opaque vector that has the length prefixed using
2 bytes, so include those 2 bytes in the handshake message length. This
is done as a minor optimization instead of calling writeVector(). */
// determine length of the handshake message
var length = b.length + 2;
// build record fragment
var rval = forge.util.createBuffer();
rval.putByte(tls.HandshakeType.client_key_exchange);
rval.putInt24(length);
// add vector length bytes
rval.putInt16(b.length);
rval.putBytes(b);
if(sp.key_exchange_algorithm === 'ecdhe_rsa') {
// ECDHE key exchange
try {
var ecdhParams = c.session.ecdhParams;
if(!ecdhParams) {
throw new Error('Missing ECDHE parameters');
}
// Generate client ECDH key pair
var clientKeyPair = tls.ecdh.generateKeyPair(ecdhParams.curveName);
var clientPrivateKey = clientKeyPair.privateKey;
var clientPublicPoint = clientKeyPair.publicKey;
// Encode client public key
var clientPublicKey = tls.ecdh.encodePoint(clientPublicPoint);
// Parse server public key
var curve = tls.ecdh.curves[ecdhParams.curveName];
var serverPublicPoint = tls.ecdh.decodePoint(curve, ecdhParams.serverPublicKey);
// Compute shared secret
var sharedSecret = tls.ecdh.computeSharedSecret(clientPrivateKey, serverPublicPoint);
sp.pre_master_secret = sharedSecret;
// Build ClientKeyExchange message with client public key
rval.putByte(tls.HandshakeType.client_key_exchange);
var length = 1 + clientPublicKey.length; // 1 byte for length + public key
rval.putInt24(length);
rval.putByte(clientPublicKey.length);
rval.putBytes(clientPublicKey);
} catch(ex) {
// Fallback to error
throw new Error('ECDHE key exchange failed: ' + ex.message);
}
} else if(sp.key_exchange_algorithm === 'dhe_rsa') {
// DHE key exchange
try {
var dhParams = c.session.dhParams;
if(!dhParams) {
throw new Error('Missing DHE parameters');
}
// Generate client DH key pair
var clientKeyPair = tls.dh.generateKeyPair(dhParams.p, dhParams.g);
var clientPrivateKey = clientKeyPair.privateKey;
var clientPublicKey = clientKeyPair.publicKey;
// Compute shared secret
var sharedSecret = tls.dh.computeSecret(clientPrivateKey, dhParams.serverPublicKey, dhParams.p);
// Convert shared secret to bytes
var secretBytes = forge.util.hexToBytes(sharedSecret.toString(16));
// Pad to proper length
while(secretBytes.length < 256) { // 2048-bit = 256 bytes
secretBytes = '\x00' + secretBytes;
}
sp.pre_master_secret = secretBytes;
// Build ClientKeyExchange message with client public key
var publicKeyBytes = forge.util.hexToBytes(clientPublicKey.toString(16));
// Pad to proper length
while(publicKeyBytes.length < 256) { // 2048-bit = 256 bytes
publicKeyBytes = '\x00' + publicKeyBytes;
}
rval.putByte(tls.HandshakeType.client_key_exchange);
var length = 2 + publicKeyBytes.length; // 2 bytes for length + public key
rval.putInt24(length);
rval.putInt16(publicKeyBytes.length);
rval.putBytes(publicKeyBytes);
} catch(ex) {
// Fallback to error
throw new Error('DHE key exchange failed: ' + ex.message);
}
} else {
// RSA key exchange (original implementation)
// create buffer to encrypt
var b = forge.util.createBuffer();
// add highest client-supported protocol to help server avoid version
// rollback attacks
b.putByte(c.session.clientHelloVersion.major);
b.putByte(c.session.clientHelloVersion.minor);
// generate and add 46 random bytes
b.putBytes(forge.random.getBytes(46));
// save pre-master secret
sp.pre_master_secret = b.getBytes();
// RSA-encrypt the pre-master secret
var key = c.session.serverCertificate.publicKey;
b = key.encrypt(sp.pre_master_secret);
/* Note: The encrypted pre-master secret will be stored in a
public-key-encrypted opaque vector that has the length prefixed using
2 bytes, so include those 2 bytes in the handshake message length. This
is done as a minor optimization instead of calling writeVector(). */
// determine length of the handshake message
var length = b.length + 2;
// build record fragment
rval.putByte(tls.HandshakeType.client_key_exchange);
rval.putInt24(length);
// add vector length bytes
rval.putInt16(b.length);
rval.putBytes(b);
}
return rval;
};
@@ -3294,7 +3609,26 @@ tls.createFinished = function(c) {
var client = (c.entity === tls.ConnectionEnd.client);
var sp = c.session.sp;
var vdl = 12;
var prf = prf_TLS1;
var prf;
if(c.version.major === tls.Versions.TLS_1_2.major &&
c.version.minor === tls.Versions.TLS_1_2.minor &&
sp.prf_algorithm !== undefined) {
// TLS 1.2 - use cipher suite specified PRF
switch(sp.prf_algorithm) {
case tls.PRFAlgorithm.tls_prf_sha256:
prf = prf_sha256;
break;
case tls.PRFAlgorithm.tls_prf_sha384:
prf = prf_sha384;
break;
default:
// fallback to TLS 1.0 PRF for unknown algorithms
prf = prf_TLS1;
}
} else {
// TLS 1.0/1.1 implementation - use legacy PRF
prf = prf_TLS1;
}
var label = client ? 'client finished' : 'server finished';
b = prf(sp.master_secret, label, b.getBytes(), vdl);
@@ -4150,6 +4484,9 @@ forge.tls.prf_tls1 = prf_TLS1;
// expose sha1 hmac method
forge.tls.hmac_sha1 = hmac_sha1;
// expose sha256 hmac method
forge.tls.hmac_sha256 = hmac_sha256;
// expose session cache creation
forge.tls.createSessionCache = tls.createSessionCache;