| /** |
| * Socket wrapping functions for TLS. |
| * |
| * @author Dave Longley |
| * |
| * Copyright (c) 2009-2012 Digital Bazaar, Inc. |
| */ |
| (function() { |
| /* ########## Begin module implementation ########## */ |
| function initModule(forge) { |
| |
| /** |
| * Wraps a forge.net socket with a TLS layer. |
| * |
| * @param options: |
| * sessionId: a session ID to reuse, null for a new connection if no session |
| * cache is provided or it is empty. |
| * 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. |
| * socket: the socket to wrap. |
| * virtualHost: the virtual server name to use in a TLS SNI extension. |
| * verify: a handler used to custom verify certificates in the chain. |
| * getCertificate: an optional callback used to get a certificate. |
| * getPrivateKey: an optional callback used to get a private key. |
| * getSignature: an optional callback used to get a signature. |
| * 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 TLS-wrapped socket. |
| */ |
| forge.tls.wrapSocket = function(options) { |
| // get raw socket |
| var socket = options.socket; |
| |
| // create TLS socket |
| var tlsSocket = { |
| id: socket.id, |
| // set handlers |
| connected: socket.connected || function(e){}, |
| closed: socket.closed || function(e){}, |
| data: socket.data || function(e){}, |
| error: socket.error || function(e){} |
| }; |
| |
| // create TLS connection |
| var c = forge.tls.createConnection({ |
| server: false, |
| sessionId: options.sessionId || null, |
| caStore: options.caStore || [], |
| sessionCache: options.sessionCache || null, |
| cipherSuites: options.cipherSuites || null, |
| virtualHost: options.virtualHost, |
| verify: options.verify, |
| getCertificate: options.getCertificate, |
| getPrivateKey: options.getPrivateKey, |
| getSignature: options.getSignature, |
| deflate: options.deflate, |
| inflate: options.inflate, |
| connected: function(c) { |
| // first handshake complete, call handler |
| if(c.handshakes === 1) { |
| tlsSocket.connected({ |
| id: socket.id, |
| type: 'connect', |
| bytesAvailable: c.data.length() |
| }); |
| } |
| }, |
| tlsDataReady: function(c) { |
| // send TLS data over socket |
| return socket.send(c.tlsData.getBytes()); |
| }, |
| dataReady: function(c) { |
| // indicate application data is ready |
| tlsSocket.data({ |
| id: socket.id, |
| type: 'socketData', |
| bytesAvailable: c.data.length() |
| }); |
| }, |
| closed: function(c) { |
| // close socket |
| socket.close(); |
| }, |
| error: function(c, e) { |
| // send error, close socket |
| tlsSocket.error({ |
| id: socket.id, |
| type: 'tlsError', |
| message: e.message, |
| bytesAvailable: 0, |
| error: e |
| }); |
| socket.close(); |
| } |
| }); |
| |
| // handle doing handshake after connecting |
| socket.connected = function(e) { |
| c.handshake(options.sessionId); |
| }; |
| |
| // handle closing TLS connection |
| socket.closed = function(e) { |
| if(c.open && c.handshaking) { |
| // error |
| tlsSocket.error({ |
| id: socket.id, |
| type: 'ioError', |
| message: 'Connection closed during handshake.', |
| bytesAvailable: 0 |
| }); |
| } |
| c.close(); |
| |
| // call socket handler |
| tlsSocket.closed({ |
| id: socket.id, |
| type: 'close', |
| bytesAvailable: 0 |
| }); |
| }; |
| |
| // handle error on socket |
| socket.error = function(e) { |
| // error |
| tlsSocket.error({ |
| id: socket.id, |
| type: e.type, |
| message: e.message, |
| bytesAvailable: 0 |
| }); |
| c.close(); |
| }; |
| |
| // handle receiving raw TLS data from socket |
| var _requiredBytes = 0; |
| socket.data = function(e) { |
| // drop data if connection not open |
| if(!c.open) { |
| socket.receive(e.bytesAvailable); |
| } else { |
| // only receive if there are enough bytes available to |
| // process a record |
| if(e.bytesAvailable >= _requiredBytes) { |
| var count = Math.max(e.bytesAvailable, _requiredBytes); |
| var data = socket.receive(count); |
| if(data !== null) { |
| _requiredBytes = c.process(data); |
| } |
| } |
| } |
| }; |
| |
| /** |
| * Destroys this socket. |
| */ |
| tlsSocket.destroy = function() { |
| socket.destroy(); |
| }; |
| |
| /** |
| * Sets this socket's TLS session cache. This should be called before |
| * the socket is connected or after it is closed. |
| * |
| * The cache is an object mapping session IDs to internal opaque state. |
| * An application might need to change the cache used by a particular |
| * tlsSocket between connections if it accesses multiple TLS hosts. |
| * |
| * @param cache the session cache to use. |
| */ |
| tlsSocket.setSessionCache = function(cache) { |
| c.sessionCache = tls.createSessionCache(cache); |
| }; |
| |
| /** |
| * Connects this socket. |
| * |
| * @param options: |
| * host: the host to connect to. |
| * port: the port to connect to. |
| * policyPort: the policy port to use (if non-default), 0 to |
| * use the flash default. |
| * policyUrl: the policy file URL to use (instead of port). |
| */ |
| tlsSocket.connect = function(options) { |
| socket.connect(options); |
| }; |
| |
| /** |
| * Closes this socket. |
| */ |
| tlsSocket.close = function() { |
| c.close(); |
| }; |
| |
| /** |
| * Determines if the socket is connected or not. |
| * |
| * @return true if connected, false if not. |
| */ |
| tlsSocket.isConnected = function() { |
| return c.isConnected && socket.isConnected(); |
| }; |
| |
| /** |
| * Writes bytes to this socket. |
| * |
| * @param bytes the bytes (as a string) to write. |
| * |
| * @return true on success, false on failure. |
| */ |
| tlsSocket.send = function(bytes) { |
| return c.prepare(bytes); |
| }; |
| |
| /** |
| * Reads bytes from this socket (non-blocking). Fewer than the number of |
| * bytes requested may be read if enough bytes are not available. |
| * |
| * This method should be called from the data handler if there are enough |
| * bytes available. To see how many bytes are available, check the |
| * 'bytesAvailable' property on the event in the data handler or call the |
| * bytesAvailable() function on the socket. If the browser is msie, then the |
| * bytesAvailable() function should be used to avoid race conditions. |
| * Otherwise, using the property on the data handler's event may be quicker. |
| * |
| * @param count the maximum number of bytes to read. |
| * |
| * @return the bytes read (as a string) or null on error. |
| */ |
| tlsSocket.receive = function(count) { |
| return c.data.getBytes(count); |
| }; |
| |
| /** |
| * Gets the number of bytes available for receiving on the socket. |
| * |
| * @return the number of bytes available for receiving. |
| */ |
| tlsSocket.bytesAvailable = function() { |
| return c.data.length(); |
| }; |
| |
| return tlsSocket; |
| }; |
| |
| } // end module implementation |
| |
| /* ########## Begin module wrapper ########## */ |
| var name = 'tlssocket'; |
| 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', './tls'], function() { |
| defineFunc.apply(null, Array.prototype.slice.call(arguments, 0)); |
| }); |
| })(); |