mirror of
https://github.com/Ylianst/MeshCommander
synced 2025-12-06 06:03:20 +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:
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
118
forge.js/dhe.js
Normal 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
216
forge.js/ecdh.js
Normal 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));
|
||||
});
|
||||
})();
|
||||
@@ -66,6 +66,8 @@ define([
|
||||
'./cipherModes',
|
||||
'./debug',
|
||||
'./des',
|
||||
'./dhe',
|
||||
'./ecdh',
|
||||
'./hmac',
|
||||
'./kem',
|
||||
'./log',
|
||||
|
||||
377
forge.js/tls.js
377
forge.js/tls.js
@@ -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,8 +1419,129 @@ 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
|
||||
// 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.',
|
||||
@@ -1345,6 +1552,7 @@ tls.handleServerKeyExchange = function(c, record, length) {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// expect an optional CertificateRequest message next
|
||||
c.expect = SCR;
|
||||
@@ -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;
|
||||
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:
|
||||
// should never happen
|
||||
throw new Error('Invalid PRF');
|
||||
// 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,6 +3220,90 @@ tls.createCertificate = function(c) {
|
||||
* @return the ClientKeyExchange byte buffer.
|
||||
*/
|
||||
tls.createClientKeyExchange = function(c) {
|
||||
var sp = c.session.sp;
|
||||
var rval = forge.util.createBuffer();
|
||||
|
||||
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();
|
||||
|
||||
@@ -3001,7 +3316,6 @@ tls.createClientKeyExchange = function(c) {
|
||||
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
|
||||
@@ -3017,12 +3331,13 @@ tls.createClientKeyExchange = function(c) {
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user