dstu-core 0.4.0 → 0.5.0

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/README.md CHANGED
@@ -55,12 +55,13 @@ console.log(dstu.secretboxOpen(key, sealed).toString()); // "a message worth pro
55
55
  | Module | Functions/classes | Notes |
56
56
  |---|---|---|
57
57
  | `crypto_secretbox` | `secretboxKeygen`, `secretboxSeal`, `secretboxOpen` | Single-message authenticated encryption. `examples/secretbox.js`. |
58
- | `crypto_box` | `boxKeygen`, `boxPublicKey`, `boxSeal`, `boxOpen` | Public-key encryption (hybrid via KDF over `hazmat::dstu9041`, `l(p)=256`, D-169). `boxSeal`/`boxOpen` are not memory-bounded — the whole message is held in memory. `examples/box.js`. |
59
- | `crypto_box512` | `box512Keygen`, `box512PublicKey`, `box512Seal`, `box512Open` | `l(p)=512`/E512/1 sibling of `crypto_box` (T-193/T-204). `examples/box512.js`. |
58
+ | `crypto_box` | `boxKeygen`, `boxPublicKey`, `boxSeal`, `boxOpen`, `BoxEncryptor`, `BoxDecryptor` | Public-key encryption (hybrid via KDF over `hazmat::dstu9041`, `l(p)=256`, D-169). `boxSeal`/`boxOpen` hold the whole message in memory; the `stream.Transform` `BoxEncryptor`/`BoxDecryptor` write and read the same bytes (format v3) chunk by chunk, as `uacrypt box-seal`/`box-open` do. `examples/box.js`. |
59
+ | `crypto_box384` | `box384Keygen`, `box384PublicKey`, `box384Seal`, `box384Open`, `Box384Encryptor`, `Box384Decryptor` | `l(p)=384`/E384/1 sibling of `crypto_box` (T-297). `examples/box384.js`. |
60
+ | `crypto_box512` | `box512Keygen`, `box512PublicKey`, `box512Seal`, `box512Open`, `Box512Encryptor`, `Box512Decryptor` | `l(p)=512`/E512/1 sibling of `crypto_box` (T-193/T-204). `examples/box512.js`. |
60
61
  | `crypto_secretstream` | `secretstreamKeygen`, `SecretStreamPushState`, `SecretStreamPullState`, `SecretStreamEncryptor`, `SecretStreamDecryptor` | Chunked streaming AEAD. The `stream.Transform` `SecretStreamEncryptor`/`SecretStreamDecryptor` wire format matches `uacrypt encrypt`/`decrypt` exactly (D-118). `examples/secretstream-file.js`. |
61
62
  | `crypto_sign` | `signKeygen`, `signVerifyingKey`, `signMessage`, `signVerify` | DSTU 4145 `m=163` digital signatures, deterministic nonce (no RNG dependency). `examples/sign.js`. |
62
63
  | `crypto_sign257` | `sign257Keygen`, `sign257VerifyingKey`, `sign257Message`, `sign257Verify` | `m=257` sibling of `crypto_sign` (T-199/T-204) — the curve real Diia-issued qualified signatures use. `examples/sign257.js`. |
63
- | `crypto_pwhash` | `pwhashHashPassword`, `pwhashVerifyPassword`, `PWHASH_INTERACTIVE`/`PWHASH_MODERATE`/`PWHASH_SENSITIVE` | Argon2id (the one deliberately non-DSTU component, D-49/D-50). `examples/password-hashing.js`. |
64
+ | `crypto_pwhash` | `pwhashHashPassword`, `pwhashVerifyPassword`, `pwhashDeriveKey`, `PWHASH_INTERACTIVE`/`PWHASH_MODERATE`/`PWHASH_SENSITIVE`, `PassphraseEncryptor`, `PassphraseDecryptor` | Argon2id (the one deliberately non-DSTU component, D-49/D-50). `pwhashDeriveKey` equals libsodium's `crypto_pwhash`. `PassphraseEncryptor`/`PassphraseDecryptor` write and read the files `uacrypt encrypt --passphrase` makes (D-219/D-240); each runs Argon2id at 256 MiB once, synchronously on the calling thread like `pwhashHashPassword`. `examples/password-hashing.js`. |
64
65
  | `crypto_auth` | `authKeygen`, `auth`, `authVerify` | Keyed message authentication (Kupyna-KMAC). `examples/misc.js`. |
65
66
  | `crypto_kdf` | `kdfKeygen`, `kdfDeriveSubkey` | Deterministic subkey derivation. `subkeyId` is a plain non-negative `number`, not `BigInt`. `examples/misc.js`. |
66
67
  | `crypto_generichash` | `kupyna256`, `kupyna512`, `Kupyna256Hasher`, `Kupyna512Hasher` | One-shot and streaming Kupyna hashing. `examples/misc.js`. |
package/js/index.js CHANGED
@@ -15,10 +15,9 @@
15
15
  */
16
16
 
17
17
  const native = require('../native/index.js');
18
- const { SecretStreamEncryptor, SecretStreamDecryptor } = require('./secretstream.js');
18
+ const streams = require('./secretstream.js');
19
19
 
20
20
  module.exports = {
21
21
  ...native,
22
- SecretStreamEncryptor,
23
- SecretStreamDecryptor,
22
+ ...streams,
24
23
  };
@@ -42,11 +42,16 @@ const CHUNK_BYTES = 8 * 1024;
42
42
  class SecretStreamEncryptor extends Transform {
43
43
  constructor(key, options) {
44
44
  super(options);
45
- this._enc = new native.SecretStreamFileEncryptor(key);
45
+ this._enc = this._startCodec(key);
46
46
  this._buf = Buffer.alloc(0);
47
47
  this.push(this._enc.prefix);
48
48
  }
49
49
 
50
+ /** The native codec for `key`; the box/passphrase subclasses start it their own way. */
51
+ _startCodec(key) {
52
+ return new native.SecretStreamFileEncryptor(key);
53
+ }
54
+
50
55
  _transform(chunk, _encoding, callback) {
51
56
  this._buf = Buffer.concat([this._buf, chunk]);
52
57
  let err;
@@ -96,14 +101,27 @@ class SecretStreamEncryptor extends Transform {
96
101
  class SecretStreamDecryptor extends Transform {
97
102
  constructor(key, options) {
98
103
  super(options);
99
- this._dec = new native.SecretStreamFileDecryptor(key);
104
+ this._dec = this._startCodec(key);
100
105
  this._buf = Buffer.alloc(0);
101
106
  }
102
107
 
108
+ /** The native codec for `key`, or null while a subclass still collects its prefix. */
109
+ _startCodec(key) {
110
+ return new native.SecretStreamFileDecryptor(key);
111
+ }
112
+
103
113
  _transform(chunk, _encoding, callback) {
104
114
  this._buf = Buffer.concat([this._buf, chunk]);
105
115
  let err;
106
116
  try {
117
+ if (this._dec === null) {
118
+ if (this._buf.length < this._prefixBytes) {
119
+ process.nextTick(callback);
120
+ return;
121
+ }
122
+ this._dec = this._openPrefixed(this._buf.subarray(0, this._prefixBytes));
123
+ this._buf = this._buf.subarray(this._prefixBytes);
124
+ }
107
125
  let pos = 0;
108
126
  for (let want = this._dec.nextLen(); this._buf.length - pos >= want; want = this._dec.nextLen()) {
109
127
  const plaintext = this._dec.feed(this._buf.subarray(pos, pos + want));
@@ -121,6 +139,7 @@ class SecretStreamDecryptor extends Transform {
121
139
  _flush(callback) {
122
140
  let err;
123
141
  try {
142
+ if (this._dec === null) throw new Error(`input too short to contain a ${this._prefixName}`);
124
143
  const plaintext = this._dec.finish();
125
144
  if (plaintext.length > 0) this.push(plaintext);
126
145
  } catch (e) {
@@ -130,4 +149,130 @@ class SecretStreamDecryptor extends Transform {
130
149
  }
131
150
  }
132
151
 
133
- module.exports = { SecretStreamEncryptor, SecretStreamDecryptor };
152
+ function checkLength(buf, bytes, name) {
153
+ if (!Buffer.isBuffer(buf) || buf.length !== bytes) {
154
+ throw new TypeError(`${name} must be a ${bytes}-byte Buffer`);
155
+ }
156
+ }
157
+
158
+ /**
159
+ * `crypto_box` sealed to `publicKey` as a stream (format v3, T-265/T-269): the same bytes
160
+ * `boxSeal` returns, written chunk by chunk; `boxOpen`/`BoxDecryptor` read them.
161
+ */
162
+ class BoxEncryptor extends SecretStreamEncryptor {
163
+ _startCodec(publicKey) {
164
+ return native.SecretStreamFileEncryptor.boxSeal(publicKey);
165
+ }
166
+ }
167
+
168
+ /** `crypto_box384` counterpart of `BoxEncryptor`. */
169
+ class Box384Encryptor extends SecretStreamEncryptor {
170
+ _startCodec(publicKey) {
171
+ return native.SecretStreamFileEncryptor.box384Seal(publicKey);
172
+ }
173
+ }
174
+
175
+ /** `crypto_box512` counterpart of `BoxEncryptor`. */
176
+ class Box512Encryptor extends SecretStreamEncryptor {
177
+ _startCodec(publicKey) {
178
+ return native.SecretStreamFileEncryptor.box512Seal(publicKey);
179
+ }
180
+ }
181
+
182
+ /**
183
+ * A passphrase file (D-219/D-240) - the bytes `uacrypt encrypt --passphrase` writes. The
184
+ * constructor runs Argon2id (256 MiB, t=3, about a second) on the calling thread.
185
+ */
186
+ class PassphraseEncryptor extends SecretStreamEncryptor {
187
+ _startCodec(passphrase) {
188
+ return native.SecretStreamFileEncryptor.passphraseSeal(passphrase);
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Collects the bytes in front of the stream file, then opens the codec with them. The secret is
194
+ * held only until then.
195
+ */
196
+ class PrefixedDecryptor extends SecretStreamDecryptor {
197
+ _startCodec(secret) {
198
+ this._secret = secret;
199
+ return null;
200
+ }
201
+
202
+ _openPrefixed(prefix) {
203
+ const dec = this._openCodec(this._secret, prefix);
204
+ this._secret = null;
205
+ return dec;
206
+ }
207
+ }
208
+
209
+ /** Reads a `crypto_box` stream (or a `boxSeal` blob) with bounded memory. */
210
+ class BoxDecryptor extends PrefixedDecryptor {
211
+ constructor(secretKey, options) {
212
+ checkLength(secretKey, 32, 'secretKey');
213
+ super(secretKey, options);
214
+ this._prefixBytes = native.BOX_OPEN_STREAM_PREFIX_BYTES;
215
+ this._prefixName = 'sealed message';
216
+ }
217
+
218
+ _openCodec(secretKey, prefix) {
219
+ return native.SecretStreamFileDecryptor.boxOpen(secretKey, prefix);
220
+ }
221
+ }
222
+
223
+ /** `crypto_box384` counterpart of `BoxDecryptor`. */
224
+ class Box384Decryptor extends PrefixedDecryptor {
225
+ constructor(secretKey, options) {
226
+ checkLength(secretKey, 48, 'secretKey');
227
+ super(secretKey, options);
228
+ this._prefixBytes = native.BOX384_OPEN_STREAM_PREFIX_BYTES;
229
+ this._prefixName = 'sealed message';
230
+ }
231
+
232
+ _openCodec(secretKey, prefix) {
233
+ return native.SecretStreamFileDecryptor.box384Open(secretKey, prefix);
234
+ }
235
+ }
236
+
237
+ /** `crypto_box512` counterpart of `BoxDecryptor`. */
238
+ class Box512Decryptor extends PrefixedDecryptor {
239
+ constructor(secretKey, options) {
240
+ checkLength(secretKey, 64, 'secretKey');
241
+ super(secretKey, options);
242
+ this._prefixBytes = native.BOX512_OPEN_STREAM_PREFIX_BYTES;
243
+ this._prefixName = 'sealed message';
244
+ }
245
+
246
+ _openCodec(secretKey, prefix) {
247
+ return native.SecretStreamFileDecryptor.box512Open(secretKey, prefix);
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Reads a passphrase file. The header is checked before Argon2 runs (on the calling thread, once
253
+ * the header has arrived); a wrong passphrase errors at the first record.
254
+ */
255
+ class PassphraseDecryptor extends PrefixedDecryptor {
256
+ constructor(passphrase, options) {
257
+ super(passphrase, options);
258
+ this._prefixBytes = native.PASSPHRASE_FILE_HEADER_BYTES;
259
+ this._prefixName = 'passphrase file header';
260
+ }
261
+
262
+ _openCodec(passphrase, header) {
263
+ return native.SecretStreamFileDecryptor.passphraseOpen(header, passphrase);
264
+ }
265
+ }
266
+
267
+ module.exports = {
268
+ SecretStreamEncryptor,
269
+ SecretStreamDecryptor,
270
+ BoxEncryptor,
271
+ BoxDecryptor,
272
+ Box384Encryptor,
273
+ Box384Decryptor,
274
+ Box512Encryptor,
275
+ Box512Decryptor,
276
+ PassphraseEncryptor,
277
+ PassphraseDecryptor,
278
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dstu-core",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "private": false,
5
5
  "description": "Node.js bindings for dstu-core (Ukrainian DSTU cryptographic standards)",
6
6
  "main": "js/index.js",
@@ -47,9 +47,9 @@
47
47
  "prepublishOnly": "napi prepublish -t npm --skip-gh-release"
48
48
  },
49
49
  "optionalDependencies": {
50
- "dstu-core-linux-x64-gnu": "0.4.0",
51
- "dstu-core-darwin-arm64": "0.4.0",
52
- "dstu-core-win32-x64-msvc": "0.4.0",
53
- "dstu-core-linux-arm64-gnu": "0.4.0"
50
+ "dstu-core-linux-x64-gnu": "0.5.0",
51
+ "dstu-core-darwin-arm64": "0.5.0",
52
+ "dstu-core-win32-x64-msvc": "0.5.0",
53
+ "dstu-core-linux-arm64-gnu": "0.5.0"
54
54
  }
55
55
  }