You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
249 lines
6.8 KiB
249 lines
6.8 KiB
/**
|
|
* Socket wrapping functions for TLS.
|
|
*
|
|
* @author Dave Longley
|
|
*
|
|
* Copyright (c) 2009-2012 Digital Bazaar, Inc.
|
|
*/
|
|
var forge = require('./forge');
|
|
require('./tls');
|
|
|
|
/**
|
|
* 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;
|
|
};
|
|
|