secret-handshake-over-hypercore
v0.1.7
Published
Tarr's Secret Handshake over a shared encrypted Hypercore feed
Downloads
18
Readme
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-hypercore
Usage
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
sharedKey
is a 32 byte buffer containing a unique shared secret key. Both parties must have this keyopts
is 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.sharedKey
A required 32 byte buffer containing a unique shared secret key.opts.publicKey
An optional public key for the identity initiating the connectionopts.secretKey
An optional secret key for the identity initiating the connection that is associated withopts.publicKey
opts.connect
An optional function that should return aDuplex
stream to create a connection to the underlying hypercore replication streamopts.stream
An optional stream that takes the place of the return value ofopts.connect()
opts.storage
An optional factory function that accepts a connection feed type and returns a random-access-storage interface that will be given tohypercore(...)
opts.preserveReceiver
An optional boolean to indicate that the connection should preserve the receiver (Connection.RX
) feed messages written to it. (Default:false
)opts.preserveSender
An optional boolean to indicate that the connection should preserve the sender (Connection.TX
) feed messages written to it. (Default:false
)opts.capabilities
An 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.publicKey
The connections identity public keyinfo.sessionPublicKey
The ephemeral public key for the connection sessioninfo.remotePublicKey
The remotes identity public keyinfo.discoveryKey
The 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.remoteSessionPublicKey
The remotes ephemeral public key for the sessioninfo.remotePublicKey
The remotes identity public key
connection.on('auth', info)
Emitted when the connection has authenticated with the other
party where info
contains:
info.sessionCapabilities
An intersection of the capabilities given by both parties for the connection sessioninfo.remoteSessionPublicKey
The remotes ephemeral public key for the sessioninfo.remotePublicKey
The remotes identity public keyinfo.signature
The signature of the authentication proofinfo.proof
The 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