node-tls
Version:
JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.
1,688 lines (1,564 loc) • 133 kB
JavaScript
/**
* A Javascript implementation of Transport Layer Security (TLS).
*
* @author Dave Longley
*
* Copyright (c) 2009-2014 Digital Bazaar, Inc.
*
* The TLS Handshake Protocol involves the following steps:
*
* - Exchange hello messages to agree on algorithms, exchange random values,
* and check for session resumption.
*
* - Exchange the necessary cryptographic parameters to allow the client and
* server to agree on a premaster secret.
*
* - Exchange certificates and cryptographic information to allow the client
* and server to authenticate themselves.
*
* - Generate a master secret from the premaster secret and exchanged random
* values.
*
* - Provide security parameters to the record layer.
*
* - Allow the client and server to verify that their peer has calculated the
* same security parameters and that the handshake occurred without tampering
* by an attacker.
*
* Up to 4 different messages may be sent during a key exchange. The server
* certificate, the server key exchange, the client certificate, and the
* client key exchange.
*
* A typical handshake (from the client's perspective).
*
* 1. Client sends ClientHello.
* 2. Client receives ServerHello.
* 3. Client receives optional Certificate.
* 4. Client receives optional ServerKeyExchange.
* 5. Client receives ServerHelloDone.
* 6. Client sends optional Certificate.
* 7. Client sends ClientKeyExchange.
* 8. Client sends optional CertificateVerify.
* 9. Client sends ChangeCipherSpec.
* 10. Client sends Finished.
* 11. Client receives ChangeCipherSpec.
* 12. Client receives Finished.
* 13. Client sends/receives application data.
*
* To reuse an existing session:
*
* 1. Client sends ClientHello with session ID for reuse.
* 2. Client receives ServerHello with same session ID if reusing.
* 3. Client receives ChangeCipherSpec message if reusing.
* 4. Client receives Finished.
* 5. Client sends ChangeCipherSpec.
* 6. Client sends Finished.
*
* Note: Client ignores HelloRequest if in the middle of a handshake.
*
* Record Layer:
*
* The record layer fragments information blocks into TLSPlaintext records
* carrying data in chunks of 2^14 bytes or less. Client message boundaries are
* not preserved in the record layer (i.e., multiple client messages of the
* same ContentType MAY be coalesced into a single TLSPlaintext record, or a
* single message MAY be fragmented across several records).
*
* struct {
* uint8 major;
* uint8 minor;
* } ProtocolVersion;
*
* struct {
* ContentType type;
* ProtocolVersion version;
* uint16 length;
* opaque fragment[TLSPlaintext.length];
* } TLSPlaintext;
*
* type:
* The higher-level protocol used to process the enclosed fragment.
*
* version:
* The version of the protocol being employed. TLS Version 1.2 uses version
* {3, 3}. TLS Version 1.0 uses version {3, 1}. Note that a client that
* supports multiple versions of TLS may not know what version will be
* employed before it receives the ServerHello.
*
* length:
* The length (in bytes) of the following TLSPlaintext.fragment. The length
* MUST NOT exceed 2^14 = 16384 bytes.
*
* fragment:
* The application data. This data is transparent and treated as an
* independent block to be dealt with by the higher-level protocol specified
* by the type field.
*
* Implementations MUST NOT send zero-length fragments of Handshake, Alert, or
* ChangeCipherSpec content types. Zero-length fragments of Application data
* MAY be sent as they are potentially useful as a traffic analysis
* countermeasure.
*
* Note: Data of different TLS record layer content types MAY be interleaved.
* Application data is generally of lower precedence for transmission than
* other content types. However, records MUST be delivered to the network in
* the same order as they are protected by the record layer. Recipients MUST
* receive and process interleaved application layer traffic during handshakes
* subsequent to the first one on a connection.
*
* struct {
* ContentType type; // same as TLSPlaintext.type
* ProtocolVersion version;// same as TLSPlaintext.version
* uint16 length;
* opaque fragment[TLSCompressed.length];
* } TLSCompressed;
*
* length:
* The length (in bytes) of the following TLSCompressed.fragment.
* The length MUST NOT exceed 2^14 + 1024.
*
* fragment:
* The compressed form of TLSPlaintext.fragment.
*
* Note: A CompressionMethod.null operation is an identity operation; no fields
* are altered. In this implementation, since no compression is supported,
* uncompressed records are always the same as compressed records.
*
* Encryption Information:
*
* The encryption and MAC functions translate a TLSCompressed structure into a
* TLSCiphertext. The decryption functions reverse the process. The MAC of the
* record also includes a sequence number so that missing, extra, or repeated
* messages are detectable.
*
* struct {
* ContentType type;
* ProtocolVersion version;
* uint16 length;
* select (SecurityParameters.cipher_type) {
* case stream: GenericStreamCipher;
* case block: GenericBlockCipher;
* case aead: GenericAEADCipher;
* } fragment;
* } TLSCiphertext;
*
* type:
* The type field is identical to TLSCompressed.type.
*
* version:
* The version field is identical to TLSCompressed.version.
*
* length:
* The length (in bytes) of the following TLSCiphertext.fragment.
* The length MUST NOT exceed 2^14 + 2048.
*
* fragment:
* The encrypted form of TLSCompressed.fragment, with the MAC.
*
* Note: Only CBC Block Ciphers are supported by this implementation.
*
* The TLSCompressed.fragment structures are converted to/from block
* TLSCiphertext.fragment structures.
*
* struct {
* opaque IV[SecurityParameters.record_iv_length];
* block-ciphered struct {
* opaque content[TLSCompressed.length];
* opaque MAC[SecurityParameters.mac_length];
* uint8 padding[GenericBlockCipher.padding_length];
* uint8 padding_length;
* };
* } GenericBlockCipher;
*
* The MAC is generated as described in Section 6.2.3.1.
*
* IV:
* The Initialization Vector (IV) SHOULD be chosen at random, and MUST be
* unpredictable. Note that in versions of TLS prior to 1.1, there was no
* IV field, and the last ciphertext block of the previous record (the "CBC
* residue") was used as the IV. This was changed to prevent the attacks
* described in [CBCATT]. For block ciphers, the IV length is of length
* SecurityParameters.record_iv_length, which is equal to the
* SecurityParameters.block_size.
*
* padding:
* Padding that is added to force the length of the plaintext to be an
* integral multiple of the block cipher's block length. The padding MAY be
* any length up to 255 bytes, as long as it results in the
* TLSCiphertext.length being an integral multiple of the block length.
* Lengths longer than necessary might be desirable to frustrate attacks on
* a protocol that are based on analysis of the lengths of exchanged
* messages. Each uint8 in the padding data vector MUST be filled with the
* padding length value. The receiver MUST check this padding and MUST use
* the bad_record_mac alert to indicate padding errors.
*
* padding_length:
* The padding length MUST be such that the total size of the
* GenericBlockCipher structure is a multiple of the cipher's block length.
* Legal values range from zero to 255, inclusive. This length specifies the
* length of the padding field exclusive of the padding_length field itself.
*
* The encrypted data length (TLSCiphertext.length) is one more than the sum of
* SecurityParameters.block_length, TLSCompressed.length,
* SecurityParameters.mac_length, and padding_length.
*
* Example: If the block length is 8 bytes, the content length
* (TLSCompressed.length) is 61 bytes, and the MAC length is 20 bytes, then the
* length before padding is 82 bytes (this does not include the IV. Thus, the
* padding length modulo 8 must be equal to 6 in order to make the total length
* an even multiple of 8 bytes (the block length). The padding length can be
* 6, 14, 22, and so on, through 254. If the padding length were the minimum
* necessary, 6, the padding would be 6 bytes, each containing the value 6.
* Thus, the last 8 octets of the GenericBlockCipher before block encryption
* would be xx 06 06 06 06 06 06 06, where xx is the last octet of the MAC.
*
* Note: With block ciphers in CBC mode (Cipher Block Chaining), it is critical
* that the entire plaintext of the record be known before any ciphertext is
* transmitted. Otherwise, it is possible for the attacker to mount the attack
* described in [CBCATT].
*
* Implementation note: Canvel et al. [CBCTIME] have demonstrated a timing
* attack on CBC padding based on the time required to compute the MAC. In
* order to defend against this attack, implementations MUST ensure that
* record processing time is essentially the same whether or not the padding
* is correct. In general, the best way to do this is to compute the MAC even
* if the padding is incorrect, and only then reject the packet. For instance,
* if the pad appears to be incorrect, the implementation might assume a
* zero-length pad and then compute the MAC. This leaves a small timing
* channel, since MAC performance depends, to some extent, on the size of the
* data fragment, but it is not believed to be large enough to be exploitable,
* due to the large block size of existing MACs and the small size of the
* timing signal.
*/
var forge = require('./forge');
require('./asn1');
require('./hmac');
require('./md5');
require('./pem');
require('./pki');
require('./random');
require('./sha1');
require('./util');
/**
* Generates pseudo random bytes by mixing the result of two hash functions,
* MD5 and SHA-1.
*
* prf_TLS1(secret, label, seed) =
* P_MD5(S1, label + seed) XOR P_SHA-1(S2, label + seed);
*
* Each P_hash function functions as follows:
*
* P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
* HMAC_hash(secret, A(2) + seed) +
* HMAC_hash(secret, A(3) + seed) + ...
* A() is defined as:
* A(0) = seed
* A(i) = HMAC_hash(secret, A(i-1))
*
* The '+' operator denotes concatenation.
*
* As many iterations A(N) as are needed are performed to generate enough
* pseudo random byte output. If an iteration creates more data than is
* necessary, then it is truncated.
*
* Therefore:
* A(1) = HMAC_hash(secret, A(0))
* = HMAC_hash(secret, seed)
* A(2) = HMAC_hash(secret, A(1))
* = HMAC_hash(secret, HMAC_hash(secret, seed))
*
* Therefore:
* P_hash(secret, seed) =
* HMAC_hash(secret, HMAC_hash(secret, A(0)) + seed) +
* HMAC_hash(secret, HMAC_hash(secret, A(1)) + seed) +
* ...
*
* Therefore:
* P_hash(secret, seed) =
* HMAC_hash(secret, HMAC_hash(secret, seed) + seed) +
* HMAC_hash(secret, HMAC_hash(secret, HMAC_hash(secret, seed)) + seed) +
* ...
*
* @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_TLS1 = function(secret, label, seed, length) {
var rval = forge.util.createBuffer();
/* For TLS 1.0, the secret is split in half, into two secrets of equal
length. If the secret has an odd length then the last byte of the first
half will be the same as the first byte of the second. The length of the
two secrets is half of the secret rounded up. */
var idx = (secret.length >> 1);
var slen = idx + (secret.length & 1);
var s1 = secret.substr(0, slen);
var s2 = secret.substr(idx, slen);
var ai = forge.util.createBuffer();
var hmac = forge.hmac.create();
seed = label + seed;
// determine the number of iterations that must be performed to generate
// enough output bytes, md5 creates 16 byte hashes, sha1 creates 20
var md5itr = Math.ceil(length / 16);
var sha1itr = Math.ceil(length / 20);
// do md5 iterations
hmac.start('MD5', s1);
var md5bytes = forge.util.createBuffer();
ai.putBytes(seed);
for(var i = 0; i < md5itr; ++i) {
// HMAC_hash(secret, A(i-1))
hmac.start(null, null);
hmac.update(ai.getBytes());
ai.putBuffer(hmac.digest());
// HMAC_hash(secret, A(i) + seed)
hmac.start(null, null);
hmac.update(ai.bytes() + seed);
md5bytes.putBuffer(hmac.digest());
}
// do sha1 iterations
hmac.start('SHA1', s2);
var sha1bytes = forge.util.createBuffer();
ai.clear();
ai.putBytes(seed);
for(var i = 0; i < sha1itr; ++i) {
// HMAC_hash(secret, A(i-1))
hmac.start(null, null);
hmac.update(ai.getBytes());
ai.putBuffer(hmac.digest());
// HMAC_hash(secret, A(i) + seed)
hmac.start(null, null);
hmac.update(ai.bytes() + seed);
sha1bytes.putBuffer(hmac.digest());
}
// XOR the md5 bytes with the sha1 bytes
rval.putBytes(forge.util.xorBytes(
md5bytes.getBytes(), sha1bytes.getBytes(), length));
return rval;
};
/**
* Generates pseudo random bytes using a SHA256 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_sha256 = function(secret, label, seed, length) {
// FIXME: implement me for TLS 1.2
};
/**
* Gets a MAC for a record using the SHA-1 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-1 hash (20 bytes) for the given record.
*/
var hmac_sha1 = function(key, seqNum, record) {
/* MAC is computed like so:
HMAC_hash(
key, seqNum +
TLSCompressed.type +
TLSCompressed.version +
TLSCompressed.length +
TLSCompressed.fragment)
*/
var hmac = forge.hmac.create();
hmac.start('SHA1', 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.
*
* @param c the TLS connection.
* @param record the TLSPlaintext record to compress.
* @param s the ConnectionState to use.
*
* @return true on success, false on failure.
*/
var deflate = function(c, record, s) {
var rval = false;
try {
var bytes = c.deflate(record.fragment.getBytes());
record.fragment = forge.util.createBuffer(bytes);
record.length = bytes.length;
rval = true;
} catch(ex) {
// deflate error, fail out
}
return rval;
};
/**
* Decompresses the TLSCompressed record into a TLSPlaintext record using the
* deflate algorithm.
*
* @param c the TLS connection.
* @param record the TLSCompressed record to decompress.
* @param s the ConnectionState to use.
*
* @return true on success, false on failure.
*/
var inflate = function(c, record, s) {
var rval = false;
try {
var bytes = c.inflate(record.fragment.getBytes());
record.fragment = forge.util.createBuffer(bytes);
record.length = bytes.length;
rval = true;
} catch(ex) {
// inflate error, fail out
}
return rval;
};
/**
* Reads a TLS variable-length vector from a byte buffer.
*
* Variable-length vectors are defined by specifying a subrange of legal
* lengths, inclusively, using the notation <floor..ceiling>. When these are
* encoded, the actual length precedes the vector's contents in the byte
* stream. The length will be in the form of a number consuming as many bytes
* as required to hold the vector's specified maximum (ceiling) length. A
* variable-length vector with an actual length field of zero is referred to
* as an empty vector.
*
* @param b the byte buffer.
* @param lenBytes the number of bytes required to store the length.
*
* @return the resulting byte buffer.
*/
var readVector = function(b, lenBytes) {
var len = 0;
switch(lenBytes) {
case 1:
len = b.getByte();
break;
case 2:
len = b.getInt16();
break;
case 3:
len = b.getInt24();
break;
case 4:
len = b.getInt32();
break;
}
// read vector bytes into a new buffer
return forge.util.createBuffer(b.getBytes(len));
};
/**
* Writes a TLS variable-length vector to a byte buffer.
*
* @param b the byte buffer.
* @param lenBytes the number of bytes required to store the length.
* @param v the byte buffer vector.
*/
var writeVector = function(b, lenBytes, v) {
// encode length at the start of the vector, where the number of bytes for
// the length is the maximum number of bytes it would take to encode the
// vector's ceiling
b.putInt(v.length(), lenBytes << 3);
b.putBuffer(v);
};
/**
* The tls implementation.
*/
var tls = {};
/**
* Version: TLS 1.2 = 3.3, TLS 1.1 = 3.2, TLS 1.0 = 3.1. Both TLS 1.1 and
* TLS 1.2 were still too new (ie: openSSL didn't implement them) at the time
* of this implementation so TLS 1.0 was implemented instead.
*/
tls.Versions = {
TLS_1_0: {major: 3, minor: 1},
TLS_1_1: {major: 3, minor: 2},
TLS_1_2: {major: 3, minor: 3}
};
tls.SupportedVersions = [
tls.Versions.TLS_1_1,
tls.Versions.TLS_1_0
];
tls.Version = tls.SupportedVersions[0];
/**
* Maximum fragment size. True maximum is 16384, but we fragment before that
* to allow for unusual small increases during compression.
*/
tls.MaxFragment = 16384 - 1024;
/**
* Whether this entity is considered the "client" or "server".
* enum { server, client } ConnectionEnd;
*/
tls.ConnectionEnd = {
server: 0,
client: 1
};
/**
* Pseudo-random function algorithm used to generate keys from the master
* secret.
* enum { tls_prf_sha256 } PRFAlgorithm;
*/
tls.PRFAlgorithm = {
tls_prf_sha256: 0
};
/**
* Bulk encryption algorithms.
* enum { null, rc4, des3, aes } BulkCipherAlgorithm;
*/
tls.BulkCipherAlgorithm = {
none: null,
rc4: 0,
des3: 1,
aes: 2
};
/**
* Cipher types.
* enum { stream, block, aead } CipherType;
*/
tls.CipherType = {
stream: 0,
block: 1,
aead: 2
};
/**
* MAC (Message Authentication Code) algorithms.
* enum { null, hmac_md5, hmac_sha1, hmac_sha256,
* hmac_sha384, hmac_sha512} MACAlgorithm;
*/
tls.MACAlgorithm = {
none: null,
hmac_md5: 0,
hmac_sha1: 1,
hmac_sha256: 2,
hmac_sha384: 3,
hmac_sha512: 4
};
/**
* Compression algorithms.
* enum { null(0), deflate(1), (255) } CompressionMethod;
*/
tls.CompressionMethod = {
none: 0,
deflate: 1
};
/**
* TLS record content types.
* enum {
* change_cipher_spec(20), alert(21), handshake(22),
* application_data(23), (255)
* } ContentType;
*/
tls.ContentType = {
change_cipher_spec: 20,
alert: 21,
handshake: 22,
application_data: 23,
heartbeat: 24
};
/**
* TLS handshake types.
* enum {
* hello_request(0), client_hello(1), server_hello(2),
* certificate(11), server_key_exchange (12),
* certificate_request(13), server_hello_done(14),
* certificate_verify(15), client_key_exchange(16),
* finished(20), (255)
* } HandshakeType;
*/
tls.HandshakeType = {
hello_request: 0,
client_hello: 1,
server_hello: 2,
certificate: 11,
server_key_exchange: 12,
certificate_request: 13,
server_hello_done: 14,
certificate_verify: 15,
client_key_exchange: 16,
finished: 20
};
/**
* TLS Alert Protocol.
*
* enum { warning(1), fatal(2), (255) } AlertLevel;
*
* enum {
* close_notify(0),
* unexpected_message(10),
* bad_record_mac(20),
* decryption_failed(21),
* record_overflow(22),
* decompression_failure(30),
* handshake_failure(40),
* bad_certificate(42),
* unsupported_certificate(43),
* certificate_revoked(44),
* certificate_expired(45),
* certificate_unknown(46),
* illegal_parameter(47),
* unknown_ca(48),
* access_denied(49),
* decode_error(50),
* decrypt_error(51),
* export_restriction(60),
* protocol_version(70),
* insufficient_security(71),
* internal_error(80),
* user_canceled(90),
* no_renegotiation(100),
* (255)
* } AlertDescription;
*
* struct {
* AlertLevel level;
* AlertDescription description;
* } Alert;
*/
tls.Alert = {};
tls.Alert.Level = {
warning: 1,
fatal: 2
};
tls.Alert.Description = {
close_notify: 0,
unexpected_message: 10,
bad_record_mac: 20,
decryption_failed: 21,
record_overflow: 22,
decompression_failure: 30,
handshake_failure: 40,
bad_certificate: 42,
unsupported_certificate: 43,
certificate_revoked: 44,
certificate_expired: 45,
certificate_unknown: 46,
illegal_parameter: 47,
unknown_ca: 48,
access_denied: 49,
decode_error: 50,
decrypt_error: 51,
export_restriction: 60,
protocol_version: 70,
insufficient_security: 71,
internal_error: 80,
user_canceled: 90,
no_renegotiation: 100
};
/**
* TLS Heartbeat Message types.
* enum {
* heartbeat_request(1),
* heartbeat_response(2),
* (255)
* } HeartbeatMessageType;
*/
tls.HeartbeatMessageType = {
heartbeat_request: 1,
heartbeat_response: 2
};
/**
* Supported cipher suites.
*/
tls.CipherSuites = {};
/**
* Gets a supported cipher suite from its 2 byte ID.
*
* @param twoBytes two bytes in a string.
*
* @return the matching supported cipher suite or null.
*/
tls.getCipherSuite = function(twoBytes) {
var rval = null;
for(var key in tls.CipherSuites) {
var cs = tls.CipherSuites[key];
if(cs.id[0] === twoBytes.charCodeAt(0) &&
cs.id[1] === twoBytes.charCodeAt(1)) {
rval = cs;
break;
}
}
return rval;
};
/**
* Called when an unexpected record is encountered.
*
* @param c the connection.
* @param record the record.
*/
tls.handleUnexpected = function(c, record) {
// if connection is client and closed, ignore unexpected messages
var ignore = (!c.open && c.entity === tls.ConnectionEnd.client);
if(!ignore) {
c.error(c, {
message: 'Unexpected message. Received TLS record out of order.',
send: true,
alert: {
level: tls.Alert.Level.fatal,
description: tls.Alert.Description.unexpected_message
}
});
}
};
/**
* Called when a client receives a HelloRequest record.
*
* @param c the connection.
* @param record the record.
* @param length the length of the handshake message.
*/
tls.handleHelloRequest = function(c, record, length) {
// ignore renegotiation requests from the server during a handshake, but
// if handshaking, send a warning alert that renegotation is denied
if(!c.handshaking && c.handshakes > 0) {
// send alert warning
tls.queue(c, tls.createAlert(c, {
level: tls.Alert.Level.warning,
description: tls.Alert.Description.no_renegotiation
}));
tls.flush(c);
}
// continue
c.process();
};
/**
* Parses a hello message from a ClientHello or ServerHello record.
*
* @param record the record to parse.
*
* @return the parsed message.
*/
tls.parseHelloMessage = function(c, record, length) {
var msg = null;
var client = (c.entity === tls.ConnectionEnd.client);
// minimum of 38 bytes in message
if(length < 38) {
c.error(c, {
message: client ?
'Invalid ServerHello message. Message too short.' :
'Invalid ClientHello message. Message too short.',
send: true,
alert: {
level: tls.Alert.Level.fatal,
description: tls.Alert.Description.illegal_parameter
}
});
} else {
// use 'remaining' to calculate # of remaining bytes in the message
var b = record.fragment;
var remaining = b.length();
msg = {
version: {
major: b.getByte(),
minor: b.getByte()
},
random: forge.util.createBuffer(b.getBytes(32)),
session_id: readVector(b, 1),
extensions: []
};
if(client) {
msg.cipher_suite = b.getBytes(2);
msg.compression_method = b.getByte();
} else {
msg.cipher_suites = readVector(b, 2);
msg.compression_methods = readVector(b, 1);
}
// read extensions if there are any bytes left in the message
remaining = length - (remaining - b.length());
if(remaining > 0) {
// parse extensions
var exts = readVector(b, 2);
while(exts.length() > 0) {
msg.extensions.push({
type: [exts.getByte(), exts.getByte()],
data: readVector(exts, 2)
});
}
// TODO: make extension support modular
if(!client) {
for(var i = 0; i < msg.extensions.length; ++i) {
var ext = msg.extensions[i];
// support SNI extension
if(ext.type[0] === 0x00 && ext.type[1] === 0x00) {
// get server name list
var snl = readVector(ext.data, 2);
while(snl.length() > 0) {
// read server name type
var snType = snl.getByte();
// only HostName type (0x00) is known, break out if
// another type is detected
if(snType !== 0x00) {
break;
}
// add host name to server name list
c.session.extensions.server_name.serverNameList.push(
readVector(snl, 2).getBytes());
}
}
}
}
}
// version already set, do not allow version change
if(c.session.version) {
if(msg.version.major !== c.session.version.major ||
msg.version.minor !== c.session.version.minor) {
return c.error(c, {
message: 'TLS version change is disallowed during renegotiation.',
send: true,
alert: {
level: tls.Alert.Level.fatal,
description: tls.Alert.Description.protocol_version
}
});
}
}
// get the chosen (ServerHello) cipher suite
if(client) {
// FIXME: should be checking configured acceptable cipher suites
c.session.cipherSuite = tls.getCipherSuite(msg.cipher_suite);
} else {
// get a supported preferred (ClientHello) cipher suite
// choose the first supported cipher suite
var tmp = forge.util.createBuffer(msg.cipher_suites.bytes());
while(tmp.length() > 0) {
// FIXME: should be checking configured acceptable suites
// cipher suites take up 2 bytes
c.session.cipherSuite = tls.getCipherSuite(tmp.getBytes(2));
if(c.session.cipherSuite !== null) {
break;
}
}
}
// cipher suite not supported
if(c.session.cipherSuite === null) {
return c.error(c, {
message: 'No cipher suites in common.',
send: true,
alert: {
level: tls.Alert.Level.fatal,
description: tls.Alert.Description.handshake_failure
},
cipherSuite: forge.util.bytesToHex(msg.cipher_suite)
});
}
// TODO: handle compression methods
if(client) {
c.session.compressionMethod = msg.compression_method;
} else {
// no compression
c.session.compressionMethod = tls.CompressionMethod.none;
}
}
return msg;
};
/**
* Creates security parameters for the given connection based on the given
* hello message.
*
* @param c the TLS connection.
* @param msg the hello message.
*/
tls.createSecurityParameters = function(c, msg) {
/* Note: security params are from TLS 1.2, some values like prf_algorithm
are ignored for TLS 1.0/1.1 and the builtin as specified in the spec is
used. */
// TODO: handle other options from server when more supported
// get client and server randoms
var client = (c.entity === tls.ConnectionEnd.client);
var msgRandom = msg.random.bytes();
var cRandom = client ? c.session.sp.client_random : msgRandom;
var sRandom = client ? msgRandom : tls.createRandom().getBytes();
// create new security parameters
c.session.sp = {
entity: c.entity,
prf_algorithm: tls.PRFAlgorithm.tls_prf_sha256,
bulk_cipher_algorithm: null,
cipher_type: null,
enc_key_length: null,
block_length: null,
fixed_iv_length: null,
record_iv_length: null,
mac_algorithm: null,
mac_length: null,
mac_key_length: null,
compression_algorithm: c.session.compressionMethod,
pre_master_secret: null,
master_secret: null,
client_random: cRandom,
server_random: sRandom
};
};
/**
* Called when a client receives a ServerHello record.
*
* When a ServerHello message will be sent:
* The server will send this message in response to a client hello message
* when it was able to find an acceptable set of algorithms. If it cannot
* find such a match, it will respond with a handshake failure alert.
*
* uint24 length;
* struct {
* ProtocolVersion server_version;
* Random random;
* SessionID session_id;
* CipherSuite cipher_suite;
* CompressionMethod compression_method;
* select(extensions_present) {
* case false:
* struct {};
* case true:
* Extension extensions<0..2^16-1>;
* };
* } ServerHello;
*
* @param c the connection.
* @param record the record.
* @param length the length of the handshake message.
*/
tls.handleServerHello = function(c, record, length) {
var msg = tls.parseHelloMessage(c, record, length);
if(c.fail) {
return;
}
// ensure server version is compatible
if(msg.version.minor <= c.version.minor) {
c.version.minor = msg.version.minor;
} else {
return c.error(c, {
message: 'Incompatible TLS version.',
send: true,
alert: {
level: tls.Alert.Level.fatal,
description: tls.Alert.Description.protocol_version
}
});
}
// indicate session version has been set
c.session.version = c.version;
// get the session ID from the message
var sessionId = msg.session_id.bytes();
// if the session ID is not blank and matches the cached one, resume
// the session
if(sessionId.length > 0 && sessionId === c.session.id) {
// resuming session, expect a ChangeCipherSpec next
c.expect = SCC;
c.session.resuming = true;
// get new server random
c.session.sp.server_random = msg.random.bytes();
} else {
// not resuming, expect a server Certificate message next
c.expect = SCE;
c.session.resuming = false;
// create new security parameters
tls.createSecurityParameters(c, msg);
}
// set new session ID
c.session.id = sessionId;
// continue
c.process();
};
/**
* Called when a server receives a ClientHello record.
*
* When a ClientHello message will be sent:
* When a client first connects to a server it is required to send the
* client hello as its first message. The client can also send a client
* hello in response to a hello request or on its own initiative in order
* to renegotiate the security parameters in an existing connection.
*
* @param c the connection.
* @param record the record.
* @param length the length of the handshake message.
*/
tls.handleClientHello = function(c, record, length) {
var msg = tls.parseHelloMessage(c, record, length);
if(c.fail) {
return;
}
// get the session ID from the message
var sessionId = msg.session_id.bytes();
// see if the given session ID is in the cache
var session = null;
if(c.sessionCache) {
session = c.sessionCache.getSession(sessionId);
if(session === null) {
// session ID not found
sessionId = '';
} else if(session.version.major !== msg.version.major ||
session.version.minor > msg.version.minor) {
// if session version is incompatible with client version, do not resume
session = null;
sessionId = '';
}
}
// no session found to resume, generate a new session ID
if(sessionId.length === 0) {
sessionId = forge.random.getBytes(32);
}
// update session
c.session.id = sessionId;
c.session.clientHelloVersion = msg.version;
c.session.sp = {};
if(session) {
// use version and security parameters from resumed session
c.version = c.session.version = session.version;
c.session.sp = session.sp;
} else {
// use highest compatible minor version
var version;
for(var i = 1; i < tls.SupportedVersions.length; ++i) {
version = tls.SupportedVersions[i];
if(version.minor <= msg.version.minor) {
break;
}
}
c.version = {major: version.major, minor: version.minor};
c.session.version = c.version;
}
// if a session is set, resume it
if(session !== null) {
// resuming session, expect a ChangeCipherSpec next
c.expect = CCC;
c.session.resuming = true;
// get new client random
c.session.sp.client_random = msg.random.bytes();
} else {
// not resuming, expect a Certificate or ClientKeyExchange
c.expect = (c.verifyClient !== false) ? CCE : CKE;
c.session.resuming = false;
// create new security parameters
tls.createSecurityParameters(c, msg);
}
// connection now open
c.open = true;
// queue server hello
tls.queue(c, tls.createRecord(c, {
type: tls.ContentType.handshake,
data: tls.createServerHello(c)
}));
if(c.session.resuming) {
// queue change cipher spec message
tls.queue(c, tls.createRecord(c, {
type: tls.ContentType.change_cipher_spec,
data: tls.createChangeCipherSpec()
}));
// create pending state
c.state.pending = tls.createConnectionState(c);
// change current write state to pending write state
c.state.current.write = c.state.pending.write;
// queue finished
tls.queue(c, tls.createRecord(c, {
type: tls.ContentType.handshake,
data: tls.createFinished(c)
}));
} else {
// queue server certificate
tls.queue(c, tls.createRecord(c, {
type: tls.ContentType.handshake,
data: tls.createCertificate(c)
}));
if(!c.fail) {
// queue server key exchange
tls.queue(c, tls.createRecord(c, {
type: tls.ContentType.handshake,
data: tls.createServerKeyExchange(c)
}));
// request client certificate if set
if(c.verifyClient !== false) {
// queue certificate request
tls.queue(c, tls.createRecord(c, {
type: tls.ContentType.handshake,
data: tls.createCertificateRequest(c)
}));
}
// queue server hello done
tls.queue(c, tls.createRecord(c, {
type: tls.ContentType.handshake,
data: tls.createServerHelloDone(c)
}));
}
}
// send records
tls.flush(c);
// continue
c.process();
};
/**
* Called when a client receives a Certificate record.
*
* When this message will be sent:
* The server must send a certificate whenever the agreed-upon key exchange
* method is not an anonymous one. This message will always immediately
* follow the server hello message.
*
* Meaning of this message:
* The certificate type must be appropriate for the selected cipher suite's
* key exchange algorithm, and is generally an X.509v3 certificate. It must
* contain a key which matches the key exchange method, as follows. Unless
* otherwise specified, the signing algorithm for the certificate must be
* the same as the algorithm for the certificate key. Unless otherwise
* specified, the public key may be of any length.
*
* opaque ASN.1Cert<1..2^24-1>;
* struct {
* ASN.1Cert certificate_list<1..2^24-1>;
* } Certificate;
*
* @param c the connection.
* @param record the record.
* @param length the length of the handshake message.
*/
tls.handleCertificate = function(c, record, length) {
// minimum of 3 bytes in message
if(length < 3) {
return c.error(c, {
message: 'Invalid Certificate message. Message too short.',
send: true,
alert: {
level: tls.Alert.Level.fatal,
description: tls.Alert.Description.illegal_parameter
}
});
}
var b = record.fragment;
var msg = {
certificate_list: readVector(b, 3)
};
/* The sender's certificate will be first in the list (chain), each
subsequent one that follows will certify the previous one, but root
certificates (self-signed) that specify the certificate authority may
be omitted under the assumption that clients must already possess it. */
var cert, asn1;
var certs = [];
try {
while(msg.certificate_list.length() > 0) {
// each entry in msg.certificate_list is a vector with 3 len bytes
cert = readVector(msg.certificate_list, 3);
asn1 = forge.asn1.fromDer(cert);
cert = forge.pki.certificateFromAsn1(asn1, true);
certs.push(cert);
}
} catch(ex) {
return c.error(c, {
message: 'Could not parse certificate list.',
cause: ex,
send: true,
alert: {
level: tls.Alert.Level.fatal,
description: tls.Alert.Description.bad_certificate
}
});
}
// ensure at least 1 certificate was provided if in client-mode
// or if verifyClient was set to true to require a certificate
// (as opposed to 'optional')
var client = (c.entity === tls.ConnectionEnd.client);
if((client || c.verifyClient === true) && certs.length === 0) {
// error, no certificate
c.error(c, {
message: client ?
'No server certificate provided.' :
'No client certificate provided.',
send: true,
alert: {
level: tls.Alert.Level.fatal,
description: tls.Alert.Description.illegal_parameter
}
});
} else if(certs.length === 0) {
// no certs to verify
// expect a ServerKeyExchange or ClientKeyExchange message next
c.expect = client ? SKE : CKE;
} else {
// save certificate in session
if(client) {
c.session.serverCertificate = certs[0];
} else {
c.session.clientCertificate = certs[0];
}
if(tls.verifyCertificateChain(c, certs)) {
// expect a ServerKeyExchange or ClientKeyExchange message next
c.expect = client ? SKE : CKE;
}
}
// continue
c.process();
};
/**
* Called when a client receives a ServerKeyExchange record.
*
* When this message will be sent:
* This message will be sent immediately after the server certificate
* message (or the server hello message, if this is an anonymous
* negotiation).
*
* The server key exchange message is sent by the server only when the
* server certificate message (if sent) does not contain enough data to
* allow the client to exchange a premaster secret.
*
* Meaning of this message:
* This message conveys cryptographic information to allow the client to
* communicate the premaster secret: either an RSA public key to encrypt
* the premaster secret with, or a Diffie-Hellman public key with which the
* client can complete a key exchange (with the result being the premaster
* secret.)
*
* enum {
* dhe_dss, dhe_rsa, dh_anon, rsa, dh_dss, dh_rsa
* } KeyExchangeAlgorithm;
*
* struct {
* opaque dh_p<1..2^16-1>;
* opaque dh_g<1..2^16-1>;
* opaque dh_Ys<1..2^16-1>;
* } ServerDHParams;
*
* struct {
* select(KeyExchangeAlgorithm) {
* case dh_anon:
* ServerDHParams params;
* case dhe_dss:
* case dhe_rsa:
* ServerDHParams params;
* digitally-signed struct {
* opaque client_random[32];
* opaque server_random[32];
* ServerDHParams params;
* } signed_params;
* case rsa:
* case dh_dss:
* case dh_rsa:
* struct {};
* };
* } ServerKeyExchange;
*
* @param c the connection.
* @param record the record.
* @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
}
});
}
// expect an optional CertificateRequest message next
c.expect = SCR;
// continue
c.process();
};
/**
* Called when a client receives a ClientKeyExchange record.
*
* @param c the connection.
* @param record the record.
* @param length the length of the handshake message.
*/
tls.handleClientKeyExchange = function(c, record, length) {
// this implementation only supports RSA, no Diffie-Hellman support
// so any length < 48 is invalid
if(length < 48) {
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
}
});
}
var b = record.fragment;
var msg = {
enc_pre_master_secret: readVector(b, 2).getBytes()
};
// do rsa decryption
var privateKey = null;
if(c.getPrivateKey) {
try {
privateKey = c.getPrivateKey(c, c.session.serverCertificate);
privateKey = forge.pki.privateKeyFromPem(privateKey);
} catch(ex) {
c.error(c, {
message: 'Could not get private key.',
cause: ex,
send: true,
alert: {
level: tls.Alert.Level.fatal,
description: tls.Alert.Description.internal_error
}
});
}
}
if(privateKey === null) {
return c.error(c, {
message: 'No private key set.',
send: true,
alert: {
level: tls.Alert.Level.fatal,
description: tls.Alert.Description.internal_error
}
});
}
try {
// decrypt 48-byte pre-master secret
var sp = c.session.sp;
sp.pre_master_secret = privateKey.decrypt(msg.enc_pre_master_secret);
// ensure client hello version matches first 2 bytes
var version = c.session.clientHelloVersion;
if(version.major !== sp.pre_master_secret.charCodeAt(0) ||
version.minor !== sp.pre_master_secret.charCodeAt(1)) {
// error, do not send alert (see BLEI attack below)
throw new Error('TLS version rollback attack detected.');
}
} catch(ex) {
/* Note: Daniel Bleichenbacher [BLEI] can be used to attack a
TLS server which is using PKCS#1 encoded RSA, so instead of
failing here, we generate 48 random bytes and use that as
the pre-master secret. */
sp.pre_master_secret = forge.random.getBytes(48);
}
// expect a CertificateVerify message if a Certificate was received that
// does not have fixed Diffie-Hellman params, otherwise expect
// ChangeCipherSpec
c.expect = CCC;
if(c.session.clientCertificate !== null) {
// only RSA support, so expect CertificateVerify
// TODO: support Diffie-Hellman
c.expect = CCV;
}
// continue
c.process();
};
/**
* Called when a client receives a CertificateRequest record.
*
* When this message will be sent:
* A non-anonymous server can optionally request a certificate from the
* client, if appropriate for the selected cipher suite. This message, if
* sent, will immediately follow the Server Key Exchange message (if it is
* sent; otherwise, the Server Certificate message).
*
* enum {
* rsa_sign(1), dss_sign(2), rsa_fixed_dh(3), dss_fixed_dh(4),
* rsa_ephemeral_dh_RESERVED(5), dss_ephemeral_dh_RESERVED(6),
* fortezza_dms_RESERVED(20), (255)
* } ClientCertificateType;
*
* opaque DistinguishedName<1..2^16-1>;
*
* struct {
* ClientCertificateType certificate_types<1..2^8-1>;
* SignatureAndHashAlgorithm supported_signature_algorithms<2^16-1>;
* DistinguishedName certificate_authorities<0..2^16-1>;
* } CertificateRequest;
*
* @param c the connection.
* @param record the record.
* @param length the length of the handshake message.
*/
tls.handleCertificateRequest = function(c, record, length) {
// minimum of 3 bytes in message
if(length < 3) {
return c.error(c, {
message: 'Invalid CertificateRequest. Message too short.',
send: true,
alert: {
level: tls.Alert.Level.fatal,
description: tls.Alert.Description.illegal_parameter
}
});
}
// TODO: TLS 1.2+ has different format including
// SignatureAndHashAlgorithm after cert types
var b = record.fragment;
var msg = {
certificate_types: readVector(b, 1),
certificate_authorities: readVector(b, 2)
};
// save certificate request in session
c.session.certificateRequest = msg;
// expect a ServerHelloDone message next
c.expect = SHD;
// continue
c.process();
};
/**
* Called when a server receives a CertificateVerify record.
*
* @param c the connection.
* @param record the record.
* @param length the length of the handshake message.
*/
tls.handleCertificateVerify = function(c, record, length) {
if(length < 2) {
return c.error(c, {
message: 'Invalid CertificateVerify. Message too short.',
send: true,
alert: {
level: tls.Alert.Level.fatal,
description: tls.Alert.Description.illegal_parameter
}
});
}
// rewind to get full bytes for message so it can be manually
// digested below (special case for CertificateVerify messages because
// they must be digested *after* handling as opposed to all others)
var b = record.fragment;
b.read -= 4;
var msgBytes = b.bytes();
b.read += 4;
var msg = {
signature: readVector(b, 2).getBytes()
};
// TODO: add support for DSA
// generate data to verify
var verify = forge.util.createBuffer();
verify.putBuffer(c.session.md5.digest());
verify.putBuffer(c.session.sha1.digest());
verify = verify.getBytes();
try {
var cert = c.session.clientCertificate;
/*b = forge.pki.rsa.decrypt(
msg.signature, cert.publicKey, true, verify.length);
if(b !== verify) {*/
if(!cert.publicKey.verify(verify, msg.signature, 'NONE')) {
throw new Error('CertificateVerify signature does not match.');
}
// digest message now that it has been handled
c.session.md5.update(msgBytes);
c.session.sha1.update(msgBytes);
} catch(ex) {
return c.error(c, {
message: 'Bad signature in CertificateVerify.',
send: true,
alert: {
level: tls.Alert.Level.fatal,
description: tls.Alert.Description.handshake_failure
}
});
}
// expect ChangeCipherSpec
c.expect = CCC;
// continue
c.process();
};
/**
* Called when a client receives a ServerHelloDone record.
*
* When this message will be sent:
* The server hello done message is sent by the server to indicate the end
* of the server hello and associated messages. After sending this message
* the server will wait for a client response.
*
* Meaning of this message:
* This message means that the server is done sending messages to support
* the key exchange, and the client can proceed with its phase of the key
* exchange.
*
* Upon receipt of the server hello done message the client should verify
* that the server provided a valid certificate if required and check that
* the server hello parameters are acceptable.
*
* struct {} ServerHelloDone;
*
* @param c the connection.
* @param record the record.
* @param length the length of the handshake message.
*/
tls.handleServerHelloDone = function(c, record, length) {
// len must be 0 bytes
if(length > 0) {
return c.error(c, {
message: 'Invalid ServerHelloDone message. Invalid length.',
send: true,
alert: {
level: tls.Alert.Level.fatal,
description: tls.Alert.Description.record_overflow
}
});
}
if(c.serverCertificate === null) {
// no server certificate was provided
var error = {
message: 'No server certificate provided. Not enough security.',
send: true,
alert: {
level: tls.Alert.Level.fatal,
description: tls.Alert.Description.insufficient_security
}
};
// call application callback
var depth = 0;
var ret = c.verify(c, error.alert.description, depth, []);
if(ret !== true) {
// check for custom alert info
if(ret || ret === 0) {
// set custom message and alert description
if(typeof ret === 'object' && !forge.util.isArray(ret)) {
if(ret.message) {
error.message = ret.message;
}
if(ret.alert) {
error.alert.description = ret.alert;
}
} else if(typeof ret === 'number') {
// set custom alert description
error.alert.description = ret;
}
}
// send error
return c.error(c, error);
}
}
// create client certificate message if requested
if(c.session.certificateRequest !== null) {
record = tls.createRecord(c, {
type: tls.ContentType.handshake,
data: tls.createCertificate(c)
});
tls.queue(c, record);
}
// create client key exchange message
record = tls.createRecord(c, {
type: tls.ContentType.handshake,
data: tls.createClientKeyExchange(c)
});
tls.queue(c, record);
// expect no messages until the following callback has been called
c.expect = SER;
// create callback to handle client signature (for client-certs)
var callback = function(c, signature) {
if(c.session.certificateRequest !== null &&
c.session.clientCertificate !== null) {
// create certificate verify message
tls.queue(c, tls.createRecord(c, {
type: tls.ContentType.handshake,
data: tls.createCertificateVerify(c, signature)
}));
}
// create change cipher spec message
tls.queue(c, tls.createRecord(c, {
type: tls.ContentType.change_cipher_spec,
data: tls.createChangeCipherSpec()
}));
// create pending state
c.state.pending = tls.createConnect