@bzync/nextsql 0.1.0 → 0.1.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.
- package/client-encryption.js +86 -15
- package/nextsql.d.ts +22 -4
- package/nextsql.js +57 -6
- package/package.json +1 -1
package/client-encryption.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// Node-specific NSCE1 implementation. The dependency injection keeps this
|
|
3
|
+
// Node-specific NSCE1/NSCE2 implementation. The dependency injection keeps this
|
|
4
4
|
// module on the driver's single public NextSQLError/Kind surface.
|
|
5
|
-
const { createCipheriv, createDecipheriv, randomBytes } = require('node:crypto');
|
|
5
|
+
const { createCipheriv, createDecipheriv, hkdfSync, randomBytes, timingSafeEqual } = require('node:crypto');
|
|
6
6
|
const fs = require('node:fs/promises');
|
|
7
7
|
|
|
8
8
|
module.exports = function createClientEncryption(deps) {
|
|
9
9
|
const { NextSQLError, Kind, encodeDecimalString, decodeDecimal, decodeNSJB, formatUUID, parseUUID } = deps;
|
|
10
10
|
const PREFIX = 'NSCE1.';
|
|
11
|
+
const DETERMINISTIC_PREFIX = 'NSCE2.';
|
|
11
12
|
const MAX_PLAINTEXT = 1 << 20;
|
|
12
13
|
const MAX_KEY_ID = 64;
|
|
13
14
|
const MAX_KEYS = 64;
|
|
14
15
|
const NONCE_SIZE = 12;
|
|
15
16
|
const TAG_SIZE = 16;
|
|
17
|
+
const DETERMINISTIC_KEY_INFO = Buffer.from('NextSQL NSCE2 AES-SIV v2', 'utf8');
|
|
16
18
|
|
|
17
19
|
const FieldType = Object.freeze({
|
|
18
20
|
UUID: Object.freeze({ kind: Kind.UUID, precision: 0, scale: 0, vecElem: 0 }),
|
|
@@ -334,18 +336,18 @@ module.exports = function createClientEncryption(deps) {
|
|
|
334
336
|
return Buffer.concat([Buffer.from([tag]), u32(raw.length), raw]);
|
|
335
337
|
}
|
|
336
338
|
|
|
337
|
-
function makeHeader(keyID, type, nonce) {
|
|
339
|
+
function makeHeader(keyID, type, nonce = null) {
|
|
338
340
|
const id = Buffer.from(keyID, 'ascii');
|
|
339
|
-
|
|
340
|
-
|
|
341
|
+
const out = Buffer.alloc(3 + id.length + 6 + (nonce ? NONCE_SIZE : 0));
|
|
342
|
+
out[0] = nonce ? 1 : 2; out[1] = nonce ? 1 : 2; out[2] = id.length; id.copy(out, 3);
|
|
341
343
|
const off = 3 + id.length;
|
|
342
344
|
out[off] = type.kind; out.writeUInt16LE(type.precision, off + 1); out.writeUInt16LE(type.scale, off + 3); out[off + 5] = type.vecElem;
|
|
343
|
-
|
|
345
|
+
if (nonce) nonce.copy(out, off + 6);
|
|
344
346
|
return out;
|
|
345
347
|
}
|
|
346
348
|
|
|
347
|
-
function aad(database, table, column, publicHeader) {
|
|
348
|
-
|
|
349
|
+
function aad(database, table, column, publicHeader, prefix = PREFIX) {
|
|
350
|
+
const parts = [Buffer.from(prefix, 'ascii')];
|
|
349
351
|
for (const name of [database, table, column]) {
|
|
350
352
|
const raw = validateName(name);
|
|
351
353
|
parts.push(u16(raw.length), raw);
|
|
@@ -355,22 +357,65 @@ module.exports = function createClientEncryption(deps) {
|
|
|
355
357
|
}
|
|
356
358
|
|
|
357
359
|
function inspectField(ciphertext) {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
+
let prefix, version, suite, nonceSize, mode;
|
|
361
|
+
if (typeof ciphertext === 'string' && ciphertext.startsWith(PREFIX)) {
|
|
362
|
+
prefix = PREFIX; version = 1; suite = 1; nonceSize = NONCE_SIZE; mode = 'randomized';
|
|
363
|
+
} else if (typeof ciphertext === 'string' && ciphertext.startsWith(DETERMINISTIC_PREFIX)) {
|
|
364
|
+
prefix = DETERMINISTIC_PREFIX; version = 2; suite = 2; nonceSize = 0; mode = 'deterministic';
|
|
365
|
+
} else fail('invalid_format', 'invalid client ciphertext prefix');
|
|
366
|
+
const enc = ciphertext.slice(prefix.length);
|
|
360
367
|
if (!enc || enc.length % 4 === 1 || !/^[A-Za-z0-9_-]+$/.test(enc) || enc.length > Math.ceil((MAX_PLAINTEXT + 101) * 4 / 3)) {
|
|
361
368
|
fail('invalid_format', 'client ciphertext length out of range');
|
|
362
369
|
}
|
|
363
370
|
const body = Buffer.from(enc, 'base64url');
|
|
364
|
-
|
|
371
|
+
if (body.toString('base64url') !== enc || body.length < 3 + 1 + 6 + nonceSize + TAG_SIZE || body[0] !== version || body[1] !== suite) fail('invalid_format', 'unsupported or truncated client ciphertext');
|
|
365
372
|
const n = body[2];
|
|
366
|
-
|
|
373
|
+
if (n < 1 || n > MAX_KEY_ID || body.length < 3 + n + 6 + nonceSize + TAG_SIZE) fail('invalid_format', 'invalid field key id length');
|
|
367
374
|
const keyID = body.subarray(3, 3 + n).toString('ascii');
|
|
368
375
|
if (!validKeyID(keyID)) fail('invalid_format', 'invalid field key id');
|
|
369
376
|
const off = 3 + n;
|
|
370
377
|
const type = normalizeType({ kind: body[off], precision: body.readUInt16LE(off + 1), scale: body.readUInt16LE(off + 3), vecElem: body[off + 5] }, 'invalid_format');
|
|
371
|
-
|
|
378
|
+
return { keyID, type, body, headerLength: off + 6 + nonceSize, mode, prefix };
|
|
372
379
|
}
|
|
373
380
|
|
|
381
|
+
function aesBlock(key, block) {
|
|
382
|
+
const cipher = createCipheriv('aes-128-ecb', key, null);
|
|
383
|
+
cipher.setAutoPadding(false);
|
|
384
|
+
return Buffer.concat([cipher.update(block), cipher.final()]);
|
|
385
|
+
}
|
|
386
|
+
function dbl(block) {
|
|
387
|
+
const out = Buffer.alloc(16); let carry = 0;
|
|
388
|
+
for (let i = 15; i >= 0; i--) { const b = block[i]; out[i] = ((b << 1) & 0xff) | carry; carry = b >>> 7; }
|
|
389
|
+
if (carry) out[15] ^= 0x87;
|
|
390
|
+
return out;
|
|
391
|
+
}
|
|
392
|
+
function xor16(a, b) { const out = Buffer.alloc(16); for (let i = 0; i < 16; i++) out[i] = a[i] ^ b[i]; return out; }
|
|
393
|
+
function cmac(key, msg) {
|
|
394
|
+
const l = aesBlock(key, Buffer.alloc(16)); const k1 = dbl(l); const k2 = dbl(k1);
|
|
395
|
+
const blocks = Math.max(1, Math.ceil(msg.length / 16)); let last = Buffer.alloc(16);
|
|
396
|
+
if (msg.length && msg.length % 16 === 0) last = xor16(msg.subarray(msg.length - 16), k1);
|
|
397
|
+
else { const rem = msg.length % 16; msg.copy(last, 0, msg.length - rem); last[rem] = 0x80; last = xor16(last, k2); }
|
|
398
|
+
let y = Buffer.alloc(16);
|
|
399
|
+
for (let i = 0; i < blocks - 1; i++) y = aesBlock(key, xor16(y, msg.subarray(i * 16, i * 16 + 16)));
|
|
400
|
+
return aesBlock(key, xor16(y, last));
|
|
401
|
+
}
|
|
402
|
+
function s2v(key, vectors) {
|
|
403
|
+
let d = cmac(key, Buffer.alloc(16));
|
|
404
|
+
for (let i = 0; i < vectors.length - 1; i++) d = xor16(dbl(d), cmac(key, vectors[i]));
|
|
405
|
+
const last = vectors[vectors.length - 1]; let t;
|
|
406
|
+
if (last.length >= 16) { t = Buffer.from(last); const off = t.length - 16; for (let i = 0; i < 16; i++) t[off + i] ^= d[i]; }
|
|
407
|
+
else { const padded = Buffer.alloc(16); last.copy(padded); padded[last.length] = 0x80; t = xor16(dbl(d), padded); }
|
|
408
|
+
return cmac(key, t);
|
|
409
|
+
}
|
|
410
|
+
function sivCrypt(material, input, tag, decrypt) {
|
|
411
|
+
const q = Buffer.from(tag); q[8] &= 0x7f; q[12] &= 0x7f;
|
|
412
|
+
const cipher = decrypt ? createDecipheriv('aes-128-ctr', material.subarray(16), q) : createCipheriv('aes-128-ctr', material.subarray(16), q);
|
|
413
|
+
return Buffer.concat([cipher.update(input), cipher.final()]);
|
|
414
|
+
}
|
|
415
|
+
function deterministicKey(material) {
|
|
416
|
+
return Buffer.from(hkdfSync('sha256', material, Buffer.alloc(0), DETERMINISTIC_KEY_INFO, 32));
|
|
417
|
+
}
|
|
418
|
+
|
|
374
419
|
async function encryptField(provider, database, table, column, type, value) {
|
|
375
420
|
if (value === null || value === undefined) return null;
|
|
376
421
|
if (!provider || typeof provider.currentFieldKey !== 'function') fail('invalid_argument', 'field key provider is required');
|
|
@@ -395,7 +440,8 @@ module.exports = function createClientEncryption(deps) {
|
|
|
395
440
|
const expected = normalizeType(expectedType);
|
|
396
441
|
if (ciphertext === null || ciphertext === undefined) return null;
|
|
397
442
|
if (!provider || typeof provider.fieldKey !== 'function') fail('invalid_argument', 'field key provider is required');
|
|
398
|
-
|
|
443
|
+
const parsed = inspectField(ciphertext);
|
|
444
|
+
if (parsed.mode !== 'randomized') fail('invalid_format', 'deterministic ciphertext requires decryptFieldDeterministic');
|
|
399
445
|
if (!sameType(expected, parsed.type)) fail('invalid_format', 'encrypted logical type mismatch');
|
|
400
446
|
let key;
|
|
401
447
|
try { key = await provider.fieldKey(database, table, column, parsed.keyID); } catch { fail('crypto', 'field key unavailable or revoked'); }
|
|
@@ -415,6 +461,31 @@ module.exports = function createClientEncryption(deps) {
|
|
|
415
461
|
}
|
|
416
462
|
}
|
|
417
463
|
|
|
464
|
+
async function encryptFieldDeterministic(provider, database, table, column, type, value) {
|
|
465
|
+
if (value === null || value === undefined) return null;
|
|
466
|
+
if (!provider || typeof provider.currentFieldKey !== 'function') fail('invalid_argument', 'field key provider is required');
|
|
467
|
+
const t = normalizeType(type); const plain = encodeScalar(t, value);
|
|
468
|
+
if (plain.length > MAX_PLAINTEXT) fail('exhausted', 'plaintext exceeds field limit');
|
|
469
|
+
let key; try { key = await provider.currentFieldKey(database, table, column); } catch { fail('crypto', 'field key unavailable'); }
|
|
470
|
+
const material = deterministicKey(keyMaterial(key)); const hdr = makeHeader(key.id, t);
|
|
471
|
+
const associated = aad(database, table, column, hdr, DETERMINISTIC_PREFIX);
|
|
472
|
+
const tag = s2v(material.subarray(0, 16), [associated, plain]);
|
|
473
|
+
return DETERMINISTIC_PREFIX + Buffer.concat([hdr, tag, sivCrypt(material, plain, tag, false)]).toString('base64url');
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
async function decryptFieldDeterministic(provider, database, table, column, expectedType, ciphertext) {
|
|
477
|
+
const expected = normalizeType(expectedType); if (ciphertext == null) return null;
|
|
478
|
+
if (!provider || typeof provider.fieldKey !== 'function') fail('invalid_argument', 'field key provider is required');
|
|
479
|
+
const parsed = inspectField(ciphertext);
|
|
480
|
+
if (parsed.mode !== 'deterministic' || !sameType(expected, parsed.type)) fail('invalid_format', 'encrypted mode or logical type mismatch');
|
|
481
|
+
let key; try { key = await provider.fieldKey(database, table, column, parsed.keyID); } catch { fail('crypto', 'field key unavailable or revoked'); }
|
|
482
|
+
const material = deterministicKey(keyMaterial(key, parsed.keyID)); const tag = parsed.body.subarray(parsed.headerLength, parsed.headerLength + 16);
|
|
483
|
+
const plain = sivCrypt(material, parsed.body.subarray(parsed.headerLength + 16), tag, true);
|
|
484
|
+
const expectedTag = s2v(material.subarray(0, 16), [aad(database, table, column, parsed.body.subarray(0, parsed.headerLength), DETERMINISTIC_PREFIX), plain]);
|
|
485
|
+
if (!timingSafeEqual(tag, expectedTag)) fail('crypto', 'ciphertext authentication failed');
|
|
486
|
+
return decodeScalar(expected, plain);
|
|
487
|
+
}
|
|
488
|
+
|
|
418
489
|
function generateFieldKey(id) {
|
|
419
490
|
if (!validKeyID(id)) fail('invalid_argument', 'invalid field key id');
|
|
420
491
|
return { id, material: randomBytes(32) };
|
|
@@ -630,5 +701,5 @@ module.exports = function createClientEncryption(deps) {
|
|
|
630
701
|
}
|
|
631
702
|
}
|
|
632
703
|
|
|
633
|
-
return { FieldType, MemoryFieldKeyring, FileFieldKeyring, decryptField, encryptField, generateFieldKey, inspectField };
|
|
704
|
+
return { FieldType, MemoryFieldKeyring, FileFieldKeyring, decryptField, decryptFieldDeterministic, encryptField, encryptFieldDeterministic, generateFieldKey, inspectField };
|
|
634
705
|
};
|
package/nextsql.d.ts
CHANGED
|
@@ -31,9 +31,10 @@ export interface Config {
|
|
|
31
31
|
/** Single-node entry point. Required for connect(); optional when nodes is set. */
|
|
32
32
|
address?: string;
|
|
33
33
|
database?: string;
|
|
34
|
-
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
34
|
+
/** Reserved; must stay empty. Multi-realm hosting was removed — a
|
|
35
|
+
* deployment serves exactly one database, and a server rejects a Hello
|
|
36
|
+
* naming any other realm. The field remains only because the wire
|
|
37
|
+
* frame's trailing realm slot does. */
|
|
37
38
|
realm?: string;
|
|
38
39
|
user: string;
|
|
39
40
|
password?: string;
|
|
@@ -212,6 +213,8 @@ export interface Conn {
|
|
|
212
213
|
encryptField(table: string, column: string, type: FieldTypeDescriptor, value: unknown): Promise<string | null>;
|
|
213
214
|
/** Authenticate and decode an opaque NSCE1 STRING; null passes through. */
|
|
214
215
|
decryptField(table: string, column: string, type: FieldTypeDescriptor, ciphertext: string | null): Promise<unknown>;
|
|
216
|
+
encryptFieldDeterministic(table: string, column: string, type: FieldTypeDescriptor, value: unknown): Promise<string | null>;
|
|
217
|
+
decryptFieldDeterministic(table: string, column: string, type: FieldTypeDescriptor, ciphertext: string | null): Promise<unknown>;
|
|
215
218
|
}
|
|
216
219
|
|
|
217
220
|
/** Routing client over every node of a NextSQL HA cluster. */
|
|
@@ -236,6 +239,8 @@ export interface HelloOK {
|
|
|
236
239
|
version: number;
|
|
237
240
|
authMethod: number;
|
|
238
241
|
secret: bigint;
|
|
242
|
+
/** Capability bits the server accepted from Hello.flags; 0 when none. */
|
|
243
|
+
flags: number;
|
|
239
244
|
}
|
|
240
245
|
|
|
241
246
|
export interface DecodeValue {
|
|
@@ -246,7 +251,14 @@ export interface DecodeValue {
|
|
|
246
251
|
|
|
247
252
|
export declare class NextSQLError extends Error {
|
|
248
253
|
readonly code: string;
|
|
249
|
-
|
|
254
|
+
/**
|
|
255
|
+
* Stable ERR_* name from docs/error-codes.md, present only on an error
|
|
256
|
+
* received over a connection that negotiated the public taxonomy and empty
|
|
257
|
+
* otherwise. `code` always carries the legacy class, so retry logic should
|
|
258
|
+
* keep reading that.
|
|
259
|
+
*/
|
|
260
|
+
readonly publicCode: string;
|
|
261
|
+
constructor(code: string, message?: string, publicCode?: string);
|
|
250
262
|
}
|
|
251
263
|
|
|
252
264
|
export declare const Kind: {
|
|
@@ -322,11 +334,17 @@ export declare function generateFieldKey(id: string): FieldKey;
|
|
|
322
334
|
export declare function inspectField(ciphertext: string): { keyID: string; type: FieldTypeDescriptor };
|
|
323
335
|
export declare function encryptField(provider: FieldKeyProvider, database: string, table: string, column: string, type: FieldTypeDescriptor, value: unknown): Promise<string | null>;
|
|
324
336
|
export declare function decryptField(provider: FieldKeyProvider, database: string, table: string, column: string, type: FieldTypeDescriptor, ciphertext: string | null): Promise<unknown>;
|
|
337
|
+
export declare function encryptFieldDeterministic(provider: FieldKeyProvider, database: string, table: string, column: string, type: FieldTypeDescriptor, value: unknown): Promise<string | null>;
|
|
338
|
+
export declare function decryptFieldDeterministic(provider: FieldKeyProvider, database: string, table: string, column: string, type: FieldTypeDescriptor, ciphertext: string | null): Promise<unknown>;
|
|
325
339
|
export declare function validateConfig(cfg: Config): void;
|
|
326
340
|
export declare function isLoopback(addr: string): boolean;
|
|
327
341
|
export declare function encodeParam(v: Param): Uint8Array;
|
|
328
342
|
export declare function struct(fields: Array<[string, unknown]>): { __struct: Array<[string, unknown]> };
|
|
329
343
|
export declare function decodeValue(buf: Uint8Array, off: number): DecodeValue;
|
|
344
|
+
/** Hello.flags bit requesting cancellation instead of a session. */
|
|
345
|
+
export declare const FlagCancel: number;
|
|
346
|
+
/** Hello.flags bit requesting the stable ERR_* error taxonomy. */
|
|
347
|
+
export declare const FlagPublicErrorCodes: number;
|
|
330
348
|
export declare function encodeHello(h: Hello): Uint8Array;
|
|
331
349
|
export declare function decodeHelloOK(b: Uint8Array): HelloOK;
|
|
332
350
|
export declare function encodeDecimalString(s: string): Uint8Array;
|
package/nextsql.js
CHANGED
|
@@ -57,6 +57,12 @@ const STATUS_TTL_MS = 500;
|
|
|
57
57
|
const AuthPassword = 1;
|
|
58
58
|
const AuthPasswordKey = 2;
|
|
59
59
|
const FlagCancel = 1;
|
|
60
|
+
// FlagPublicErrorCodes asks the server for the stable ERR_* error taxonomy
|
|
61
|
+
// (docs/error-codes.md). A server that does not implement it ignores the bit
|
|
62
|
+
// and keeps the NSQL v1 error shape, which decodeError still reads, so setting
|
|
63
|
+
// it is safe against any server version. The server echoes it in HelloOK.flags
|
|
64
|
+
// when it was accepted.
|
|
65
|
+
const FlagPublicErrorCodes = 2;
|
|
60
66
|
const FlagNull = 0x01;
|
|
61
67
|
|
|
62
68
|
const Kind = {
|
|
@@ -104,10 +110,16 @@ const MAX_STRUCT_FIELDS = 128;
|
|
|
104
110
|
const MAX_COLLECTION_LEN = 1 << 20;
|
|
105
111
|
|
|
106
112
|
class NextSQLError extends Error {
|
|
107
|
-
constructor(code, message) {
|
|
113
|
+
constructor(code, message, publicCode) {
|
|
108
114
|
super(message || code);
|
|
109
115
|
this.name = 'NextSQLError';
|
|
110
116
|
this.code = code;
|
|
117
|
+
// publicCode is the stable ERR_* name the server sent, present only on
|
|
118
|
+
// an error received over a connection that negotiated the taxonomy and
|
|
119
|
+
// empty otherwise. code always carries the legacy class, so existing
|
|
120
|
+
// retry checks keep working and nothing should branch on publicCode
|
|
121
|
+
// being present.
|
|
122
|
+
this.publicCode = publicCode || '';
|
|
111
123
|
}
|
|
112
124
|
}
|
|
113
125
|
|
|
@@ -116,7 +128,9 @@ const {
|
|
|
116
128
|
MemoryFieldKeyring,
|
|
117
129
|
FileFieldKeyring,
|
|
118
130
|
decryptField,
|
|
131
|
+
decryptFieldDeterministic,
|
|
119
132
|
encryptField,
|
|
133
|
+
encryptFieldDeterministic,
|
|
120
134
|
generateFieldKey,
|
|
121
135
|
inspectField,
|
|
122
136
|
} = require('./client-encryption')({
|
|
@@ -1279,10 +1293,19 @@ function encodeHello(h) {
|
|
|
1279
1293
|
}
|
|
1280
1294
|
|
|
1281
1295
|
function decodeHelloOK(b) {
|
|
1282
|
-
if (b.length !== 11) {
|
|
1296
|
+
if (b.length !== 11 && b.length !== 13) {
|
|
1283
1297
|
throw new NextSQLError('protocol', 'bad hello-ok length');
|
|
1284
1298
|
}
|
|
1285
|
-
|
|
1299
|
+
let flags = 0;
|
|
1300
|
+
if (b.length === 13) {
|
|
1301
|
+
flags = u16(b, 11);
|
|
1302
|
+
// The server omits the field when it accepted no capability, so a
|
|
1303
|
+
// present-but-zero field is a second encoding of the v1 hello-ok.
|
|
1304
|
+
if (flags === 0) {
|
|
1305
|
+
throw new NextSQLError('protocol', 'empty hello-ok flags');
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
return { version: u16(b, 0), authMethod: b[2], secret: u64(b, 3), flags };
|
|
1286
1309
|
}
|
|
1287
1310
|
|
|
1288
1311
|
function encodeAuth(password) {
|
|
@@ -1374,7 +1397,18 @@ function decodeDataBatch(b) {
|
|
|
1374
1397
|
function decodeError(b) {
|
|
1375
1398
|
const code = readU16String(b, 0, MAX_NAME);
|
|
1376
1399
|
const msg = readU16String(b, code.next, MAX_NAME);
|
|
1377
|
-
|
|
1400
|
+
// Optional trailing field, present only from a server that accepted
|
|
1401
|
+
// FlagPublicErrorCodes. Its absence is normal — an older server ignores the
|
|
1402
|
+
// request bit — so this must never be required.
|
|
1403
|
+
let publicCode = '';
|
|
1404
|
+
if (msg.next < b.length) {
|
|
1405
|
+
const p = readU16String(b, msg.next, MAX_NAME);
|
|
1406
|
+
if (p.value === '') {
|
|
1407
|
+
throw new NextSQLError('protocol', 'empty public error code');
|
|
1408
|
+
}
|
|
1409
|
+
publicCode = p.value;
|
|
1410
|
+
}
|
|
1411
|
+
return new NextSQLError(code.value, msg.value, publicCode);
|
|
1378
1412
|
}
|
|
1379
1413
|
|
|
1380
1414
|
function decodeCommandComplete(b) {
|
|
@@ -1567,8 +1601,8 @@ function connectSocket(cfg) {
|
|
|
1567
1601
|
if (cfg.tls.ca) {
|
|
1568
1602
|
opts.ca = cfg.tls.ca;
|
|
1569
1603
|
}
|
|
1570
|
-
if (cfg.tls.rejectUnauthorized ===
|
|
1571
|
-
opts.rejectUnauthorized =
|
|
1604
|
+
if (typeof cfg.tls.rejectUnauthorized === 'boolean') {
|
|
1605
|
+
opts.rejectUnauthorized = cfg.tls.rejectUnauthorized;
|
|
1572
1606
|
}
|
|
1573
1607
|
const sock = tls.connect(opts, () => resolve(sock));
|
|
1574
1608
|
sock.on('error', onErr);
|
|
@@ -1724,11 +1758,13 @@ class Conn {
|
|
|
1724
1758
|
this.wire = wire;
|
|
1725
1759
|
this.secret = 0n;
|
|
1726
1760
|
this.busy = false;
|
|
1761
|
+
this.publicErrorCodes = false;
|
|
1727
1762
|
}
|
|
1728
1763
|
|
|
1729
1764
|
async handshake() {
|
|
1730
1765
|
await this.wire.writeFrame(Type.Hello, encodeHello({
|
|
1731
1766
|
version: VERSION,
|
|
1767
|
+
flags: FlagPublicErrorCodes,
|
|
1732
1768
|
database: this.cfg.database || '',
|
|
1733
1769
|
user: this.cfg.user,
|
|
1734
1770
|
realm: this.cfg.realm || '',
|
|
@@ -1739,6 +1775,9 @@ class Conn {
|
|
|
1739
1775
|
}
|
|
1740
1776
|
const ok = decodeHelloOK(msg.payload);
|
|
1741
1777
|
this.secret = ok.secret;
|
|
1778
|
+
// Diagnostic only: decodeError reads the field whenever it is present,
|
|
1779
|
+
// so nothing depends on this having been echoed.
|
|
1780
|
+
this.publicErrorCodes = (ok.flags & FlagPublicErrorCodes) !== 0;
|
|
1742
1781
|
await this.wire.writeFrame(Type.Auth, encodeAuth(this.cfg.password || ''));
|
|
1743
1782
|
msg = await this.wire.readFrame();
|
|
1744
1783
|
if (msg.type !== Type.AuthOK) {
|
|
@@ -1888,6 +1927,14 @@ class Conn {
|
|
|
1888
1927
|
return decryptField(this.cfg.fieldKeys, this.cfg.database || '', table, column, type, ciphertext);
|
|
1889
1928
|
}
|
|
1890
1929
|
|
|
1930
|
+
async encryptFieldDeterministic(table, column, type, value) {
|
|
1931
|
+
return encryptFieldDeterministic(this.cfg.fieldKeys, this.cfg.database || '', table, column, type, value);
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
async decryptFieldDeterministic(table, column, type, ciphertext) {
|
|
1935
|
+
return decryptFieldDeterministic(this.cfg.fieldKeys, this.cfg.database || '', table, column, type, ciphertext);
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1891
1938
|
async prepare(sql) {
|
|
1892
1939
|
if (!this.wire) {
|
|
1893
1940
|
throw new NextSQLError('unavailable', 'connection closed');
|
|
@@ -2152,6 +2199,8 @@ async function connectCluster(cfg) {
|
|
|
2152
2199
|
|
|
2153
2200
|
module.exports = {
|
|
2154
2201
|
connect,
|
|
2202
|
+
FlagCancel,
|
|
2203
|
+
FlagPublicErrorCodes,
|
|
2155
2204
|
connectCluster,
|
|
2156
2205
|
Cluster,
|
|
2157
2206
|
NextSQLError,
|
|
@@ -2181,7 +2230,9 @@ module.exports = {
|
|
|
2181
2230
|
MemoryFieldKeyring,
|
|
2182
2231
|
FileFieldKeyring,
|
|
2183
2232
|
decryptField,
|
|
2233
|
+
decryptFieldDeterministic,
|
|
2184
2234
|
encryptField,
|
|
2235
|
+
encryptFieldDeterministic,
|
|
2185
2236
|
generateFieldKey,
|
|
2186
2237
|
inspectField,
|
|
2187
2238
|
};
|