@h3l1os/mp4vault 2.0.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.
@@ -0,0 +1,204 @@
1
+ import { AES } from './AES.js';
2
+ import { Convert } from './Convert.js';
3
+ import { BUFFER_SIZE } from './constants.js';
4
+ import { Readable } from './node/Readable.js';
5
+ import { Writable } from './node/Writable.js';
6
+ import type { IReadable, IWritable } from './types.js';
7
+
8
+ export class EmbedBinary {
9
+ _readable: IReadable | null;
10
+ private _key: Buffer | null;
11
+ private _password: string | null;
12
+ private _iv: Buffer | null;
13
+ private _salt: Buffer | null;
14
+ private _encryptor: AES | null = null;
15
+
16
+ constructor(params: {
17
+ readable?: IReadable;
18
+ filename?: string;
19
+ file?: { name?: string };
20
+ key?: Buffer | null;
21
+ password?: string | null;
22
+ iv?: Buffer | null;
23
+ salt?: Buffer | null;
24
+ } = {}) {
25
+ this._readable = null;
26
+
27
+ if (params.readable) {
28
+ this._readable = params.readable;
29
+ } else if (params.filename && !params.file) {
30
+ this._readable = new Readable({ filename: params.filename });
31
+ } else if (params.file) {
32
+ // Browser File objects handled via browser Readable
33
+ this._readable = new Readable({ filename: (params.file as { name: string }).name });
34
+ }
35
+
36
+ this._key = params.key || null;
37
+ this._password = params.password || null;
38
+ this._iv = params.iv || null;
39
+ this._salt = params.salt || null;
40
+ }
41
+
42
+ async getExpectedSize(): Promise<number> {
43
+ if (!this._readable) {
44
+ throw new Error('No readable source. Provide a filename, file, or readable');
45
+ }
46
+ const readableSize = await this._readable.size();
47
+
48
+ if (this._key || this._password) {
49
+ return 2 + AES.saltByteLength + AES.ivByteLength + readableSize + AES.authTagByteLength;
50
+ } else {
51
+ return 2 + readableSize;
52
+ }
53
+ }
54
+
55
+ getEncryptor(): AES {
56
+ if (this._encryptor) {
57
+ return this._encryptor;
58
+ }
59
+
60
+ this._encryptor = new AES({
61
+ key: this._key || undefined,
62
+ password: this._password || undefined,
63
+ iv: this._iv || undefined,
64
+ salt: this._salt || undefined,
65
+ });
66
+
67
+ if (!this._iv) {
68
+ this._iv = this._encryptor.getIV();
69
+ }
70
+ if (!this._salt) {
71
+ this._salt = this._encryptor.getSalt();
72
+ }
73
+
74
+ return this._encryptor;
75
+ }
76
+
77
+ getIV(): Buffer {
78
+ if (!this._iv) {
79
+ throw new Error('IV is not yet ready. Run getEncryptor() first, or specify one yourself');
80
+ }
81
+ return this._iv;
82
+ }
83
+
84
+ static async restoreFromReadable(
85
+ readable: IReadable,
86
+ params: { key?: Buffer; password?: string } = {},
87
+ offset = 0,
88
+ size: number | null = null,
89
+ writable: IWritable | null = null,
90
+ ): Promise<IWritable> {
91
+ const firstByte = (await readable.getSlice(offset + 0, 1))[0];
92
+ const typeByte = (await readable.getSlice(offset + 1, 1))[0];
93
+
94
+ if (!size) {
95
+ size = (await readable.size()) - offset;
96
+ }
97
+
98
+ let decryptor: AES | null = null;
99
+
100
+ if (Convert.isByteIn(firstByte, 2, 1)) {
101
+ let readOffset = offset + 2;
102
+
103
+ const salt = await readable.getSlice(readOffset, AES.saltByteLength);
104
+ readOffset += AES.saltByteLength;
105
+
106
+ const iv = await readable.getSlice(readOffset, AES.ivByteLength);
107
+
108
+ const authTagOffset = offset + size - AES.authTagByteLength;
109
+ const authTag = await readable.getSlice(authTagOffset, AES.authTagByteLength);
110
+
111
+ const decryptParams: {
112
+ key?: Buffer;
113
+ password?: string;
114
+ iv: Buffer;
115
+ salt: Buffer;
116
+ authTag: Buffer;
117
+ } = {
118
+ iv: Buffer.from(iv),
119
+ salt: Buffer.from(salt),
120
+ authTag: Buffer.from(authTag),
121
+ };
122
+ if (params.key) decryptParams.key = params.key;
123
+ if (params.password) decryptParams.password = params.password;
124
+
125
+ decryptor = new AES(decryptParams);
126
+ }
127
+
128
+ if (Convert.isByteIn(typeByte, 11, 1)) {
129
+ if (!writable) {
130
+ writable = new Writable();
131
+ }
132
+
133
+ let bodySize = size - 2;
134
+ let bodyOffset = offset + 2;
135
+ if (decryptor) {
136
+ bodySize = bodySize - AES.saltByteLength - AES.ivByteLength - AES.authTagByteLength;
137
+ bodyOffset = offset + 2 + AES.saltByteLength + AES.ivByteLength;
138
+ }
139
+
140
+ for (let i = 0; i < bodySize; i += BUFFER_SIZE) {
141
+ const copySize = Math.min(BUFFER_SIZE, bodySize - i);
142
+ const chunk = await readable.getSlice(bodyOffset + i, copySize);
143
+ if (decryptor) {
144
+ const isLast = (i + copySize >= bodySize);
145
+ const decrypted = decryptor.decrypt(Buffer.from(chunk), isLast);
146
+ await writable.write(decrypted);
147
+ } else {
148
+ await writable.write(chunk);
149
+ }
150
+ }
151
+
152
+ if (decryptor && bodySize === 0) {
153
+ const final = decryptor.decrypt(null, true);
154
+ if (final.length > 0) {
155
+ await writable.write(final);
156
+ }
157
+ }
158
+
159
+ await readable.close();
160
+
161
+ return writable;
162
+ }
163
+
164
+ throw new Error('Unknown embed type');
165
+ }
166
+
167
+ async writeTo(writable: IWritable): Promise<void> {
168
+ if (!this._readable) return;
169
+
170
+ let encryptor: AES | null = null;
171
+ let firstByte = Convert.randomByteIn(2, 0);
172
+ if (this._key || this._password) {
173
+ encryptor = this.getEncryptor();
174
+ firstByte = Convert.randomByteIn(2, 1);
175
+ }
176
+
177
+ await writable.write([firstByte]);
178
+ await writable.write([Convert.randomByteIn(11, 1)]);
179
+
180
+ if (encryptor) {
181
+ await writable.write(this._encryptor!.getSalt() || Buffer.alloc(AES.saltByteLength));
182
+ await writable.write(this.getIV());
183
+ }
184
+
185
+ const bodySize = await this._readable.size();
186
+ for (let i = 0; i < bodySize; i += BUFFER_SIZE) {
187
+ const copySize = Math.min(BUFFER_SIZE, bodySize - i);
188
+ const chunk = await this._readable.getSlice(i, copySize);
189
+ if (encryptor) {
190
+ const isLast = (i + copySize >= bodySize);
191
+ const encrypted = encryptor.encrypt(Buffer.from(chunk), isLast);
192
+ await writable.write(encrypted);
193
+ } else {
194
+ await writable.write(chunk);
195
+ }
196
+ }
197
+
198
+ if (encryptor) {
199
+ await writable.write(encryptor.getAuthTag());
200
+ }
201
+
202
+ await this._readable.close();
203
+ }
204
+ }
@@ -0,0 +1,231 @@
1
+ import { Pack } from './Pack.js';
2
+ import { AES } from './AES.js';
3
+ import { Convert } from './Convert.js';
4
+ import { MAX_HEADER_SIZE } from './constants.js';
5
+ import type { IReadable, IWritable } from './types.js';
6
+
7
+ export class EmbedObject {
8
+ _object: Record<string, unknown> | null;
9
+ private _binary: Buffer | null;
10
+ private _key: Buffer | null;
11
+ private _password: string | null;
12
+ private _iv: Buffer | null;
13
+ private _salt: Buffer | null;
14
+ private _readBytes: number;
15
+ private _encryptor: AES | null = null;
16
+
17
+ constructor(params: {
18
+ object?: Record<string, unknown>;
19
+ key?: Buffer | null;
20
+ password?: string | null;
21
+ iv?: Buffer | null;
22
+ salt?: Buffer | null;
23
+ readBytes?: number;
24
+ } = {}) {
25
+ this._object = params.object || null;
26
+ this._binary = params.object ? Convert.objectToBuffer(params.object) : null;
27
+
28
+ this._key = params.key || null;
29
+ this._password = params.password || null;
30
+ this._iv = params.iv || null;
31
+ this._salt = params.salt || null;
32
+ this._readBytes = params.readBytes || 0;
33
+ }
34
+
35
+ get readBytes(): number {
36
+ return this._readBytes;
37
+ }
38
+
39
+ async getExpectedSize(): Promise<number> {
40
+ if (this._key || this._password) {
41
+ return 2 + AES.saltByteLength + AES.ivByteLength + 4 + this._binary!.length + AES.authTagByteLength;
42
+ } else {
43
+ return 2 + 4 + this._binary!.length;
44
+ }
45
+ }
46
+
47
+ get object(): Record<string, unknown> | null {
48
+ return this._object;
49
+ }
50
+
51
+ getEncryptor(): AES {
52
+ if (this._encryptor) {
53
+ return this._encryptor;
54
+ }
55
+
56
+ this._encryptor = new AES({
57
+ key: this._key || undefined,
58
+ password: this._password || undefined,
59
+ iv: this._iv || undefined,
60
+ salt: this._salt || undefined,
61
+ });
62
+
63
+ if (!this._iv) {
64
+ this._iv = this._encryptor.getIV();
65
+ }
66
+ if (!this._salt) {
67
+ this._salt = this._encryptor.getSalt();
68
+ }
69
+
70
+ return this._encryptor;
71
+ }
72
+
73
+ getIV(): Buffer {
74
+ if (!this._iv) {
75
+ throw new Error('IV is not yet ready. Run getEncryptor() first, or specify one yourself');
76
+ }
77
+ return this._iv;
78
+ }
79
+
80
+ static async restoreFromReadable(
81
+ readable: IReadable,
82
+ params: {
83
+ key?: Buffer;
84
+ password?: string | null;
85
+ object?: Record<string, unknown>;
86
+ readBytes?: number;
87
+ iv?: Buffer;
88
+ salt?: Buffer;
89
+ } = {},
90
+ offset = 0,
91
+ ): Promise<EmbedObject> {
92
+ const firstByte = (await readable.getSlice(offset + 0, 1))[0];
93
+ const typeByte = (await readable.getSlice(offset + 1, 1))[0];
94
+
95
+ let size: number | null = null;
96
+ let readBytes = 2;
97
+
98
+ if (Convert.isByteIn(firstByte, 2, 1)) {
99
+ // encrypted
100
+ const salt = await readable.getSlice(offset + 2, AES.saltByteLength);
101
+ readBytes += AES.saltByteLength;
102
+
103
+ const iv = await readable.getSlice(offset + 2 + AES.saltByteLength, AES.ivByteLength);
104
+ readBytes += AES.ivByteLength;
105
+
106
+ const headerDataOffset = offset + 2 + AES.saltByteLength + AES.ivByteLength;
107
+
108
+ const sizeChunk = await readable.getSlice(headerDataOffset, 4);
109
+ readBytes += 4;
110
+
111
+ const decryptParams: {
112
+ key?: Buffer;
113
+ password?: string;
114
+ iv: Buffer;
115
+ salt: Buffer;
116
+ authTag?: Buffer;
117
+ } = {
118
+ iv: Buffer.from(iv),
119
+ salt: Buffer.from(salt),
120
+ };
121
+ if (params.key) decryptParams.key = params.key;
122
+ if (params.password) decryptParams.password = params.password;
123
+
124
+ // peek at encrypted size
125
+ const peekDecryptor = new AES(decryptParams);
126
+ const sizeDecrypted = peekDecryptor.decrypt(Buffer.from(sizeChunk));
127
+ size = Pack.unpack('>I', sizeDecrypted)[0];
128
+
129
+ if (!size) {
130
+ throw new Error('Can not get size of EmbedObject to restore');
131
+ }
132
+
133
+ if (size > MAX_HEADER_SIZE) {
134
+ throw new Error('Header is too large to extract');
135
+ }
136
+
137
+ const fullEncryptedSize = 4 + size;
138
+ const authTagOffset = headerDataOffset + fullEncryptedSize;
139
+ const authTag = await readable.getSlice(authTagOffset, AES.authTagByteLength);
140
+ readBytes += size + AES.authTagByteLength;
141
+
142
+ decryptParams.authTag = Buffer.from(authTag);
143
+ const fullDecryptor = new AES(decryptParams);
144
+
145
+ const fullChunk = await readable.getSlice(headerDataOffset, fullEncryptedSize);
146
+ const decrypted = fullDecryptor.decrypt(Buffer.from(fullChunk), true);
147
+
148
+ const jsonPayload = decrypted.subarray(4);
149
+ params.object = Convert.bufferToObject(jsonPayload) as Record<string, unknown>;
150
+ } else {
151
+ // raw
152
+ const sizeBytes = await readable.getSlice(offset + 2, 4);
153
+ readBytes += 4;
154
+
155
+ size = Pack.unpack('>I', sizeBytes)[0];
156
+
157
+ if (!size) {
158
+ throw new Error('Can not get size of EmbedObject to restore');
159
+ }
160
+
161
+ if (size > MAX_HEADER_SIZE) {
162
+ throw new Error('Header is too large to extract');
163
+ }
164
+
165
+ const chunk = await readable.getSlice(offset + 6, size);
166
+ readBytes += size;
167
+
168
+ params.object = Convert.bufferToObject(Buffer.from(chunk)) as Record<string, unknown>;
169
+ }
170
+
171
+ delete params.iv;
172
+ delete params.salt;
173
+ await readable.close();
174
+
175
+ params.readBytes = readBytes;
176
+ return new EmbedObject(params);
177
+ }
178
+
179
+ async writeTo(writable: IWritable): Promise<void> {
180
+ const binary = this.getBinary();
181
+ await writable.write(binary);
182
+ }
183
+
184
+ getBinary(): Uint8Array {
185
+ if (this._key || this._password) {
186
+ return this.getEncrypted();
187
+ } else {
188
+ return this.getRaw();
189
+ }
190
+ }
191
+
192
+ getRaw(): Uint8Array {
193
+ const payload = this._binary!;
194
+ const ret = new Uint8Array(payload.length + 2 + 4);
195
+
196
+ const firstByte = Convert.randomByteIn(2, 0);
197
+ const secondByte = Convert.randomByteIn(11, 0);
198
+ ret.set([firstByte], 0);
199
+ ret.set([secondByte], 1);
200
+ ret.set(Pack.pack('>I', [payload.length]), 2);
201
+ ret.set(payload, 6);
202
+
203
+ return ret;
204
+ }
205
+
206
+ getEncrypted(): Uint8Array {
207
+ const encryptor = this.getEncryptor();
208
+
209
+ const packedSize = Pack.pack('>I', [this._binary!.length]);
210
+ const plaintext = Buffer.concat([Buffer.from(packedSize), this._binary!]);
211
+
212
+ const ciphertext = encryptor.encrypt(plaintext, true);
213
+ const iv = this.getIV();
214
+ const salt = encryptor.getSalt() || Buffer.alloc(AES.saltByteLength);
215
+ const authTag = encryptor.getAuthTag();
216
+
217
+ const ret = new Uint8Array(2 + salt.length + iv.length + ciphertext.length + authTag.length);
218
+ const firstByte = Convert.randomByteIn(2, 1);
219
+ const secondByte = Convert.randomByteIn(11, 0);
220
+ ret.set([firstByte], 0);
221
+ ret.set([secondByte], 1);
222
+
223
+ let pos = 2;
224
+ ret.set(salt, pos); pos += salt.length;
225
+ ret.set(iv, pos); pos += iv.length;
226
+ ret.set(ciphertext, pos); pos += ciphertext.length;
227
+ ret.set(authTag, pos);
228
+
229
+ return ret;
230
+ }
231
+ }