whatsapp-nodejs 0.0.1-security → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of whatsapp-nodejs might be problematic. Click here for more details.
- package/.eslintignore +2 -0
- package/.eslintrc.js +59 -0
- package/.prettierrc +11 -0
- package/README.md +51 -3
- package/package.json +97 -3
- package/proto/ClientHello.proto +167 -0
- package/proto/HandshakeMessage.proto +26 -0
- package/proto/Message.proto +795 -0
- package/proto/SessionStructure.proto +113 -0
- package/proto/WhisperTextProtocol.proto +29 -0
- package/src/SocketClient.js +241 -0
- package/src/SocketManager.js +130 -0
- package/src/WASocketClient.js +170 -0
- package/src/Whatsapp.js +169 -0
- package/src/WhatsappServer.js +17 -0
- package/src/bin/decoder.js +1 -0
- package/src/bin/dict.js +1 -0
- package/src/bin/dict1.js +1 -0
- package/src/bin/dict2.js +1 -0
- package/src/bin/encoder.js +1 -0
- package/src/config.js +71 -0
- package/src/db.js +44 -0
- package/src/index.js +7 -0
- package/src/lib/SocketProxy.js +225 -0
- package/src/lib/libsignal-protocol.js +14 -0
- package/src/lib/utils.js +123 -0
- package/src/logger.js +79 -0
- package/src/packet/ProtocolEntity.js +39 -0
- package/src/packet/ProtocolTreeNode.js +202 -0
- package/src/protobuf/pb.js +32415 -0
- package/src/protocol/CipherState.js +48 -0
- package/src/protocol/FallbackPatternModifier.js +48 -0
- package/src/protocol/ForwarderHandshakeState.js +56 -0
- package/src/protocol/HandShake.js +273 -0
- package/src/protocol/HandshakePattern.js +62 -0
- package/src/protocol/HandshakeState.js +223 -0
- package/src/protocol/PKCS7.js +89 -0
- package/src/protocol/SwitchableHandshakeState.js +64 -0
- package/src/protocol/WASymmetricState.js +116 -0
- package/src/protocol/crypto.js +112 -0
- package/src/protocol/handshakepatterns/HandshakePattern.js +62 -0
- package/src/protocol/handshakepatterns/IKHandshakePattern.js +17 -0
- package/src/protocol/handshakepatterns/XXHandshakePattern.js +9 -0
- package/src/schema/account.js +65 -0
- package/src/schema/axolotl.js +8 -0
- package/src/schema/business.js +15 -0
- package/src/schema/common.js +26 -0
- package/src/schema/dayreport.js +21 -0
- package/src/schema/identify.js +49 -0
- package/src/schema/message.js +44 -0
- package/src/schema/prekey.js +51 -0
- package/src/schema/recvmessage.js +33 -0
- package/src/schema/senderkey.js +20 -0
- package/src/schema/session.js +22 -0
- package/src/schema/signedprekeys.js +56 -0
- package/test/WASocketClient.test.js +23 -0
- package/test/handshake.test.js +38 -0
- package/test/logger.test.js +17 -0
- package/test/whatsapp.test.js +17 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const crypto = require('./crypto');
|
|
2
|
+
|
|
3
|
+
class CipherState {
|
|
4
|
+
constructor(name) {
|
|
5
|
+
this.name = name;
|
|
6
|
+
this.key = Buffer.alloc(0); // aes 加解密的 key 字段
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
setKey(key) {
|
|
10
|
+
this.key = key;
|
|
11
|
+
this.setNonce(0);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
setNonce(nonce) {
|
|
15
|
+
this.nonce = nonce;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
hasKey() {
|
|
19
|
+
return this.key && this.key.length;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
rekey() {
|
|
23
|
+
const key = crypto.encryptAES256GCM(
|
|
24
|
+
Buffer.alloc(32).fill(0),
|
|
25
|
+
this.key,
|
|
26
|
+
Buffer.alloc(0),
|
|
27
|
+
2 ** 64 - 1
|
|
28
|
+
);
|
|
29
|
+
this.setKey(key);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
encryptAES256GCM(ad, plaintext) {
|
|
33
|
+
if (!this.key.length) return plaintext;
|
|
34
|
+
const result = crypto.encryptAES256GCM(plaintext, this.key, ad, this.nonce);
|
|
35
|
+
this.nonce++;
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
decryptAES256GCM(ad, ciphertext) {
|
|
40
|
+
if (!this.key.length) return ciphertext;
|
|
41
|
+
const result = crypto.decryptAES256GCM(ciphertext, this.key, ad, this.nonce);
|
|
42
|
+
this.nonce++;
|
|
43
|
+
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = CipherState;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const HandshakePattern = require('./HandshakePattern');
|
|
2
|
+
|
|
3
|
+
class FallbackPatternModifier {
|
|
4
|
+
VALID_FIRST_MESSAGES = [['e'], ['s'], ['e', 's']];
|
|
5
|
+
|
|
6
|
+
constructor() {
|
|
7
|
+
this.name = 'fallback';
|
|
8
|
+
this._name = 'fallback';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
_is_modifiable(handsakepattern) {
|
|
12
|
+
const str = handsakepattern.message_patterns[0].join('');
|
|
13
|
+
for (let i = 0; i < this.VALID_FIRST_MESSAGES.length; i++) {
|
|
14
|
+
const element = this.VALID_FIRST_MESSAGES[i].join('');
|
|
15
|
+
if (element === str) return true;
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
_get_message_patterns(handshakepattern) {
|
|
21
|
+
return handshakepattern.message_patterns.slice(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
_get_initiator_pre_messages(handshakepattern) {
|
|
25
|
+
return handshakepattern.message_patterns[0];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
_get_responder_pre_messages() {}
|
|
29
|
+
|
|
30
|
+
_interpret_as_bob(handshakepattern) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
modify(pattern) {
|
|
35
|
+
if (!this._is_modifiable(pattern)) {
|
|
36
|
+
throw new Error(`pattern ${pattern.name} is not modifiable by ${this.name}`);
|
|
37
|
+
}
|
|
38
|
+
const name = pattern.origin_name + pattern.modifiers.concat(this.name).join('+');
|
|
39
|
+
return new HandshakePattern(
|
|
40
|
+
name,
|
|
41
|
+
this._get_message_patterns(pattern),
|
|
42
|
+
this._get_initiator_pre_messages(pattern),
|
|
43
|
+
this._get_responder_pre_messages(pattern),
|
|
44
|
+
this._interpret_as_bob(pattern)
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
module.exports = FallbackPatternModifier;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const HandShakeState = require('./HandshakeState');
|
|
2
|
+
|
|
3
|
+
class ForwarderHandshakeState extends HandShakeState {
|
|
4
|
+
constructor(handshakestate) {
|
|
5
|
+
super();
|
|
6
|
+
this._handshakestate = handshakestate;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
initialize(
|
|
10
|
+
handshake_pattern,
|
|
11
|
+
initiator,
|
|
12
|
+
prologue,
|
|
13
|
+
s = null,
|
|
14
|
+
e = null,
|
|
15
|
+
rs = null,
|
|
16
|
+
re = null,
|
|
17
|
+
psks = null
|
|
18
|
+
) {
|
|
19
|
+
return this._handshakestate.initialize(
|
|
20
|
+
handshake_pattern,
|
|
21
|
+
initiator,
|
|
22
|
+
prologue,
|
|
23
|
+
s,
|
|
24
|
+
e,
|
|
25
|
+
rs,
|
|
26
|
+
re,
|
|
27
|
+
psks
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
write_message(payload, message_buffer) {
|
|
32
|
+
return this._handshakestate.write_message(payload, message_buffer);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
read_message(message, payload_buffer) {
|
|
36
|
+
return this._handshakestate.read_message(message, payload_buffer);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get re() {
|
|
40
|
+
return this._handshakestate.re;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get rs() {
|
|
44
|
+
return this._handshakestate.rs;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get s() {
|
|
48
|
+
return this._handshakestate.s;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get e() {
|
|
52
|
+
return this._handshakestate.e;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = ForwarderHandshakeState;
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
const CipherState = require('./CipherState');
|
|
2
|
+
const WASymmetricState = require('./WASymmetricState');
|
|
3
|
+
const HandshakeState = require('./HandshakeState');
|
|
4
|
+
const SwitchableHandshakeState = require('./SwitchableHandshakeState');
|
|
5
|
+
const IKHandshakePattern = require('./handshakepatterns/IKHandshakePattern');
|
|
6
|
+
const FallbackPatternModifier = require('./FallbackPatternModifier');
|
|
7
|
+
const XXHandshakePattern = require('./handshakepatterns/XXHandshakePattern');
|
|
8
|
+
|
|
9
|
+
const Decoder = require('../bin/decoder');
|
|
10
|
+
const { ClientHello, HandshakeMessage } = require('../protobuf/pb');
|
|
11
|
+
|
|
12
|
+
class HandShake {
|
|
13
|
+
constructor(waSocketClient) {
|
|
14
|
+
this.waSocketClient = waSocketClient;
|
|
15
|
+
|
|
16
|
+
const versionMajor = 5;
|
|
17
|
+
const versionMinor = 2;
|
|
18
|
+
const waversion = Buffer.from([versionMajor, versionMinor]);
|
|
19
|
+
|
|
20
|
+
this.HEADER = Buffer.concat([Buffer.from('WA'), waversion]);
|
|
21
|
+
this._prologue = Buffer.concat([Buffer.from('WA'), waversion]);
|
|
22
|
+
|
|
23
|
+
this.EDGE_HEADER = Buffer.concat([Buffer.from('ED'), Buffer.from([0, 1])]);
|
|
24
|
+
|
|
25
|
+
const cipherState = new CipherState('AESGCM');
|
|
26
|
+
const waSymmetricState = new WASymmetricState(cipherState);
|
|
27
|
+
const handshakeState = new SwitchableHandshakeState(new HandshakeState(waSymmetricState));
|
|
28
|
+
this.handshakeState = handshakeState;
|
|
29
|
+
|
|
30
|
+
this.isHandShake = false;
|
|
31
|
+
|
|
32
|
+
this.waSocketClient
|
|
33
|
+
.on('data', async data => {
|
|
34
|
+
if (!this.isHandShake) return;
|
|
35
|
+
try {
|
|
36
|
+
const buffer = this.decrypt(data);
|
|
37
|
+
const decoder = new Decoder();
|
|
38
|
+
const node = await decoder.getProtocolTreeNode(buffer);
|
|
39
|
+
if (node && node.tag) {
|
|
40
|
+
this.waSocketClient.emit('node', node);
|
|
41
|
+
}
|
|
42
|
+
const str = node.toString();
|
|
43
|
+
console.info('recv ==>', str);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.error('解包失败', e);
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
// TODO
|
|
49
|
+
.on('sendencryptmessage', async data => {
|
|
50
|
+
this.waSocketClient.send(this.encrypt(data), true);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
toBuffer(plaintext) {
|
|
55
|
+
if (typeof plaintext === 'undefined') return Buffer.alloc(0);
|
|
56
|
+
return typeof plaintext === 'string'
|
|
57
|
+
? Buffer.from(plaintext, 'base64')
|
|
58
|
+
: Buffer.from(plaintext);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
decrypt(message) {
|
|
62
|
+
return this.recvCipherState.decryptAES256GCM(Buffer.alloc(0), this.toBuffer(message));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
encrypt(message) {
|
|
66
|
+
return this.sendCipherState.encryptAES256GCM(Buffer.alloc(0), this.toBuffer(message));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async start(config, serverStaticPublic) {
|
|
70
|
+
let clientStaticKeypair = Buffer.from(config.clientStaticKeypair, 'base64');
|
|
71
|
+
clientStaticKeypair = {
|
|
72
|
+
private: clientStaticKeypair.slice(0, 32),
|
|
73
|
+
public: clientStaticKeypair.slice(-32),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
let cipherstatepair;
|
|
77
|
+
if (serverStaticPublic) {
|
|
78
|
+
try {
|
|
79
|
+
console.debug('perform use startHandshakeIK');
|
|
80
|
+
cipherstatepair = await this.startHandshakeIK(
|
|
81
|
+
config,
|
|
82
|
+
clientStaticKeypair,
|
|
83
|
+
serverStaticPublic
|
|
84
|
+
);
|
|
85
|
+
} catch (e) {
|
|
86
|
+
console.error('perform use startHandshakeIK error', e);
|
|
87
|
+
console.debug('perform use switchHandshakeXXFallback');
|
|
88
|
+
cipherstatepair = await this.switchHandshakeXXFallback(
|
|
89
|
+
clientStaticKeypair,
|
|
90
|
+
this.createPayload(config),
|
|
91
|
+
e.serverHello
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
console.debug('perform use startHandshakeXX');
|
|
96
|
+
cipherstatepair = await this.startHandshakeXX(config, clientStaticKeypair);
|
|
97
|
+
}
|
|
98
|
+
console.info('Handshake success.');
|
|
99
|
+
this.isHandShake = true;
|
|
100
|
+
return cipherstatepair;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async switchHandshakeXXFallback(clientStaticKeypair, payload, serverHello) {
|
|
104
|
+
console.debug('switchHandshakeXXFallback');
|
|
105
|
+
this.handshakeState.switch(
|
|
106
|
+
new FallbackPatternModifier().modify(new XXHandshakePattern()),
|
|
107
|
+
true,
|
|
108
|
+
this._prologue,
|
|
109
|
+
clientStaticKeypair
|
|
110
|
+
);
|
|
111
|
+
const payload_buffer = [];
|
|
112
|
+
const message = Buffer.concat([
|
|
113
|
+
Buffer.from(serverHello.ephemeral, 'base64'),
|
|
114
|
+
Buffer.from(serverHello.static, 'base64'),
|
|
115
|
+
Buffer.from(serverHello.payload, 'base64'),
|
|
116
|
+
]);
|
|
117
|
+
this.handshakeState.read_message(message, payload_buffer);
|
|
118
|
+
|
|
119
|
+
const message_array = [];
|
|
120
|
+
const cipherpair = this.handshakeState.write_message(payload, message_array);
|
|
121
|
+
const message_buffer = Buffer.from(message_array);
|
|
122
|
+
|
|
123
|
+
const clientFinishBuffer = HandshakeMessage.HandshakeMessage.encode(
|
|
124
|
+
HandshakeMessage.HandshakeMessage.create({
|
|
125
|
+
clientFinish: {
|
|
126
|
+
static: message_buffer.slice(0, 48),
|
|
127
|
+
payload: message_buffer.slice(48),
|
|
128
|
+
},
|
|
129
|
+
})
|
|
130
|
+
).finish();
|
|
131
|
+
this.waSocketClient.send(clientFinishBuffer);
|
|
132
|
+
// this.waSocketClient.setSendLogin(true);
|
|
133
|
+
[this.sendCipherState, this.recvCipherState] = cipherpair;
|
|
134
|
+
// 登陆成功
|
|
135
|
+
return cipherpair;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async startHandshakeIK(config, clientStaticKeypair, serverStaticPublic) {
|
|
139
|
+
const ik = new IKHandshakePattern();
|
|
140
|
+
this.handshakeState.initialize(
|
|
141
|
+
ik,
|
|
142
|
+
true,
|
|
143
|
+
this._prologue,
|
|
144
|
+
clientStaticKeypair,
|
|
145
|
+
null,
|
|
146
|
+
serverStaticPublic
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const clientInfoBuffer = this.createPayload(config);
|
|
150
|
+
const messageArray = [];
|
|
151
|
+
this.handshakeState.write_message(clientInfoBuffer, messageArray);
|
|
152
|
+
|
|
153
|
+
const messageBuffer = Buffer.from(messageArray);
|
|
154
|
+
const ephemeral_public = messageBuffer.slice(0, 32);
|
|
155
|
+
const static_public = messageBuffer.slice(32, 32 + 48);
|
|
156
|
+
const payload = messageBuffer.slice(32 + 48);
|
|
157
|
+
const clientHelloBuffer = HandshakeMessage.HandshakeMessage.encode(
|
|
158
|
+
HandshakeMessage.HandshakeMessage.create({
|
|
159
|
+
clientHello: {
|
|
160
|
+
ephemeral: ephemeral_public,
|
|
161
|
+
static: static_public,
|
|
162
|
+
payload,
|
|
163
|
+
},
|
|
164
|
+
})
|
|
165
|
+
).finish();
|
|
166
|
+
|
|
167
|
+
this.waSocketClient.send(this.EDGE_HEADER, false);
|
|
168
|
+
this.waSocketClient.send(config.edgeRoutingInfo || 'CAwIBQ==');
|
|
169
|
+
this.waSocketClient.send(this.HEADER, false);
|
|
170
|
+
this.waSocketClient.send(clientHelloBuffer);
|
|
171
|
+
|
|
172
|
+
return new Promise((resolve, reject) => {
|
|
173
|
+
this.waSocketClient.once('data', async buffer => {
|
|
174
|
+
this.isHandShake = true;
|
|
175
|
+
const { serverHello } = HandshakeMessage.HandshakeMessage.decode(buffer).toJSON();
|
|
176
|
+
console.debug('serverHello', serverHello);
|
|
177
|
+
if (serverHello.static && serverHello.static.length) {
|
|
178
|
+
const e = new Error('');
|
|
179
|
+
e.serverHello = serverHello;
|
|
180
|
+
reject(e);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
serverHello.static = Buffer.alloc(0);
|
|
185
|
+
if (serverHello.static && serverHello.static.length) {
|
|
186
|
+
serverHello.static = Buffer.from(serverHello.static, 'base64');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const message = Buffer.concat([
|
|
190
|
+
Buffer.from(serverHello.ephemeral, 'base64'),
|
|
191
|
+
serverHello.static,
|
|
192
|
+
Buffer.from(serverHello.payload, 'base64'),
|
|
193
|
+
]);
|
|
194
|
+
const cipherpair = this.handshakeState.read_message(message, []);
|
|
195
|
+
[this.sendCipherState, this.recvCipherState] = cipherpair;
|
|
196
|
+
// 登陆成功
|
|
197
|
+
resolve(cipherpair);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
generatePushName() {
|
|
203
|
+
const strings = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
204
|
+
const len = Math.floor(Math.random() * 4 + 4);
|
|
205
|
+
let name = '';
|
|
206
|
+
const stringsLen = strings.length;
|
|
207
|
+
for (let i = 0; i < len; i++) {
|
|
208
|
+
name += strings[Math.floor(Math.random() * stringsLen)];
|
|
209
|
+
}
|
|
210
|
+
return name;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
createPayload(config) {
|
|
214
|
+
console.debug('client version:', config.version);
|
|
215
|
+
console.debug(
|
|
216
|
+
'account:',
|
|
217
|
+
config.cc,
|
|
218
|
+
config.mobile,
|
|
219
|
+
config.mcc,
|
|
220
|
+
config.mnc,
|
|
221
|
+
'platform',
|
|
222
|
+
config.platform
|
|
223
|
+
);
|
|
224
|
+
console.debug('client info:', config.oSVersion, config.manufacturer, config.deviceName);
|
|
225
|
+
const appVersion = config.version.split('.');
|
|
226
|
+
const clientConfig = {
|
|
227
|
+
username: config.mobile,
|
|
228
|
+
passive: false, // 是否接收被动消息
|
|
229
|
+
// passive: config.passive,
|
|
230
|
+
useragent: {
|
|
231
|
+
platform: config.platform || 0,
|
|
232
|
+
appVersion: {
|
|
233
|
+
primary: appVersion[0],
|
|
234
|
+
secondary: appVersion[1],
|
|
235
|
+
tertiary: appVersion[2],
|
|
236
|
+
quaternary: appVersion[3],
|
|
237
|
+
},
|
|
238
|
+
mcc: config.mcc || '000',
|
|
239
|
+
mnc: config.mnc || '000',
|
|
240
|
+
osVersion: config.oSVersion,
|
|
241
|
+
manufacturer: config.manufacturer,
|
|
242
|
+
device: config.deviceName,
|
|
243
|
+
osBuildNumber: config.oSVersion,
|
|
244
|
+
phoneId: config.fdid || '',
|
|
245
|
+
localeLanguageIso_639_1: config.lg || 'en',
|
|
246
|
+
localeCountryIso_3166_1Alpha_2: config.lc || 'US',
|
|
247
|
+
device2: 'unknown',
|
|
248
|
+
// releaseChannel: 0,
|
|
249
|
+
},
|
|
250
|
+
pushName: config.pushName || this.generatePushName(),
|
|
251
|
+
sessionId: Math.floor(Math.random() * 2 ** 32) + 1,
|
|
252
|
+
shortConnect: false,
|
|
253
|
+
connectType: 1,
|
|
254
|
+
// dnsSource: {
|
|
255
|
+
// dnsMethod: 2,
|
|
256
|
+
// appCached: 1,
|
|
257
|
+
// },
|
|
258
|
+
// connectAttemptCount: 1,
|
|
259
|
+
// tag23: 1,
|
|
260
|
+
// tag24: 134,
|
|
261
|
+
};
|
|
262
|
+
if (Number(config.platform) === 10) {
|
|
263
|
+
clientConfig.tag23 = 1;
|
|
264
|
+
clientConfig.tag24 = 5;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const ClientConfig = ClientHello.C2S;
|
|
268
|
+
const buffer = ClientConfig.encode(ClientConfig.create(clientConfig)).finish();
|
|
269
|
+
return buffer;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
module.exports = HandShake;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
class HandshakePattern {
|
|
2
|
+
REGEX_PATTERN_NAME_MODIFIERS = new RegExp('([A-Z1-9]{1,4})([a-z0-9+]+)*');
|
|
3
|
+
|
|
4
|
+
TEMPLATE_REPR = '{name}:\n{patterns}';
|
|
5
|
+
|
|
6
|
+
TEMPLATE_REPR_PATTERNS_WITH_PRE = '{pre_patterns}\n ...\n{message_patterns}';
|
|
7
|
+
|
|
8
|
+
TEMPLATE_REPR_MESSAGE_SEND = ' -> {tokens}';
|
|
9
|
+
|
|
10
|
+
TEMPLATE_REPR_MESSAGE_RECV = ' <- {tokens}';
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
name,
|
|
14
|
+
message_patterns,
|
|
15
|
+
initiator_pre_messages = null,
|
|
16
|
+
responder_pre_message_pattern = null,
|
|
17
|
+
interpret_as_bob = false
|
|
18
|
+
) {
|
|
19
|
+
this._name = name; // type: str
|
|
20
|
+
// this._origin_pattern, this._modifiers = this.__class__.parse_handshakepattern(this._name)
|
|
21
|
+
this._origin_pattern = name;
|
|
22
|
+
this._modifiers = [];
|
|
23
|
+
this._message_patterns = message_patterns; // type: tuple[tuple[str]]
|
|
24
|
+
this._initiator_pre_message_pattern = initiator_pre_messages || []; // type: tuple[str]
|
|
25
|
+
this._responder_pre_message_pattern = responder_pre_message_pattern || []; // type: tuple[str]
|
|
26
|
+
this._interpret_as_bob = interpret_as_bob; // type: bool
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get name() {
|
|
30
|
+
return this._name;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get interpret_as_bob() {
|
|
34
|
+
return this._interpret_as_bob;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get message_patterns() {
|
|
38
|
+
return this._message_patterns;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get initiator_pre_message_pattern() {
|
|
42
|
+
return this._initiator_pre_message_pattern;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get responder_pre_message_pattern() {
|
|
46
|
+
return this._responder_pre_message_pattern;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get origin_name() {
|
|
50
|
+
return this._origin_pattern;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get modifiers() {
|
|
54
|
+
return this._modifiers;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// static parse_handshakepattern(handshake_pattern_name) {
|
|
58
|
+
// let matches = handshake_pattern_name.search(this.REGEX_PATTERN_NAME_MODIFIERS)
|
|
59
|
+
// }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = HandshakePattern;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
const utils = require('../lib/utils');
|
|
2
|
+
const crypto = require('./crypto');
|
|
3
|
+
|
|
4
|
+
class HandshakeState {
|
|
5
|
+
_TEMPLATE_PROTOCOL_NAME = 'Noise_{handshake}_{dh}_{cipher}_{hash}';
|
|
6
|
+
|
|
7
|
+
constructor(symmetricstate) {
|
|
8
|
+
this._symmetricstate = symmetricstate; // type: SymmetricState
|
|
9
|
+
this._s = null; // type: KeyPair config.client_static_keypair
|
|
10
|
+
this._e = null; // type: KeyPair | None 本地生成的一对公私钥
|
|
11
|
+
this._rs = null; // type: PublicKey | None 服务端公钥
|
|
12
|
+
this._re = null; // type: PublicKey | None 本次会话,服务端返回的公钥
|
|
13
|
+
this._initiator = null;
|
|
14
|
+
this._message_patterns = null; // type: list[tuple[str]]
|
|
15
|
+
this._protocol_name = null; // type: str | None
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get protocol_name() {
|
|
19
|
+
return this._protocol_name;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get symmetricstate() {
|
|
23
|
+
return this._symmetricstate;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get rs() {
|
|
27
|
+
return this._rs;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get re() {
|
|
31
|
+
return this._re;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get s() {
|
|
35
|
+
return this._s;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get e() {
|
|
39
|
+
return this._e;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get dh() {
|
|
43
|
+
return this._dh;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
initialize(
|
|
47
|
+
handshake_pattern,
|
|
48
|
+
initiator, // xx 时 为 true
|
|
49
|
+
prologue, // Buffer.from('WA',[4,1]) = Buffer.from([0x57,0x41,0x04,0x01])
|
|
50
|
+
s = null,
|
|
51
|
+
e = null,
|
|
52
|
+
rs = null,
|
|
53
|
+
re = null,
|
|
54
|
+
psks = null
|
|
55
|
+
) {
|
|
56
|
+
// console.log('handshake_pattern', handshake_pattern);
|
|
57
|
+
// Noise_XX_25519_AESGCM_SHA256
|
|
58
|
+
this._protocol_name = this._derive_protocol_name(handshake_pattern.name);
|
|
59
|
+
this._symmetricstate.initialize_symmetric(this._protocol_name);
|
|
60
|
+
this._symmetricstate.mix_hash(prologue);
|
|
61
|
+
this._initiator = initiator;
|
|
62
|
+
this._s = s;
|
|
63
|
+
this._e = e;
|
|
64
|
+
this._rs = rs;
|
|
65
|
+
this._re = re;
|
|
66
|
+
this._psks = psks;
|
|
67
|
+
this._pskmode = handshake_pattern.name.indexOf('psk') !== -1;
|
|
68
|
+
if (
|
|
69
|
+
handshake_pattern.initiator_pre_message_pattern.length ||
|
|
70
|
+
handshake_pattern.responder_pre_message_pattern.length
|
|
71
|
+
) {
|
|
72
|
+
if (initiator) {
|
|
73
|
+
for (let i = 0; i < handshake_pattern.initiator_pre_message_pattern.length; i++) {
|
|
74
|
+
const token = handshake_pattern.initiator_pre_message_pattern[i];
|
|
75
|
+
if (token === 's') {
|
|
76
|
+
this._symmetricstate.mix_hash(s.public);
|
|
77
|
+
}
|
|
78
|
+
if (token === 'e') {
|
|
79
|
+
this._symmetricstate.mix_hash(e.public);
|
|
80
|
+
if (this._pskmode) {
|
|
81
|
+
this._symmetricstate.mix_key(e.public);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (let i = 0; i < handshake_pattern.responder_pre_message_pattern.length; i++) {
|
|
86
|
+
const token = handshake_pattern.responder_pre_message_pattern[i];
|
|
87
|
+
if (token === 's') {
|
|
88
|
+
this._symmetricstate.mix_hash(rs);
|
|
89
|
+
}
|
|
90
|
+
if (token === 'e') {
|
|
91
|
+
this._symmetricstate.mix_hash(re);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
for (let i = 0; i < handshake_pattern.initiator_pre_message_pattern.length; i++) {
|
|
96
|
+
const token = handshake_pattern.initiator_pre_message_pattern[i];
|
|
97
|
+
if (token === 's') {
|
|
98
|
+
this._symmetricstate.mix_hash(rs);
|
|
99
|
+
}
|
|
100
|
+
if (token === 'e') {
|
|
101
|
+
this._symmetricstate.mix_hash(re);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
for (let i = 0; i < handshake_pattern.responder_pre_message_pattern.length; i++) {
|
|
105
|
+
const token = handshake_pattern.responder_pre_message_pattern[i];
|
|
106
|
+
if (token === 's') {
|
|
107
|
+
this._symmetricstate.mix_hash(s.public);
|
|
108
|
+
}
|
|
109
|
+
if (token === 'e') {
|
|
110
|
+
this._symmetricstate.mix_hash(e.public);
|
|
111
|
+
if (this._pskmode) {
|
|
112
|
+
this._symmetricstate.mix_key(e.public);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
this._message_patterns = handshake_pattern.message_patterns;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
_derive_protocol_name(handshake_pattern_name) {
|
|
122
|
+
return `Noise_${handshake_pattern_name}_25519_${this._symmetricstate.ciphername}_${this._symmetricstate.hashname}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
write_message(payload, message_buffer) {
|
|
126
|
+
const message_pattern = this._message_patterns.shift();
|
|
127
|
+
for (let i = 0; i < message_pattern.length; i++) {
|
|
128
|
+
const token = message_pattern[i];
|
|
129
|
+
if (token === 'e') {
|
|
130
|
+
this._e = utils.generateKeyPair();
|
|
131
|
+
this._e.public.map(val => message_buffer.push(val));
|
|
132
|
+
this._symmetricstate.mix_hash(this._e.public);
|
|
133
|
+
if (this._pskmode) {
|
|
134
|
+
this._symmetricstate.mix_key(this._e.public);
|
|
135
|
+
}
|
|
136
|
+
} else if (token === 's') {
|
|
137
|
+
const encrypted = this._symmetricstate.encrypt_and_hash(this._s.public);
|
|
138
|
+
encrypted.map(val => message_buffer.push(val));
|
|
139
|
+
} else if (token === 'ee') {
|
|
140
|
+
this._symmetricstate.mix_key(crypto.dh(this._e, this._re));
|
|
141
|
+
} else if (token === 'es') {
|
|
142
|
+
if (this._initiator) {
|
|
143
|
+
this._symmetricstate.mix_key(crypto.dh(this._e, this._rs));
|
|
144
|
+
} else {
|
|
145
|
+
this._symmetricstate.mix_key(crypto.dh(this._s, this._re));
|
|
146
|
+
}
|
|
147
|
+
} else if (token === 'se') {
|
|
148
|
+
if (this._initiator) {
|
|
149
|
+
this._symmetricstate.mix_key(crypto.dh(this._s, this._re));
|
|
150
|
+
} else {
|
|
151
|
+
this._symmetricstate.mix_key(crypto.dh(this._e, this._rs));
|
|
152
|
+
}
|
|
153
|
+
} else if (token === 'ss') {
|
|
154
|
+
this._symmetricstate.mix_key(crypto.dh(this._s, this._rs));
|
|
155
|
+
} else if (token === 'psk') {
|
|
156
|
+
this._symmetricstate.mix_key_and_hash(this._psks.shift());
|
|
157
|
+
} else {
|
|
158
|
+
throw new Error(`Unsupported token ${token} found in message_pattern`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const res = this._symmetricstate.encrypt_and_hash(payload);
|
|
162
|
+
if (res && res.length) {
|
|
163
|
+
res.map(val => message_buffer.push(val));
|
|
164
|
+
}
|
|
165
|
+
if (this._message_patterns.length === 0) {
|
|
166
|
+
return this._symmetricstate.split();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
read_message(message, payload_buffer) {
|
|
171
|
+
const message_pattern = this._message_patterns.shift();
|
|
172
|
+
let temp;
|
|
173
|
+
for (let i = 0; i < message_pattern.length; i++) {
|
|
174
|
+
const token = message_pattern[i];
|
|
175
|
+
console.debug('read_message', token);
|
|
176
|
+
if (token === 'e') {
|
|
177
|
+
this._re = message.slice(0, 32);
|
|
178
|
+
message = message.slice(32);
|
|
179
|
+
this._symmetricstate.mix_hash(this._re);
|
|
180
|
+
if (this._pskmode) {
|
|
181
|
+
this._symmetricstate.mix_key(this._re);
|
|
182
|
+
}
|
|
183
|
+
} else if (token === 's') {
|
|
184
|
+
if (this._symmetricstate.cipherstate_has_key()) {
|
|
185
|
+
temp = message.slice(0, 32 + 16);
|
|
186
|
+
} else {
|
|
187
|
+
temp = message.slice(0, 32);
|
|
188
|
+
}
|
|
189
|
+
message = message.slice(temp.length);
|
|
190
|
+
this._rs = this._symmetricstate.decrypt_and_hash(temp);
|
|
191
|
+
} else if (token === 'ee') {
|
|
192
|
+
this._symmetricstate.mix_key(crypto.dh(this._e, this._re));
|
|
193
|
+
} else if (token === 'es') {
|
|
194
|
+
if (this._initiator) {
|
|
195
|
+
this._symmetricstate.mix_key(crypto.dh(this._e, this._rs));
|
|
196
|
+
} else {
|
|
197
|
+
this._symmetricstate.mix_key(crypto.dh(this._s, this._re));
|
|
198
|
+
}
|
|
199
|
+
} else if (token === 'se') {
|
|
200
|
+
if (this._initiator) {
|
|
201
|
+
this._symmetricstate.mix_key(crypto.dh(this._s, this._re));
|
|
202
|
+
} else {
|
|
203
|
+
this._symmetricstate.mix_key(crypto.dh(this._e, this._rs));
|
|
204
|
+
}
|
|
205
|
+
} else if (token === 'ss') {
|
|
206
|
+
this._symmetricstate.mix_key(crypto.dh(this._s, this._rs));
|
|
207
|
+
} else if (token === 'psk') {
|
|
208
|
+
this._symmetricstate.mix_key_and_hash(this._psks.shift());
|
|
209
|
+
} else {
|
|
210
|
+
throw new Error(`Unsupported token ${token} found in message_pattern`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const res = this._symmetricstate.decrypt_and_hash(message);
|
|
214
|
+
if (res && res.length) {
|
|
215
|
+
res.map(val => payload_buffer.push(val));
|
|
216
|
+
}
|
|
217
|
+
if (this._message_patterns.length === 0) {
|
|
218
|
+
return this._symmetricstate.split();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
module.exports = HandshakeState;
|