1
0
mirror of https://github.com/Ylianst/MeshCommander synced 2025-12-13 06:43:19 +00:00

TLS: implement ECDHE/DHE key exchange and cipher suites

- Updated cipher suites to include modern algorithms with SHA-256 and SHA-384 support.
- Implemented Diffie-Hellman Ephemeral (DHE) and Elliptic Curve Diffie-Hellman (ECDH) key exchange methods.
- Added HMAC-SHA256 and HMAC-SHA384 for message authentication.
- Improved TLS handshake process to accommodate new key exchange algorithms.

This update strengthens the security of the TLS implementation and aligns with current best practices.
This commit is contained in:
EstiFeit
2025-09-09 22:30:39 +03:00
parent 926e71b060
commit e56b9c0ece
8 changed files with 1203 additions and 66 deletions

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 ########## */