diff options
author | Minteck <contact@minteck.org> | 2023-02-23 19:34:56 +0100 |
---|---|---|
committer | Minteck <contact@minteck.org> | 2023-02-23 19:34:56 +0100 |
commit | 3d1cd02f27518f1a04374c7c8320cd5d82ede6e9 (patch) | |
tree | 75be5fba4368472fb11c8015aee026b2b9a71888 /includes/external/school/node_modules/node-forge/js/tls.js | |
parent | 8cc1f13c17fa2fb5a4410542d39e650e02945634 (diff) | |
download | pluralconnect-3d1cd02f27518f1a04374c7c8320cd5d82ede6e9.tar.gz pluralconnect-3d1cd02f27518f1a04374c7c8320cd5d82ede6e9.tar.bz2 pluralconnect-3d1cd02f27518f1a04374c7c8320cd5d82ede6e9.zip |
Updated 40 files, added 37 files, deleted 1103 files and renamed 3905 files (automated)
Diffstat (limited to 'includes/external/school/node_modules/node-forge/js/tls.js')
-rw-r--r-- | includes/external/school/node_modules/node-forge/js/tls.js | 4316 |
1 files changed, 4316 insertions, 0 deletions
diff --git a/includes/external/school/node_modules/node-forge/js/tls.js b/includes/external/school/node_modules/node-forge/js/tls.js new file mode 100644 index 0000000..b3bb2e8 --- /dev/null +++ b/includes/external/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)); +}); +})(); |