From 5f5e48e4140345d166b8c1a3ee0847b0d9e2d893 Mon Sep 17 00:00:00 2001 From: Chris Clark Date: Mon, 30 Nov 2015 14:04:52 -0800 Subject: [PATCH] Add support for client certificates --- docs/index.md | 3 ++ src/common/connection.js | 48 ++++++++++++++--------- src/common/schemas/input/api-options.json | 12 ++++++ 3 files changed, 44 insertions(+), 19 deletions(-) diff --git a/docs/index.md b/docs/index.md index 1fa7a01c..559981e3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -112,7 +112,10 @@ The RippleAPI constructor optionally takes one argument, an object with the foll Name | Type | Description ---- | ---- | ----------- authorization | string | *Optional* Username and password for HTTP basic authentication to the rippled server in the format **username:password**. +certificate | string | *Optional* A string containing the certificate key of the client in PEM format. (Can be an array of certificates). feeCushion | number | *Optional* Factor to multiply estimated fee by to provide a cushion in case the required fee rises during submission of a transaction. Defaults to `1.2`. +key | string | *Optional* A string containing the private key of the client in PEM format. (Can be an array of keys). +passphrase | string | *Optional* The passphrase for the private key of the client. proxy | uri string | *Optional* URI for HTTP/HTTPS proxy to use to connect to the rippled server. proxyAuthorization | string | *Optional* Username and password for HTTP basic authentication to the proxy in the format **username:password**. server | uri string | *Optional* URI for rippled websocket port to connect to. Must start with `wss://` or `ws://`. diff --git a/src/common/connection.js b/src/common/connection.js index 0960a3e9..f0dfa0ab 100644 --- a/src/common/connection.js +++ b/src/common/connection.js @@ -1,4 +1,5 @@ 'use strict'; +const _ = require('lodash'); const {EventEmitter} = require('events'); const WebSocket = require('ws'); const parseURL = require('url').parse; @@ -26,6 +27,9 @@ class Connection extends EventEmitter { this._proxyAuthorization = options.proxyAuthorization; this._authorization = options.authorization; this._trustedCertificates = options.trustedCertificates; + this._key = options.key; + this._passphrase = options.passphrase; + this._certificate = options.certificate; this._timeout = options.timeout || (20 * 1000); this._isReady = false; this._ws = null; @@ -104,20 +108,21 @@ class Connection extends EventEmitter { }); } - _createWebSocket(url, proxyURL, proxyAuthorization, authorization, - trustedCertificates) { + _createWebSocket() { const options = {}; - if (proxyURL !== undefined) { - const parsedURL = parseURL(url); - const proxyOptions = parseURL(proxyURL); - proxyOptions.secureEndpoint = (parsedURL.protocol === 'wss:'); - proxyOptions.secureProxy = (proxyOptions.protocol === 'https:'); - if (proxyAuthorization !== undefined) { - proxyOptions.auth = proxyAuthorization; - } - if (trustedCertificates !== undefined) { - proxyOptions.ca = trustedCertificates; - } + if (this._proxyURL !== undefined) { + const parsedURL = parseURL(this._url); + const parsedProxyURL = parseURL(this._proxyURL); + const proxyOverrides = _.omit({ + secureEndpoint: (parsedURL.protocol === 'wss:'), + secureProxy: (parsedProxyURL.protocol === 'https:'), + auth: this._proxyAuthorization, + ca: this._trustedCertificates, + key: this._key, + passphrase: this._passphrase, + cert: this._certificate + }, _.isUndefined); + const proxyOptions = _.assign({}, parsedProxyURL, proxyOverrides); let HttpsProxyAgent; try { HttpsProxyAgent = require('https-proxy-agent'); @@ -126,11 +131,18 @@ class Connection extends EventEmitter { } options.agent = new HttpsProxyAgent(proxyOptions); } - if (authorization !== undefined) { - const base64 = new Buffer(authorization).toString('base64'); + if (this._authorization !== undefined) { + const base64 = new Buffer(this._authorization).toString('base64'); options.headers = {Authorization: `Basic ${base64}`}; } - const websocket = new WebSocket(url, options); + const optionsOverrides = _.omit({ + ca: this._trustedCertificates, + key: this._key, + passphrase: this._passphrase, + cert: this._certificate + }, _.isUndefined); + const websocketOptions = _.assign({}, options, optionsOverrides); + const websocket = new WebSocket(this._url, websocketOptions); // we will have a listener for each outstanding request, // so we have to raise the limit (the default is 10) websocket.setMaxListeners(Infinity); @@ -148,9 +160,7 @@ class Connection extends EventEmitter { } else if (this._state === WebSocket.CONNECTING) { this._ws.once('open', resolve); } else { - this._ws = this._createWebSocket(this._url, this._proxyURL, - this._proxyAuthorization, this._authorization, - this._trustedCertificates); + this._ws = this._createWebSocket(); this._ws.on('message', this._onMessage.bind(this)); this._onUnexpectedCloseBound = this._onUnexpectedClose.bind(this); this._ws.once('close', this._onUnexpectedCloseBound); diff --git a/src/common/schemas/input/api-options.json b/src/common/schemas/input/api-options.json index 0ad9e345..9272dd46 100644 --- a/src/common/schemas/input/api-options.json +++ b/src/common/schemas/input/api-options.json @@ -42,6 +42,18 @@ "type": "string", "description": "A PEM-formatted SSL certificate to trust when connecting to a proxy." } + }, + "key": { + "type": "string", + "description": "A string containing the private key of the client in PEM format. (Can be an array of keys)." + }, + "passphrase": { + "type": "string", + "description": "The passphrase for the private key of the client." + }, + "certificate": { + "type": "string", + "description": "A string containing the certificate key of the client in PEM format. (Can be an array of certificates)." } }, "additionalProperties": false