secure-peer

peer-to-peer encrypted streams using public key cryptography and signing

Github星跟踪图

secure-peer

Create encrypted peer-to-peer streams using public key cryptography and signing.

No certificates, no authorities. Each side of the connection has the same kind
of keys so it doesn't matter which side initiates the connection.

build status

example

First generate some public/private keypairs with
rsa-json:

$ rsa-json > a.json
$ rsa-json > b.json
var secure = require('secure-peer');
var peer = secure(require('./a.json'));
var through = require('through');

var net = require('net');
var server = net.createServer(function (rawStream) {
    var sec = peer(function (stream) {
        stream.pipe(through(function (buf) {
            this.emit('data', String(buf).toUpperCase());
        })).pipe(stream);
    });
    sec.pipe(rawStream).pipe(sec);
});
server.listen(5000);
var secure = require('secure-peer');
var peer = secure(require('./b.json'));

var net = require('net');
var rawStream = net.connect(5000);

var sec = peer(function (stream) {
    stream.pipe(process.stdout);
    stream.end('beep boop\n');
});
sec.pipe(rawStream).pipe(sec);

sec.on('identify', function (id) {
    // you can asynchronously verify that the key matches the known value here
    id.accept();
});

For extra security, you should keep a file around with known hosts to verify
that the public key you receive on the first connection doesn't change later
on like how ~/.ssh/known_hosts works.

Maintaining a known hosts file is outside the scope of this module.

protocol

secure-peer implements a very simple protocol that gives gives confidential content, although the identity of the peers (pubkeys) are leaked to a passive evesdropper.

The protocol begins by sending a handshake
code

    var outgoing = {
        token : crypto.randomBytes(64).toString('base64'),
        key : {
            type : 'rsa',
            public : keys.public,
        },
        dh : {
            group : dh.group,
            public : dh.getPublicKey('base64')
        },
        ciphers : ciphers
    };

The header contains a token (used to prevent replay attacks), your public key, a new diffie-helman public key,
and the list of supported ciphers.

This is then signed with the corresponding private key, and sent as a line of json. code All binary values are base64 encoded.

Upon receiving the remote peer's header, the signature is verified (against their supplied public key), and the identity event is emitted. A secure-peer client (i.e. the peer that initiated the connection) should now check that they have achived a connection to the peer they expected to connect to. It would be expected for a server to receive connections from potentially unknown peers.

If all the identity listeners call ack.accept() then the connection is accepted,
and the secret is derived from the diffie-helman keys.

The cipher is selected by a ordinal voting algorithm. Each peer sends an preference ordered list of supported ciphers. The cipher is picked by selecting the peer with the greatest token, and then using their most preferred token which is also supported by the other peer.

Security Hole (minor): Since a peer can choose their token "randomly" they can choose particularily high tokens that are highly likely to allow them to pick the cipher. This could be used for a cipher downgrade attack... cipher selection is generally problematic... but this weakness is probably best remedied by some out of band way to make sure the network upgrade cycle is short. It might be better to elect the cipher picking peer by a fair method (where the winner is provably fair, such picking the peer who's token is closest to hash(p1.token + p2.token). Another option might be to allow the server (i.e. person who picked up the phone) to select the cipher since they are in a slightly more vulnerable position (and more likely to be the victum in an attack).

Now, stream packets may be sent to the remote peer. Each packet is framed along with an incrementing sequence number, and the token sent by the remote peer, this prevents replay attacks, because the peer will not accept packets with the wrong token, and prevents reordering attacks, since the peer will not accept replayed packets if the sequence numbers are not in order.

Security Hole: Although the session cannot be replayed, the initial handshake can be replayed. This will cause the server to believe it has established a connection, if the attacker does not send any content then the attack will not be discovered. This could probably be used for a denial of service attack, or maybe to cause the server to leak information via timing, in cases where they implement a protocol that streams realtime data without waiting for the client to send anything.

methods

var secure = require('secure-peer')

var peer = secure(keys, opts={})

Return a function to create streams given the keys supplied.

keys.private should be a private PEM string and keys.public should be a
public PEM string.

You can generate keypairs with rsa-json.

You can set a preference ordering array of ciphers to use with opts.ciphers.
Both sides will use a deterministic ordinal voting algorithm to determine which
cipher to use.
See openssl list-cipher-algorithms for the whole list.

var sec = peer(cb)

Create a new duplex stream sec that caries the encrypted contents. This stream
is safe to stream over the wire, including untrusted networks.

cb is a shorthand to listen on the 'connection' event just like
net.createServer().

events

sec.on('connection', function (stream) {})

Emitted with the decrypted plaintext stream when the secure connection has been
established successfully.

stream.id is the identify object from the 'identify' event.

sec.on('identify', function (id) {})

Emitted when the connection identifies with its public key, id.key.

Each listener must call either id.accept() or id.reject().

The connection won't be accepted until all listeners call id.accept(). If any
listener calls id.reject(), the connection will be aborted.

id.accept()

Accept the connection. This function must be called for every listener on the
'identify' event for the connection to succeed.

id.reject()

Reject the connection. The connection will not succeed even if id.accept() was
called in another listener.

sec.on('header', function (header) {})

Emitted when the remote side provides a signed header.payload json string signed
with its private key in header.hash.

install

With npm do:

npm install secure-peer

license

MIT

主要指标

概览
名称与所有者substack/secure-peer
主编程语言JavaScript
编程语言JavaScript (语言数: 1)
平台
许可证Other
所有者活动
创建于2012-11-08 10:49:40
推送于2015-05-05 23:27:14
最后一次提交2015-05-05 16:27:14
发布数7
最新版本名称0.2.1 (发布于 2013-08-03 01:48:03)
第一版名称0.0.0 (发布于 2012-11-08 02:47:32)
用户参与
星数205
关注者数17
派生数15
提交数74
已启用问题?
问题数6
打开的问题数3
拉请求数1
打开的拉请求数1
关闭的拉请求数1
项目设置
已启用Wiki?
已存档?
是复刻?
已锁定?
是镜像?
是私有?