-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathindex.js
146 lines (115 loc) · 3.82 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
'use strict'
var sodium = require('chloride')
var Reader = require('pull-reader')
var increment = require('increment-buffer')
var through = require('pull-through')
var split = require('split-buffer')
var isBuffer = Buffer.isBuffer
var concat = Buffer.concat
var box = sodium.crypto_secretbox_easy
var unbox = sodium.crypto_secretbox_open_easy
function unbox_detached (mac, boxed, nonce, key) {
return sodium.crypto_secretbox_open_easy(concat([mac, boxed]), nonce, key)
}
var max = 1024*4
var NONCE_LEN = 24
var HEADER_LEN = 2+16+16
function isZeros(b) {
for(var i = 0; i < b.length; i++)
if(b[i] !== 0) return false
return true
}
function randomSecret(n) {
var rand = new Buffer(n)
sodium.randombytes(rand)
return rand
}
function copy (a) {
var b = new Buffer(a.length)
a.copy(b, 0, 0, a.length)
return b
}
exports.createBoxStream =
exports.createEncryptStream = function (key, init_nonce) {
if(key.length === 56) {
init_nonce = key.slice(32, 56)
key = key.slice(0, 32)
}
else if(!(key.length === 32 && init_nonce.length === 24))
throw new Error('nonce must be 24 bytes')
// we need two nonces because increment mutates,
// and we need the next for the header,
// and the next next nonce for the packet
var nonce1 = copy(init_nonce), nonce2 = copy(init_nonce)
var head = new Buffer(18)
return through(function (data) {
if('string' === typeof data)
data = new Buffer(data, 'utf8')
else if(!isBuffer(data))
return this.emit('error', new Error('must be buffer'))
if(data.length === 0) return
var input = split(data, max)
for(var i = 0; i < input.length; i++) {
head.writeUInt16BE(input[i].length, 0)
var boxed = box(input[i], increment(nonce2), key)
//write the mac into the header.
boxed.copy(head, 2, 0, 16)
this.queue(box(head, nonce1, key))
this.queue(boxed.slice(16, 16 + input[i].length))
increment(increment(nonce1)); increment(nonce2)
}
}, function (err) {
if(err) return this.queue(null)
//handle special-case of empty session
//final header is same length as header except all zeros (inside box)
var final = new Buffer(2+16); final.fill(0)
this.queue(box(final, nonce1, key))
this.queue(null)
})
}
exports.createUnboxStream =
exports.createDecryptStream = function (key, nonce) {
if(key.length == 56) {
nonce = key.slice(32, 56)
key = key.slice(0, 32)
}
else if(!(key.length === 32 && nonce.length === 24))
throw new Error('nonce must be 24 bytes')
nonce = copy(nonce)
var reader = Reader(), first = true, ended
var first = true
return function (read) {
reader(read)
return function (end, cb) {
if(end) return reader.abort(end, cb)
//use abort when the input was invalid,
//but the source hasn't actually ended yet.
function abort(err) {
reader.abort(ended = err || true, cb)
}
if(ended) return cb(ended)
reader.read(HEADER_LEN, function (err, cipherheader) {
if(err === true) return cb(ended = new Error('unexpected hangup'))
if(err) return cb(ended = err)
var header = unbox(cipherheader, nonce, key)
if(!header)
return abort(new Error('invalid header'))
//valid end of stream
if(isZeros(header))
return cb(ended = true)
var length = header.readUInt16BE(0)
var mac = header.slice(2, 34)
reader.read(length, function (err, cipherpacket) {
if(err) return cb(ended = err)
//recreate a valid packet
//TODO: PR to sodium bindings for detached box/open
var plainpacket = unbox_detached(mac, cipherpacket, increment(nonce), key)
if(!plainpacket)
return abort(new Error('invalid packet'))
increment(nonce)
cb(null, plainpacket)
})
})
}
}
}