secret-handshake-over-hypercore v0.1.7
secret-handshake-over-hypercore
A variant of Tarr's Secret Handshake over a shared Hypercore feed to create secure ephemeral authenticated channels based on capabilities.
Installation
$ npm install secret-handshake-over-hypercoreUsage
const shh = require('secret-handshake-over-hypercore')
// connect to peer
shh.connect(sharedKey, opts)
// generate a key pair
shh.keygen() // returns publicKey and secretKey pair
// create capability
shh.capability('my capability') // returns 32 byte BLAKE2b hash of 'my capability' (without white space)
// or a keyed capability
shh.capability('my capability', publicKey)Example
The example below demonstrates a connection between a client and a
server where read and write capabilities are required. If both
parties do not have an equal intersection of capabilities, then
authentication will fail and the streams will connect.
Server
const shh = require('secret-handshake-over-hypercore')
const net = require('net')
const sharedKey = Buffer.from('12abf5a9165201b0d5f284d7d902f57b19ca0a6f974bcd8fcc3162c93b2b75f1', 'hex')
const server = net.createServer((socket) => {
const stream = shh.connect(sharedKey, {
stream: socket,
capabilities: [
shh.capability('read'),
shh.capability('write'),
]
})
stream.on('handshake', () => {
stream.write('hello')
})
})
server.listen(3000)Client
const shh = require('secret-handshake-over-hypercore')
const sharedKey = Buffer.from('12abf5a9165201b0d5f284d7d902f57b19ca0a6f974bcd8fcc3162c93b2b75f1', 'hex')
const stream = shh.connect(sharedKey, {
connect() { return new.connect(3000) },
capabilities: [
shh.capability('write'),
shh.capability('read'),
]
})
stream.on('handshake', () => {
stream.write('hello')
})How?
This module uses a shared key to create a hypercore replication feed that is read only. This feed is the initial feed used to create a secure channel for sender and receiver hypercore feeds that both sides of the connection have to read from and write to. These hypercore feeds are used to exchange enough messages using a secret handshake to create a shared secret key. The sender and receiver feeds are re-established with session key pairs and the shared secret key is used to box messages that are written to both sides. The initial feed's public key is a BLAKE2b hash of the shared key. The shared key is never shared.
API
shh.keygen()
Generate an ed25519 key pair. The publicKey is suitable for use as a
shared key and can be delegated securely to interested parties.
const { publicKey, secretKey } = shh.keygen()stream = shh.connect(sharedKey[, opts])
Creates an encrypted connection for a sharedKey over a stream
connected to Duplex stream like net.Socket.
const stream = shh.connect(sharedKey, {
connect() {
return net.connect(3000) // connect to localhost:3000
}
})Where
sharedKeyis a 32 byte buffer containing a unique shared secret key. Both parties must have this keyoptsis the same as Connection options
capability = shh.capability(capabilityType, [, key])
Creates a 32 byte BLAKE2b hash of capabilityType keyed with an optional
32 byte buffer key. See Connection Capablities.
const { publicKey, secretKey } = shh.keygen()
const capabilities = [
shh.capability('auth', publicKey),
shh.capability('read', publicKey),
shh.capability('write', publicKey),
]connection = new shh.Connection(opts)
The Connection class is a Duplex stream that wraps the handshake
and hypercore feeds used to establish a secure channel. It can be written
to and read from safely after the 'handshake' event has been emitted.
The constructor accepts a single argument opts where:
opts.sharedKeyA required 32 byte buffer containing a unique shared secret key.opts.publicKeyAn optional public key for the identity initiating the connectionopts.secretKeyAn optional secret key for the identity initiating the connection that is associated withopts.publicKeyopts.connectAn optional function that should return aDuplexstream to create a connection to the underlying hypercore replication streamopts.streamAn optional stream that takes the place of the return value ofopts.connect()opts.storageAn optional factory function that accepts a connection feed type and returns a random-access-storage interface that will be given tohypercore(...)opts.preserveReceiverAn optional boolean to indicate that the connection should preserve the receiver (Connection.RX) feed messages written to it. (Default:false)opts.preserveSenderAn optional boolean to indicate that the connection should preserve the sender (Connection.TX) feed messages written to it. (Default:false)opts.capabilitiesAn optional array of capabilities that you express and require the connecting party to also provide. See Connection Capabilities
connection.encryptionKey
The initial shared encryption key used to encrypt messages in the sender
feed. This property is set after the 'handshake' event is emitted.
connection.hasHandshake
A predicate boolean to indicate if the connection has completed
the handshake. This should be true after the 'handshake' event is
emitted.
connection.connecting
A predicate boolean to indicate if the connection is currently in a
connecting phase. This is set to true when connection.connect() is
called and set to false right before the 'connect' event.
connection.connected
A predicate boolean to indicate if the connection is currently connected
to another party. This is set to true right before the 'connect'
event is emitted.
connection.reading
A predicate boolean to indicate if the connection is currently reading from the other party.
connection.on('handshake', connection)
Emitted when the connection establishes a handshake and can be written to and read from
connection.on('connect', stream, info)
Emitted when the connection connects to the remote where stream
is the top level replication feed and info contains
info.publicKeyThe connections identity public keyinfo.sessionPublicKeyThe ephemeral public key for the connection sessioninfo.remotePublicKeyThe remotes identity public keyinfo.discoveryKeyThe hypercore discovery key for the initial remote feed
connection.on(Connection.FEED, feed)
Emitted when the top level hypercore feed is ready.
connection.on(Connection.TXI, sender)
Emitted when the initial sender hypercore feed is ready.
connection.on(Connection.RXI, receiver)
Emitted when the initial receiver hypercore feed is ready.
connection.on(Connection.TX, sender)
Emitted when the session sender hypercore feed is ready.
connection.on(Connection.RX, receiver)
Emitted when the session receiver hypercore feed is ready.
connection.on('hello', info)
Emitted when the connection receives a hello from the other
party where info contain:
info.remoteSessionPublicKeyThe remotes ephemeral public key for the sessioninfo.remotePublicKeyThe remotes identity public key
connection.on('auth', info)
Emitted when the connection has authenticated with the other
party where info contains:
info.sessionCapabilitiesAn intersection of the capabilities given by both parties for the connection sessioninfo.remoteSessionPublicKeyThe remotes ephemeral public key for the sessioninfo.remotePublicKeyThe remotes identity public keyinfo.signatureThe signature of the authentication proofinfo.proofThe authentication proof
connection.on('error', err)
Emitted when a error occurs in the connection.
Connection Feed Types
The connection established over the stream uses several hypercore feeds
to ensure a secure channel between both parties. The types of feeds used
are represented as string constants and are given to the
opts.storage(feedType) factory function, if supplied by the user. This
is useful if you want to provide your own storage backend.
Connection.FEED
The top level hypercore feed established for a secure symmetric channel. This feed is never written to.
Connection.FEED = 'feed'Connection.TXI
The initial sender hypercore feed that both parties establish as an initial
feed to write to that the other party will read from. This feed is used
for the handshake and then destroyed before establishing a session
sender feed. This feed is represented as the Connection.RXI feed on
the other end.
Connection.TXI = 'txi'Connection.RXI
The initial receiver hypercore feed that both parties establish as an initial
feed to read from that the other party will write to. This feed is used
for the handshake and then destroyed before establishing a session
receiver feed. This feed is represented as the Connection.TXI feed on
the other end.
Connection.RXI = 'rxi'Connection.TX
The ephemeral sender hypercore feed that both parties establish after a handshake. Messages written to this feed are encrypted with a unique key derived from a shared encryption key, the current message counter, and an incremented nonce making each message encrypted with a unique key. This feed is both readable and writable.
Connection.TX = 'tx'Connection.RX
The ephemeral receiver hypercore feed that both parties establish after a handshake. Messages written to this feed are encrypted with a unique key derived from a shared encryption key, the current message counter, and an incremented nonce making each message encrypted with a unique key. This feed is readable only.
Connection.RX = 'rx'Connection Capabilities
Connection capabilities provide a way to require extra information both parties in the handshake need to know. When given, the handshake requires that both parties share at least one capability during authentication. The intersection of the capabilities between two parties is used as an operand for deriving the initial encryption key used to encrypt messages in the connection.
Capabilities are created with the shh.capability() function.
Capabilities are 32 byte BLAKE2b hashes of the input given to the
function.
> shh.capability('auth')
<Buffer f4 77 ca 2d 48 ad 0a 39 77 8a f2 86 e1 2b 90 8d a8 53 12 cb 0b e4 e1 f8 c8 eb c0 fd 2e d4 1c af>Capabilities are given to shh.connect() or new Connection(). If
capabilities are not desired then both parties should omit them.
License
MIT