kersiosocket 0.5.0-dev → 0.6.0-dev
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.
- package/BUILD-INFO.md +8 -7
- package/CHANGELOG.md +11 -0
- package/README.md +151 -105
- package/bridges/whatsmeow/README.md +42 -0
- package/docs/ENGINE-MATRIX.md +23 -0
- package/docs/PROTOCOL-ROADMAP.md +32 -1
- package/docs/RESEARCH-BASELINE.md +11 -0
- package/examples/basic.js +7 -15
- package/licenses/THIRD-PARTY-NOTICES.md +5 -0
- package/package.json +14 -20
- package/src/backends/baileys.js +121 -0
- package/src/backends/base.js +102 -0
- package/src/backends/whatsmeow-process.js +202 -0
- package/src/backends/zapo.js +123 -0
- package/src/client/kersio-client.js +305 -0
- package/src/client/session-manager.js +67 -0
- package/src/index.js +12 -1
- package/src/pairing/verified-commit.js +46 -0
- package/src/session/locking.js +233 -0
- package/src/socket/connection.js +16 -3
- package/src/socket/noise.js +22 -2
- package/src/wabinary/tokens.json +3 -1285
- package/test/backend-adapters.test.js +59 -0
- package/test/connection.test.js +24 -4
- package/test/locking-archive.test.js +49 -0
- package/test/noise.test.js +22 -0
- package/test/session-manager.test.js +34 -0
- package/test/unified-client.test.js +63 -0
- package/test/verified-commit.test.js +43 -0
- package/test/whatsmeow-process.test.js +43 -0
- package/licenses/whatsmeow-MPL-2.0.txt +0 -374
package/src/socket/connection.js
CHANGED
|
@@ -22,6 +22,9 @@ const DEFAULTS = {
|
|
|
22
22
|
url: 'wss://web.whatsapp.com/ws/chat',
|
|
23
23
|
origin: 'https://web.whatsapp.com',
|
|
24
24
|
clientPayload: EMPTY, // empty buffer is transport smoke-test only; real login needs verified auth payload
|
|
25
|
+
// Fail closed by default: callers must supply a pinned, independently tested server-certificate verifier.
|
|
26
|
+
strictServerCertificateVerification: true,
|
|
27
|
+
verifyServerCertificate: null,
|
|
25
28
|
// stableMs: koneksi baru dianggap 'sehat' (counter backoff direset) kalau bertahan selama ini
|
|
26
29
|
reconnect: { enabled: true, minDelay: 1000, maxDelay: 30000, factor: 2, stableMs: 30000 }
|
|
27
30
|
}
|
|
@@ -48,6 +51,7 @@ class Connection extends EventEmitter {
|
|
|
48
51
|
this.counters = { bytesIn: 0, bytesOut: 0, framesIn: 0, framesOut: 0 }
|
|
49
52
|
this.frameLatency = new LatencyTracker() // dekripsi + dispatch per frame
|
|
50
53
|
this.handshakeMs = 0
|
|
54
|
+
this.serverCertificateVerified = false
|
|
51
55
|
this._t = null
|
|
52
56
|
this._decoder = null
|
|
53
57
|
this._hs = null
|
|
@@ -110,7 +114,7 @@ class Connection extends EventEmitter {
|
|
|
110
114
|
...this.counters,
|
|
111
115
|
handshakeMs: this.handshakeMs,
|
|
112
116
|
frameLatencyMs: this.frameLatency.summary(),
|
|
113
|
-
serverCertificateVerified:
|
|
117
|
+
serverCertificateVerified: this.serverCertificateVerified,
|
|
114
118
|
rssMB: +(m.rss / 1048576).toFixed(1),
|
|
115
119
|
heapUsedMB: +(m.heapUsed / 1048576).toFixed(1)
|
|
116
120
|
}
|
|
@@ -129,7 +133,11 @@ class Connection extends EventEmitter {
|
|
|
129
133
|
|
|
130
134
|
_onOpen() {
|
|
131
135
|
this._decoder = new FrameDecoder()
|
|
132
|
-
this._hs = new ClientHandshake({
|
|
136
|
+
this._hs = new ClientHandshake({
|
|
137
|
+
staticKey: this.staticKey,
|
|
138
|
+
verifyServerCertificate: this.config.verifyServerCertificate,
|
|
139
|
+
requireServerCertificateVerification: this.config.strictServerCertificateVerification
|
|
140
|
+
})
|
|
133
141
|
this.startedAt = Date.now()
|
|
134
142
|
this._setState('handshaking')
|
|
135
143
|
this._write(encodeFrame(this._hs.hello(), WA_HEADER))
|
|
@@ -160,6 +168,7 @@ class Connection extends EventEmitter {
|
|
|
160
168
|
const hs = this._hs
|
|
161
169
|
const finish = hs.processServerHello(frame, this.clientPayload)
|
|
162
170
|
this._noise = hs.noise
|
|
171
|
+
this.serverCertificateVerified = hs.serverCertificateVerified === true
|
|
163
172
|
this._hs = null
|
|
164
173
|
// Publish the completed Noise state before writing ClientFinish. A fake or
|
|
165
174
|
// custom transport may synchronously deliver the server reply from send().
|
|
@@ -167,7 +176,7 @@ class Connection extends EventEmitter {
|
|
|
167
176
|
this.handshakeMs = Date.now() - this.startedAt
|
|
168
177
|
this._setState('noise-ready', {
|
|
169
178
|
serverStatic: hs.serverStatic,
|
|
170
|
-
serverCertificateVerified:
|
|
179
|
+
serverCertificateVerified: this.serverCertificateVerified
|
|
171
180
|
})
|
|
172
181
|
this._write(encodeFrame(finish))
|
|
173
182
|
return
|
|
@@ -219,6 +228,10 @@ class Connection extends EventEmitter {
|
|
|
219
228
|
_abortConnection(error, transport = this._t, generation = this._generation) {
|
|
220
229
|
const err = error instanceof Error ? error : new Error(String(error))
|
|
221
230
|
this._safeEmit('connection.error', err)
|
|
231
|
+
if (['ERR_NOISE_CERTIFICATE_VERIFIER_MISSING', 'ERR_NOISE_CERTIFICATE_REJECTED'].includes(err.code)) {
|
|
232
|
+
// Configuration/trust failures are not transient network failures. Stop auto-retry.
|
|
233
|
+
this.ended = true
|
|
234
|
+
}
|
|
222
235
|
if (!transport || transport !== this._t || generation !== this._generation) return
|
|
223
236
|
// Transition/cleanup synchronously. The transport's eventual close event is de-duplicated.
|
|
224
237
|
this._onClose(1002, Buffer.from('protocol/transport failure'), transport, generation)
|
package/src/socket/noise.js
CHANGED
|
@@ -81,8 +81,11 @@ class ClientHandshake {
|
|
|
81
81
|
/**
|
|
82
82
|
* @param {{ staticKey: {public:Buffer, private:Buffer}, ephemeralKey?: object, header?: Buffer }} opts
|
|
83
83
|
*/
|
|
84
|
-
constructor({ staticKey, ephemeralKey = generateKeyPair(), header = WA_HEADER }) {
|
|
84
|
+
constructor({ staticKey, ephemeralKey = generateKeyPair(), header = WA_HEADER, verifyServerCertificate, requireServerCertificateVerification = false }) {
|
|
85
85
|
this.staticKey = staticKey
|
|
86
|
+
this.verifyServerCertificate = verifyServerCertificate
|
|
87
|
+
this.requireServerCertificateVerification = requireServerCertificateVerification
|
|
88
|
+
this.serverCertificateVerified = false
|
|
86
89
|
this.ephemeralKey = ephemeralKey
|
|
87
90
|
this.noise = new NoiseState(header)
|
|
88
91
|
this.noise.authenticate(ephemeralKey.public)
|
|
@@ -97,7 +100,8 @@ class ClientHandshake {
|
|
|
97
100
|
|
|
98
101
|
/**
|
|
99
102
|
* Proses ServerHello, kembalikan ClientFinish (protobuf).
|
|
100
|
-
*
|
|
103
|
+
* Verifikasi sertifikat dipasok oleh verifier yang menggunakan root/intermediate key
|
|
104
|
+
* resmi yang dipin dan test vector independen. Tidak ada verifier bawaan yang diklaim.
|
|
101
105
|
* @param {Buffer} data isi frame ServerHello
|
|
102
106
|
* @param {Buffer} clientPayload ClientPayload (login/registrasi), sudah di-encode protobuf
|
|
103
107
|
*/
|
|
@@ -112,6 +116,22 @@ class ClientHandshake {
|
|
|
112
116
|
n.mixIntoKey(sharedKey(this.ephemeralKey.private, this.serverStatic))
|
|
113
117
|
|
|
114
118
|
this.serverCertificate = n.decrypt(sh.payload)
|
|
119
|
+
if (typeof this.verifyServerCertificate === 'function') {
|
|
120
|
+
const verified = this.verifyServerCertificate(this.serverCertificate, {
|
|
121
|
+
serverStatic: Buffer.from(this.serverStatic),
|
|
122
|
+
header: Buffer.from(WA_HEADER)
|
|
123
|
+
})
|
|
124
|
+
if (verified !== true) {
|
|
125
|
+
const error = new Error('Noise: sertifikat server ditolak oleh verifier')
|
|
126
|
+
error.code = 'ERR_NOISE_CERTIFICATE_REJECTED'
|
|
127
|
+
throw error
|
|
128
|
+
}
|
|
129
|
+
this.serverCertificateVerified = true
|
|
130
|
+
} else if (this.requireServerCertificateVerification) {
|
|
131
|
+
const error = new Error('Noise: koneksi ditolak karena verifier sertifikat server belum dikonfigurasi')
|
|
132
|
+
error.code = 'ERR_NOISE_CERTIFICATE_VERIFIER_MISSING'
|
|
133
|
+
throw error
|
|
134
|
+
}
|
|
115
135
|
|
|
116
136
|
const encStatic = n.encrypt(this.staticKey.public)
|
|
117
137
|
n.mixIntoKey(sharedKey(this.staticKey.private, sh.ephemeral))
|