Skip to content
This repository has been archived by the owner on Jul 31, 2020. It is now read-only.

Commit

Permalink
Merge pull request #164 from brave/staging
Browse files Browse the repository at this point in the history
v1.4.0
  • Loading branch information
ayumi authored Oct 6, 2017
2 parents 3c75303 + 9f9048e commit 45761fc
Show file tree
Hide file tree
Showing 13 changed files with 7,553 additions and 4,402 deletions.
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?}
*/
module.exports.decrypt = function (ciphertext/* : Uint8Array */,
nonce/* : Uint8Array */, secretboxKey/* : Uint8Array */) {
Expand Down
Loading

0 comments on commit 45761fc

Please sign in to comment.