| /** |
| * 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)); |
| }); |
| })(); |