Skip to content
This repository was archived by the owner on Jul 31, 2020. It is now read-only.
Merged
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
1 change: 1 addition & 0 deletions .flowconfig
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
[ignore]
.*/node_modules/protobufjs/tests/data/.*
10 changes: 5 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ language: node_js
node_js:
- node
script:
- yarn lint
- yarn flow
- yarn check
- yarn browsertest
- yarn coverage
- npm run lint
- npm run flow
- npm run check
- npm run browsertest
- npm run coverage
dist: trusty
sudo: false
os: linux
Expand Down
16 changes: 7 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,32 @@ A client/server for Brave sync

## Building

[Yarn](https://yarnpkg.com/) must be installed first, though npm and yarn will both work for the commands below. If you're adding packages to package.json, please `yarn install` and commit changes to yarn.lock.

Install dependencies:

```
yarn install
npm install
```

Build a bundled JS library for the client:

```
yarn run build
npm run build
```

Run the server:

```sh
yarn run start
npm run start
```

## Testing

The sync client uses Browserify to transform Node js into browser js. To unittest
the library in a browser (default: electron), run `yarn browsertest`.
To test in a different browser run `yarn browsertest -- --browser chrome`.
the library in a browser (default: electron), run `npm browsertest`.
To test in a different browser run `npm browsertest -- --browser chrome`.
Results appear in both the browser inspector and your terminal.

To run tests in Node, just do `yarn test`.
To run tests in Node, just do `npm test`.

To do a basic client/server integration test against the production server, run
`npm run client` and navigate to `http://localhost:8000/`). The page
Expand All @@ -56,7 +54,7 @@ export AWS_SECRET_ACCESS_KEY="{secret stuff}"

Run the server with file watching and autoreloading:
```sh
yarn run start-dev
npm run start-dev
```

### Client integration
Expand Down
9 changes: 5 additions & 4 deletions client/init.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict'

const crypto = require('../lib/crypto')
const {deriveKeys} = require('../lib/crypto')
const crypto = require('brave-crypto')
const messages = require('./constants/messages')
const {syncVersion} = require('./config')

Expand All @@ -15,7 +16,7 @@ module.exports.init = function () {
ipc.once(messages.GOT_INIT_DATA, (e, seed, deviceId, config) => {
if (seed === null) {
// Generate a new "persona"
seed = crypto.getSeed()
seed = crypto.getSeed() // 32 bytes
deviceId = new Uint8Array([0])
ipc.send(messages.SAVE_INIT_DATA, seed, deviceId)
// TODO: The background process should listen for SAVE_INIT_DATA and emit
Expand All @@ -24,11 +25,11 @@ module.exports.init = function () {
// XXX: workaround #17
seed = seed instanceof Array ? new Uint8Array(seed) : seed
deviceId = deviceId instanceof Array ? new Uint8Array(deviceId) : deviceId
if (!(seed instanceof Uint8Array) || seed.length !== crypto.SEED_SIZE) {
if (!(seed instanceof Uint8Array) || seed.length !== crypto.DEFAULT_SEED_SIZE) {
reject(new Error('Invalid crypto seed'))
return
}
resolve({keys: crypto.deriveKeys(seed), deviceId, config})
resolve({keys: deriveKeys(seed), deviceId, config})
})
})
}
124 changes: 10 additions & 114 deletions lib/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,133 +3,28 @@
'use strict'

const nacl = require('tweetnacl')

// Length in bytes of random seed that is synced between devices
const SEED_SIZE = 32
const crypto = require('brave-crypto')

// Not strictly necessary but recommended by rfc5869 section 3.1
const HKDF_SALT = new Uint8Array([72, 203, 156, 43, 64, 229, 225, 127, 214, 158, 50, 29, 130, 186, 182, 207, 6, 108, 47, 254, 245, 71, 198, 109, 44, 108, 32, 193, 221, 126, 119, 143, 112, 113, 87, 184, 239, 231, 230, 234, 28, 135, 54, 42, 9, 243, 39, 30, 179, 147, 194, 211, 212, 239, 225, 52, 192, 219, 145, 40, 95, 19, 142, 98])

module.exports.SEED_SIZE = SEED_SIZE

/**
* Implementation of HMAC SHA512 from https://github.com/dchest/tweetnacl-auth-js
* @param {Uint8Array} message
* @param {Uint8Array} key
* @returns {Uint8Array}
*/
module.exports.hmac = function (message/* : Uint8Array */, key/* : Uint8Array */) {
if (!(message instanceof Uint8Array) || !(key instanceof Uint8Array)) {
throw new Error('Inputs must be Uint8Arrays.')
}

const BLOCK_SIZE = 128
const HASH_SIZE = 64
const buf = new Uint8Array(BLOCK_SIZE + Math.max(HASH_SIZE, message.length))
var i, innerHash

if (key.length > BLOCK_SIZE) {
key = nacl.hash(key)
}

for (i = 0; i < BLOCK_SIZE; i++) buf[i] = 0x36
for (i = 0; i < key.length; i++) buf[i] ^= key[i]
buf.set(message, BLOCK_SIZE)
innerHash = nacl.hash(buf.subarray(0, BLOCK_SIZE + message.length))

for (i = 0; i < BLOCK_SIZE; i++) buf[i] = 0x5c
for (i = 0; i < key.length; i++) buf[i] ^= key[i]
buf.set(innerHash, BLOCK_SIZE)
return nacl.hash(buf.subarray(0, BLOCK_SIZE + innerHash.length))
}

/**
* Returns HKDF output according to rfc5869 using sha512
* @param {Uint8Array} ikm input keying material
* @param {Uint8Array} info context-specific info
* @param {number} extractLength length of extracted output keying material in
* octets
* @param {Uint8Array=} salt optional salt
* @returns {Uint8Array}
*/
module.exports.getHKDF = function (ikm/* : Uint8Array */, info/* : Uint8Array */,
extractLen, salt/* : Uint8Array */) {
const hashLength = 512 / 8

if (typeof extractLen !== 'number' || extractLen < 0 ||
extractLen > hashLength * 255) {
throw Error('Invalid extract length.')
}

// Extract
if (!(salt instanceof Uint8Array) || salt.length === 0) {
salt = new Uint8Array(hashLength)
}
var prk = module.exports.hmac(ikm, salt) // Pseudorandom Key

// Expand
var n = Math.ceil(extractLen / hashLength)
var t = []
t[0] = new Uint8Array()
info = info || new Uint8Array()
var okm = new Uint8Array(extractLen)

let filled = 0
for (var i = 1; i <= n; i++) {
let prev = t[i - 1]
let input = new Uint8Array(info.length + prev.length + 1)
input.set(prev)
input.set(info, prev.length)
input.set(new Uint8Array([i]), prev.length + info.length)
let output = module.exports.hmac(input, prk)
t[i] = output

let remaining = extractLen - filled
if (remaining === 0) {
return okm
} else if (output.length <= remaining) {
okm.set(output, filled)
filled = filled + output.length
} else {
okm.set(output.slice(0, remaining), filled)
return okm
}
}
}

/**
* Generates a random seed.
* @returns {Uint8Array}
*/
module.exports.getSeed = function () {
return nacl.randomBytes(SEED_SIZE)
}

/**
* Derives Ed25519 keypair and secretbox key from a seed.
* @param {Uint8Array} seed
* @param {Uint8Array=} seed
* @returns {{publicKey: <Uint8Array>, secretKey: <Uint8Array>,
* fingerprint: <string>, secretboxKey: <Uint8Array>}}
*/
module.exports.deriveKeys = function (seed/* : Uint8Array */) {
seed = seed || crypto.getSeed()
if (!(seed instanceof Uint8Array)) {
throw new Error('Seed must be Uint8Array.')
}
// Derive the Ed25519 signing keypair
const output = module.exports.getHKDF(seed, new Uint8Array([0]),
nacl.sign.seedLength, HKDF_SALT)
const result = nacl.sign.keyPair.fromSeed(output)
const result = crypto.deriveSigningKeysFromSeed(seed, HKDF_SALT)
// Fingerprint is the 32-byte public key as a hex string
result.fingerprint = ''
result.publicKey.forEach((byte) => {
let char = byte.toString(16)
if (char.length === 1) {
char = '0' + char
}
result.fingerprint += char
})
result.fingerprint = crypto.uint8ToHex(result.publicKey)
// Secretbox key is the NaCl symmetric encryption/authentication key
result.secretboxKey = module.exports.getHKDF(seed, new Uint8Array([1]),
result.secretboxKey = crypto.getHKDF(seed, new Uint8Array([1]),
nacl.secretbox.keyLength, HKDF_SALT)
return result
}
Expand All @@ -149,9 +44,10 @@ module.exports.sign = function (message/* : Uint8Array */,
/**
* Verifies a message using Ed25519 and returns the message without signature.
* This is only used for authentication by the server.
* Returns null if verification fails
* @param {Uint8Array} message
* @param {Uint8Array} publicKey
* @returns {Uint8Array}
* @returns {Uint8Array?}
*/
module.exports.verify = function (message/* : Uint8Array */,
publicKey/* : Uint8Array */) {
Expand Down Expand Up @@ -205,12 +101,12 @@ module.exports.encrypt = function (message/* : Uint8Array */,
}

/**
* Decrypts and verifies a message using Nacl secretbox. Returns false if
* Decrypts and verifies a message using Nacl secretbox. Returns null if
* verification fails.
* @param {Uint8Array} ciphertext
* @param {Uint8Array} nonce
* @param {Uint8Array} secretboxKey
* @returns {Uint8Array|boolean}
* @returns {Uint8Array?}
Copy link
Member Author

Choose a reason for hiding this comment

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

this is a change from tweetnacl 1.0.0 - see dchest/tweetnacl-js#132. AFAIK it only affected tests (which were fixed)

*/
module.exports.decrypt = function (ciphertext/* : Uint8Array */,
nonce/* : Uint8Array */, secretboxKey/* : Uint8Array */) {
Expand Down
Loading