-
Notifications
You must be signed in to change notification settings - Fork 17
/
symmetric-state.js
162 lines (133 loc) · 5 KB
/
symmetric-state.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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/* eslint-disable camelcase */
const { sodium_malloc, sodium_memzero } = require('sodium-universal/memory')
const assert = require('nanoassert')
module.exports = ({ hash, cipherState }) => {
const STATELEN = hash.HASHLEN + hash.HASHLEN + cipherState.STATELEN
const HASHLEN = hash.HASHLEN
const CHAINING_KEY_BEGIN = 0
const CHAINING_KEY_END = hash.HASHLEN
const HASH_BEGIN = CHAINING_KEY_END
const HASH_END = HASH_BEGIN + hash.HASHLEN
const CIPHER_BEGIN = HASH_END
const CIPHER_END = CIPHER_BEGIN + cipherState.STATELEN
function initializeSymmetric (state, protocolName) {
assert(state.byteLength === STATELEN)
assert(protocolName.byteLength != null)
sodium_memzero(state)
if (protocolName.byteLength <= HASHLEN) state.set(protocolName, HASH_BEGIN)
else hash.hash(state.subarray(HASH_BEGIN, HASH_END), [protocolName])
state.subarray(CHAINING_KEY_BEGIN, CHAINING_KEY_END).set(state.subarray(HASH_BEGIN, HASH_END))
cipherState.initializeKey(state.subarray(CIPHER_BEGIN, CIPHER_END), null)
}
const TempKey = sodium_malloc(HASHLEN)
function mixKey (state, inputKeyMaterial, dhlen, pklen) {
assert(state.byteLength === STATELEN)
assert(inputKeyMaterial.byteLength != null)
hash.hkdf(
state.subarray(CHAINING_KEY_BEGIN, CHAINING_KEY_END),
TempKey,
null,
state.subarray(CHAINING_KEY_BEGIN, CHAINING_KEY_END),
inputKeyMaterial,
dhlen,
pklen
)
// HASHLEN is always 64 here, so we truncate to 32 bytes per the spec
cipherState.initializeKey(state.subarray(CIPHER_BEGIN, CIPHER_END), TempKey.subarray(0, 32))
sodium_memzero(TempKey)
}
function mixHash (state, data) {
assert(state.byteLength === STATELEN)
const h = state.subarray(HASH_BEGIN, HASH_END)
hash.hash(h, [h, data])
}
const TempHash = sodium_malloc(HASHLEN)
function mixKeyAndHash (state, inputKeyMaterial, dhlen, pklen) {
assert(state.byteLength === STATELEN)
assert(inputKeyMaterial.byteLength != null)
hash.hkdf(
state.subarray(CHAINING_KEY_BEGIN, CHAINING_KEY_END),
TempHash,
TempKey,
state.subarray(CHAINING_KEY_BEGIN, CHAINING_KEY_END),
inputKeyMaterial,
dhlen,
pklen
)
mixHash(state, TempHash)
sodium_memzero(TempHash)
// HASHLEN is always 64 here, so we truncate to 32 bytes per the spec
cipherState.initializeKey(state.subarray(CIPHER_BEGIN, CIPHER_END), TempKey.subarray(0, 32))
sodium_memzero(TempKey)
}
function getHandshakeHash (state, out) {
assert(state.byteLength === STATELEN)
assert(out.byteLength === HASHLEN)
out.set(state.subarray(HASH_BEGIN, HASH_END))
}
// ciphertext is the output here
function encryptAndHash (state, ciphertext, plaintext) {
assert(state.byteLength === STATELEN)
assert(ciphertext.byteLength != null)
assert(plaintext.byteLength != null)
const cstate = state.subarray(CIPHER_BEGIN, CIPHER_END)
const h = state.subarray(HASH_BEGIN, HASH_END)
cipherState.encryptWithAd(cstate, ciphertext, h, plaintext)
encryptAndHash.bytesRead = cipherState.encryptWithAd.bytesRead
encryptAndHash.bytesWritten = cipherState.encryptWithAd.bytesWritten
mixHash(state, ciphertext.subarray(0, encryptAndHash.bytesWritten))
}
encryptAndHash.bytesRead = 0
encryptAndHash.bytesWritten = 0
// plaintext is the output here
function decryptAndHash (state, plaintext, ciphertext) {
assert(state.byteLength === STATELEN)
assert(plaintext.byteLength != null)
assert(ciphertext.byteLength != null)
const cstate = state.subarray(CIPHER_BEGIN, CIPHER_END)
const h = state.subarray(HASH_BEGIN, HASH_END)
cipherState.decryptWithAd(cstate, plaintext, h, ciphertext)
decryptAndHash.bytesRead = cipherState.decryptWithAd.bytesRead
decryptAndHash.bytesWritten = cipherState.decryptWithAd.bytesWritten
mixHash(state, ciphertext.subarray(0, decryptAndHash.bytesRead))
}
decryptAndHash.bytesRead = 0
decryptAndHash.bytesWritten = 0
const TempKey1 = sodium_malloc(HASHLEN)
const TempKey2 = sodium_malloc(HASHLEN)
const zerolen = new Uint8Array(0)
function split (state, cipherstate1, cipherstate2, dhlen, pklen) {
assert(state.byteLength === STATELEN)
assert(cipherstate1.byteLength === cipherState.STATELEN)
assert(cipherstate2.byteLength === cipherState.STATELEN)
hash.hkdf(
TempKey1,
TempKey2,
null,
state.subarray(CHAINING_KEY_BEGIN, CHAINING_KEY_END),
zerolen,
dhlen,
pklen
)
// HASHLEN is always 64 here, so we truncate to 32 bytes per the spec
cipherState.initializeKey(cipherstate1, TempKey1.subarray(0, 32))
cipherState.initializeKey(cipherstate2, TempKey2.subarray(0, 32))
sodium_memzero(TempKey1)
sodium_memzero(TempKey2)
}
function _hasKey (state) {
return cipherState.hasKey(state.subarray(CIPHER_BEGIN, CIPHER_END))
}
return {
STATELEN,
initializeSymmetric,
mixKey,
mixHash,
mixKeyAndHash,
getHandshakeHash,
encryptAndHash,
decryptAndHash,
split,
_hasKey
}
}