summaryrefslogtreecommitdiff
path: root/school/node_modules/node-forge/js/tls.js
diff options
context:
space:
mode:
authorMinteck <contact@minteck.org>2023-01-10 14:54:04 +0100
committerMinteck <contact@minteck.org>2023-01-10 14:54:04 +0100
commit99c1d9af689e5325f3cf535c4007b3aeb8325229 (patch)
treee663b3c2ebdbd67c818ac0c5147f0ce1d2463cda /school/node_modules/node-forge/js/tls.js
parent9871b03912fc28ad38b4037ebf26a78aa937baba (diff)
downloadpluralconnect-99c1d9af689e5325f3cf535c4007b3aeb8325229.tar.gz
pluralconnect-99c1d9af689e5325f3cf535c4007b3aeb8325229.tar.bz2
pluralconnect-99c1d9af689e5325f3cf535c4007b3aeb8325229.zip
Update - This is an automated commit
Diffstat (limited to 'school/node_modules/node-forge/js/tls.js')
-rw-r--r--school/node_modules/node-forge/js/tls.js4316
1 files changed, 4316 insertions, 0 deletions
diff --git a/school/node_modules/node-forge/js/tls.js b/school/node_modules/node-forge/js/tls.js
new file mode 100644
index 0000000..b3bb2e8
--- /dev/null
+++ b/school/node_modules/node-forge/js/tls.js
@@ -0,0 +1,4316 @@
+/**
+ * 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.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/**
+ * 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.createConnectionState(c);
+
+ // change current write state to pending write state
+ c.state.current.write = c.state.pending.write;
+
+ // create finished message
+ tls.queue(c, tls.createRecord(c, {
+ type: tls.ContentType.handshake,
+ data: tls.createFinished(c)
+ }));
+
+ // expect a server ChangeCipherSpec message next
+ c.expect = SCC;
+
+ // send records
+ tls.flush(c);
+
+ // continue
+ c.process();
+ };
+
+ // if there is no certificate request or no client certificate, do
+ // callback immediately
+ if(c.session.certificateRequest === null ||
+ c.session.clientCertificate === null) {
+ return callback(c, null);
+ }
+
+ // otherwise get the client signature
+ tls.getClientSignature(c, callback);
+};
+
+/**
+ * Called when a ChangeCipherSpec record is received.
+ *
+ * @param c the connection.
+ * @param record the record.
+ */
+tls.handleChangeCipherSpec = function(c, record) {
+ if(record.fragment.getByte() !== 0x01) {
+ return c.error(c, {
+ message: 'Invalid ChangeCipherSpec message received.',
+ send: true,
+ alert: {
+ level: tls.Alert.Level.fatal,
+ description: tls.Alert.Description.illegal_parameter
+ }
+ });
+ }
+
+ // create pending state if:
+ // 1. Resuming session in client mode OR
+ // 2. NOT resuming session in server mode
+ var client = (c.entity === tls.ConnectionEnd.client);
+ if((c.session.resuming && client) || (!c.session.resuming && !client)) {
+ c.state.pending = tls.createConnectionState(c);
+ }
+
+ // change current read state to pending read state
+ c.state.current.read = c.state.pending.read;
+
+ // clear pending state if:
+ // 1. NOT resuming session in client mode OR
+ // 2. resuming a session in server mode
+ if((!c.session.resuming && client) || (c.session.resuming && !client)) {
+ c.state.pending = null;
+ }
+
+ // expect a Finished record next
+ c.expect = client ? SFI : CFI;
+
+ // continue
+ c.process();
+};
+
+/**
+ * Called when a Finished record is received.
+ *
+ * When this message will be sent:
+ * A finished message is always sent immediately after a change
+ * cipher spec message to verify that the key exchange and
+ * authentication processes were successful. It is essential that a
+ * change cipher spec message be received between the other
+ * handshake messages and the Finished message.
+ *
+ * Meaning of this message:
+ * The finished message is the first protected with the just-
+ * negotiated algorithms, keys, and secrets. Recipients of finished
+ * messages must verify that the contents are correct. Once a side
+ * has sent its Finished message and received and validated the
+ * Finished message from its peer, it may begin to send and receive
+ * application data over the connection.
+ *
+ * struct {
+ * opaque verify_data[verify_data_length];
+ * } Finished;
+ *
+ * verify_data
+ * PRF(master_secret, finished_label, Hash(handshake_messages))
+ * [0..verify_data_length-1];
+ *
+ * finished_label
+ * For Finished messages sent by the client, the string
+ * "client finished". For Finished messages sent by the server, the
+ * string "server finished".
+ *
+ * verify_data_length depends on the cipher suite. If it is not specified
+ * by the cipher suite, then it is 12. Versions of TLS < 1.2 always used
+ * 12 bytes.
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleFinished = function(c, record, length) {
+ // rewind to get full bytes for message so it can be manually
+ // digested below (special case for Finished 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;
+
+ // message contains only verify_data
+ var vd = record.fragment.getBytes();
+
+ // ensure verify data is correct
+ b = forge.util.createBuffer();
+ b.putBuffer(c.session.md5.digest());
+ b.putBuffer(c.session.sha1.digest());
+
+ // set label based on entity type
+ 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
+ var sp = c.session.sp;
+ var vdl = 12;
+ var prf = prf_TLS1;
+ b = prf(sp.master_secret, label, b.getBytes(), vdl);
+ if(b.getBytes() !== vd) {
+ return c.error(c, {
+ message: 'Invalid verify_data in Finished message.',
+ send: true,
+ alert: {
+ level: tls.Alert.Level.fatal,
+ description: tls.Alert.Description.decrypt_error
+ }
+ });
+ }
+
+ // digest finished message now that it has been handled
+ c.session.md5.update(msgBytes);
+ c.session.sha1.update(msgBytes);
+
+ // resuming session as client or NOT resuming session as server
+ if((c.session.resuming && client) || (!c.session.resuming && !client)) {
+ // create change cipher spec message
+ tls.queue(c, tls.createRecord(c, {
+ type: tls.ContentType.change_cipher_spec,
+ data: tls.createChangeCipherSpec()
+ }));
+
+ // change current write state to pending write state, clear pending
+ c.state.current.write = c.state.pending.write;
+ c.state.pending = null;
+
+ // create finished message
+ tls.queue(c, tls.createRecord(c, {
+ type: tls.ContentType.handshake,
+ data: tls.createFinished(c)
+ }));
+ }
+
+ // expect application data next
+ c.expect = client ? SAD : CAD;
+
+ // handshake complete
+ c.handshaking = false;
+ ++c.handshakes;
+
+ // save access to peer certificate
+ c.peerCertificate = client ?
+ c.session.serverCertificate : c.session.clientCertificate;
+
+ // send records
+ tls.flush(c);
+
+ // now connected
+ c.isConnected = true;
+ c.connected(c);
+
+ // continue
+ c.process();
+};
+
+/**
+ * Called when an Alert record is received.
+ *
+ * @param c the connection.
+ * @param record the record.
+ */
+tls.handleAlert = function(c, record) {
+ // read alert
+ var b = record.fragment;
+ var alert = {
+ level: b.getByte(),
+ description: b.getByte()
+ };
+
+ // TODO: consider using a table?
+ // get appropriate message
+ var msg;
+ switch(alert.description) {
+ case tls.Alert.Description.close_notify:
+ msg = 'Connection closed.';
+ break;
+ case tls.Alert.Description.unexpected_message:
+ msg = 'Unexpected message.';
+ break;
+ case tls.Alert.Description.bad_record_mac:
+ msg = 'Bad record MAC.';
+ break;
+ case tls.Alert.Description.decryption_failed:
+ msg = 'Decryption failed.';
+ break;
+ case tls.Alert.Description.record_overflow:
+ msg = 'Record overflow.';
+ break;
+ case tls.Alert.Description.decompression_failure:
+ msg = 'Decompression failed.';
+ break;
+ case tls.Alert.Description.handshake_failure:
+ msg = 'Handshake failure.';
+ break;
+ case tls.Alert.Description.bad_certificate:
+ msg = 'Bad certificate.';
+ break;
+ case tls.Alert.Description.unsupported_certificate:
+ msg = 'Unsupported certificate.';
+ break;
+ case tls.Alert.Description.certificate_revoked:
+ msg = 'Certificate revoked.';
+ break;
+ case tls.Alert.Description.certificate_expired:
+ msg = 'Certificate expired.';
+ break;
+ case tls.Alert.Description.certificate_unknown:
+ msg = 'Certificate unknown.';
+ break;
+ case tls.Alert.Description.illegal_parameter:
+ msg = 'Illegal parameter.';
+ break;
+ case tls.Alert.Description.unknown_ca:
+ msg = 'Unknown certificate authority.';
+ break;
+ case tls.Alert.Description.access_denied:
+ msg = 'Access denied.';
+ break;
+ case tls.Alert.Description.decode_error:
+ msg = 'Decode error.';
+ break;
+ case tls.Alert.Description.decrypt_error:
+ msg = 'Decrypt error.';
+ break;
+ case tls.Alert.Description.export_restriction:
+ msg = 'Export restriction.';
+ break;
+ case tls.Alert.Description.protocol_version:
+ msg = 'Unsupported protocol version.';
+ break;
+ case tls.Alert.Description.insufficient_security:
+ msg = 'Insufficient security.';
+ break;
+ case tls.Alert.Description.internal_error:
+ msg = 'Internal error.';
+ break;
+ case tls.Alert.Description.user_canceled:
+ msg = 'User canceled.';
+ break;
+ case tls.Alert.Description.no_renegotiation:
+ msg = 'Renegotiation not supported.';
+ break;
+ default:
+ msg = 'Unknown error.';
+ break;
+ }
+
+ // close connection on close_notify, not an error
+ if(alert.description === tls.Alert.Description.close_notify) {
+ return c.close();
+ }
+
+ // call error handler
+ c.error(c, {
+ message: msg,
+ send: false,
+ // origin is the opposite end
+ origin: (c.entity === tls.ConnectionEnd.client) ? 'server' : 'client',
+ alert: alert
+ });
+
+ // continue
+ c.process();
+};
+
+/**
+ * Called when a Handshake record is received.
+ *
+ * @param c the connection.
+ * @param record the record.
+ */
+tls.handleHandshake = function(c, record) {
+ // get the handshake type and message length
+ var b = record.fragment;
+ var type = b.getByte();
+ var length = b.getInt24();
+
+ // see if the record fragment doesn't yet contain the full message
+ if(length > b.length()) {
+ // cache the record, clear its fragment, and reset the buffer read
+ // pointer before the type and length were read
+ c.fragmented = record;
+ record.fragment = forge.util.createBuffer();
+ b.read -= 4;
+
+ // continue
+ return c.process();
+ }
+
+ // full message now available, clear cache, reset read pointer to
+ // before type and length
+ c.fragmented = null;
+ b.read -= 4;
+
+ // save the handshake bytes for digestion after handler is found
+ // (include type and length of handshake msg)
+ var bytes = b.bytes(length + 4);
+
+ // restore read pointer
+ b.read += 4;
+
+ // handle expected message
+ if(type in hsTable[c.entity][c.expect]) {
+ // initialize server session
+ if(c.entity === tls.ConnectionEnd.server && !c.open && !c.fail) {
+ c.handshaking = true;
+ c.session = {
+ version: null,
+ extensions: {
+ server_name: {
+ serverNameList: []
+ }
+ },
+ cipherSuite: null,
+ compressionMethod: null,
+ serverCertificate: null,
+ clientCertificate: null,
+ md5: forge.md.md5.create(),
+ sha1: forge.md.sha1.create()
+ };
+ }
+
+ /* Update handshake messages digest. Finished and CertificateVerify
+ messages are not digested here. They can't be digested as part of
+ the verify_data that they contain. These messages are manually
+ digested in their handlers. HelloRequest messages are simply never
+ included in the handshake message digest according to spec. */
+ if(type !== tls.HandshakeType.hello_request &&
+ type !== tls.HandshakeType.certificate_verify &&
+ type !== tls.HandshakeType.finished) {
+ c.session.md5.update(bytes);
+ c.session.sha1.update(bytes);
+ }
+
+ // handle specific handshake type record
+ hsTable[c.entity][c.expect][type](c, record, length);
+ } else {
+ // unexpected record
+ tls.handleUnexpected(c, record);
+ }
+};
+
+/**
+ * Called when an ApplicationData record is received.
+ *
+ * @param c the connection.
+ * @param record the record.
+ */
+tls.handleApplicationData = function(c, record) {
+ // buffer data, notify that its ready
+ c.data.putBuffer(record.fragment);
+ c.dataReady(c);
+
+ // continue
+ c.process();
+};
+
+/**
+ * Called when a Heartbeat record is received.
+ *
+ * @param c the connection.
+ * @param record the record.
+ */
+tls.handleHeartbeat = function(c, record) {
+ // get the heartbeat type and payload
+ var b = record.fragment;
+ var type = b.getByte();
+ var length = b.getInt16();
+ var payload = b.getBytes(length);
+
+ if(type === tls.HeartbeatMessageType.heartbeat_request) {
+ // discard request during handshake or if length is too large
+ if(c.handshaking || length > payload.length) {
+ // continue
+ return c.process();
+ }
+ // retransmit payload
+ tls.queue(c, tls.createRecord(c, {
+ type: tls.ContentType.heartbeat,
+ data: tls.createHeartbeat(
+ tls.HeartbeatMessageType.heartbeat_response, payload)
+ }));
+ tls.flush(c);
+ } else if(type === tls.HeartbeatMessageType.heartbeat_response) {
+ // check payload against expected payload, discard heartbeat if no match
+ if(payload !== c.expectedHeartbeatPayload) {
+ // continue
+ return c.process();
+ }
+
+ // notify that a valid heartbeat was received
+ if(c.heartbeatReceived) {
+ c.heartbeatReceived(c, forge.util.createBuffer(payload));
+ }
+ }
+
+ // continue
+ c.process();
+};
+
+/**
+ * The transistional state tables for receiving TLS records. It maps the
+ * current TLS engine state and a received record to a function to handle the
+ * record and update the state.
+ *
+ * For instance, if the current state is SHE, then the TLS engine is expecting
+ * a ServerHello record. Once a record is received, the handler function is
+ * looked up using the state SHE and the record's content type.
+ *
+ * The resulting function will either be an error handler or a record handler.
+ * The function will take whatever action is appropriate and update the state
+ * for the next record.
+ *
+ * The states are all based on possible server record types. Note that the
+ * client will never specifically expect to receive a HelloRequest or an alert
+ * from the server so there is no state that reflects this. These messages may
+ * occur at any time.
+ *
+ * There are two tables for mapping states because there is a second tier of
+ * types for handshake messages. Once a record with a content type of handshake
+ * is received, the handshake record handler will look up the handshake type in
+ * the secondary map to get its appropriate handler.
+ *
+ * Valid message orders are as follows:
+ *
+ * =======================FULL HANDSHAKE======================
+ * Client Server
+ *
+ * ClientHello -------->
+ * ServerHello
+ * Certificate*
+ * ServerKeyExchange*
+ * CertificateRequest*
+ * <-------- ServerHelloDone
+ * Certificate*
+ * ClientKeyExchange
+ * CertificateVerify*
+ * [ChangeCipherSpec]
+ * Finished -------->
+ * [ChangeCipherSpec]
+ * <-------- Finished
+ * Application Data <-------> Application Data
+ *
+ * =====================SESSION RESUMPTION=====================
+ * Client Server
+ *
+ * ClientHello -------->
+ * ServerHello
+ * [ChangeCipherSpec]
+ * <-------- Finished
+ * [ChangeCipherSpec]
+ * Finished -------->
+ * Application Data <-------> Application Data
+ */
+// client expect states (indicate which records are expected to be received)
+var SHE = 0; // rcv server hello
+var SCE = 1; // rcv server certificate
+var SKE = 2; // rcv server key exchange
+var SCR = 3; // rcv certificate request
+var SHD = 4; // rcv server hello done
+var SCC = 5; // rcv change cipher spec
+var SFI = 6; // rcv finished
+var SAD = 7; // rcv application data
+var SER = 8; // not expecting any messages at this point
+
+// server expect states
+var CHE = 0; // rcv client hello
+var CCE = 1; // rcv client certificate
+var CKE = 2; // rcv client key exchange
+var CCV = 3; // rcv certificate verify
+var CCC = 4; // rcv change cipher spec
+var CFI = 5; // rcv finished
+var CAD = 6; // rcv application data
+var CER = 7; // not expecting any messages at this point
+
+// map client current expect state and content type to function
+var __ = tls.handleUnexpected;
+var R0 = tls.handleChangeCipherSpec;
+var R1 = tls.handleAlert;
+var R2 = tls.handleHandshake;
+var R3 = tls.handleApplicationData;
+var R4 = tls.handleHeartbeat;
+var ctTable = [];
+ctTable[tls.ConnectionEnd.client] = [
+// CC,AL,HS,AD,HB
+/*SHE*/[__,R1,R2,__,R4],
+/*SCE*/[__,R1,R2,__,R4],
+/*SKE*/[__,R1,R2,__,R4],
+/*SCR*/[__,R1,R2,__,R4],
+/*SHD*/[__,R1,R2,__,R4],
+/*SCC*/[R0,R1,__,__,R4],
+/*SFI*/[__,R1,R2,__,R4],
+/*SAD*/[__,R1,R2,R3,R4],
+/*SER*/[__,R1,R2,__,R4]
+];
+
+// map server current expect state and content type to function
+ctTable[tls.ConnectionEnd.server] = [
+// CC,AL,HS,AD
+/*CHE*/[__,R1,R2,__,R4],
+/*CCE*/[__,R1,R2,__,R4],
+/*CKE*/[__,R1,R2,__,R4],
+/*CCV*/[__,R1,R2,__,R4],
+/*CCC*/[R0,R1,__,__,R4],
+/*CFI*/[__,R1,R2,__,R4],
+/*CAD*/[__,R1,R2,R3,R4],
+/*CER*/[__,R1,R2,__,R4]
+];
+
+// map client current expect state and handshake type to function
+var H0 = tls.handleHelloRequest;
+var H1 = tls.handleServerHello;
+var H2 = tls.handleCertificate;
+var H3 = tls.handleServerKeyExchange;
+var H4 = tls.handleCertificateRequest;
+var H5 = tls.handleServerHelloDone;
+var H6 = tls.handleFinished;
+var hsTable = [];
+hsTable[tls.ConnectionEnd.client] = [
+// HR,01,SH,03,04,05,06,07,08,09,10,SC,SK,CR,HD,15,CK,17,18,19,FI
+/*SHE*/[__,__,H1,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
+/*SCE*/[H0,__,__,__,__,__,__,__,__,__,__,H2,H3,H4,H5,__,__,__,__,__,__],
+/*SKE*/[H0,__,__,__,__,__,__,__,__,__,__,__,H3,H4,H5,__,__,__,__,__,__],
+/*SCR*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,H4,H5,__,__,__,__,__,__],
+/*SHD*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,H5,__,__,__,__,__,__],
+/*SCC*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
+/*SFI*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,H6],
+/*SAD*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
+/*SER*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__]
+];
+
+// map server current expect state and handshake type to function
+// Note: CAD[CH] does not map to FB because renegotation is prohibited
+var H7 = tls.handleClientHello;
+var H8 = tls.handleClientKeyExchange;
+var H9 = tls.handleCertificateVerify;
+hsTable[tls.ConnectionEnd.server] = [
+// 01,CH,02,03,04,05,06,07,08,09,10,CC,12,13,14,CV,CK,17,18,19,FI
+/*CHE*/[__,H7,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
+/*CCE*/[__,__,__,__,__,__,__,__,__,__,__,H2,__,__,__,__,__,__,__,__,__],
+/*CKE*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,H8,__,__,__,__],
+/*CCV*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,H9,__,__,__,__,__],
+/*CCC*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
+/*CFI*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,H6],
+/*CAD*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
+/*CER*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__]
+];
+
+/**
+ * Generates the master_secret and keys using the given security parameters.
+ *
+ * The security parameters for a TLS connection state are defined as such:
+ *
+ * struct {
+ * ConnectionEnd entity;
+ * PRFAlgorithm prf_algorithm;
+ * BulkCipherAlgorithm bulk_cipher_algorithm;
+ * CipherType cipher_type;
+ * uint8 enc_key_length;
+ * uint8 block_length;
+ * uint8 fixed_iv_length;
+ * uint8 record_iv_length;
+ * MACAlgorithm mac_algorithm;
+ * uint8 mac_length;
+ * uint8 mac_key_length;
+ * CompressionMethod compression_algorithm;
+ * opaque master_secret[48];
+ * opaque client_random[32];
+ * opaque server_random[32];
+ * } SecurityParameters;
+ *
+ * Note that this definition is from TLS 1.2. In TLS 1.0 some of these
+ * parameters are ignored because, for instance, the PRFAlgorithm is a
+ * builtin-fixed algorithm combining iterations of MD5 and SHA-1 in TLS 1.0.
+ *
+ * The Record Protocol requires an algorithm to generate keys required by the
+ * current connection state.
+ *
+ * The master secret is expanded into a sequence of secure bytes, which is then
+ * split to a client write MAC key, a server write MAC key, a client write
+ * encryption key, and a server write encryption key. In TLS 1.0 a client write
+ * IV and server write IV are also generated. Each of these is generated from
+ * the byte sequence in that order. Unused values are empty. In TLS 1.2, some
+ * AEAD ciphers may additionally require a client write IV and a server write
+ * IV (see Section 6.2.3.3).
+ *
+ * When keys, MAC keys, and IVs are generated, the master secret is used as an
+ * entropy source.
+ *
+ * To generate the key material, compute:
+ *
+ * master_secret = PRF(pre_master_secret, "master secret",
+ * ClientHello.random + ServerHello.random)
+ *
+ * key_block = PRF(SecurityParameters.master_secret,
+ * "key expansion",
+ * SecurityParameters.server_random +
+ * SecurityParameters.client_random);
+ *
+ * until enough output has been generated. Then, the key_block is
+ * partitioned as follows:
+ *
+ * client_write_MAC_key[SecurityParameters.mac_key_length]
+ * server_write_MAC_key[SecurityParameters.mac_key_length]
+ * client_write_key[SecurityParameters.enc_key_length]
+ * server_write_key[SecurityParameters.enc_key_length]
+ * client_write_IV[SecurityParameters.fixed_iv_length]
+ * server_write_IV[SecurityParameters.fixed_iv_length]
+ *
+ * In TLS 1.2, the client_write_IV and server_write_IV are only generated for
+ * implicit nonce techniques as described in Section 3.2.1 of [AEAD]. This
+ * implementation uses TLS 1.0 so IVs are generated.
+ *
+ * Implementation note: The currently defined cipher suite which requires the
+ * most material is AES_256_CBC_SHA256. It requires 2 x 32 byte keys and 2 x 32
+ * byte MAC keys, for a total 128 bytes of key material. In TLS 1.0 it also
+ * requires 2 x 16 byte IVs, so it actually takes 160 bytes of key material.
+ *
+ * @param c the connection.
+ * @param sp the security parameters to use.
+ *
+ * @return the security keys.
+ */
+tls.generateKeys = function(c, sp) {
+ // TLS_RSA_WITH_AES_128_CBC_SHA (required to be compliant with TLS 1.2) &
+ // TLS_RSA_WITH_AES_256_CBC_SHA are the only cipher suites implemented
+ // at present
+
+ // TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA is required to be compliant with
+ // 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
+ var prf;
+ switch(sp.prf_algorithm) {
+ case tls.PRFAlgorithm.tls_prf_sha256:
+ prf = prf_sha256;
+ break;
+ default:
+ // should never happen
+ throw new Error('Invalid PRF');
+ }
+ */
+
+ // TLS 1.0/1.1 implementation
+ var prf = prf_TLS1;
+
+ // concatenate server and client random
+ var random = sp.client_random + sp.server_random;
+
+ // only create master secret if session is new
+ if(!c.session.resuming) {
+ // create master secret, clean up pre-master secret
+ sp.master_secret = prf(
+ sp.pre_master_secret, 'master secret', random, 48).bytes();
+ sp.pre_master_secret = null;
+ }
+
+ // generate the amount of key material needed
+ random = sp.server_random + sp.client_random;
+ var length = 2 * sp.mac_key_length + 2 * sp.enc_key_length;
+
+ // include IV for TLS/1.0
+ var tls10 = (c.version.major === tls.Versions.TLS_1_0.major &&
+ c.version.minor === tls.Versions.TLS_1_0.minor);
+ if(tls10) {
+ length += 2 * sp.fixed_iv_length;
+ }
+ var km = prf(sp.master_secret, 'key expansion', random, length);
+
+ // split the key material into the MAC and encryption keys
+ var rval = {
+ client_write_MAC_key: km.getBytes(sp.mac_key_length),
+ server_write_MAC_key: km.getBytes(sp.mac_key_length),
+ client_write_key: km.getBytes(sp.enc_key_length),
+ server_write_key: km.getBytes(sp.enc_key_length)
+ };
+
+ // include TLS 1.0 IVs
+ if(tls10) {
+ rval.client_write_IV = km.getBytes(sp.fixed_iv_length);
+ rval.server_write_IV = km.getBytes(sp.fixed_iv_length);
+ }
+
+ return rval;
+};
+
+/**
+ * Creates a new initialized TLS connection state. A connection state has
+ * a read mode and a write mode.
+ *
+ * compression state:
+ * The current state of the compression algorithm.
+ *
+ * cipher state:
+ * The current state of the encryption algorithm. This will consist of the
+ * scheduled key for that connection. For stream ciphers, this will also
+ * contain whatever state information is necessary to allow the stream to
+ * continue to encrypt or decrypt data.
+ *
+ * MAC key:
+ * The MAC key for the connection.
+ *
+ * sequence number:
+ * Each connection state contains a sequence number, which is maintained
+ * separately for read and write states. The sequence number MUST be set to
+ * zero whenever a connection state is made the active state. Sequence
+ * numbers are of type uint64 and may not exceed 2^64-1. Sequence numbers do
+ * not wrap. If a TLS implementation would need to wrap a sequence number,
+ * it must renegotiate instead. A sequence number is incremented after each
+ * record: specifically, the first record transmitted under a particular
+ * connection state MUST use sequence number 0.
+ *
+ * @param c the connection.
+ *
+ * @return the new initialized TLS connection state.
+ */
+tls.createConnectionState = function(c) {
+ var client = (c.entity === tls.ConnectionEnd.client);
+
+ var createMode = function() {
+ var mode = {
+ // two 32-bit numbers, first is most significant
+ sequenceNumber: [0, 0],
+ macKey: null,
+ macLength: 0,
+ macFunction: null,
+ cipherState: null,
+ cipherFunction: function(record) {return true;},
+ compressionState: null,
+ compressFunction: function(record) {return true;},
+ updateSequenceNumber: function() {
+ if(mode.sequenceNumber[1] === 0xFFFFFFFF) {
+ mode.sequenceNumber[1] = 0;
+ ++mode.sequenceNumber[0];
+ } else {
+ ++mode.sequenceNumber[1];
+ }
+ }
+ };
+ return mode;
+ };
+ var state = {
+ read: createMode(),
+ write: createMode()
+ };
+
+ // update function in read mode will decrypt then decompress a record
+ state.read.update = function(c, record) {
+ if(!state.read.cipherFunction(record, state.read)) {
+ c.error(c, {
+ message: 'Could not decrypt record or bad MAC.',
+ send: true,
+ alert: {
+ level: tls.Alert.Level.fatal,
+ // doesn't matter if decryption failed or MAC was
+ // invalid, return the same error so as not to reveal
+ // which one occurred
+ description: tls.Alert.Description.bad_record_mac
+ }
+ });
+ } else if(!state.read.compressFunction(c, record, state.read)) {
+ c.error(c, {
+ message: 'Could not decompress record.',
+ send: true,
+ alert: {
+ level: tls.Alert.Level.fatal,
+ description: tls.Alert.Description.decompression_failure
+ }
+ });
+ }
+ return !c.fail;
+ };
+
+ // update function in write mode will compress then encrypt a record
+ state.write.update = function(c, record) {
+ if(!state.write.compressFunction(c, record, state.write)) {
+ // error, but do not send alert since it would require
+ // compression as well
+ c.error(c, {
+ message: 'Could not compress record.',
+ send: false,
+ alert: {
+ level: tls.Alert.Level.fatal,
+ description: tls.Alert.Description.internal_error
+ }
+ });
+ } else if(!state.write.cipherFunction(record, state.write)) {
+ // error, but do not send alert since it would require
+ // encryption as well
+ c.error(c, {
+ message: 'Could not encrypt record.',
+ send: false,
+ alert: {
+ level: tls.Alert.Level.fatal,
+ description: tls.Alert.Description.internal_error
+ }
+ });
+ }
+ return !c.fail;
+ };
+
+ // handle security parameters
+ if(c.session) {
+ var sp = c.session.sp;
+ c.session.cipherSuite.initSecurityParameters(sp);
+
+ // generate keys
+ sp.keys = tls.generateKeys(c, sp);
+ state.read.macKey = client ?
+ sp.keys.server_write_MAC_key : sp.keys.client_write_MAC_key;
+ state.write.macKey = client ?
+ sp.keys.client_write_MAC_key : sp.keys.server_write_MAC_key;
+
+ // cipher suite setup
+ c.session.cipherSuite.initConnectionState(state, c, sp);
+
+ // compression setup
+ switch(sp.compression_algorithm) {
+ case tls.CompressionMethod.none:
+ break;
+ case tls.CompressionMethod.deflate:
+ state.read.compressFunction = inflate;
+ state.write.compressFunction = deflate;
+ break;
+ default:
+ throw new Error('Unsupported compression algorithm.');
+ }
+ }
+
+ return state;
+};
+
+/**
+ * Creates a Random structure.
+ *
+ * struct {
+ * uint32 gmt_unix_time;
+ * opaque random_bytes[28];
+ * } Random;
+ *
+ * gmt_unix_time:
+ * The current time and date in standard UNIX 32-bit format (seconds since
+ * the midnight starting Jan 1, 1970, UTC, ignoring leap seconds) according
+ * to the sender's internal clock. Clocks are not required to be set
+ * correctly by the basic TLS protocol; higher-level or application
+ * protocols may define additional requirements. Note that, for historical
+ * reasons, the data element is named using GMT, the predecessor of the
+ * current worldwide time base, UTC.
+ * random_bytes:
+ * 28 bytes generated by a secure random number generator.
+ *
+ * @return the Random structure as a byte array.
+ */
+tls.createRandom = function() {
+ // get UTC milliseconds
+ var d = new Date();
+ var utc = +d + d.getTimezoneOffset() * 60000;
+ var rval = forge.util.createBuffer();
+ rval.putInt32(utc);
+ rval.putBytes(forge.random.getBytes(28));
+ return rval;
+};
+
+/**
+ * Creates a TLS record with the given type and data.
+ *
+ * @param c the connection.
+ * @param options:
+ * type: the record type.
+ * data: the plain text data in a byte buffer.
+ *
+ * @return the created record.
+ */
+tls.createRecord = function(c, options) {
+ if(!options.data) {
+ return null;
+ }
+ var record = {
+ type: options.type,
+ version: {
+ major: c.version.major,
+ minor: c.version.minor
+ },
+ length: options.data.length(),
+ fragment: options.data
+ };
+ return record;
+};
+
+/**
+ * Creates a TLS alert record.
+ *
+ * @param c the connection.
+ * @param alert:
+ * level: the TLS alert level.
+ * description: the TLS alert description.
+ *
+ * @return the created alert record.
+ */
+tls.createAlert = function(c, alert) {
+ var b = forge.util.createBuffer();
+ b.putByte(alert.level);
+ b.putByte(alert.description);
+ return tls.createRecord(c, {
+ type: tls.ContentType.alert,
+ data: b
+ });
+};
+
+/* The structure of a TLS handshake message.
+ *
+ * struct {
+ * HandshakeType msg_type; // handshake type
+ * uint24 length; // bytes in message
+ * select(HandshakeType) {
+ * case hello_request: HelloRequest;
+ * case client_hello: ClientHello;
+ * case server_hello: ServerHello;
+ * case certificate: Certificate;
+ * case server_key_exchange: ServerKeyExchange;
+ * case certificate_request: CertificateRequest;
+ * case server_hello_done: ServerHelloDone;
+ * case certificate_verify: CertificateVerify;
+ * case client_key_exchange: ClientKeyExchange;
+ * case finished: Finished;
+ * } body;
+ * } Handshake;
+ */
+
+/**
+ * Creates a ClientHello message.
+ *
+ * opaque SessionID<0..32>;
+ * enum { null(0), deflate(1), (255) } CompressionMethod;
+ * uint8 CipherSuite[2];
+ *
+ * struct {
+ * ProtocolVersion client_version;
+ * Random random;
+ * SessionID session_id;
+ * CipherSuite cipher_suites<2..2^16-2>;
+ * CompressionMethod compression_methods<1..2^8-1>;
+ * select(extensions_present) {
+ * case false:
+ * struct {};
+ * case true:
+ * Extension extensions<0..2^16-1>;
+ * };
+ * } ClientHello;
+ *
+ * The extension format for extended client hellos and server hellos is:
+ *
+ * struct {
+ * ExtensionType extension_type;
+ * opaque extension_data<0..2^16-1>;
+ * } Extension;
+ *
+ * Here:
+ *
+ * - "extension_type" identifies the particular extension type.
+ * - "extension_data" contains information specific to the particular
+ * extension type.
+ *
+ * The extension types defined in this document are:
+ *
+ * enum {
+ * server_name(0), max_fragment_length(1),
+ * client_certificate_url(2), trusted_ca_keys(3),
+ * truncated_hmac(4), status_request(5), (65535)
+ * } ExtensionType;
+ *
+ * @param c the connection.
+ *
+ * @return the ClientHello byte buffer.
+ */
+tls.createClientHello = function(c) {
+ // save hello version
+ c.session.clientHelloVersion = {
+ major: c.version.major,
+ minor: c.version.minor
+ };
+
+ // create supported cipher suites
+ var cipherSuites = forge.util.createBuffer();
+ for(var i = 0; i < c.cipherSuites.length; ++i) {
+ var cs = c.cipherSuites[i];
+ cipherSuites.putByte(cs.id[0]);
+ cipherSuites.putByte(cs.id[1]);
+ }
+ var cSuites = cipherSuites.length();
+
+ // create supported compression methods, null always supported, but
+ // also support deflate if connection has inflate and deflate methods
+ var compressionMethods = forge.util.createBuffer();
+ compressionMethods.putByte(tls.CompressionMethod.none);
+ // FIXME: deflate support disabled until issues with raw deflate data
+ // without zlib headers are resolved
+ /*
+ if(c.inflate !== null && c.deflate !== null) {
+ compressionMethods.putByte(tls.CompressionMethod.deflate);
+ }
+ */
+ var cMethods = compressionMethods.length();
+
+ // create TLS SNI (server name indication) extension if virtual host
+ // has been specified, see RFC 3546
+ var extensions = forge.util.createBuffer();
+ if(c.virtualHost) {
+ // create extension struct
+ var ext = forge.util.createBuffer();
+ ext.putByte(0x00); // type server_name (ExtensionType is 2 bytes)
+ ext.putByte(0x00);
+
+ /* In order to provide the server name, clients MAY include an
+ * extension of type "server_name" in the (extended) client hello.
+ * The "extension_data" field of this extension SHALL contain
+ * "ServerNameList" where:
+ *
+ * struct {
+ * NameType name_type;
+ * select(name_type) {
+ * case host_name: HostName;
+ * } name;
+ * } ServerName;
+ *
+ * enum {
+ * host_name(0), (255)
+ * } NameType;
+ *
+ * opaque HostName<1..2^16-1>;
+ *
+ * struct {
+ * ServerName server_name_list<1..2^16-1>
+ * } ServerNameList;
+ */
+ var serverName = forge.util.createBuffer();
+ serverName.putByte(0x00); // type host_name
+ writeVector(serverName, 2, forge.util.createBuffer(c.virtualHost));
+
+ // ServerNameList is in extension_data
+ var snList = forge.util.createBuffer();
+ writeVector(snList, 2, serverName);
+ writeVector(ext, 2, snList);
+ extensions.putBuffer(ext);
+ }
+ var extLength = extensions.length();
+ if(extLength > 0) {
+ // add extension vector length
+ extLength += 2;
+ }
+
+ // determine length of the handshake message
+ // cipher suites and compression methods size will need to be
+ // updated if more get added to the list
+ var sessionId = c.session.id;
+ var length =
+ sessionId.length + 1 + // session ID vector
+ 2 + // version (major + minor)
+ 4 + 28 + // random time and random bytes
+ 2 + cSuites + // cipher suites vector
+ 1 + cMethods + // compression methods vector
+ extLength; // extensions vector
+
+ // build record fragment
+ var rval = forge.util.createBuffer();
+ rval.putByte(tls.HandshakeType.client_hello);
+ rval.putInt24(length); // handshake length
+ rval.putByte(c.version.major); // major version
+ rval.putByte(c.version.minor); // minor version
+ rval.putBytes(c.session.sp.client_random); // random time + bytes
+ writeVector(rval, 1, forge.util.createBuffer(sessionId));
+ writeVector(rval, 2, cipherSuites);
+ writeVector(rval, 1, compressionMethods);
+ if(extLength > 0) {
+ writeVector(rval, 2, extensions);
+ }
+ return rval;
+};
+
+/**
+ * Creates a ServerHello message.
+ *
+ * @param c the connection.
+ *
+ * @return the ServerHello byte buffer.
+ */
+tls.createServerHello = function(c) {
+ // determine length of the handshake message
+ var sessionId = c.session.id;
+ var length =
+ sessionId.length + 1 + // session ID vector
+ 2 + // version (major + minor)
+ 4 + 28 + // random time and random bytes
+ 2 + // chosen cipher suite
+ 1; // chosen compression method
+
+ // build record fragment
+ var rval = forge.util.createBuffer();
+ rval.putByte(tls.HandshakeType.server_hello);
+ rval.putInt24(length); // handshake length
+ rval.putByte(c.version.major); // major version
+ rval.putByte(c.version.minor); // minor version
+ rval.putBytes(c.session.sp.server_random); // random time + bytes
+ writeVector(rval, 1, forge.util.createBuffer(sessionId));
+ rval.putByte(c.session.cipherSuite.id[0]);
+ rval.putByte(c.session.cipherSuite.id[1]);
+ rval.putByte(c.session.compressionMethod);
+ return rval;
+};
+
+/**
+ * Creates a Certificate message.
+ *
+ * When this message will be sent:
+ * This is the first message the client can send after receiving a server
+ * hello done message and the first message the server can send after
+ * sending a ServerHello. This client message is only sent if the server
+ * requests a certificate. If no suitable certificate is available, the
+ * client should send a certificate message containing no certificates. If
+ * client authentication is required by the server for the handshake to
+ * continue, it may respond with a fatal handshake failure alert.
+ *
+ * opaque ASN.1Cert<1..2^24-1>;
+ *
+ * struct {
+ * ASN.1Cert certificate_list<0..2^24-1>;
+ * } Certificate;
+ *
+ * @param c the connection.
+ *
+ * @return the Certificate byte buffer.
+ */
+tls.createCertificate = function(c) {
+ // TODO: check certificate request to ensure types are supported
+
+ // get a certificate (a certificate as a PEM string)
+ var client = (c.entity === tls.ConnectionEnd.client);
+ var cert = null;
+ if(c.getCertificate) {
+ var hint;
+ if(client) {
+ hint = c.session.certificateRequest;
+ } else {
+ hint = c.session.extensions.server_name.serverNameList;
+ }
+ cert = c.getCertificate(c, hint);
+ }
+
+ // buffer to hold certificate list
+ var certList = forge.util.createBuffer();
+ if(cert !== null) {
+ try {
+ // normalize cert to a chain of certificates
+ if(!forge.util.isArray(cert)) {
+ cert = [cert];
+ }
+ var asn1 = null;
+ for(var i = 0; i < cert.length; ++i) {
+ var msg = forge.pem.decode(cert[i])[0];
+ if(msg.type !== 'CERTIFICATE' &&
+ msg.type !== 'X509 CERTIFICATE' &&
+ msg.type !== 'TRUSTED CERTIFICATE') {
+ var error = new Error('Could not convert certificate from PEM; PEM ' +
+ 'header type is not "CERTIFICATE", "X509 CERTIFICATE", or ' +
+ '"TRUSTED CERTIFICATE".');
+ error.headerType = msg.type;
+ throw error;
+ }
+ if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+ throw new Error('Could not convert certificate from PEM; PEM is encrypted.');
+ }
+
+ var der = forge.util.createBuffer(msg.body);
+ if(asn1 === null) {
+ asn1 = forge.asn1.fromDer(der.bytes(), false);
+ }
+
+ // certificate entry is itself a vector with 3 length bytes
+ var certBuffer = forge.util.createBuffer();
+ writeVector(certBuffer, 3, der);
+
+ // add cert vector to cert list vector
+ certList.putBuffer(certBuffer);
+ }
+
+ // save certificate
+ cert = forge.pki.certificateFromAsn1(asn1);
+ if(client) {
+ c.session.clientCertificate = cert;
+ } else {
+ c.session.serverCertificate = cert;
+ }
+ } catch(ex) {
+ return c.error(c, {
+ message: 'Could not send certificate list.',
+ cause: ex,
+ send: true,
+ alert: {
+ level: tls.Alert.Level.fatal,
+ description: tls.Alert.Description.bad_certificate
+ }
+ });
+ }
+ }
+
+ // determine length of the handshake message
+ var length = 3 + certList.length(); // cert list vector
+
+ // build record fragment
+ var rval = forge.util.createBuffer();
+ rval.putByte(tls.HandshakeType.certificate);
+ rval.putInt24(length);
+ writeVector(rval, 3, certList);
+ return rval;
+};
+
+/**
+ * Creates a ClientKeyExchange message.
+ *
+ * When this message will be sent:
+ * This message is always sent by the client. It will immediately follow the
+ * client certificate message, if it is sent. Otherwise it will be the first
+ * message sent by the client after it receives the server hello done
+ * message.
+ *
+ * Meaning of this message:
+ * With this message, the premaster secret is set, either though direct
+ * transmission of the RSA-encrypted secret, or by the transmission of
+ * Diffie-Hellman parameters which will allow each side to agree upon the
+ * same premaster secret. When the key exchange method is DH_RSA or DH_DSS,
+ * client certification has been requested, and the client was able to
+ * respond with a certificate which contained a Diffie-Hellman public key
+ * whose parameters (group and generator) matched those specified by the
+ * server in its certificate, this message will not contain any data.
+ *
+ * Meaning of this message:
+ * If RSA is being used for key agreement and authentication, the client
+ * generates a 48-byte premaster secret, encrypts it using the public key
+ * from the server's certificate or the temporary RSA key provided in a
+ * server key exchange message, and sends the result in an encrypted
+ * premaster secret message. This structure is a variant of the client
+ * key exchange message, not a message in itself.
+ *
+ * struct {
+ * select(KeyExchangeAlgorithm) {
+ * case rsa: EncryptedPreMasterSecret;
+ * case diffie_hellman: ClientDiffieHellmanPublic;
+ * } exchange_keys;
+ * } ClientKeyExchange;
+ *
+ * struct {
+ * ProtocolVersion client_version;
+ * opaque random[46];
+ * } PreMasterSecret;
+ *
+ * struct {
+ * public-key-encrypted PreMasterSecret pre_master_secret;
+ * } EncryptedPreMasterSecret;
+ *
+ * A public-key-encrypted element is encoded as a vector <0..2^16-1>.
+ *
+ * @param c the connection.
+ *
+ * @return the ClientKeyExchange byte buffer.
+ */
+tls.createClientKeyExchange = function(c) {
+ // create buffer to encrypt
+ var b = forge.util.createBuffer();
+
+ // add highest client-supported protocol to help server avoid version
+ // rollback attacks
+ b.putByte(c.session.clientHelloVersion.major);
+ b.putByte(c.session.clientHelloVersion.minor);
+
+ // generate and add 46 random bytes
+ b.putBytes(forge.random.getBytes(46));
+
+ // save pre-master secret
+ var sp = c.session.sp;
+ sp.pre_master_secret = b.getBytes();
+
+ // RSA-encrypt the pre-master secret
+ var key = c.session.serverCertificate.publicKey;
+ b = key.encrypt(sp.pre_master_secret);
+
+ /* Note: The encrypted pre-master secret will be stored in a
+ public-key-encrypted opaque vector that has the length prefixed using
+ 2 bytes, so include those 2 bytes in the handshake message length. This
+ is done as a minor optimization instead of calling writeVector(). */
+
+ // determine length of the handshake message
+ var length = b.length + 2;
+
+ // build record fragment
+ var rval = forge.util.createBuffer();
+ rval.putByte(tls.HandshakeType.client_key_exchange);
+ rval.putInt24(length);
+ // add vector length bytes
+ rval.putInt16(b.length);
+ rval.putBytes(b);
+ return rval;
+};
+
+/**
+ * Creates a ServerKeyExchange message.
+ *
+ * @param c the connection.
+ *
+ * @return the ServerKeyExchange byte buffer.
+ */
+tls.createServerKeyExchange = function(c) {
+ // this implementation only supports RSA, no Diffie-Hellman support,
+ // so this record is empty
+
+ // determine length of the handshake message
+ var length = 0;
+
+ // build record fragment
+ var rval = forge.util.createBuffer();
+ if(length > 0) {
+ rval.putByte(tls.HandshakeType.server_key_exchange);
+ rval.putInt24(length);
+ }
+ return rval;
+};
+
+/**
+ * Gets the signed data used to verify a client-side certificate. See
+ * tls.createCertificateVerify() for details.
+ *
+ * @param c the connection.
+ * @param callback the callback to call once the signed data is ready.
+ */
+tls.getClientSignature = function(c, callback) {
+ // generate data to RSA encrypt
+ var b = forge.util.createBuffer();
+ b.putBuffer(c.session.md5.digest());
+ b.putBuffer(c.session.sha1.digest());
+ b = b.getBytes();
+
+ // create default signing function as necessary
+ c.getSignature = c.getSignature || function(c, b, callback) {
+ // do rsa encryption, call callback
+ var privateKey = null;
+ if(c.getPrivateKey) {
+ try {
+ privateKey = c.getPrivateKey(c, c.session.clientCertificate);
+ 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) {
+ c.error(c, {
+ message: 'No private key set.',
+ send: true,
+ alert: {
+ level: tls.Alert.Level.fatal,
+ description: tls.Alert.Description.internal_error
+ }
+ });
+ } else {
+ b = privateKey.sign(b, null);
+ }
+ callback(c, b);
+ };
+
+ // get client signature
+ c.getSignature(c, b, callback);
+};
+
+/**
+ * Creates a CertificateVerify message.
+ *
+ * Meaning of this message:
+ * This structure conveys the client's Diffie-Hellman public value
+ * (Yc) if it was not already included in the client's certificate.
+ * The encoding used for Yc is determined by the enumerated
+ * PublicValueEncoding. This structure is a variant of the client
+ * key exchange message, not a message in itself.
+ *
+ * When this message will be sent:
+ * This message is used to provide explicit verification of a client
+ * certificate. This message is only sent following a client
+ * certificate that has signing capability (i.e. all certificates
+ * except those containing fixed Diffie-Hellman parameters). When
+ * sent, it will immediately follow the client key exchange message.
+ *
+ * struct {
+ * Signature signature;
+ * } CertificateVerify;
+ *
+ * CertificateVerify.signature.md5_hash
+ * MD5(handshake_messages);
+ *
+ * Certificate.signature.sha_hash
+ * SHA(handshake_messages);
+ *
+ * Here handshake_messages refers to all handshake messages sent or
+ * received starting at client hello up to but not including this
+ * message, including the type and length fields of the handshake
+ * messages.
+ *
+ * select(SignatureAlgorithm) {
+ * case anonymous: struct { };
+ * case rsa:
+ * digitally-signed struct {
+ * opaque md5_hash[16];
+ * opaque sha_hash[20];
+ * };
+ * case dsa:
+ * digitally-signed struct {
+ * opaque sha_hash[20];
+ * };
+ * } Signature;
+ *
+ * In digital signing, one-way hash functions are used as input for a
+ * signing algorithm. A digitally-signed element is encoded as an opaque
+ * vector <0..2^16-1>, where the length is specified by the signing
+ * algorithm and key.
+ *
+ * In RSA signing, a 36-byte structure of two hashes (one SHA and one
+ * MD5) is signed (encrypted with the private key). It is encoded with
+ * PKCS #1 block type 0 or type 1 as described in [PKCS1].
+ *
+ * In DSS, the 20 bytes of the SHA hash are run directly through the
+ * Digital Signing Algorithm with no additional hashing.
+ *
+ * @param c the connection.
+ * @param signature the signature to include in the message.
+ *
+ * @return the CertificateVerify byte buffer.
+ */
+tls.createCertificateVerify = function(c, signature) {
+ /* Note: The signature will be stored in a "digitally-signed" opaque
+ vector that has the length prefixed using 2 bytes, so include those
+ 2 bytes in the handshake message length. This is done as a minor
+ optimization instead of calling writeVector(). */
+
+ // determine length of the handshake message
+ var length = signature.length + 2;
+
+ // build record fragment
+ var rval = forge.util.createBuffer();
+ rval.putByte(tls.HandshakeType.certificate_verify);
+ rval.putInt24(length);
+ // add vector length bytes
+ rval.putInt16(signature.length);
+ rval.putBytes(signature);
+ return rval;
+};
+
+/**
+ * Creates a CertificateRequest message.
+ *
+ * @param c the connection.
+ *
+ * @return the CertificateRequest byte buffer.
+ */
+tls.createCertificateRequest = function(c) {
+ // TODO: support other certificate types
+ var certTypes = forge.util.createBuffer();
+
+ // common RSA certificate type
+ certTypes.putByte(0x01);
+
+ // TODO: verify that this data format is correct
+ // add distinguished names from CA store
+ var cAs = forge.util.createBuffer();
+ for(var key in c.caStore.certs) {
+ var cert = c.caStore.certs[key];
+ var dn = forge.pki.distinguishedNameToAsn1(cert.subject);
+ cAs.putBuffer(forge.asn1.toDer(dn));
+ }
+
+ // TODO: TLS 1.2+ has a different format
+
+ // determine length of the handshake message
+ var length =
+ 1 + certTypes.length() +
+ 2 + cAs.length();
+
+ // build record fragment
+ var rval = forge.util.createBuffer();
+ rval.putByte(tls.HandshakeType.certificate_request);
+ rval.putInt24(length);
+ writeVector(rval, 1, certTypes);
+ writeVector(rval, 2, cAs);
+ return rval;
+};
+
+/**
+ * Creates a ServerHelloDone message.
+ *
+ * @param c the connection.
+ *
+ * @return the ServerHelloDone byte buffer.
+ */
+tls.createServerHelloDone = function(c) {
+ // build record fragment
+ var rval = forge.util.createBuffer();
+ rval.putByte(tls.HandshakeType.server_hello_done);
+ rval.putInt24(0);
+ return rval;
+};
+
+/**
+ * Creates a ChangeCipherSpec message.
+ *
+ * The change cipher spec protocol exists to signal transitions in
+ * ciphering strategies. The protocol consists of a single message,
+ * which is encrypted and compressed under the current (not the pending)
+ * connection state. The message consists of a single byte of value 1.
+ *
+ * struct {
+ * enum { change_cipher_spec(1), (255) } type;
+ * } ChangeCipherSpec;
+ *
+ * @return the ChangeCipherSpec byte buffer.
+ */
+tls.createChangeCipherSpec = function() {
+ var rval = forge.util.createBuffer();
+ rval.putByte(0x01);
+ return rval;
+};
+
+/**
+ * Creates a Finished message.
+ *
+ * struct {
+ * opaque verify_data[12];
+ * } Finished;
+ *
+ * verify_data
+ * PRF(master_secret, finished_label, MD5(handshake_messages) +
+ * SHA-1(handshake_messages)) [0..11];
+ *
+ * finished_label
+ * For Finished messages sent by the client, the string "client
+ * finished". For Finished messages sent by the server, the
+ * string "server finished".
+ *
+ * handshake_messages
+ * All of the data from all handshake messages up to but not
+ * including this message. This is only data visible at the
+ * handshake layer and does not include record layer headers.
+ * This is the concatenation of all the Handshake structures as
+ * defined in 7.4 exchanged thus far.
+ *
+ * @param c the connection.
+ *
+ * @return the Finished byte buffer.
+ */
+tls.createFinished = function(c) {
+ // generate verify_data
+ var b = forge.util.createBuffer();
+ b.putBuffer(c.session.md5.digest());
+ b.putBuffer(c.session.sha1.digest());
+
+ // TODO: determine prf function and verify length for TLS 1.2
+ var client = (c.entity === tls.ConnectionEnd.client);
+ var sp = c.session.sp;
+ var vdl = 12;
+ var prf = prf_TLS1;
+ var label = client ? 'client finished' : 'server finished';
+ b = prf(sp.master_secret, label, b.getBytes(), vdl);
+
+ // build record fragment
+ var rval = forge.util.createBuffer();
+ rval.putByte(tls.HandshakeType.finished);
+ rval.putInt24(b.length());
+ rval.putBuffer(b);
+ return rval;
+};
+
+/**
+ * Creates a HeartbeatMessage (See RFC 6520).
+ *
+ * struct {
+ * HeartbeatMessageType type;
+ * uint16 payload_length;
+ * opaque payload[HeartbeatMessage.payload_length];
+ * opaque padding[padding_length];
+ * } HeartbeatMessage;
+ *
+ * The total length of a HeartbeatMessage MUST NOT exceed 2^14 or
+ * max_fragment_length when negotiated as defined in [RFC6066].
+ *
+ * type: The message type, either heartbeat_request or heartbeat_response.
+ *
+ * payload_length: The length of the payload.
+ *
+ * payload: The payload consists of arbitrary content.
+ *
+ * padding: The padding is random content that MUST be ignored by the
+ * receiver. The length of a HeartbeatMessage is TLSPlaintext.length
+ * for TLS and DTLSPlaintext.length for DTLS. Furthermore, the
+ * length of the type field is 1 byte, and the length of the
+ * payload_length is 2. Therefore, the padding_length is
+ * TLSPlaintext.length - payload_length - 3 for TLS and
+ * DTLSPlaintext.length - payload_length - 3 for DTLS. The
+ * padding_length MUST be at least 16.
+ *
+ * The sender of a HeartbeatMessage MUST use a random padding of at
+ * least 16 bytes. The padding of a received HeartbeatMessage message
+ * MUST be ignored.
+ *
+ * If the payload_length of a received HeartbeatMessage is too large,
+ * the received HeartbeatMessage MUST be discarded silently.
+ *
+ * @param c the connection.
+ * @param type the tls.HeartbeatMessageType.
+ * @param payload the heartbeat data to send as the payload.
+ * @param [payloadLength] the payload length to use, defaults to the
+ * actual payload length.
+ *
+ * @return the HeartbeatRequest byte buffer.
+ */
+tls.createHeartbeat = function(type, payload, payloadLength) {
+ if(typeof payloadLength === 'undefined') {
+ payloadLength = payload.length;
+ }
+ // build record fragment
+ var rval = forge.util.createBuffer();
+ rval.putByte(type); // heartbeat message type
+ rval.putInt16(payloadLength); // payload length
+ rval.putBytes(payload); // payload
+ // padding
+ var plaintextLength = rval.length();
+ var paddingLength = Math.max(16, plaintextLength - payloadLength - 3);
+ rval.putBytes(forge.random.getBytes(paddingLength));
+ return rval;
+};
+
+/**
+ * Fragments, compresses, encrypts, and queues a record for delivery.
+ *
+ * @param c the connection.
+ * @param record the record to queue.
+ */
+tls.queue = function(c, record) {
+ // error during record creation
+ if(!record) {
+ return;
+ }
+
+ // if the record is a handshake record, update handshake hashes
+ if(record.type === tls.ContentType.handshake) {
+ var bytes = record.fragment.bytes();
+ c.session.md5.update(bytes);
+ c.session.sha1.update(bytes);
+ bytes = null;
+ }
+
+ // handle record fragmentation
+ var records;
+ if(record.fragment.length() <= tls.MaxFragment) {
+ records = [record];
+ } else {
+ // fragment data as long as it is too long
+ records = [];
+ var data = record.fragment.bytes();
+ while(data.length > tls.MaxFragment) {
+ records.push(tls.createRecord(c, {
+ type: record.type,
+ data: forge.util.createBuffer(data.slice(0, tls.MaxFragment))
+ }));
+ data = data.slice(tls.MaxFragment);
+ }
+ // add last record
+ if(data.length > 0) {
+ records.push(tls.createRecord(c, {
+ type: record.type,
+ data: forge.util.createBuffer(data)
+ }));
+ }
+ }
+
+ // compress and encrypt all fragmented records
+ for(var i = 0; i < records.length && !c.fail; ++i) {
+ // update the record using current write state
+ var rec = records[i];
+ var s = c.state.current.write;
+ if(s.update(c, rec)) {
+ // store record
+ c.records.push(rec);
+ }
+ }
+};
+
+/**
+ * Flushes all queued records to the output buffer and calls the
+ * tlsDataReady() handler on the given connection.
+ *
+ * @param c the connection.
+ *
+ * @return true on success, false on failure.
+ */
+tls.flush = function(c) {
+ for(var i = 0; i < c.records.length; ++i) {
+ var record = c.records[i];
+
+ // add record header and fragment
+ c.tlsData.putByte(record.type);
+ c.tlsData.putByte(record.version.major);
+ c.tlsData.putByte(record.version.minor);
+ c.tlsData.putInt16(record.fragment.length());
+ c.tlsData.putBuffer(c.records[i].fragment);
+ }
+ c.records = [];
+ return c.tlsDataReady(c);
+};
+
+/**
+ * Maps a pki.certificateError to a tls.Alert.Description.
+ *
+ * @param error the error to map.
+ *
+ * @return the alert description.
+ */
+var _certErrorToAlertDesc = function(error) {
+ switch(error) {
+ case true:
+ return true;
+ case forge.pki.certificateError.bad_certificate:
+ return tls.Alert.Description.bad_certificate;
+ case forge.pki.certificateError.unsupported_certificate:
+ return tls.Alert.Description.unsupported_certificate;
+ case forge.pki.certificateError.certificate_revoked:
+ return tls.Alert.Description.certificate_revoked;
+ case forge.pki.certificateError.certificate_expired:
+ return tls.Alert.Description.certificate_expired;
+ case forge.pki.certificateError.certificate_unknown:
+ return tls.Alert.Description.certificate_unknown;
+ case forge.pki.certificateError.unknown_ca:
+ return tls.Alert.Description.unknown_ca;
+ default:
+ return tls.Alert.Description.bad_certificate;
+ }
+};
+
+/**
+ * Maps a tls.Alert.Description to a pki.certificateError.
+ *
+ * @param desc the alert description.
+ *
+ * @return the certificate error.
+ */
+var _alertDescToCertError = function(desc) {
+ switch(desc) {
+ case true:
+ return true;
+ case tls.Alert.Description.bad_certificate:
+ return forge.pki.certificateError.bad_certificate;
+ case tls.Alert.Description.unsupported_certificate:
+ return forge.pki.certificateError.unsupported_certificate;
+ case tls.Alert.Description.certificate_revoked:
+ return forge.pki.certificateError.certificate_revoked;
+ case tls.Alert.Description.certificate_expired:
+ return forge.pki.certificateError.certificate_expired;
+ case tls.Alert.Description.certificate_unknown:
+ return forge.pki.certificateError.certificate_unknown;
+ case tls.Alert.Description.unknown_ca:
+ return forge.pki.certificateError.unknown_ca;
+ default:
+ return forge.pki.certificateError.bad_certificate;
+ }
+};
+
+/**
+ * Verifies a certificate chain against the given connection's
+ * Certificate Authority store.
+ *
+ * @param c the TLS connection.
+ * @param chain the certificate chain to verify, with the root or highest
+ * authority at the end.
+ *
+ * @return true if successful, false if not.
+ */
+tls.verifyCertificateChain = function(c, chain) {
+ try {
+ // verify chain
+ forge.pki.verifyCertificateChain(c.caStore, chain,
+ function verify(vfd, depth, chain) {
+ // convert pki.certificateError to tls alert description
+ var desc = _certErrorToAlertDesc(vfd);
+
+ // call application callback
+ var ret = c.verify(c, vfd, depth, chain);
+ if(ret !== true) {
+ if(typeof ret === 'object' && !forge.util.isArray(ret)) {
+ // throw custom error
+ var error = new Error('The application rejected the certificate.');
+ error.send = true;
+ error.alert = {
+ level: tls.Alert.Level.fatal,
+ description: tls.Alert.Description.bad_certificate
+ };
+ if(ret.message) {
+ error.message = ret.message;
+ }
+ if(ret.alert) {
+ error.alert.description = ret.alert;
+ }
+ throw error;
+ }
+
+ // convert tls alert description to pki.certificateError
+ if(ret !== vfd) {
+ ret = _alertDescToCertError(ret);
+ }
+ }
+
+ return ret;
+ });
+ } catch(ex) {
+ // build tls error if not already customized
+ var err = ex;
+ if(typeof err !== 'object' || forge.util.isArray(err)) {
+ err = {
+ send: true,
+ alert: {
+ level: tls.Alert.Level.fatal,
+ description: _certErrorToAlertDesc(ex)
+ }
+ };
+ }
+ if(!('send' in err)) {
+ err.send = true;
+ }
+ if(!('alert' in err)) {
+ err.alert = {
+ level: tls.Alert.Level.fatal,
+ description: _certErrorToAlertDesc(err.error)
+ };
+ }
+
+ // send error
+ c.error(c, err);
+ }
+
+ return !c.fail;
+};
+
+/**
+ * Creates a new TLS session cache.
+ *
+ * @param cache optional map of session ID to cached session.
+ * @param capacity the maximum size for the cache (default: 100).
+ *
+ * @return the new TLS session cache.
+ */
+tls.createSessionCache = function(cache, capacity) {
+ var rval = null;
+
+ // assume input is already a session cache object
+ if(cache && cache.getSession && cache.setSession && cache.order) {
+ rval = cache;
+ } else {
+ // create cache
+ rval = {};
+ rval.cache = cache || {};
+ rval.capacity = Math.max(capacity || 100, 1);
+ rval.order = [];
+
+ // store order for sessions, delete session overflow
+ for(var key in cache) {
+ if(rval.order.length <= capacity) {
+ rval.order.push(key);
+ } else {
+ delete cache[key];
+ }
+ }
+
+ // get a session from a session ID (or get any session)
+ rval.getSession = function(sessionId) {
+ var session = null;
+ var key = null;
+
+ // if session ID provided, use it
+ if(sessionId) {
+ key = forge.util.bytesToHex(sessionId);
+ } else if(rval.order.length > 0) {
+ // get first session from cache
+ key = rval.order[0];
+ }
+
+ if(key !== null && key in rval.cache) {
+ // get cached session and remove from cache
+ session = rval.cache[key];
+ delete rval.cache[key];
+ for(var i in rval.order) {
+ if(rval.order[i] === key) {
+ rval.order.splice(i, 1);
+ break;
+ }
+ }
+ }
+
+ return session;
+ };
+
+ // set a session in the cache
+ rval.setSession = function(sessionId, session) {
+ // remove session from cache if at capacity
+ if(rval.order.length === rval.capacity) {
+ var key = rval.order.shift();
+ delete rval.cache[key];
+ }
+ // add session to cache
+ var key = forge.util.bytesToHex(sessionId);
+ rval.order.push(key);
+ rval.cache[key] = session;
+ };
+ }
+
+ return rval;
+};
+
+/**
+ * Creates a new TLS connection.
+ *
+ * See public createConnection() docs for more details.
+ *
+ * @param options the options for this connection.
+ *
+ * @return the new TLS connection.
+ */
+tls.createConnection = function(options) {
+ var caStore = null;
+ if(options.caStore) {
+ // if CA store is an array, convert it to a CA store object
+ if(forge.util.isArray(options.caStore)) {
+ caStore = forge.pki.createCaStore(options.caStore);
+ } else {
+ caStore = options.caStore;
+ }
+ } else {
+ // create empty CA store
+ caStore = forge.pki.createCaStore();
+ }
+
+ // setup default cipher suites
+ var cipherSuites = options.cipherSuites || null;
+ if(cipherSuites === null) {
+ cipherSuites = [];
+ for(var key in tls.CipherSuites) {
+ cipherSuites.push(tls.CipherSuites[key]);
+ }
+ }
+
+ // set default entity
+ var entity = (options.server || false) ?
+ tls.ConnectionEnd.server : tls.ConnectionEnd.client;
+
+ // create session cache if requested
+ var sessionCache = options.sessionCache ?
+ tls.createSessionCache(options.sessionCache) : null;
+
+ // create TLS connection
+ var c = {
+ version: {major: tls.Version.major, minor: tls.Version.minor},
+ entity: entity,
+ sessionId: options.sessionId,
+ caStore: caStore,
+ sessionCache: sessionCache,
+ cipherSuites: cipherSuites,
+ connected: options.connected,
+ virtualHost: options.virtualHost || null,
+ verifyClient: options.verifyClient || false,
+ verify: options.verify || function(cn, vfd, dpth, cts) {return vfd;},
+ getCertificate: options.getCertificate || null,
+ getPrivateKey: options.getPrivateKey || null,
+ getSignature: options.getSignature || null,
+ input: forge.util.createBuffer(),
+ tlsData: forge.util.createBuffer(),
+ data: forge.util.createBuffer(),
+ tlsDataReady: options.tlsDataReady,
+ dataReady: options.dataReady,
+ heartbeatReceived: options.heartbeatReceived,
+ closed: options.closed,
+ error: function(c, ex) {
+ // set origin if not set
+ ex.origin = ex.origin ||
+ ((c.entity === tls.ConnectionEnd.client) ? 'client' : 'server');
+
+ // send TLS alert
+ if(ex.send) {
+ tls.queue(c, tls.createAlert(c, ex.alert));
+ tls.flush(c);
+ }
+
+ // error is fatal by default
+ var fatal = (ex.fatal !== false);
+ if(fatal) {
+ // set fail flag
+ c.fail = true;
+ }
+
+ // call error handler first
+ options.error(c, ex);
+
+ if(fatal) {
+ // fatal error, close connection, do not clear fail
+ c.close(false);
+ }
+ },
+ deflate: options.deflate || null,
+ inflate: options.inflate || null
+ };
+
+ /**
+ * Resets a closed TLS connection for reuse. Called in c.close().
+ *
+ * @param clearFail true to clear the fail flag (default: true).
+ */
+ c.reset = function(clearFail) {
+ c.version = {major: tls.Version.major, minor: tls.Version.minor};
+ c.record = null;
+ c.session = null;
+ c.peerCertificate = null;
+ c.state = {
+ pending: null,
+ current: null
+ };
+ c.expect = (c.entity === tls.ConnectionEnd.client) ? SHE : CHE;
+ c.fragmented = null;
+ c.records = [];
+ c.open = false;
+ c.handshakes = 0;
+ c.handshaking = false;
+ c.isConnected = false;
+ c.fail = !(clearFail || typeof(clearFail) === 'undefined');
+ c.input.clear();
+ c.tlsData.clear();
+ c.data.clear();
+ c.state.current = tls.createConnectionState(c);
+ };
+
+ // do initial reset of connection
+ c.reset();
+
+ /**
+ * Updates the current TLS engine state based on the given record.
+ *
+ * @param c the TLS connection.
+ * @param record the TLS record to act on.
+ */
+ var _update = function(c, record) {
+ // get record handler (align type in table by subtracting lowest)
+ var aligned = record.type - tls.ContentType.change_cipher_spec;
+ var handlers = ctTable[c.entity][c.expect];
+ if(aligned in handlers) {
+ handlers[aligned](c, record);
+ } else {
+ // unexpected record
+ tls.handleUnexpected(c, record);
+ }
+ };
+
+ /**
+ * Reads the record header and initializes the next record on the given
+ * connection.
+ *
+ * @param c the TLS connection with the next record.
+ *
+ * @return 0 if the input data could be processed, otherwise the
+ * number of bytes required for data to be processed.
+ */
+ var _readRecordHeader = function(c) {
+ var rval = 0;
+
+ // get input buffer and its length
+ var b = c.input;
+ var len = b.length();
+
+ // need at least 5 bytes to initialize a record
+ if(len < 5) {
+ rval = 5 - len;
+ } else {
+ // enough bytes for header
+ // initialize record
+ c.record = {
+ type: b.getByte(),
+ version: {
+ major: b.getByte(),
+ minor: b.getByte()
+ },
+ length: b.getInt16(),
+ fragment: forge.util.createBuffer(),
+ ready: false
+ };
+
+ // check record version
+ var compatibleVersion = (c.record.version.major === c.version.major);
+ if(compatibleVersion && c.session && c.session.version) {
+ // session version already set, require same minor version
+ compatibleVersion = (c.record.version.minor === c.version.minor);
+ }
+ if(!compatibleVersion) {
+ c.error(c, {
+ message: 'Incompatible TLS version.',
+ send: true,
+ alert: {
+ level: tls.Alert.Level.fatal,
+ description: tls.Alert.Description.protocol_version
+ }
+ });
+ }
+ }
+
+ return rval;
+ };
+
+ /**
+ * Reads the next record's contents and appends its message to any
+ * previously fragmented message.
+ *
+ * @param c the TLS connection with the next record.
+ *
+ * @return 0 if the input data could be processed, otherwise the
+ * number of bytes required for data to be processed.
+ */
+ var _readRecord = function(c) {
+ var rval = 0;
+
+ // ensure there is enough input data to get the entire record
+ var b = c.input;
+ var len = b.length();
+ if(len < c.record.length) {
+ // not enough data yet, return how much is required
+ rval = c.record.length - len;
+ } else {
+ // there is enough data to parse the pending record
+ // fill record fragment and compact input buffer
+ c.record.fragment.putBytes(b.getBytes(c.record.length));
+ b.compact();
+
+ // update record using current read state
+ var s = c.state.current.read;
+ if(s.update(c, c.record)) {
+ // see if there is a previously fragmented message that the
+ // new record's message fragment should be appended to
+ if(c.fragmented !== null) {
+ // if the record type matches a previously fragmented
+ // record, append the record fragment to it
+ if(c.fragmented.type === c.record.type) {
+ // concatenate record fragments
+ c.fragmented.fragment.putBuffer(c.record.fragment);
+ c.record = c.fragmented;
+ } else {
+ // error, invalid fragmented record
+ c.error(c, {
+ message: 'Invalid fragmented record.',
+ send: true,
+ alert: {
+ level: tls.Alert.Level.fatal,
+ description:
+ tls.Alert.Description.unexpected_message
+ }
+ });
+ }
+ }
+
+ // record is now ready
+ c.record.ready = true;
+ }
+ }
+
+ return rval;
+ };
+
+ /**
+ * Performs a handshake using the TLS Handshake Protocol, as a client.
+ *
+ * This method should only be called if the connection is in client mode.
+ *
+ * @param sessionId the session ID to use, null to start a new one.
+ */
+ c.handshake = function(sessionId) {
+ // error to call this in non-client mode
+ if(c.entity !== tls.ConnectionEnd.client) {
+ // not fatal error
+ c.error(c, {
+ message: 'Cannot initiate handshake as a server.',
+ fatal: false
+ });
+ } else if(c.handshaking) {
+ // handshake is already in progress, fail but not fatal error
+ c.error(c, {
+ message: 'Handshake already in progress.',
+ fatal: false
+ });
+ } else {
+ // clear fail flag on reuse
+ if(c.fail && !c.open && c.handshakes === 0) {
+ c.fail = false;
+ }
+
+ // now handshaking
+ c.handshaking = true;
+
+ // default to blank (new session)
+ sessionId = sessionId || '';
+
+ // if a session ID was specified, try to find it in the cache
+ var session = null;
+ if(sessionId.length > 0) {
+ if(c.sessionCache) {
+ session = c.sessionCache.getSession(sessionId);
+ }
+
+ // matching session not found in cache, clear session ID
+ if(session === null) {
+ sessionId = '';
+ }
+ }
+
+ // no session given, grab a session from the cache, if available
+ if(sessionId.length === 0 && c.sessionCache) {
+ session = c.sessionCache.getSession();
+ if(session !== null) {
+ sessionId = session.id;
+ }
+ }
+
+ // set up session
+ c.session = {
+ id: sessionId,
+ version: null,
+ cipherSuite: null,
+ compressionMethod: null,
+ serverCertificate: null,
+ certificateRequest: null,
+ clientCertificate: null,
+ sp: {},
+ md5: forge.md.md5.create(),
+ sha1: forge.md.sha1.create()
+ };
+
+ // use existing session information
+ if(session) {
+ // only update version on connection, session version not yet set
+ c.version = session.version;
+ c.session.sp = session.sp;
+ }
+
+ // generate new client random
+ c.session.sp.client_random = tls.createRandom().getBytes();
+
+ // connection now open
+ c.open = true;
+
+ // send hello
+ tls.queue(c, tls.createRecord(c, {
+ type: tls.ContentType.handshake,
+ data: tls.createClientHello(c)
+ }));
+ tls.flush(c);
+ }
+ };
+
+ /**
+ * Called when TLS protocol data has been received from somewhere and should
+ * be processed by the TLS engine.
+ *
+ * @param data the TLS protocol data, as a string, to process.
+ *
+ * @return 0 if the data could be processed, otherwise the number of bytes
+ * required for data to be processed.
+ */
+ c.process = function(data) {
+ var rval = 0;
+
+ // buffer input data
+ if(data) {
+ c.input.putBytes(data);
+ }
+
+ // process next record if no failure, process will be called after
+ // each record is handled (since handling can be asynchronous)
+ if(!c.fail) {
+ // reset record if ready and now empty
+ if(c.record !== null &&
+ c.record.ready && c.record.fragment.isEmpty()) {
+ c.record = null;
+ }
+
+ // if there is no pending record, try to read record header
+ if(c.record === null) {
+ rval = _readRecordHeader(c);
+ }
+
+ // read the next record (if record not yet ready)
+ if(!c.fail && c.record !== null && !c.record.ready) {
+ rval = _readRecord(c);
+ }
+
+ // record ready to be handled, update engine state
+ if(!c.fail && c.record !== null && c.record.ready) {
+ _update(c, c.record);
+ }
+ }
+
+ return rval;
+ };
+
+ /**
+ * Requests that application data be packaged into a TLS record. The
+ * tlsDataReady handler will be called when the TLS record(s) have been
+ * prepared.
+ *
+ * @param data the application data, as a raw 'binary' encoded string, to
+ * be sent; to send utf-16/utf-8 string data, use the return value
+ * of util.encodeUtf8(str).
+ *
+ * @return true on success, false on failure.
+ */
+ c.prepare = function(data) {
+ tls.queue(c, tls.createRecord(c, {
+ type: tls.ContentType.application_data,
+ data: forge.util.createBuffer(data)
+ }));
+ return tls.flush(c);
+ };
+
+ /**
+ * Requests that a heartbeat request be packaged into a TLS record for
+ * transmission. The tlsDataReady handler will be called when TLS record(s)
+ * have been prepared.
+ *
+ * When a heartbeat response has been received, the heartbeatReceived
+ * handler will be called with the matching payload. This handler can
+ * be used to clear a retransmission timer, etc.
+ *
+ * @param payload the heartbeat data to send as the payload in the message.
+ * @param [payloadLength] the payload length to use, defaults to the
+ * actual payload length.
+ *
+ * @return true on success, false on failure.
+ */
+ c.prepareHeartbeatRequest = function(payload, payloadLength) {
+ if(payload instanceof forge.util.ByteBuffer) {
+ payload = payload.bytes();
+ }
+ if(typeof payloadLength === 'undefined') {
+ payloadLength = payload.length;
+ }
+ c.expectedHeartbeatPayload = payload;
+ tls.queue(c, tls.createRecord(c, {
+ type: tls.ContentType.heartbeat,
+ data: tls.createHeartbeat(
+ tls.HeartbeatMessageType.heartbeat_request, payload, payloadLength)
+ }));
+ return tls.flush(c);
+ };
+
+ /**
+ * Closes the connection (sends a close_notify alert).
+ *
+ * @param clearFail true to clear the fail flag (default: true).
+ */
+ c.close = function(clearFail) {
+ // save session if connection didn't fail
+ if(!c.fail && c.sessionCache && c.session) {
+ // only need to preserve session ID, version, and security params
+ var session = {
+ id: c.session.id,
+ version: c.session.version,
+ sp: c.session.sp
+ };
+ session.sp.keys = null;
+ c.sessionCache.setSession(session.id, session);
+ }
+
+ if(c.open) {
+ // connection no longer open, clear input
+ c.open = false;
+ c.input.clear();
+
+ // if connected or handshaking, send an alert
+ if(c.isConnected || c.handshaking) {
+ c.isConnected = c.handshaking = false;
+
+ // send close_notify alert
+ tls.queue(c, tls.createAlert(c, {
+ level: tls.Alert.Level.warning,
+ description: tls.Alert.Description.close_notify
+ }));
+ tls.flush(c);
+ }
+
+ // call handler
+ c.closed(c);
+ }
+
+ // reset TLS connection, do not clear fail flag
+ c.reset(clearFail);
+ };
+
+ return c;
+};
+
+/* TLS API */
+forge.tls = forge.tls || {};
+
+// expose non-functions
+for(var key in tls) {
+ if(typeof tls[key] !== 'function') {
+ forge.tls[key] = tls[key];
+ }
+}
+
+// expose prf_tls1 for testing
+forge.tls.prf_tls1 = prf_TLS1;
+
+// expose sha1 hmac method
+forge.tls.hmac_sha1 = hmac_sha1;
+
+// expose session cache creation
+forge.tls.createSessionCache = tls.createSessionCache;
+
+/**
+ * Creates a new TLS connection. This does not make any assumptions about the
+ * transport layer that TLS is working on top of, ie: it does not assume there
+ * is a TCP/IP connection or establish one. A TLS connection is totally
+ * abstracted away from the layer is runs on top of, it merely establishes a
+ * secure channel between a client" and a "server".
+ *
+ * A TLS connection contains 4 connection states: pending read and write, and
+ * current read and write.
+ *
+ * At initialization, the current read and write states will be null. Only once
+ * the security parameters have been set and the keys have been generated can
+ * the pending states be converted into current states. Current states will be
+ * updated for each record processed.
+ *
+ * A custom certificate verify callback may be provided to check information
+ * like the common name on the server's certificate. It will be called for
+ * every certificate in the chain. It has the following signature:
+ *
+ * variable func(c, certs, index, preVerify)
+ * Where:
+ * c The TLS connection
+ * verified Set to true if certificate was verified, otherwise the alert
+ * tls.Alert.Description for why the certificate failed.
+ * depth The current index in the chain, where 0 is the server's cert.
+ * certs The certificate chain, *NOTE* if the server was anonymous then
+ * the chain will be empty.
+ *
+ * The function returns true on success and on failure either the appropriate
+ * tls.Alert.Description or an object with 'alert' set to the appropriate
+ * tls.Alert.Description and 'message' set to a custom error message. If true
+ * is not returned then the connection will abort using, in order of
+ * availability, first the returned alert description, second the preVerify
+ * alert description, and lastly the default 'bad_certificate'.
+ *
+ * There are three callbacks that can be used to make use of client-side
+ * certificates where each takes the TLS connection as the first parameter:
+ *
+ * getCertificate(conn, hint)
+ * The second parameter is a hint as to which certificate should be
+ * returned. If the connection entity is a client, then the hint will be
+ * the CertificateRequest message from the server that is part of the
+ * TLS protocol. If the connection entity is a server, then it will be
+ * the servername list provided via an SNI extension the ClientHello, if
+ * one was provided (empty array if not). The hint can be examined to
+ * determine which certificate to use (advanced). Most implementations
+ * will just return a certificate. The return value must be a
+ * PEM-formatted certificate or an array of PEM-formatted certificates
+ * that constitute a certificate chain, with the first in the array/chain
+ * being the client's certificate.
+ * getPrivateKey(conn, certificate)
+ * The second parameter is an forge.pki X.509 certificate object that
+ * is associated with the requested private key. The return value must
+ * be a PEM-formatted private key.
+ * getSignature(conn, bytes, callback)
+ * This callback can be used instead of getPrivateKey if the private key
+ * is not directly accessible in javascript or should not be. For
+ * instance, a secure external web service could provide the signature
+ * in exchange for appropriate credentials. The second parameter is a
+ * string of bytes to be signed that are part of the TLS protocol. These
+ * bytes are used to verify that the private key for the previously
+ * provided client-side certificate is accessible to the client. The
+ * callback is a function that takes 2 parameters, the TLS connection
+ * and the RSA encrypted (signed) bytes as a string. This callback must
+ * be called once the signature is ready.
+ *
+ * @param options the options for this connection:
+ * server: true if the connection is server-side, false for client.
+ * sessionId: a session ID to reuse, null for a new connection.
+ * caStore: an array of certificates to trust.
+ * sessionCache: a session cache to use.
+ * cipherSuites: an optional array of cipher suites to use,
+ * see tls.CipherSuites.
+ * connected: function(conn) called when the first handshake completes.
+ * virtualHost: the virtual server name to use in a TLS SNI extension.
+ * verifyClient: true to require a client certificate in server mode,
+ * 'optional' to request one, false not to (default: false).
+ * verify: a handler used to custom verify certificates in the chain.
+ * getCertificate: an optional callback used to get a certificate or
+ * a chain of certificates (as an array).
+ * getPrivateKey: an optional callback used to get a private key.
+ * getSignature: an optional callback used to get a signature.
+ * tlsDataReady: function(conn) called when TLS protocol data has been
+ * prepared and is ready to be used (typically sent over a socket
+ * connection to its destination), read from conn.tlsData buffer.
+ * dataReady: function(conn) called when application data has
+ * been parsed from a TLS record and should be consumed by the
+ * application, read from conn.data buffer.
+ * closed: function(conn) called when the connection has been closed.
+ * error: function(conn, error) called when there was an error.
+ * deflate: function(inBytes) if provided, will deflate TLS records using
+ * the deflate algorithm if the server supports it.
+ * inflate: function(inBytes) if provided, will inflate TLS records using
+ * the deflate algorithm if the server supports it.
+ *
+ * @return the new TLS connection.
+ */
+forge.tls.createConnection = tls.createConnection;
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'tls';
+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',
+ './asn1',
+ './hmac',
+ './md',
+ './pem',
+ './pki',
+ './random',
+ './util'], function() {
+ defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();