Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 17 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ in the below methods, `keys` is an object of the following form:

``` js
{
"feedType": "ed25519"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been thinking about this recently with binary encoding of ids.

What I don't like about this is that ed25519 is a cryptographic curve thing... but that doesn't tell us anything about the feed type... like are the messages in this feed encoded as JSON, or are they some binary format? what's the spec for verifying this feedType?

I calling this feedType "classic", which happens to be ed25519 curve connection + signing, sha256 hashing, JSON encoding. "gabby" is another feedType which uses ed25519 but has some other stuff going on I gather

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup, exactly. Same is true for the hash function.

In the protocol meetup last may we therefore renamed the SHA to „scuttlebutt happend anyway“.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was also going to comment about the feedType and I might be a little late, but I think this is specifically referring to the type of encryption used in the message.

So, isn't something like encryption a possible better name for this property?

"curve": "ed25519",
"public": "<base64_public_key>.ed25519",
"private": "<base64_private_key>.ed25519",
Expand All @@ -53,11 +54,15 @@ when stored in a file, the file also contains a comment warning the reader
about safe private key security.
Comment lines are prefixed with `#` after removing them the result is valid JSON.

The `curve` property is a legacy property used to denote the feed type and may be deprecated in the future.

### hash (data, encoding) => id
Returns the sha256 hash of a given data. If encoding is not provided then it is assumed to be _binary_.

### getTag (ssb_id) => tag
The SSB ids contain a tag at the end. This function returns it.
### getFeedType (ssbId) => feedType

Each SSB ID contains a feed type at the end. This function returns it.

So if you have a string like `@gaQw6zD4pHrg8zmrqku24zTSAINhRg=.ed25519` this function would return `ed25519`.
This is useful as SSB start providing features for different encryption methods and cyphers.

Expand All @@ -77,22 +82,24 @@ variations and parts `loadOrCreate` (async), `load`, `create`
`createSync` `loadSync`. But since you only need to load once,
using the combined function is easiest.

`keys` is an object as described in [`keys`](#keys) section.

### loadOrCreate (filename, cb)

If a sync file access method is not available, `loadOrCreate` can be called with a
callback. that callback will be called with `cb(null, keys)`. If loading
the keys errored, new keys are created.

### generate(curve, seed) => keys
### generate(feedType, seed) => keys

generate a key, with optional seed.
curve defaults to `ed25519` (and no other type is currently supported)

feedType defaults to `ed25519` but also supports `ed25519.test`

seed should be a 32 byte buffer.

`keys` is an object as described in [`keys`](#keys) section.

New feed types can be added with `ssbKeys.use()`.

### signObj(keys, hmac_key?, obj)

signs a javascript object, and then adds a signature property to it.
Expand All @@ -109,7 +116,6 @@ The fine details of the signature format are described in the [protocol guide](h

verify a signed object. `hmac_key` must be the same value as passed to `signObj`.


### box(content, recipients) => boxed

encrypt a message content to many recipients. msg will be JSON encoded, then encrypted
Expand Down Expand Up @@ -145,15 +151,11 @@ symmetrically encrypt an object with `key` (a buffer)

symmetrically decrypt an object with `key` (a buffer)

### LICENSE

MIT





### use(feedType, { generate, sign, verify }) => ssbKeys

add new feed type to be used with `ssbKeys.generate()`

### LICENSE

MIT

115 changes: 73 additions & 42 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
'use strict'
var sodium = require('chloride')

var pb = require('private-box')

var u = require('./util')

var isBuffer = Buffer.isBuffer

//UTILS
Expand All @@ -22,57 +19,85 @@ var hmac = sodium.crypto_auth

exports.hash = u.hash

exports.getTag = u.getTag
exports.getFeedType = u.getFeedType
exports.getTag = u.getFeedType // deprecated
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add that annoying console.log warning ?

const warnings = [
  'please no',
  'naughty developer',
  'I ask you to stop',
  'I will stop working',
  'I warn you'
]
var patience = warnings.length

exports.getTag = function () {
  if (patience === 0) throw new Error('ssb-keys quits')
  console.log(warnings[--patience])
  
  return u.getFeedType()
}


function isObject (o) {
return 'object' === typeof o
}

function isFunction (f) {
return 'function' === typeof f
}

function isString(s) {
return 'string' === typeof s
}

var curves = {}
curves.ed25519 = require('./sodium')
const feedTypes = {
ed25519: require('./sodium'),
'ed25519.test': require('./sodium')
}

exports.use = (name, object) => {
if (typeof name !== 'string' || name.length === 0) {
throw new Error(`Invalid name: "${name}", expected string with non-zero length`)
}

const requiredMethods = [
'generate',
'sign',
'verify'
]

const isNotObject = typeof object !== 'object'
const isInvalidObject = isNotObject || requiredMethods.some(methodName =>
typeof object[methodName] !== 'function'
)

if (isInvalidObject) {
const expectedMethods = requiredMethods.join(', ')
console.log(object)
throw new Error(`Invalid object. Missing required methods, expected: ${expectedMethods}`)
}

if (feedTypes[name] != null) {
throw new Error(`Duplicate feed type: "${name}"`)
}

feedTypes[name] = object
}

function getCurve(keys) {
var curve = keys.curve
function getFeedType(keys) {
let { feedType } = keys
feedType = feedType || keys.curve
Copy link
Member

@mixmix mixmix Feb 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<style> If you're using es6 tools do this instead:

const {
  feedType = keys.curve
} = keys

actually this is a mess (it works well for destructuring multiple opts with defaults)
This is clearer surely

const feedType = keys.feedType || keys.curve


if(!keys.curve && isString(keys.public))
if(!feedType && isString(keys.public))
keys = keys.public

if(!curve && isString(keys))
curve = u.getTag(keys)
if(!feedType && isString(keys))
feedType = u.getFeedType(keys)

if(!curves[curve]) {
throw new Error(
'unkown curve:' + curve +
' expected: '+Object.keys(curves)
)
if(!feedTypes[feedType]) {
throw new Error(`unkown feed type: "${feedType}", expected: "${Object.keys(feedTypes)}"`)
}

return curve
return feedType
}

//this should return a key pair:
// {curve: curve, public: Buffer, private: Buffer}
// { feedType: string, curve: string, public: Buffer, private: Buffer}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is a known requirement, then why don't you check this is what the generate does when you register a new type with the use() method?

exports.generate = function (feedType, seed) {
feedType = feedType || 'ed25519'

exports.generate = function (curve, seed) {
curve = curve || 'ed25519'
if(feedTypes[feedType] == null)
throw new Error(`unknown feed type: "${feedType}"`)

if(!curves[curve])
throw new Error('unknown curve:'+curve)

return u.keysToJSON(curves[curve].generate(seed), curve)
return u.keysToJSON(feedTypes[feedType].generate(seed), feedType)
}

//import functions for loading/saving keys from storage
var storage = require('./storage')(exports.generate)
for(var key in storage) exports[key] = storage[key]
exports.load = storage.load
exports.loadSync = storage.loadSync
exports.create = storage.create
exports.createSync = storage.createSync
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️



exports.loadOrCreate = function (filename, cb) {
Expand All @@ -96,14 +121,17 @@ exports.loadOrCreateSync = function (filename) {

function sign (keys, msg) {
if(isString(msg))
msg = new Buffer(msg)
msg = Buffer.from(msg)
if(!isBuffer(msg))
throw new Error('msg should be buffer')
var curve = getCurve(keys)
var feedType = getFeedType(keys)

return curves[curve]
const prefix = feedTypes[feedType]
.sign(u.toBuffer(keys.private || keys), msg)
.toString('base64')+'.sig.'+curve
.toString('base64')
const suffix = `.sig.${feedType}`

return prefix + suffix

}

Expand All @@ -112,10 +140,10 @@ function sign (keys, msg) {
function verify (keys, sig, msg) {
if(isObject(sig))
throw new Error('signature should be base64 string, did you mean verifyObj(public, signed_obj)')
return curves[getCurve(keys)].verify(
return feedTypes[getFeedType(keys)].verify(
u.toBuffer(keys.public || keys),
u.toBuffer(sig),
isBuffer(msg) ? msg : new Buffer(msg)
isBuffer(msg) ? msg : Buffer.from(msg)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<non-expert> I think it might be a good idea to explicitly declare the encoding with Buffer.from

i.e.

Buffer.from(msg, 'base64')
Buffer.from(msg, 'utf8') // I think this is the default

)
}

Expand All @@ -124,7 +152,7 @@ function verify (keys, sig, msg) {
exports.signObj = function (keys, hmac_key, obj) {
if(!obj) obj = hmac_key, hmac_key = null
var _obj = clone(obj)
var b = new Buffer(JSON.stringify(_obj, null, 2))
var b = Buffer.from(JSON.stringify(_obj, null, 2))
if(hmac_key) b = hmac(b, u.toBuffer(hmac_key))
_obj.signature = sign(keys, b)
return _obj
Expand All @@ -135,13 +163,13 @@ exports.verifyObj = function (keys, hmac_key, obj) {
obj = clone(obj)
var sig = obj.signature
delete obj.signature
var b = new Buffer(JSON.stringify(obj, null, 2))
var b = Buffer.from(JSON.stringify(obj, null, 2))
if(hmac_key) b = hmac(b, u.toBuffer(hmac_key))
return verify(keys, sig, b)
}

exports.box = function (msg, recipients) {
msg = new Buffer(JSON.stringify(msg))
msg = Buffer.from(JSON.stringify(msg))

recipients = recipients.map(function (keys) {
return sodium.crypto_sign_ed25519_pk_to_curve25519(u.toBuffer(keys.public || keys))
Expand All @@ -163,7 +191,9 @@ exports.unboxBody = function (boxed, key) {
var msg = pb.multibox_open_body(boxed, key)
try {
return JSON.parse(''+msg)
} catch (_) { }
} catch (_) {
return undefined
}
}

exports.unbox = function (boxed, keys) {
Expand All @@ -174,8 +204,9 @@ exports.unbox = function (boxed, keys) {
try {
var msg = pb.multibox_open(boxed, sk)
return JSON.parse(''+msg)
} catch (_) { }
return
} catch (_) {
return undefined
}
}

exports.secretBox = function secretBox (data, key) {
Expand All @@ -188,4 +219,4 @@ exports.secretUnbox = function secretUnbox (ctxt, key) {
var ptxt = sodium.crypto_secretbox_open_easy(ctxt, key.slice(0, 24), key)
if(!ptxt) return
return JSON.parse(ptxt.toString())
}
}
13 changes: 6 additions & 7 deletions local-storage.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
'use strict'
var u = require('./util')

function isFunction (f) {
return 'function' == typeof f
}

module.exports = function (generate) {

function create (filename, curve, legacy) {
var keys = generate(curve, legacy)
function create (filename, feedType, legacy) {
var keys = generate(feedType, legacy)
localStorage[filename] = JSON.stringify(keys)
return keys
}
Expand All @@ -19,12 +18,12 @@ module.exports = function (generate) {

return {
createSync: create,
create: function(filename, curve, legacy, cb) {
create: function(filename, feedType, legacy, cb) {
if(isFunction(legacy))
cb = legacy, legacy = null
if(isFunction(curve))
cb = curve, curve = null
cb(null, create(filename, curve, legacy))
if(isFunction(feedType))
cb = feedType, feedType = null
cb(null, create(filename, feedType, legacy))
},
loadSync: load,
load: function (filename, cb) {
Expand Down
Loading